From 69a4ccc4e6a1740d00491b2ddb57f751011f431e Mon Sep 17 00:00:00 2001 From: Ben Sherman Date: Thu, 12 Dec 2024 13:24:30 -0600 Subject: [PATCH] Add shims for standard types Signed-off-by: Ben Sherman --- .../src/main/java/script/dsl/ScriptDsl.java | 3 +- .../src/main/java/script/types/Iterable.java | 82 +++++++++++++++++++ .../src/main/java/script/types/List.java | 48 +++++++++++ .../src/main/java/script/types/Map.java | 48 +++++++++++ .../src/main/java/script/types/Path.java | 50 +++++++++++ .../src/main/java/script/types/Set.java | 27 ++++++ .../src/main/java/script/types/ShimType.java | 27 ++++++ .../src/main/java/script/types/String.java | 38 +++++++++ .../src/main/java/script/types/Types.java | 19 ++++- .../main/java/nextflow/lsp/ast/ASTUtils.java | 43 +++++++++- .../script/ScriptCompletionProvider.java | 12 ++- 11 files changed, 389 insertions(+), 8 deletions(-) create mode 100644 modules/compiler/src/main/java/script/types/Iterable.java create mode 100644 modules/compiler/src/main/java/script/types/List.java create mode 100644 modules/compiler/src/main/java/script/types/Map.java create mode 100644 modules/compiler/src/main/java/script/types/Path.java create mode 100644 modules/compiler/src/main/java/script/types/Set.java create mode 100644 modules/compiler/src/main/java/script/types/ShimType.java create mode 100644 modules/compiler/src/main/java/script/types/String.java diff --git a/modules/compiler/src/main/java/script/dsl/ScriptDsl.java b/modules/compiler/src/main/java/script/dsl/ScriptDsl.java index 97ea223..5bbad12 100644 --- a/modules/compiler/src/main/java/script/dsl/ScriptDsl.java +++ b/modules/compiler/src/main/java/script/dsl/ScriptDsl.java @@ -100,8 +100,7 @@ The directory where a module script is located (equivalent to `projectDir` if us @Description(""" Get one or more files from a path or glob pattern. Returns a Path or list of Paths if there are multiple files. """) - /* Path | Collection */ - Object file(Map opts, String filePattern); + Path file(Map opts, String filePattern); @Description(""" Convenience method for `file()` that always returns a list. diff --git a/modules/compiler/src/main/java/script/types/Iterable.java b/modules/compiler/src/main/java/script/types/Iterable.java new file mode 100644 index 0000000..6e3110c --- /dev/null +++ b/modules/compiler/src/main/java/script/types/Iterable.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013-2024, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nextflow.script.types; + +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +import nextflow.script.dsl.Description; + +@Description(""" + An iterable is a common interface for collections that support iteration. + + [Read more](https://nextflow.io/docs/latest/reference/stdlib.html#iterable) +""") +public interface Iterable { + + @Description(""" + Returns `true` if any value in the iterable satisfies the given condition. + """) + boolean any(Predicate predicate); + + @Description(""" + Returns a new iterable with each value transformed by the given closure. + """) + Iterable collect(Function mapper); + + @Description(""" + Invoke the given closure for each value in the iterable. + """) + void each(Consumer action); + + @Description(""" + Returns `true` if every value in the iterable satisfies the given condition. + """) + boolean every(Predicate predicate); + + @Description(""" + Returns the values in the iterable that satisfy the given condition. + """) + Iterable findAll(Predicate predicate); + + @Description(""" + Returns a new iterable with each value transformed by the given closure. + """) + R inject(R initialValue, BiFunction accumulator); + + @Description(""" + Returns the number of values in the iterable. + """) + int size(); + + @Description(""" + Returns a sorted list of the iterable's values. + """) + List sort(); + + @Description(""" + Returns the sum of the values in the iterable. The values should support the `+` operator. + """) + E sum(); + + @Description(""" + Converts the iterable to a set. Duplicate values are excluded. + """) + Set toSet(); + +} diff --git a/modules/compiler/src/main/java/script/types/List.java b/modules/compiler/src/main/java/script/types/List.java new file mode 100644 index 0000000..ecf38c4 --- /dev/null +++ b/modules/compiler/src/main/java/script/types/List.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2024, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nextflow.script.types; + +import nextflow.script.dsl.Description; + +@Description(""" + A list is an unordered collection of elements. + + [Read more](https://nextflow.io/docs/latest/reference/stdlib.html#list) +""") +@ShimType(java.util.List.class) +public interface List extends Iterable { + + @Description(""" + Collates the list into a list of sub-lists of length `size`, stepping through the list `step` elements for each sub-list. If `keepRemainder` is `true`, any remaining elements are included as a partial sub-list, otherwise they are excluded. + """) + List collate(int size, int step, boolean keepRemainder); + + @Description(""" + Returns the first element in the list. Raises an error if the list is empty. + """) + E first(); + + @Description(""" + Returns the list of integers from 0 to *n - 1*, where *n* is the number of elements in the list. + """) + List getIndices(); + + @Description(""" + Returns a list of 2-tuples corresponding to the value and index of each element in the list. + """) + List withIndex(); + +} diff --git a/modules/compiler/src/main/java/script/types/Map.java b/modules/compiler/src/main/java/script/types/Map.java new file mode 100644 index 0000000..b30e2c9 --- /dev/null +++ b/modules/compiler/src/main/java/script/types/Map.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2024, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nextflow.script.types; + +import java.util.function.BiConsumer; + +import nextflow.script.dsl.Description; + +@Description(""" + A map "maps" keys to values. Each key can map to at most one value -- a map cannot contain duplicate keys. + + [Read more](https://nextflow.io/docs/latest/reference/stdlib.html#map) +""") +@ShimType(java.util.Map.class) +public interface Map { + + @Description(""" + Invoke the given closure for each key-value pair in the map. The closure should accept two parameters corresponding to the key and value of an entry. + """) + void each(BiConsumer action); + + @Description(""" + Returns a set of the key-value pairs in the map. + """) + Set> entrySet(); + + @Description(""" + A map entry is a key-value pair. + """) + interface Entry { + K getKey(); + V getValue(); + } + +} diff --git a/modules/compiler/src/main/java/script/types/Path.java b/modules/compiler/src/main/java/script/types/Path.java new file mode 100644 index 0000000..733b451 --- /dev/null +++ b/modules/compiler/src/main/java/script/types/Path.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2024, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nextflow.script.types; + +import nextflow.script.dsl.Constant; +import nextflow.script.dsl.Description; + +@Description(""" + A Path is a handle for hierarchichal paths such as local files and directories, HTTP/FTP URLs, and object storage paths (e.g. Amazon S3). + + [Read more](https://nextflow.io/docs/latest/reference/stdlib.html#path) +""") +@ShimType(java.nio.file.Path.class) +public interface Path { + + @Constant("text") + @Description(""" + Returns the file content as a string value. + """) + String getText(); + + @Description(""" + Reads the file line by line and returns the content as a list of strings. + """) + List readLines(); + + @Description(""" + Splits a CSV file into a list of records. + """) + List splitCsv(); + + @Description(""" + Splits a text file into a list of lines. + """) + List splitText(); + +} diff --git a/modules/compiler/src/main/java/script/types/Set.java b/modules/compiler/src/main/java/script/types/Set.java new file mode 100644 index 0000000..ce4c939 --- /dev/null +++ b/modules/compiler/src/main/java/script/types/Set.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2024, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nextflow.script.types; + +import nextflow.script.dsl.Description; + +@Description(""" + A set is an unordered collection that cannot contain duplicate elements. + + [Read more](https://nextflow.io/docs/latest/reference/stdlib.html#set) +""") +@ShimType(java.util.Set.class) +public interface Set extends Iterable { +} diff --git a/modules/compiler/src/main/java/script/types/ShimType.java b/modules/compiler/src/main/java/script/types/ShimType.java new file mode 100644 index 0000000..89f5136 --- /dev/null +++ b/modules/compiler/src/main/java/script/types/ShimType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2024, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nextflow.script.types; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE }) +public @interface ShimType { + Class value(); +} diff --git a/modules/compiler/src/main/java/script/types/String.java b/modules/compiler/src/main/java/script/types/String.java new file mode 100644 index 0000000..e375f46 --- /dev/null +++ b/modules/compiler/src/main/java/script/types/String.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2024, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nextflow.script.types; + +import nextflow.script.dsl.Description; + +@Description(""" + A string is an immutable array of characters. + + [Read more](https://nextflow.io/docs/latest/reference/stdlib.html#string) +""") +@ShimType(java.lang.String.class) +public interface String { + + @Description(""" + Parses the string into an integer. + """) + Integer toInteger(); + + @Description(""" + Splits the string into a list of substrings using the given delimiters. Each character in the delimiter string is treated as a separate delimiter. + """) + List tokenize(String delimiters); + +} diff --git a/modules/compiler/src/main/java/script/types/Types.java b/modules/compiler/src/main/java/script/types/Types.java index fc761a1..86322c6 100644 --- a/modules/compiler/src/main/java/script/types/Types.java +++ b/modules/compiler/src/main/java/script/types/Types.java @@ -15,6 +15,7 @@ */ package nextflow.script.types; +import java.lang.String; import java.nio.file.Path; import java.util.List; @@ -29,10 +30,22 @@ public class Types { new ClassNode(Path.class) ); + // TODO: normalize ClassNode -> String, rename shim types public static String normalize(String name) { - if( "Object".equals(name) ) - return "?"; - return name; + return switch (name) { + case "Object" -> "?"; + default -> name; + }; + // if( "BiConsumer".equals(name) ) + // return "Closure<(K, V)>"; + // if( "BiFunction".equals(name) ) + // return "Closure<(R, E) -> R>"; + // if( "Consumer".equals(name) ) + // return "Closure<(E)>"; + // if( "Function".equals(name) ) + // return "Closure<(E) -> R>"; + // if( "Predicate".equals(name) ) + // return "Closure<(E) -> boolean>"; } } diff --git a/modules/language-server/src/main/java/nextflow/lsp/ast/ASTUtils.java b/modules/language-server/src/main/java/nextflow/lsp/ast/ASTUtils.java index afa3f59..3da8cee 100644 --- a/modules/language-server/src/main/java/nextflow/lsp/ast/ASTUtils.java +++ b/modules/language-server/src/main/java/nextflow/lsp/ast/ASTUtils.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -31,6 +32,7 @@ import nextflow.script.dsl.Description; import nextflow.script.types.Channel; import nextflow.script.types.NamedTuple; +import nextflow.script.types.ShimType; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; @@ -129,6 +131,25 @@ public static Iterator getReferences(ASTNode node, ASTNodeCache ast, bo .iterator(); } + private static final Map SHIM_TYPES = getShimTypes(); + + private static Map getShimTypes() { + var types = List.of( + nextflow.script.types.List.class, + nextflow.script.types.Map.class, + nextflow.script.types.Path.class, + nextflow.script.types.Set.class, + nextflow.script.types.String.class + ); + return types.stream().collect(Collectors.toMap( + (clazz) -> { + var shim = clazz.getAnnotation(ShimType.class).value(); + return ClassHelper.makeCached(shim); + }, + (clazz) -> ClassHelper.makeCached(clazz) + )); + } + /** * Get the type (i.e. class node) of an ast node. * @@ -136,6 +157,14 @@ public static Iterator getReferences(ASTNode node, ASTNodeCache ast, bo * @param ast */ public static ClassNode getType(ASTNode node, ASTNodeCache ast) { + var result = getType0(node, ast); + var shim = SHIM_TYPES.get(result); + if( shim != null ) + return shim; + return result; + } + + private static ClassNode getType0(ASTNode node, ASTNodeCache ast) { if( node instanceof ClassExpression ce ) { // type(Foo.bar) -> type(Foo) return ce.getType(); @@ -316,7 +345,7 @@ public static List getMethodOverloadsFromCallExpression(MethodCall n else { var leftType = getType(mce.getObjectExpression(), ast); if( leftType != null ) - return leftType.getMethods(mce.getMethodAsString()); + return getMethodsForType(leftType, mce.getMethodAsString()); } } @@ -332,6 +361,18 @@ public static List getMethodOverloadsFromCallExpression(MethodCall n return Collections.emptyList(); } + private static List getMethodsForType(ClassNode cn, String name) { + try { + return cn.getAllDeclaredMethods().stream() + .filter(mn -> mn.getName().equals(name)) + .filter(mn -> !ClassHelper.isObjectType(mn.getDeclaringClass())) + .collect(Collectors.toList()); + } + catch( NullPointerException e ) { + return Collections.emptyList(); + } + } + private static int getArgumentsScore(Parameter[] parameters, ArgumentListExpression arguments, int argIndex) { var paramsCount = parameters.length; var expressionsCount = arguments.getExpressions().size(); diff --git a/modules/language-server/src/main/java/nextflow/lsp/services/script/ScriptCompletionProvider.java b/modules/language-server/src/main/java/nextflow/lsp/services/script/ScriptCompletionProvider.java index 3481e70..3fa7eab 100644 --- a/modules/language-server/src/main/java/nextflow/lsp/services/script/ScriptCompletionProvider.java +++ b/modules/language-server/src/main/java/nextflow/lsp/services/script/ScriptCompletionProvider.java @@ -201,7 +201,11 @@ private void populateItemsFromObjectScope(Expression object, String namePrefix, break; } - cn = cn.getSuperClass(); + cn = cn.getSuperClass() != null + ? cn.getSuperClass() + : cn.getInterfaces().length == 1 + ? cn.getInterfaces()[0] + : null; } } @@ -219,7 +223,11 @@ private void populateMethodsFromObjectScope(Expression object, String namePrefix break; } - cn = cn.getSuperClass(); + cn = cn.getSuperClass() != null + ? cn.getSuperClass() + : cn.getInterfaces().length == 1 + ? cn.getInterfaces()[0] + : null; } }