Skip to content

Commit 3e9a2c0

Browse files
authored
Add type checking utils (#6482)
Signed-off-by: Ben Sherman <[email protected]>
1 parent 0b7f180 commit 3e9a2c0

File tree

24 files changed

+626
-199
lines changed

24 files changed

+626
-199
lines changed

docs/reference/process.md

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ The `disk` directive allows you to define how much local disk storage the proces
649649

650650
```nextflow
651651
process hello {
652-
disk '2 GB'
652+
disk 2.GB
653653
654654
script:
655655
"""
@@ -692,17 +692,17 @@ The `errorStrategy` directive allows you to define how the process manages an er
692692

693693
The following error strategies are available:
694694

695-
`terminate` (default)
695+
`'terminate'` (default)
696696
: When a task fails, terminate the pipeline immediately and report an error. Pending and running jobs are killed.
697697

698-
`finish`
698+
`'finish'`
699699
: When a task fails, wait for submitted and running tasks to finish and then terminate the pipeline, reporting an error.
700700

701-
`ignore`
701+
`'ignore'`
702702
: 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.
703703
: See the {ref}`stdlib-namespaces-workflow` namespace for more information.
704704

705-
`retry`
705+
`'retry'`
706706
: When a task fails, retry it.
707707

708708
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 {
908908

909909
See also: [cpus](#cpus) and [memory](#memory).
910910

911-
(process-maxsubmitawait)=
912-
913-
### maxSubmitAwait
914-
915-
The `maxSubmitAwait` directive allows you to specify how long a task can remain in submission queue without being executed.
916-
Elapsed this time the task execution will fail.
917-
918-
When used along with `retry` error strategy, it can be useful to re-schedule the task to a difference queue or
919-
resource requirement. For example:
920-
921-
```nextflow
922-
process hello {
923-
errorStrategy 'retry'
924-
maxSubmitAwait '10 mins'
925-
maxRetries 3
926-
queue "${task.submitAttempt==1 ? 'spot-compute' : 'on-demand-compute'}"
927-
928-
script:
929-
"""
930-
your_command --here
931-
"""
932-
}
933-
```
934-
935-
In the above example the task is submitted to the `spot-compute` on the first attempt (`task.submitAttempt==1`). If the
936-
task execution does not start in the 10 minutes, a failure is reported and a new submission is attempted using the
937-
queue named `on-demand-compute`.
938-
939911
(process-maxerrors)=
940912

941913
### maxErrors
@@ -1003,6 +975,34 @@ There is a subtle but important difference between `maxRetries` and the `maxErro
1003975

1004976
See also: [errorStrategy](#errorstrategy) and [maxErrors](#maxerrors).
1005977

978+
(process-maxsubmitawait)=
979+
980+
### maxSubmitAwait
981+
982+
The `maxSubmitAwait` directive allows you to specify how long a task can remain in submission queue without being executed.
983+
Elapsed this time the task execution will fail.
984+
985+
When used along with `retry` error strategy, it can be useful to re-schedule the task to a difference queue or
986+
resource requirement. For example:
987+
988+
```nextflow
989+
process hello {
990+
errorStrategy 'retry'
991+
maxSubmitAwait 10.m
992+
maxRetries 3
993+
queue "${task.submitAttempt==1 ? 'spot-compute' : 'on-demand-compute'}"
994+
995+
script:
996+
"""
997+
your_command --here
998+
"""
999+
}
1000+
```
1001+
1002+
In the above example the task is submitted to the `spot-compute` on the first attempt (`task.submitAttempt==1`). If the
1003+
task execution does not start in the 10 minutes, a failure is reported and a new submission is attempted using the
1004+
queue named `on-demand-compute`.
1005+
10061006
(process-memory)=
10071007

10081008
### memory
@@ -1011,7 +1011,7 @@ The `memory` directive allows you to define how much memory the process is allow
10111011

10121012
```nextflow
10131013
process hello {
1014-
memory '2 GB'
1014+
memory 2.GB
10151015
10161016
script:
10171017
"""
@@ -1746,7 +1746,7 @@ The `time` directive allows you to define how long a process is allowed to run.
17461746

17471747
```nextflow
17481748
process hello {
1749-
time '1h'
1749+
time 1.h
17501750
17511751
script:
17521752
"""
@@ -1757,15 +1757,13 @@ process hello {
17571757

17581758
The following time unit suffixes can be used when specifying the duration value:
17591759

1760-
| Unit | Description |
1761-
| ------------------------------- | ------------ |
1762-
| `ms`, `milli`, `millis` | Milliseconds |
1763-
| `s`, `sec`, `second`, `seconds` | Seconds |
1764-
| `m`, `min`, `minute`, `minutes` | Minutes |
1765-
| `h`, `hour`, `hours` | Hours |
1766-
| `d`, `day`, `days` | Days |
1767-
1768-
Multiple units can be used in a single declaration, for example: `'1day 6hours 3minutes 30seconds'`
1760+
| Unit | Description |
1761+
| ---- | ------------ |
1762+
| `ms` | Milliseconds |
1763+
| `s` | Seconds |
1764+
| `m` | Minutes |
1765+
| `h` | Hours |
1766+
| `d` | Days |
17691767

17701768
See {ref}`stdlib-types-duration` for more information.
17711769

modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptCompiler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import nextflow.script.control.ResolveIncludeVisitor;
4343
import nextflow.script.control.ScriptResolveVisitor;
4444
import nextflow.script.control.ScriptToGroovyVisitor;
45+
import nextflow.script.control.StripTypesVisitor;
4546
import nextflow.script.control.TypeCheckingVisitor;
4647
import nextflow.script.parser.ScriptParserPluginFactory;
4748
import org.codehaus.groovy.ast.ASTNode;
@@ -290,6 +291,7 @@ private void analyze(SourceUnit source) {
290291

291292
// convert to Groovy
292293
new ScriptToGroovyVisitor(source).visit();
294+
new StripTypesVisitor(source).visitClass(cn);
293295
new PathCompareVisitor(source).visitClass(cn);
294296
new OpCriteriaVisitor(source).visitClass(cn);
295297
new GStringToStringVisitor(source).visitClass(cn);

modules/nextflow/src/test/groovy/nextflow/script/parser/v2/ScriptLoaderV2Test.groovy

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,26 @@ class ScriptLoaderV2Test extends Dsl2Spec {
232232
noExceptionThrown()
233233
}
234234

235+
def 'should strip unsupported type annotations' () {
236+
237+
given:
238+
def session = new Session()
239+
def parser = new ScriptLoaderV2(session)
240+
241+
def TEXT = '''
242+
// strip cast type
243+
['1', '1.fastq', '2.fastq'] as Tuple<String,String,String>
244+
245+
// strip type annotation in variable declaration
246+
def ch: Channel = channel.empty()
247+
'''
248+
249+
when:
250+
parser.parse(TEXT)
251+
parser.runScript()
252+
253+
then:
254+
noExceptionThrown()
255+
}
256+
235257
}

modules/nf-lang/src/main/java/nextflow/config/control/VariableScopeVisitor.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,11 @@ private void checkMethodCall(MethodCallExpression node) {
285285
if( !node.isImplicitThis() )
286286
return;
287287
var name = node.getMethodAsString();
288-
var defNode = vsc.findDslFunction(name, node);
289-
if( defNode != null )
290-
node.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, defNode);
288+
var methods = vsc.findDslFunction(name, node);
289+
if( methods.size() == 1 )
290+
node.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, methods.get(0));
291+
else if( !methods.isEmpty() )
292+
node.putNodeMetaData(ASTNodeMarker.METHOD_OVERLOADS, methods);
291293
else if( !KEYWORDS.contains(name) )
292294
vsc.addError("`" + name + "` is not defined", node.getMethod());
293295
}

modules/nf-lang/src/main/java/nextflow/script/ast/ASTNodeMarker.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public enum ASTNodeMarker {
2727
// denotes that an assignment is an implicit declaration
2828
IMPLICIT_DECLARATION,
2929

30+
// the inferred return type of a closure expression
31+
INFERRED_RETURN_TYPE,
32+
3033
// the inferred type of an expression
3134
INFERRED_TYPE,
3235

@@ -39,6 +42,9 @@ public enum ASTNodeMarker {
3942
// the verbatim text of a Groovy-style type annotation (ClassNode)
4043
LEGACY_TYPE,
4144

45+
// the list of candidate MethodNode's for a MethodCallExpression
46+
METHOD_OVERLOADS,
47+
4248
// the MethodNode targeted by a MethodCallExpression
4349
METHOD_TARGET,
4450

@@ -48,6 +54,9 @@ public enum ASTNodeMarker {
4854
// denotes a nullable type annotation (ClassNode)
4955
NULLABLE,
5056

57+
// the FieldNode targeted by a PropertyExpression
58+
PROPERTY_TARGET,
59+
5160
// the starting quote sequence of a string literal or gstring expression
5261
QUOTE_CHAR,
5362

modules/nf-lang/src/main/java/nextflow/script/ast/ASTUtils.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@
1818

1919
import java.util.Arrays;
2020
import java.util.Collections;
21+
import java.util.LinkedHashMap;
2122
import java.util.List;
23+
import java.util.Map;
2224
import java.util.Optional;
2325
import java.util.stream.Stream;
2426

27+
import groovy.transform.NamedParams;
2528
import org.codehaus.groovy.ast.ClassHelper;
2629
import org.codehaus.groovy.ast.ClassNode;
2730
import org.codehaus.groovy.ast.AnnotatedNode;
@@ -30,9 +33,12 @@
3033
import org.codehaus.groovy.ast.Parameter;
3134
import org.codehaus.groovy.ast.PropertyNode;
3235
import org.codehaus.groovy.ast.Variable;
36+
import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
37+
import org.codehaus.groovy.ast.expr.ClassExpression;
3338
import org.codehaus.groovy.ast.expr.ClosureExpression;
3439
import org.codehaus.groovy.ast.expr.ConstantExpression;
3540
import org.codehaus.groovy.ast.expr.Expression;
41+
import org.codehaus.groovy.ast.expr.ListExpression;
3642
import org.codehaus.groovy.ast.expr.MapExpression;
3743
import org.codehaus.groovy.ast.expr.MapEntryExpression;
3844
import org.codehaus.groovy.ast.expr.MethodCall;
@@ -145,6 +151,40 @@ public static List<MapEntryExpression> asNamedArgs(MethodCall call) {
145151
: Collections.emptyList();
146152
}
147153

154+
/**
155+
* Given a parameter with a @NamedParams annotation,
156+
* return the map of named params.
157+
*
158+
* @param parameter
159+
*/
160+
public static Map<String, AnnotationNode> asNamedParams(Parameter parameter) {
161+
var namedParams = new LinkedHashMap<String, AnnotationNode>();
162+
parameter.getAnnotations().stream()
163+
.filter(an -> an.getClassNode().getName().equals(NamedParams.class.getName()))
164+
.flatMap(an -> {
165+
var value = an.getMember("value");
166+
return value instanceof ListExpression le
167+
? le.getExpressions().stream()
168+
: Stream.empty();
169+
})
170+
.forEach((value) -> {
171+
if( !(value instanceof AnnotationConstantExpression) )
172+
return;
173+
var ace = (AnnotationConstantExpression) value;
174+
var namedParam = (AnnotationNode) ace.getValue();
175+
var name = namedParam.getMember("value").getText();
176+
namedParams.put(name, namedParam);
177+
});
178+
return namedParams;
179+
}
180+
181+
public static Parameter asNamedParam(AnnotationNode node) {
182+
var name = node.getMember("value").getText();
183+
var typeX = (ClassExpression) node.getMember("type");
184+
var type = typeX != null ? typeX.getType() : ClassHelper.dynamicType();
185+
return new Parameter(type, name);
186+
}
187+
148188
public static VariableExpression asVarX(Statement statement) {
149189
return statement instanceof ExpressionStatement es ? asVarX(es.getExpression()) : null;
150190
}

modules/nf-lang/src/main/java/nextflow/script/ast/OutputNode.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,20 @@
1515
*/
1616
package nextflow.script.ast;
1717

18-
import org.codehaus.groovy.ast.ASTNode;
1918
import org.codehaus.groovy.ast.ClassNode;
19+
import org.codehaus.groovy.ast.Parameter;
2020
import org.codehaus.groovy.ast.stmt.Statement;
2121

2222
/**
2323
* An output declaration.
2424
*
2525
* @author Ben Sherman <[email protected]>
2626
*/
27-
public class OutputNode extends ASTNode {
28-
public final String name;
29-
public final ClassNode type;
27+
public class OutputNode extends Parameter {
3028
public final Statement body;
3129

3230
public OutputNode(String name, ClassNode type, Statement body) {
33-
this.name = name;
34-
this.type = type;
31+
super(type, name);
3532
this.body = body;
3633
}
3734
}

modules/nf-lang/src/main/java/nextflow/script/ast/ProcessNodeV2.java

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,30 +69,28 @@ private static ClassNode dummyReturnType(Statement block) {
6969
if( outputs.size() == 1 ) {
7070
var first = outputs.get(0);
7171
var output = ((ExpressionStatement) first).getExpression();
72-
if( outputName(output) == null )
72+
if( outputTarget(output) == null )
7373
return output.getType();
7474
}
7575
var cn = new ClassNode(Record.class);
7676
outputs.stream()
7777
.map(stmt -> ((ExpressionStatement) stmt).getExpression())
78-
.map(output -> outputName(output))
79-
.filter(name -> name != null)
80-
.forEach((name) -> {
81-
var type = ClassHelper.dynamicType();
82-
var fn = new FieldNode(name, Modifier.PUBLIC, type, cn, null);
78+
.map(output -> outputTarget(output))
79+
.filter(target -> target != null)
80+
.forEach((target) -> {
81+
var fn = new FieldNode(target.getName(), Modifier.PUBLIC, target.getType(), cn, null);
8382
fn.setDeclaringClass(cn);
8483
cn.addField(fn);
8584
});
8685
return cn;
8786
}
8887

89-
private static String outputName(Expression output) {
88+
private static VariableExpression outputTarget(Expression output) {
9089
if( output instanceof VariableExpression ve ) {
91-
return ve.getName();
90+
return ve;
9291
}
93-
else if( output instanceof AssignmentExpression ae ) {
94-
var target = (VariableExpression)ae.getLeftExpression();
95-
return target.getName();
92+
if( output instanceof AssignmentExpression ae ) {
93+
return (VariableExpression)ae.getLeftExpression();
9694
}
9795
return null;
9896
}

0 commit comments

Comments
 (0)