diff --git a/docs/reference/process.md b/docs/reference/process.md index 43eff92099..80db9eae42 100644 --- a/docs/reference/process.md +++ b/docs/reference/process.md @@ -649,7 +649,7 @@ The `disk` directive allows you to define how much local disk storage the proces ```nextflow process hello { - disk '2 GB' + disk 2.GB script: """ @@ -692,17 +692,17 @@ The `errorStrategy` directive allows you to define how the process manages an er The following error strategies are available: -`terminate` (default) +`'terminate'` (default) : When a task fails, terminate the pipeline immediately and report an error. Pending and running jobs are killed. -`finish` +`'finish'` : When a task fails, wait for submitted and running tasks to finish and then terminate the pipeline, reporting an error. -`ignore` +`'ignore'` : When a task fails, ignore it and continue the pipeline execution. If the `workflow.failOnIgnore` config option is set to `true`, the pipeline will report an error (i.e. return a non-zero exit code) upon completion. Otherwise, the pipeline will complete successfully. : See the {ref}`stdlib-namespaces-workflow` namespace for more information. -`retry` +`'retry'` : When a task fails, retry it. When setting the `errorStrategy` directive to `ignore` the process doesn't stop on an error condition, it just reports a message notifying you of the error event. @@ -908,34 +908,6 @@ process hello { See also: [cpus](#cpus) and [memory](#memory). -(process-maxsubmitawait)= - -### maxSubmitAwait - -The `maxSubmitAwait` directive allows you to specify how long a task can remain in submission queue without being executed. -Elapsed this time the task execution will fail. - -When used along with `retry` error strategy, it can be useful to re-schedule the task to a difference queue or -resource requirement. For example: - -```nextflow -process hello { - errorStrategy 'retry' - maxSubmitAwait '10 mins' - maxRetries 3 - queue "${task.submitAttempt==1 ? 'spot-compute' : 'on-demand-compute'}" - - script: - """ - your_command --here - """ -} -``` - -In the above example the task is submitted to the `spot-compute` on the first attempt (`task.submitAttempt==1`). If the -task execution does not start in the 10 minutes, a failure is reported and a new submission is attempted using the -queue named `on-demand-compute`. - (process-maxerrors)= ### maxErrors @@ -1003,6 +975,34 @@ There is a subtle but important difference between `maxRetries` and the `maxErro See also: [errorStrategy](#errorstrategy) and [maxErrors](#maxerrors). +(process-maxsubmitawait)= + +### maxSubmitAwait + +The `maxSubmitAwait` directive allows you to specify how long a task can remain in submission queue without being executed. +Elapsed this time the task execution will fail. + +When used along with `retry` error strategy, it can be useful to re-schedule the task to a difference queue or +resource requirement. For example: + +```nextflow +process hello { + errorStrategy 'retry' + maxSubmitAwait 10.m + maxRetries 3 + queue "${task.submitAttempt==1 ? 'spot-compute' : 'on-demand-compute'}" + + script: + """ + your_command --here + """ +} +``` + +In the above example the task is submitted to the `spot-compute` on the first attempt (`task.submitAttempt==1`). If the +task execution does not start in the 10 minutes, a failure is reported and a new submission is attempted using the +queue named `on-demand-compute`. + (process-memory)= ### memory @@ -1011,7 +1011,7 @@ The `memory` directive allows you to define how much memory the process is allow ```nextflow process hello { - memory '2 GB' + memory 2.GB script: """ @@ -1746,7 +1746,7 @@ The `time` directive allows you to define how long a process is allowed to run. ```nextflow process hello { - time '1h' + time 1.h script: """ @@ -1757,15 +1757,13 @@ process hello { The following time unit suffixes can be used when specifying the duration value: -| Unit | Description | -| ------------------------------- | ------------ | -| `ms`, `milli`, `millis` | Milliseconds | -| `s`, `sec`, `second`, `seconds` | Seconds | -| `m`, `min`, `minute`, `minutes` | Minutes | -| `h`, `hour`, `hours` | Hours | -| `d`, `day`, `days` | Days | - -Multiple units can be used in a single declaration, for example: `'1day 6hours 3minutes 30seconds'` +| Unit | Description | +| ---- | ------------ | +| `ms` | Milliseconds | +| `s` | Seconds | +| `m` | Minutes | +| `h` | Hours | +| `d` | Days | See {ref}`stdlib-types-duration` for more information. diff --git a/modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptCompiler.java b/modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptCompiler.java index b956d40eaf..109907c4bb 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptCompiler.java +++ b/modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptCompiler.java @@ -42,6 +42,7 @@ import nextflow.script.control.ResolveIncludeVisitor; import nextflow.script.control.ScriptResolveVisitor; import nextflow.script.control.ScriptToGroovyVisitor; +import nextflow.script.control.StripTypesVisitor; import nextflow.script.control.TypeCheckingVisitor; import nextflow.script.parser.ScriptParserPluginFactory; import org.codehaus.groovy.ast.ASTNode; @@ -290,6 +291,7 @@ private void analyze(SourceUnit source) { // convert to Groovy new ScriptToGroovyVisitor(source).visit(); + new StripTypesVisitor(source).visitClass(cn); new PathCompareVisitor(source).visitClass(cn); new OpCriteriaVisitor(source).visitClass(cn); new GStringToStringVisitor(source).visitClass(cn); diff --git a/modules/nextflow/src/test/groovy/nextflow/script/parser/v2/ScriptLoaderV2Test.groovy b/modules/nextflow/src/test/groovy/nextflow/script/parser/v2/ScriptLoaderV2Test.groovy index 3770c46db9..58716c5bf9 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/parser/v2/ScriptLoaderV2Test.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/parser/v2/ScriptLoaderV2Test.groovy @@ -232,4 +232,26 @@ class ScriptLoaderV2Test extends Dsl2Spec { noExceptionThrown() } + def 'should strip unsupported type annotations' () { + + given: + def session = new Session() + def parser = new ScriptLoaderV2(session) + + def TEXT = ''' + // strip cast type + ['1', '1.fastq', '2.fastq'] as Tuple + + // strip type annotation in variable declaration + def ch: Channel = channel.empty() + ''' + + when: + parser.parse(TEXT) + parser.runScript() + + then: + noExceptionThrown() + } + } diff --git a/modules/nf-lang/src/main/java/nextflow/config/control/VariableScopeVisitor.java b/modules/nf-lang/src/main/java/nextflow/config/control/VariableScopeVisitor.java index e2fac3e502..ab00abdcd8 100644 --- a/modules/nf-lang/src/main/java/nextflow/config/control/VariableScopeVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/config/control/VariableScopeVisitor.java @@ -285,9 +285,11 @@ private void checkMethodCall(MethodCallExpression node) { if( !node.isImplicitThis() ) return; var name = node.getMethodAsString(); - var defNode = vsc.findDslFunction(name, node); - if( defNode != null ) - node.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, defNode); + var methods = vsc.findDslFunction(name, node); + if( methods.size() == 1 ) + node.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, methods.get(0)); + else if( !methods.isEmpty() ) + node.putNodeMetaData(ASTNodeMarker.METHOD_OVERLOADS, methods); else if( !KEYWORDS.contains(name) ) vsc.addError("`" + name + "` is not defined", node.getMethod()); } diff --git a/modules/nf-lang/src/main/java/nextflow/script/ast/ASTNodeMarker.java b/modules/nf-lang/src/main/java/nextflow/script/ast/ASTNodeMarker.java index d8d7b0b4a2..cd3b6ab869 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/ast/ASTNodeMarker.java +++ b/modules/nf-lang/src/main/java/nextflow/script/ast/ASTNodeMarker.java @@ -27,6 +27,9 @@ public enum ASTNodeMarker { // denotes that an assignment is an implicit declaration IMPLICIT_DECLARATION, + // the inferred return type of a closure expression + INFERRED_RETURN_TYPE, + // the inferred type of an expression INFERRED_TYPE, @@ -39,6 +42,9 @@ public enum ASTNodeMarker { // the verbatim text of a Groovy-style type annotation (ClassNode) LEGACY_TYPE, + // the list of candidate MethodNode's for a MethodCallExpression + METHOD_OVERLOADS, + // the MethodNode targeted by a MethodCallExpression METHOD_TARGET, @@ -48,6 +54,9 @@ public enum ASTNodeMarker { // denotes a nullable type annotation (ClassNode) NULLABLE, + // the FieldNode targeted by a PropertyExpression + PROPERTY_TARGET, + // the starting quote sequence of a string literal or gstring expression QUOTE_CHAR, diff --git a/modules/nf-lang/src/main/java/nextflow/script/ast/ASTUtils.java b/modules/nf-lang/src/main/java/nextflow/script/ast/ASTUtils.java index a39baf5626..edcafc61d3 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/ast/ASTUtils.java +++ b/modules/nf-lang/src/main/java/nextflow/script/ast/ASTUtils.java @@ -18,10 +18,13 @@ import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Stream; +import groovy.transform.NamedParams; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.AnnotatedNode; @@ -30,9 +33,12 @@ import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.PropertyNode; import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.expr.AnnotationConstantExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; import org.codehaus.groovy.ast.expr.MapExpression; import org.codehaus.groovy.ast.expr.MapEntryExpression; import org.codehaus.groovy.ast.expr.MethodCall; @@ -145,6 +151,40 @@ public static List asNamedArgs(MethodCall call) { : Collections.emptyList(); } + /** + * Given a parameter with a @NamedParams annotation, + * return the map of named params. + * + * @param parameter + */ + public static Map asNamedParams(Parameter parameter) { + var namedParams = new LinkedHashMap(); + parameter.getAnnotations().stream() + .filter(an -> an.getClassNode().getName().equals(NamedParams.class.getName())) + .flatMap(an -> { + var value = an.getMember("value"); + return value instanceof ListExpression le + ? le.getExpressions().stream() + : Stream.empty(); + }) + .forEach((value) -> { + if( !(value instanceof AnnotationConstantExpression) ) + return; + var ace = (AnnotationConstantExpression) value; + var namedParam = (AnnotationNode) ace.getValue(); + var name = namedParam.getMember("value").getText(); + namedParams.put(name, namedParam); + }); + return namedParams; + } + + public static Parameter asNamedParam(AnnotationNode node) { + var name = node.getMember("value").getText(); + var typeX = (ClassExpression) node.getMember("type"); + var type = typeX != null ? typeX.getType() : ClassHelper.dynamicType(); + return new Parameter(type, name); + } + public static VariableExpression asVarX(Statement statement) { return statement instanceof ExpressionStatement es ? asVarX(es.getExpression()) : null; } diff --git a/modules/nf-lang/src/main/java/nextflow/script/ast/OutputNode.java b/modules/nf-lang/src/main/java/nextflow/script/ast/OutputNode.java index 13b31fdcf3..186c120422 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/ast/OutputNode.java +++ b/modules/nf-lang/src/main/java/nextflow/script/ast/OutputNode.java @@ -15,8 +15,8 @@ */ package nextflow.script.ast; -import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.stmt.Statement; /** @@ -24,14 +24,11 @@ * * @author Ben Sherman */ -public class OutputNode extends ASTNode { - public final String name; - public final ClassNode type; +public class OutputNode extends Parameter { public final Statement body; public OutputNode(String name, ClassNode type, Statement body) { - this.name = name; - this.type = type; + super(type, name); this.body = body; } } diff --git a/modules/nf-lang/src/main/java/nextflow/script/ast/ProcessNodeV2.java b/modules/nf-lang/src/main/java/nextflow/script/ast/ProcessNodeV2.java index 6aec41e1b1..88bf82d8f1 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/ast/ProcessNodeV2.java +++ b/modules/nf-lang/src/main/java/nextflow/script/ast/ProcessNodeV2.java @@ -69,30 +69,28 @@ private static ClassNode dummyReturnType(Statement block) { if( outputs.size() == 1 ) { var first = outputs.get(0); var output = ((ExpressionStatement) first).getExpression(); - if( outputName(output) == null ) + if( outputTarget(output) == null ) return output.getType(); } var cn = new ClassNode(Record.class); outputs.stream() .map(stmt -> ((ExpressionStatement) stmt).getExpression()) - .map(output -> outputName(output)) - .filter(name -> name != null) - .forEach((name) -> { - var type = ClassHelper.dynamicType(); - var fn = new FieldNode(name, Modifier.PUBLIC, type, cn, null); + .map(output -> outputTarget(output)) + .filter(target -> target != null) + .forEach((target) -> { + var fn = new FieldNode(target.getName(), Modifier.PUBLIC, target.getType(), cn, null); fn.setDeclaringClass(cn); cn.addField(fn); }); return cn; } - private static String outputName(Expression output) { + private static VariableExpression outputTarget(Expression output) { if( output instanceof VariableExpression ve ) { - return ve.getName(); + return ve; } - else if( output instanceof AssignmentExpression ae ) { - var target = (VariableExpression)ae.getLeftExpression(); - return target.getName(); + if( output instanceof AssignmentExpression ae ) { + return (VariableExpression)ae.getLeftExpression(); } return null; } diff --git a/modules/nf-lang/src/main/java/nextflow/script/ast/WorkflowNode.java b/modules/nf-lang/src/main/java/nextflow/script/ast/WorkflowNode.java index 61a21dece2..2e5387c64a 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/ast/WorkflowNode.java +++ b/modules/nf-lang/src/main/java/nextflow/script/ast/WorkflowNode.java @@ -16,7 +16,6 @@ package nextflow.script.ast; import java.lang.reflect.Modifier; -import java.util.Optional; import nextflow.script.types.Record; import org.codehaus.groovy.ast.ClassHelper; @@ -65,28 +64,33 @@ public boolean isCodeSnippet() { return getLineNumber() == -1; } - private static ClassNode dummyReturnType(Statement emits) { + private static ClassNode dummyReturnType(Statement block) { + var emits = asBlockStatements(block); + if( emits.size() == 1 ) { + var first = emits.get(0); + var emit = ((ExpressionStatement) first).getExpression(); + if( emitTarget(emit) == null ) + return emit.getType(); + } var cn = new ClassNode(Record.class); - asBlockStatements(emits).stream() + emits.stream() .map(stmt -> ((ExpressionStatement) stmt).getExpression()) - .map(emit -> emitName(emit)) - .filter(name -> name != null) - .forEach((name) -> { - var type = ClassHelper.dynamicType(); - var fn = new FieldNode(name, Modifier.PUBLIC, type, cn, null); + .map(emit -> emitTarget(emit)) + .filter(target -> target != null) + .forEach((target) -> { + var fn = new FieldNode(target.getName(), Modifier.PUBLIC, target.getType(), cn, null); fn.setDeclaringClass(cn); cn.addField(fn); }); return cn; } - private static String emitName(Expression emit) { + private static VariableExpression emitTarget(Expression emit) { if( emit instanceof VariableExpression ve ) { - return ve.getName(); + return ve; } - else if( emit instanceof AssignmentExpression ae ) { - var left = (VariableExpression)ae.getLeftExpression(); - return left.getName(); + if( emit instanceof AssignmentExpression ae ) { + return (VariableExpression)ae.getLeftExpression(); } return null; } diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/ResolveVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/ResolveVisitor.java index 7bdcd81fdd..6fe6445df0 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/ResolveVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/ResolveVisitor.java @@ -16,6 +16,7 @@ package nextflow.script.control; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -110,14 +111,41 @@ public void visitCatchStatement(CatchStatement cs) { } public void resolveOrFail(ClassNode type, ASTNode node) { - if( !resolve(type) ) - addError("`" + type.toString(false) + "` is not defined", node); + var unresolvedTypes = new LinkedList(); + resolve(type, unresolvedTypes); + for( var ut : unresolvedTypes ) + addError("`" + ut.toString(false) + "` is not defined", node); } - public boolean resolve(ClassNode type) { - var genericsTypes = type.getGenericsTypes(); - resolveGenericsTypes(genericsTypes); + private boolean resolve(ClassNode type) { + var unresolvedTypes = new LinkedList(); + resolve(type, unresolvedTypes); + return unresolvedTypes.isEmpty(); + } + /** + * Resolve a type annotation, including generic type arguments. + * + * Returns the list of types that could not be resolved. + * + * @param type + * @param unresolvedTypes + */ + private void resolve(ClassNode type, List unresolvedTypes) { + if( !resolveType(type) ) + unresolvedTypes.add(type); + var gts = type.getGenericsTypes(); + if( gts == null ) + return; + for( var gt : gts ) { + if( gt.isResolved() ) + continue; + resolve(gt.getType(), unresolvedTypes); + gt.setResolved(gt.getType().isResolved()); + } + } + + private boolean resolveType(ClassNode type) { if( type.isPrimaryClassNode() ) return true; if( type.isResolved() ) @@ -135,27 +163,6 @@ public boolean resolve(ClassNode type) { return resolveFromClassResolver(type.getName()) != null; } - private boolean resolveGenericsTypes(GenericsType[] types) { - if( types == null ) - return true; - boolean resolved = true; - for( var type : types ) { - if( !resolveGenericsType(type) ) - resolved = false; - } - return resolved; - } - - private boolean resolveGenericsType(GenericsType genericsType) { - if( genericsType.isResolved() ) - return true; - var type = genericsType.getType(); - resolveOrFail(type, genericsType); - if( resolveGenericsTypes(type.getGenericsTypes()) ) - genericsType.setResolved(genericsType.getType().isResolved()); - return genericsType.isResolved(); - } - protected boolean resolveFromModule(ClassNode type) { var name = type.getName(); var module = sourceUnit.getAST(); diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/ScriptResolveVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/ScriptResolveVisitor.java index 603c2f3845..2d93018daa 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/ScriptResolveVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/ScriptResolveVisitor.java @@ -103,20 +103,20 @@ public void visitWorkflow(WorkflowNode node) { for( var take : node.getParameters() ) resolver.resolveOrFail(take.getType(), take); resolver.visit(node.main); - resolveWorkflowEmits(node.emits); + resolveTypedOutputs(node.emits); resolver.visit(node.emits); resolver.visit(node.publishers); resolver.visit(node.onComplete); resolver.visit(node.onError); } - private void resolveWorkflowEmits(Statement emits) { - for( var stmt : asBlockStatements(emits) ) { + private void resolveTypedOutputs(Statement block) { + for( var stmt : asBlockStatements(block) ) { var stmtX = (ExpressionStatement)stmt; - var emit = stmtX.getExpression(); + var output = stmtX.getExpression(); var target = - emit instanceof AssignmentExpression ae ? ae.getLeftExpression() : - emit instanceof VariableExpression ve ? ve : + output instanceof AssignmentExpression ae ? ae.getLeftExpression() : + output instanceof VariableExpression ve ? ve : null; if( target instanceof VariableExpression ve ) @@ -130,6 +130,7 @@ public void visitProcessV2(ProcessNodeV2 node) { resolver.resolveOrFail(input.getType(), input); resolver.visit(node.directives); resolver.visit(node.stagers); + resolveTypedOutputs(node.outputs); resolver.visit(node.outputs); resolver.visit(node.topics); resolver.visit(node.when); @@ -159,6 +160,7 @@ public void visitFunction(FunctionNode node) { @Override public void visitOutput(OutputNode node) { + resolver.resolveOrFail(node.getType(), node.getType()); resolver.visit(node.body); } diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/ScriptToGroovyVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/ScriptToGroovyVisitor.java index 7e9bffb1a4..dbfb610181 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/ScriptToGroovyVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/ScriptToGroovyVisitor.java @@ -144,8 +144,8 @@ public void visitWorkflow(WorkflowNode node) { var main = node.main instanceof BlockStatement block ? block : new BlockStatement(); visitWorkflowEmits(node.emits, main); visitWorkflowPublishers(node.publishers, main); - visitWorkflowHandler(node.onComplete, "onComplete", main); - visitWorkflowHandler(node.onError, "onError", main); + visitWorkflowHandler(node.onComplete, "setOnComplete", main); + visitWorkflowHandler(node.onError, "setOnError", main); var bodyDef = stmt(createX( "nextflow.script.BodyDef", @@ -239,7 +239,7 @@ public void visitOutputs(OutputBlockNode node) { var statements = node.declarations.stream() .map((output) -> { new PublishDslVisitor().visit(output.body); - var name = constX(output.name); + var name = constX(output.getName()); var body = closureX(null, output.body); return stmt(callThisX("declare", args(name, body))); }) diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/StripTypesVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/StripTypesVisitor.java new file mode 100644 index 0000000000..ed8711478f --- /dev/null +++ b/modules/nf-lang/src/main/java/nextflow/script/control/StripTypesVisitor.java @@ -0,0 +1,92 @@ +/* + * 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.control; + +import java.util.List; + +import nextflow.script.types.Record; +import nextflow.script.types.Tuple; +import nextflow.script.types.Value; +import org.codehaus.groovy.ast.ClassCodeExpressionTransformer; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.expr.CastExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.DeclarationExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.control.SourceUnit; + +/** + * Strip type annotations that are used by the Nextflow type checker + * but not supported by the Groovy runtime. + * + * For example, Nextflow allows the Tuple type to be specified + * with variable type arguments, which is not supported by the JVM, + * so these type annotations must be removed. + * + * @author Ben Sherman + */ +public class StripTypesVisitor extends ClassCodeExpressionTransformer { + + private static final List STRIP_TYPES = List.of( + ClassHelper.makeWithoutCaching("nextflow.Channel"), + ClassHelper.makeCached(Record.class), + ClassHelper.makeCached(Tuple.class), + ClassHelper.makeCached(Value.class) + ); + + private SourceUnit sourceUnit; + + public StripTypesVisitor(SourceUnit sourceUnit) { + this.sourceUnit = sourceUnit; + } + + @Override + protected SourceUnit getSourceUnit() { + return sourceUnit; + } + + @Override + public Expression transform(Expression node) { + if( node instanceof CastExpression ce ) + return stripTypeAnnotation(ce); + + if( node instanceof ClosureExpression ce ) + super.visitClosureExpression(ce); + + if( node instanceof DeclarationExpression de ) + stripTypeAnnotation(de); + + return super.transform(node); + } + + private Expression stripTypeAnnotation(CastExpression node) { + return STRIP_TYPES.contains(node.getType()) + ? node.getExpression() + : node; + } + + private Expression stripTypeAnnotation(DeclarationExpression node) { + if( node.getLeftExpression() instanceof VariableExpression ve ) { + if( STRIP_TYPES.contains(ve.getType()) ) + node.setLeftExpression(new VariableExpression(ve.getName())); + } + return node; + } + +} diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeChecker.java b/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeChecker.java index de44e08b32..21103f0711 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeChecker.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeChecker.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -26,6 +27,7 @@ import nextflow.script.dsl.Operator; import nextflow.script.ast.ProcessNode; import nextflow.script.ast.WorkflowNode; +import nextflow.script.types.Record; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; @@ -95,14 +97,18 @@ public void checkUnusedVariables() { } } - public void pushScope(Class classScope) { + public void pushScope(ClassNode classScope) { currentScope = new VariableScope(currentScope); if( classScope != null ) - currentScope.setClassScope(ClassHelper.makeCached(classScope)); + currentScope.setClassScope(classScope); + } + + public void pushScope(Class classScope) { + pushScope(ClassHelper.makeCached(classScope)); } public void pushScope() { - pushScope(null); + pushScope((ClassNode) null); } public void popScope() { @@ -219,7 +225,7 @@ public static boolean isOperator(MethodNode mn) { private static PropertyNode wrapMethodAsVariable(MethodNode mn, String name) { var cn = mn.getDeclaringClass(); - var fn = new FieldNode(name, mn.getModifiers() & 0xF, mn.getReturnType(), cn, null); + var fn = new FieldNode(name, mn.getModifiers() & 0xF, methodOutputType(mn), cn, null); fn.setHasNoRealSourcePosition(true); fn.setDeclaringClass(cn); fn.setSynthetic(true); @@ -229,28 +235,45 @@ private static PropertyNode wrapMethodAsVariable(MethodNode mn, String name) { return pn; } + private static ClassNode methodOutputType(MethodNode mn) { + if( !(mn instanceof ProcessNode || mn instanceof WorkflowNode) ) + return mn.getReturnType(); + var cn = new ClassNode(Record.class); + var fn = new FieldNode("out", mn.getModifiers() & 0xF, mn.getReturnType(), cn, null); + fn.setHasNoRealSourcePosition(true); + fn.setDeclaringClass(cn); + fn.setSynthetic(true); + cn.addField(fn); + return cn; + } + /** * Find the definition of a built-in function. * * @param name * @param node + * @param directive */ - public MethodNode findDslFunction(String name, ASTNode node) { + public List findDslFunction(String name, ASTNode node, boolean directive) { VariableScope scope = currentScope; while( scope != null ) { ClassNode cn = scope.getClassScope(); while( cn != null ) { - for( var mn : cn.getMethods() ) { - // built-in functions are methods not annotated as @Constant - if( findAnnotation(mn, Constant.class).isPresent() ) - continue; - if( !name.equals(mn.getName()) ) - continue; - if( findAnnotation(mn, Deprecated.class).isPresent() ) - addParanoidWarning("`" + name + "` is deprecated and will be removed in a future version", node); - return mn; - } - + // built-in functions are methods not annotated as @Constant + var methods = cn.getDeclaredMethods(name).stream() + .filter(mn -> !findAnnotation(mn, Constant.class).isPresent()) + .toList(); + + if( methods.size() == 1 && findAnnotation(methods.get(0), Deprecated.class).isPresent() ) + addParanoidWarning("`" + name + "` is deprecated and will be removed in a future version", node); + + if( !methods.isEmpty() ) + return methods; + + // directives can only come from the immediate dsl scope + if( directive && scope == currentScope ) + return Collections.emptyList(); + cn = cn.getInterfaces().length > 0 ? cn.getInterfaces()[0] : null; @@ -258,7 +281,13 @@ public MethodNode findDslFunction(String name, ASTNode node) { scope = scope.getParent(); } - return includes.get(name); + return includes.containsKey(name) + ? List.of(includes.get(name)) + : Collections.emptyList(); + } + + public List findDslFunction(String name, ASTNode node) { + return findDslFunction(name, node, false); } public void addWarning(String message, String tokenText, ASTNode node) { diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeVisitor.java index 4ded46998d..4d3fd88a70 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeVisitor.java @@ -15,9 +15,11 @@ */ package nextflow.script.control; +import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.List; +import groovy.lang.groovydoc.GroovydocHolder; import nextflow.script.ast.ASTNodeMarker; import nextflow.script.ast.AssignmentExpression; import nextflow.script.ast.FeatureFlagNode; @@ -41,10 +43,12 @@ import nextflow.script.dsl.ProcessDsl; import nextflow.script.dsl.ScriptDsl; import nextflow.script.dsl.WorkflowDsl; +import nextflow.script.types.ParamsMap; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.DynamicVariable; +import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.Variable; @@ -101,6 +105,7 @@ public void declare() { if( moduleNode instanceof ScriptNode sn ) { for( var includeNode : sn.getIncludes() ) declareInclude(includeNode); + declareParams(sn); for( var workflowNode : sn.getWorkflows() ) { if( !workflowNode.isEntry() ) declareMethod(workflowNode); @@ -124,6 +129,29 @@ private void declareInclude(IncludeNode node) { } } + private ClassNode paramsType; + + private void declareParams(ScriptNode sn) { + var params = sn.getParams(); + var entry = sn.getEntry(); + if( params == null || entry == null ) + return; + + var cn = new ClassNode(ParamsMap.class); + for( var param : params.declarations ) { + var name = param.getName(); + var type = param.getType(); + var fn = new FieldNode(name, Modifier.PUBLIC, type, cn, null); + fn.setHasNoRealSourcePosition(true); + fn.setDeclaringClass(cn); + fn.setSynthetic(true); + fn.putNodeMetaData(GroovydocHolder.DOC_COMMENT, param.getGroovydoc()); + cn.addField(fn); + } + + this.paramsType = cn; + } + private void declareMethod(MethodNode mn) { var cn = currentScope().getClassScope(); var name = mn.getName(); @@ -180,7 +208,7 @@ public void visitParams(ParamBlockNode node) { var name = param.getName(); var other = declaredParams.get(name); if( other != null ) - vsc.addError("Parameter " + name + "` is already declared", param, "First declared here", other); + vsc.addError("Parameter `" + name + "` is already declared", param, "First declared here", other); else declaredParams.put(name, param); @@ -193,7 +221,14 @@ public void visitParams(ParamBlockNode node) { @Override public void visitWorkflow(WorkflowNode node) { - vsc.pushScope(node.isEntry() ? EntryWorkflowDsl.class : WorkflowDsl.class); + var classScope = ClassHelper.makeCached(node.isEntry() ? EntryWorkflowDsl.class : WorkflowDsl.class); + if( node.isEntry() && paramsType != null ) { + classScope = new ClassNode(classScope.getTypeClass()); + var paramsMethod = classScope.getDeclaredMethods("getParams").get(0); + paramsMethod.setReturnType(paramsType); + } + + vsc.pushScope(classScope); currentDefinition = node; node.setVariableScope(currentScope()); @@ -364,9 +399,11 @@ private MethodCallExpression checkDirective(Statement node, String typeLabel, bo return null; } var name = call.getMethodAsString(); - var mn = vsc.findDslFunction(name, call.getMethod()); - if( mn != null ) - call.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, mn); + var methods = vsc.findDslFunction(name, call, true); + if( methods.size() == 1 ) + call.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, methods.get(0)); + else if( !methods.isEmpty() ) + call.putNodeMetaData(ASTNodeMarker.METHOD_OVERLOADS, methods); else vsc.addError("Unrecognized " + typeLabel + " `" + name + "`", node); return call; @@ -617,12 +654,16 @@ private void checkMethodCall(MethodCallExpression node) { if( !node.isImplicitThis() ) return; var name = node.getMethodAsString(); - var mn = vsc.findDslFunction(name, node.getMethod()); - if( mn != null ) { + var methods = vsc.findDslFunction(name, node); + if( methods.size() == 1 ) { + var mn = methods.get(0); if( VariableScopeChecker.isDataflowMethod(mn) ) checkDataflowMethod(node, mn); node.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, mn); } + else if( !methods.isEmpty() ) { + node.putNodeMetaData(ASTNodeMarker.METHOD_OVERLOADS, methods); + } else if( !KEYWORDS.contains(name) ) { vsc.addError("`" + name + "` is not defined", node.getMethod()); } diff --git a/modules/nf-lang/src/main/java/nextflow/script/dsl/ProcessDsl.java b/modules/nf-lang/src/main/java/nextflow/script/dsl/ProcessDsl.java index 950880ffd7..80150c0a3f 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/dsl/ProcessDsl.java +++ b/modules/nf-lang/src/main/java/nextflow/script/dsl/ProcessDsl.java @@ -20,6 +20,8 @@ import java.util.Map; import java.util.Set; +import groovy.transform.NamedParam; +import groovy.transform.NamedParams; import nextflow.script.types.Duration; import nextflow.script.types.MemoryUnit; import nextflow.script.types.TaskConfig; @@ -49,7 +51,16 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#accelerator) """) - void accelerator(Map value); + void accelerator( + @NamedParams({ + @NamedParam(value = "request", type = Integer.class), + @NamedParam(value = "limit", type = Integer.class), + @NamedParam(value = "type", type = String.class), + }) + Map opts + ); + void accelerator(Map opts, Integer value); + void accelerator(Integer value); @Description(""" The `afterScript` directive allows you to execute a custom (Bash) snippet *after* the task script. @@ -63,6 +74,14 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#arch) """) + void arch( + @NamedParams({ + @NamedParam(value = "name", type = String.class), + @NamedParam(value = "target", type = String.class), + }) + Map opts + ); + void arch(Map opts, String value); void arch(String value); @Description(""" @@ -85,6 +104,7 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#cache) """) void cache(String value); + void cache(Boolean value); @Description(""" The `clusterOptions` directive allows the usage of any native configuration option accepted by your cluster submit command. You can use it to request non-standard resources or use settings that are specific to your cluster and not supported out of the box by Nextflow. @@ -92,6 +112,8 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#clusteroptions) """) void clusterOptions(String value); + void clusterOptions(List values); + void clusterOptions(String... values); @Description(""" The `conda` directive allows for the definition of the process dependencies using the [Conda](https://conda.io) package manager. @@ -126,13 +148,21 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#debug) """) - void debug(boolean value); + void debug(Boolean value); @Description(""" The `disk` directive allows you to define how much local disk storage the process is allowed to use. [Read more](https://nextflow.io/docs/latest/reference/process.html#disk) """) + void disk( + @NamedParams({ + @NamedParam(value = "request", type = MemoryUnit.class), + @NamedParam(value = "type", type = String.class), + }) + Map opts + ); + void disk(Map opts, MemoryUnit value); void disk(MemoryUnit value); @Description(""" @@ -161,7 +191,7 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#fair) """) - void fair(boolean value); + void fair(Boolean value); @Description(""" The `label` directive allows you to annotate a process with a mnemonic identifier of your choice. @@ -182,7 +212,7 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#maxerrors) """) - void maxErrors(int value); + void maxErrors(Integer value); @Description(""" The `maxForks` directive allows you to define the maximum number of tasks (per process) that can be executed in parallel. @@ -196,7 +226,7 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#maxretries) """) - void maxRetries(int value); + void maxRetries(Integer value); @Description(""" The `maxSubmitAwait` directives allows you to specify how long a task can remain in the submission queue. If a task remains in the queue beyond this time limit, it will fail. @@ -231,14 +261,18 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#pod) """) - void pod(List value); + void pod(Map opts); + void pod(List> entries); @Description(""" The `publishDir` directive allows you to publish the process output files to a directory. [Read more](https://nextflow.io/docs/latest/reference/process.html#publishdir) """) - void publishDir(List value); + void publishDir(Map opts); + void publishDir(Map opts, String value); + void publishDir(String value); + void publishDir(List> entries); @Description(""" The `queue` directive allows you to specify the queue to which jobs are submitted when using a grid executor. @@ -259,7 +293,15 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#resourcelimits) """) - void resourceLimits(Map value); + void resourceLimits( + @NamedParams({ + @NamedParam(value = "cpus", type = Integer.class), + @NamedParam(value = "disk", type = MemoryUnit.class), + @NamedParam(value = "memory", type = MemoryUnit.class), + @NamedParam(value = "time", type = Duration.class), + }) + Map opts + ); @Description(""" The `scratch` directive allows you to execute each task in a temporary directory that is local to the compute node. @@ -267,6 +309,7 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#scratch) """) void scratch(String value); + void scratch(Boolean value); @Description(""" The `secret` directive allows you to securely provide secrets to a process. @@ -281,6 +324,8 @@ interface DirectiveDsl extends DslScope { [Read more](https://nextflow.io/docs/latest/reference/process.html#shell) """) void shell(String value); + void shell(List values); + void shell(String... values); @Description(""" The `spack` directive allows you to provide software dependencies using the [Spack](https://spack.io) package manager. @@ -409,13 +454,37 @@ interface OutputDslV2 extends DslScope { @Description(""" Get a file from the task environment that matches the given pattern. """) - Path file(Map opts, String name); + Path file( + @NamedParams({ + @NamedParam(value = "followLinks", type = Boolean.class), + @NamedParam(value = "glob", type = Boolean.class), + @NamedParam(value = "hidden", type = Boolean.class), + @NamedParam(value = "includeInputs", type = Boolean.class), + @NamedParam(value = "maxDepth", type = Integer.class), + @NamedParam(value = "optional", type = Boolean.class), + @NamedParam(value = "type", type = String.class), + }) + Map opts, + String name + ); Path file(String name); @Description(""" Get the files from the task environment that match the given pattern. """) - Set files(Map opts, String pattern); + Set files( + @NamedParams({ + @NamedParam(value = "followLinks", type = Boolean.class), + @NamedParam(value = "glob", type = Boolean.class), + @NamedParam(value = "hidden", type = Boolean.class), + @NamedParam(value = "includeInputs", type = Boolean.class), + @NamedParam(value = "maxDepth", type = Integer.class), + @NamedParam(value = "optional", type = Boolean.class), + @NamedParam(value = "type", type = String.class), + }) + Map opts, + String pattern + ); Set files(String pattern); @Description(""" diff --git a/modules/nf-lang/src/main/java/nextflow/script/dsl/ScriptDsl.java b/modules/nf-lang/src/main/java/nextflow/script/dsl/ScriptDsl.java index 5131ae2151..49db52f7ed 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/dsl/ScriptDsl.java +++ b/modules/nf-lang/src/main/java/nextflow/script/dsl/ScriptDsl.java @@ -21,6 +21,8 @@ import java.util.Map; import groovy.lang.Closure; +import groovy.transform.NamedParam; +import groovy.transform.NamedParams; import nextflow.script.namespaces.ChannelNamespace; import nextflow.script.namespaces.LogNamespace; import nextflow.script.namespaces.NextflowNamespace; @@ -131,13 +133,35 @@ The directory where a module script is located (equivalent to `projectDir` if us *NOTE: This function will return a collection if the glob pattern yields zero or multiple files. Use `files()` to get a collection of files.* """) - Path file(Map opts, String filePattern); + Path file( + @NamedParams({ + @NamedParam(value = "checkIfExists", type = Boolean.class), + @NamedParam(value = "followLinks", type = Boolean.class), + @NamedParam(value = "glob", type = Boolean.class), + @NamedParam(value = "hidden", type = Boolean.class), + @NamedParam(value = "maxDepth", type = Integer.class), + @NamedParam(value = "type", type = String.class), + }) + Map opts, + String filePattern + ); Path file(String filePattern); @Description(""" Get a collection of files from a file name or glob pattern. """) - Collection files(Map opts, String filePattern); + Collection files( + @NamedParams({ + @NamedParam(value = "checkIfExists", type = Boolean.class), + @NamedParam(value = "followLinks", type = Boolean.class), + @NamedParam(value = "glob", type = Boolean.class), + @NamedParam(value = "hidden", type = Boolean.class), + @NamedParam(value = "maxDepth", type = Integer.class), + @NamedParam(value = "type", type = String.class), + }) + Map opts, + String filePattern + ); Collection files(String filePattern); @Description(""" diff --git a/modules/nf-lang/src/main/java/nextflow/script/formatter/ScriptFormattingVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/formatter/ScriptFormattingVisitor.java index bb20962385..ed45c8f896 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/formatter/ScriptFormattingVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/formatter/ScriptFormattingVisitor.java @@ -271,8 +271,8 @@ public void visitWorkflow(WorkflowNode node) { visitTypedInputs(takes); } if( !node.main.isEmpty() ) { - fmt.appendNewLine(); if( takes.length > 0 || !node.emits.isEmpty() || !node.publishers.isEmpty() ) { + fmt.appendNewLine(); fmt.appendIndent(); fmt.append("main:\n"); } @@ -607,10 +607,10 @@ public void visitOutputs(OutputBlockNode node) { public void visitOutput(OutputNode node) { fmt.appendLeadingComments(node); fmt.appendIndent(); - fmt.append(node.name); - if( fmt.hasType(node.type) ) { + fmt.append(node.getName()); + if( fmt.hasType(node) ) { fmt.append(": "); - fmt.visitTypeAnnotation(node.type); + fmt.visitTypeAnnotation(node.getType()); } fmt.append(" {\n"); fmt.incIndent(); diff --git a/modules/nf-lang/src/main/java/nextflow/script/namespaces/ChannelNamespace.java b/modules/nf-lang/src/main/java/nextflow/script/namespaces/ChannelNamespace.java index 5a0e68011d..9f940821af 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/namespaces/ChannelNamespace.java +++ b/modules/nf-lang/src/main/java/nextflow/script/namespaces/ChannelNamespace.java @@ -20,9 +20,12 @@ import java.util.Map; import groovy.lang.Closure; +import groovy.transform.NamedParam; +import groovy.transform.NamedParams; import nextflow.script.dsl.Description; import nextflow.script.dsl.Namespace; import nextflow.script.types.Channel; +import nextflow.script.types.Value; public interface ChannelNamespace extends Namespace { @@ -31,7 +34,7 @@ public interface ChannelNamespace extends Namespace { [Read more](https://nextflow.io/docs/latest/reference/channel.html#empty) """) - Channel empty(); + Channel empty(); @Deprecated @Description(""" @@ -56,7 +59,7 @@ public interface ChannelNamespace extends Namespace { [Read more](https://nextflow.io/docs/latest/reference/channel.html#fromfilepairs) """) - Channel fromFilePairs(Map opts, String pattern, Closure grouping); + Channel fromFilePairs(Map opts, String pattern, Closure grouping); @Description(""" Create a channel that emits all paths matching a name or glob pattern. @@ -77,7 +80,19 @@ public interface ChannelNamespace extends Namespace { [Read more](https://nextflow.io/docs/latest/reference/channel.html#frompath) """) - Channel fromPath(Map opts, String pattern); + Channel fromPath( + @NamedParams({ + @NamedParam(value = "checkIfExists", type = Boolean.class), + @NamedParam(value = "followLinks", type = Boolean.class), + @NamedParam(value = "glob", type = Boolean.class), + @NamedParam(value = "hidden", type = Boolean.class), + @NamedParam(value = "maxDepth", type = Integer.class), + @NamedParam(value = "relative", type = Boolean.class), + @NamedParam(value = "type", type = String.class), + }) + Map opts, + String pattern + ); Channel fromPath(String pattern); @Description(""" @@ -85,7 +100,14 @@ public interface ChannelNamespace extends Namespace { [Read more](https://nextflow.io/docs/latest/reference/channel.html#fromsra) """) - Channel fromSRA(Map opts, String query); + Channel fromSRA(Map opts, String query); + + @Description(""" + Create a channel that emits an incrementing index (starting from zero) at a periodic interval. + + [Read more](https://nextflow.io/docs/latest/reference/channel.html#interval) + """) + Channel interval(String interval); @Description(""" Create a channel that emits each argument. @@ -99,14 +121,14 @@ public interface ChannelNamespace extends Namespace { [Read more](https://nextflow.io/docs/latest/reference/channel.html#topic) """) - Channel topic(String name); + Channel topic(String name); @Description(""" Create a value channel. [Read more](https://nextflow.io/docs/latest/reference/channel.html#value) """) - Channel value(E value); + Value value(V value); @Description(""" Create a channel that watches for filesystem events for all files matching the given pattern. diff --git a/modules/nf-lang/src/main/java/nextflow/script/parser/ScriptAstBuilder.java b/modules/nf-lang/src/main/java/nextflow/script/parser/ScriptAstBuilder.java index 9e7b5ab9a6..1f5c517b12 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/parser/ScriptAstBuilder.java +++ b/modules/nf-lang/src/main/java/nextflow/script/parser/ScriptAstBuilder.java @@ -448,7 +448,7 @@ private ProcessNode processDef(ProcessDefContext ctx) { collectSyntaxError(new SyntaxException("The `stage:` section is not supported in a legacy process", stagers)); if( !previewTypes && !topics.isEmpty() ) - collectSyntaxError(new SyntaxException("The `topic:` section is not supported in a legacy process", stagers)); + collectSyntaxError(new SyntaxException("The `topic:` section is not supported in a legacy process", topics)); if( ctx.body.blockStatements() != null ) { if( !directives.isEmpty() || ctx.body.processInputs() != null || !outputs.isEmpty() || !topics.isEmpty() ) @@ -503,8 +503,8 @@ private Parameter processInput(ProcessInputContext ctx) { : processTupleInput(type, names, ctx); for( var name : names ) checkInvalidVarName(name, result); - if( ctx.type() == null ) - collectSyntaxError(new SyntaxException("Process input must have a type annotation", result)); + if( names.size() == 1 && ctx.type() == null ) + collectWarning("Process input should have a type annotation", names.get(0), result); saveTrailingComment(result, ctx); return result; } diff --git a/modules/nf-lang/src/main/java/nextflow/script/types/Channel.java b/modules/nf-lang/src/main/java/nextflow/script/types/Channel.java index 7853642c1c..b691d8f69d 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/types/Channel.java +++ b/modules/nf-lang/src/main/java/nextflow/script/types/Channel.java @@ -25,6 +25,8 @@ import java.util.function.Predicate; import groovy.lang.Closure; +import groovy.transform.NamedParam; +import groovy.transform.NamedParams; import nextflow.script.dsl.Description; import nextflow.script.dsl.Operator; @@ -41,7 +43,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#branch) """) - Record branch(Closure closure); + Record branch(Function closure); @Operator @Description(""" @@ -49,7 +51,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#buffer) """) - Channel buffer(Closure openingCondition, Closure closingCondition); + Channel buffer(Closure openingCondition, Closure closingCondition); @Operator @Description(""" @@ -57,7 +59,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#collate) """) - Channel collate(int size, int step, boolean remainder); + Channel collate(int size, int step, boolean remainder); @Operator @Description(""" @@ -73,7 +75,8 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#collectfile) """) - Channel collectFile(Map opts, Closure closure); + Channel collectFile(Map opts, Closure closure); + Channel collectFile(Closure closure); @Operator @Description(""" @@ -81,9 +84,16 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#combine) """) - Channel combine(Map opts, Channel right); - Channel combine(Channel right); - Channel combine(Collection right); + Channel combine( + @NamedParams({ + @NamedParam(value = "by") + }) + Map opts, + Channel right + ); + Channel combine(Channel right); + Channel combine(Value right); + Channel combine(Collection right); @Operator @Description(""" @@ -91,7 +101,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#concat) """) - Channel concat(Channel... others); + Channel concat(Channel... others); @Operator @Description(""" @@ -107,7 +117,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#cross) """) - Channel cross(Channel right); + Channel cross(Channel right); @Operator @Description(""" @@ -115,7 +125,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#distinct) """) - Channel distinct(); + Channel distinct(); @Operator @Description(""" @@ -124,6 +134,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#dump) """) Channel dump(Map opts); + Channel dump(); @Operator @Description(""" @@ -158,7 +169,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#flatten) """) - Channel flatten(); + Channel flatten(); @Operator @Description(""" @@ -166,7 +177,16 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#grouptuple) """) - Channel groupTuple(Map opts); + Channel groupTuple( + @NamedParams({ + @NamedParam(value = "by"), + @NamedParam(value = "remainder", type = Boolean.class), + @NamedParam(value = "size", type = Integer.class), + @NamedParam(value = "sort") + }) + Map opts + ); + Channel groupTuple(); @Operator @Description(""" @@ -174,7 +194,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#ifempty) """) - Channel ifEmpty(Object value); + Channel ifEmpty(Object value); @Operator @Description(""" @@ -182,8 +202,17 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#join) """) - Channel join(Map opts, Channel right); - Channel join(Channel right); + Channel join( + @NamedParams({ + @NamedParam(value = "by"), + @NamedParam(value = "failOnDuplicate", type = Boolean.class), + @NamedParam(value = "failOnMismatch", type = Boolean.class), + @NamedParam(value = "remainder", type = Boolean.class) + }) + Map opts, + Channel right + ); + Channel join(Channel right); @Operator @Description(""" @@ -215,7 +244,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#merge) """) - Channel merge(Channel... others); + Channel merge(Channel... others); @Operator @Description(""" @@ -239,7 +268,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#multimap) """) - Record multiMap(Closure closure); + Record multiMap(Function closure); @Operator @Description(""" @@ -247,7 +276,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#randomsample) """) - Channel randomSample(int n, Long seed); + Channel randomSample(int n, Long seed); @Operator @Description(""" @@ -272,8 +301,8 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#splitcsv) """) - Channel splitCsv(Map opts); - Channel splitCsv(); + Channel splitCsv(Map opts); + Channel splitCsv(); @Operator @Description(""" @@ -281,8 +310,8 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#splitfasta) """) - Channel splitFasta(Map opts); - Channel splitFasta(); + Channel splitFasta(Map opts); + Channel splitFasta(); @Operator @Description(""" @@ -290,8 +319,8 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#splitfastq) """) - Channel splitFastq(Map opts); - Channel splitFastq(); + Channel splitFastq(Map opts); + Channel splitFastq(); @Operator @Description(""" @@ -299,8 +328,8 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#splitjson) """) - Channel splitJson(Map opts); - Channel splitJson(); + Channel splitJson(Map opts); + Channel splitJson(); @Operator @Description(""" @@ -308,8 +337,8 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#splittext) """) - Channel splitText(Map opts); - Channel splitText(); + Channel splitText(Map opts); + Channel splitText(); @Operator @Description(""" @@ -325,7 +354,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#sum) """) - Value sum(Closure transform); + Value sum(Closure transform); @Operator @Description(""" @@ -333,7 +362,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#take) """) - Channel take(int n); + Channel take(int n); @Operator @Description(""" @@ -341,7 +370,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#tap) """) - Channel tap(Closure holder); + Channel tap(Closure holder); @Operator @Description(""" @@ -349,7 +378,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#to;ist) """) - Value toList(); + Value toList(); @Operator @Description(""" @@ -357,7 +386,7 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#tosortedlist) """) - Value toSortedList(); + Value toSortedList(); @Operator @Description(""" @@ -365,7 +394,8 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#transpose) """) - Channel transpose(Map opts); + Channel transpose(Map opts); + Channel transpose(); @Operator @Description(""" @@ -373,8 +403,8 @@ public interface Channel { [Read more](https://nextflow.io/docs/latest/reference/operator.html#unique) """) - Channel unique(Closure comparator); - Channel unique(); + Channel unique(Closure comparator); + Channel unique(); @Operator @Description(""" diff --git a/modules/nf-lang/src/main/java/nextflow/script/types/TaskConfig.java b/modules/nf-lang/src/main/java/nextflow/script/types/TaskConfig.java index ca937f6e8c..afe941f899 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/types/TaskConfig.java +++ b/modules/nf-lang/src/main/java/nextflow/script/types/TaskConfig.java @@ -214,7 +214,7 @@ public interface TaskConfig { [Read more](https://nextflow.io/docs/latest/reference/process.html#ext) """) - Map getExt(); + Map getExt(); @Constant("fair") @Description(""" diff --git a/modules/nf-lang/src/main/java/nextflow/script/types/Types.java b/modules/nf-lang/src/main/java/nextflow/script/types/Types.java index 6209f6069a..9ba77a1ba8 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/types/Types.java +++ b/modules/nf-lang/src/main/java/nextflow/script/types/Types.java @@ -131,7 +131,7 @@ else if( type.isResolved() ) else builder.append(getName(type.getNameWithoutPackage())); - if( !placeholder && type.isUsingGenerics() ) { + if( !placeholder && type.getGenericsTypes() != null ) { builder.append('<'); genericsTypeNames(type.getGenericsTypes(), builder); builder.append('>'); diff --git a/modules/nf-lang/src/test/groovy/nextflow/script/control/ScriptResolveTest.groovy b/modules/nf-lang/src/test/groovy/nextflow/script/control/ScriptResolveTest.groovy index 6c8f9e8e2d..955e956efc 100644 --- a/modules/nf-lang/src/test/groovy/nextflow/script/control/ScriptResolveTest.groovy +++ b/modules/nf-lang/src/test/groovy/nextflow/script/control/ScriptResolveTest.groovy @@ -201,6 +201,45 @@ class ScriptResolveTest extends Specification { def 'should report an error for an undefined type' () { when: def errors = check( + '''\ + 'hello' as FooBar + ''' + ) + then: + errors.size() == 1 + errors[0].getStartLine() == 1 + errors[0].getStartColumn() == 1 + errors[0].getOriginalMessage() == '`FooBar` is not defined' + + when: + errors = check( + '''\ + [] as List + ''' + ) + then: + errors.size() == 1 + errors[0].getStartLine() == 1 + errors[0].getStartColumn() == 1 + errors[0].getOriginalMessage() == '`FooBar` is not defined' + + when: + errors = check( + '''\ + [:] as Map + ''' + ) + then: + errors.size() == 2 + errors[0].getStartLine() == 1 + errors[0].getStartColumn() == 1 + errors[0].getOriginalMessage() == '`Foo` is not defined' + errors[1].getStartLine() == 1 + errors[1].getStartColumn() == 1 + errors[1].getOriginalMessage() == '`Bar` is not defined' + + when: + errors = check( '''\ x = new FooBar() '''