Skip to content

Commit 4f7ad43

Browse files
bentshermanadamrtalbot
authored andcommitted
nf-lang: Add type checking utils (#5913)
Signed-off-by: Ben Sherman <[email protected]> Signed-off-by: adamrtalbot <[email protected]>
1 parent 99ed251 commit 4f7ad43

File tree

15 files changed

+383
-121
lines changed

15 files changed

+383
-121
lines changed

modules/nf-lang/src/main/antlr/ConfigParser.g4

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ multipleAssignmentStatement
238238
;
239239

240240
assignmentStatement
241-
: left=expression nls
241+
: target=expression nls
242242
op=(ASSIGN
243243
| ADD_ASSIGN
244244
| SUB_ASSIGN
@@ -254,7 +254,7 @@ assignmentStatement
254254
| POWER_ASSIGN
255255
| ELVIS_ASSIGN
256256
) nls
257-
right=expression
257+
source=expression
258258
;
259259

260260
// -- expression statement
@@ -383,7 +383,7 @@ indexPropertyArgs
383383
: LBRACK expressionList RBRACK
384384
;
385385

386-
// -- variable, type identifiers
386+
// -- variable, function, type identifiers
387387
identifier
388388
: Identifier
389389
| CapitalizedIdentifier

modules/nf-lang/src/main/antlr/ScriptParser.g4

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ multipleAssignmentStatement
349349
;
350350

351351
assignmentStatement
352-
: left=expression nls
352+
: target=expression nls
353353
op=(ASSIGN
354354
| ADD_ASSIGN
355355
| SUB_ASSIGN
@@ -365,7 +365,7 @@ assignmentStatement
365365
| POWER_ASSIGN
366366
| ELVIS_ASSIGN
367367
) nls
368-
right=expression
368+
source=expression
369369
;
370370

371371
// -- expression statement
@@ -497,7 +497,7 @@ indexPropertyArgs
497497
: LBRACK expressionList RBRACK
498498
;
499499

500-
// -- variable, type identifiers
500+
// -- variable, function, type identifiers
501501
identifier
502502
: Identifier
503503
| CapitalizedIdentifier

modules/nf-lang/src/main/java/nextflow/config/parser/ConfigAstBuilder.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -501,36 +501,36 @@ private void checkInvalidVarName(String name, ASTNode node) {
501501
}
502502

503503
private Statement assignment(MultipleAssignmentStatementContext ctx) {
504-
var left = variableNames(ctx.variableNames());
505-
var right = expression(ctx.expression());
506-
return stmt(ast( assignX(left, right), ctx ));
504+
var target = variableNames(ctx.variableNames());
505+
var source = expression(ctx.expression());
506+
return stmt(ast( assignX(target, source), ctx ));
507507
}
508508

509509
private Statement assignment(AssignmentStatementContext ctx) {
510-
var left = expression(ctx.left);
511-
if( left instanceof VariableExpression && isInsideParentheses(left) ) {
512-
if( left.<Number>getNodeMetaData(ASTNodeMarker.INSIDE_PARENTHESES_LEVEL).intValue() > 1 )
510+
var target = expression(ctx.target);
511+
if( target instanceof VariableExpression && isInsideParentheses(target) ) {
512+
if( target.<Number>getNodeMetaData(ASTNodeMarker.INSIDE_PARENTHESES_LEVEL).intValue() > 1 )
513513
throw createParsingFailedException("Nested parenthesis is not allowed in multiple assignment, e.g. ((a)) = b", ctx);
514514

515-
var tuple = ast( new TupleExpression(left), ctx.left );
516-
return stmt(ast( binX(tuple, token(ctx.op), expression(ctx.right)), ctx ));
515+
var tuple = ast( new TupleExpression(target), ctx.target );
516+
return stmt(ast( binX(tuple, token(ctx.op), expression(ctx.source)), ctx ));
517517
}
518518

519-
if ( isAssignmentLhsValid(left) )
520-
return stmt(ast( binX(left, token(ctx.op), expression(ctx.right)), ctx ));
519+
if ( isValidAssignmentTarget(target) )
520+
return stmt(ast( binX(target, token(ctx.op), expression(ctx.source)), ctx ));
521521

522522
throw createParsingFailedException("Invalid assignment target -- must be a variable, index, or property expression", ctx);
523523
}
524524

525-
private boolean isAssignmentLhsValid(Expression left) {
525+
private boolean isValidAssignmentTarget(Expression target) {
526526
// e.g. p = 123
527-
if( left instanceof VariableExpression && !isInsideParentheses(left) )
527+
if( target instanceof VariableExpression && !isInsideParentheses(target) )
528528
return true;
529529
// e.g. obj.p = 123
530-
if( left instanceof PropertyExpression )
530+
if( target instanceof PropertyExpression )
531531
return true;
532532
// e.g. map[a] = 123 OR map['a'] = 123 OR map["$a"] = 123
533-
if( left instanceof BinaryExpression be && be.getOperation().getType() == Types.LEFT_SQUARE_BRACKET )
533+
if( target instanceof BinaryExpression be && be.getOperation().getType() == Types.LEFT_SQUARE_BRACKET )
534534
return true;
535535
return false;
536536
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ public enum ASTNodeMarker {
2424
// denotes a fully-qualified type annotation (ClassNode)
2525
FULLY_QUALIFIED,
2626

27-
// denotes that a variable declaration was inferred from an assignment
27+
// denotes that an assignment is an implicit declaration
2828
IMPLICIT_DECLARATION,
2929

30+
// the inferred type of an expression
31+
INFERRED_TYPE,
32+
3033
// the number of enclosing parentheses around an expression
3134
INSIDE_PARENTHESES_LEVEL,
3235

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.codehaus.groovy.ast.expr.MethodCall;
3737
import org.codehaus.groovy.ast.expr.MethodCallExpression;
3838
import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
39+
import org.codehaus.groovy.ast.expr.PropertyExpression;
3940
import org.codehaus.groovy.ast.expr.TupleExpression;
4041
import org.codehaus.groovy.ast.expr.VariableExpression;
4142
import org.codehaus.groovy.ast.stmt.BlockStatement;
@@ -152,6 +153,18 @@ public static MethodNode asMethodVariable(Variable variable) {
152153
return null;
153154
}
154155

156+
/**
157+
* Given a property expression which represents a process or workflow
158+
* output, return the underlying process or workflow.
159+
*
160+
* @param node
161+
*/
162+
public static MethodNode asMethodOutput(PropertyExpression node) {
163+
if( node.getObjectExpression() instanceof VariableExpression ve && "out".equals(node.getPropertyAsString()) )
164+
return asMethodVariable(ve.getAccessedVariable());
165+
return null;
166+
}
167+
155168
/**
156169
* Given an annotated node (e.g. class, field, method), Find the first
157170
* annotation of the given type in the node's list of annotations.

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,24 @@
1515
*/
1616
package nextflow.script.ast;
1717

18+
import java.lang.reflect.Modifier;
19+
import java.util.Optional;
20+
21+
import nextflow.script.types.Channel;
22+
import nextflow.script.types.NamedTuple;
23+
import org.codehaus.groovy.ast.ClassHelper;
1824
import org.codehaus.groovy.ast.ClassNode;
25+
import org.codehaus.groovy.ast.FieldNode;
1926
import org.codehaus.groovy.ast.MethodNode;
2027
import org.codehaus.groovy.ast.Parameter;
2128
import org.codehaus.groovy.ast.expr.Expression;
29+
import org.codehaus.groovy.ast.expr.MethodCallExpression;
30+
import org.codehaus.groovy.ast.expr.VariableExpression;
2231
import org.codehaus.groovy.ast.stmt.EmptyStatement;
2332
import org.codehaus.groovy.ast.stmt.Statement;
2433

34+
import static nextflow.script.ast.ASTUtils.*;
35+
2536
/**
2637
* A process definition.
2738
*
@@ -37,7 +48,7 @@ public class ProcessNode extends MethodNode {
3748
public final Statement stub;
3849

3950
public ProcessNode(String name, Statement directives, Statement inputs, Statement outputs, Expression when, String type, Statement exec, Statement stub) {
40-
super(name, 0, null, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
51+
super(name, 0, dummyReturnType(outputs), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
4152
this.directives = directives;
4253
this.inputs = inputs;
4354
this.outputs = outputs;
@@ -46,4 +57,32 @@ public ProcessNode(String name, Statement directives, Statement inputs, Statemen
4657
this.exec = exec;
4758
this.stub = stub;
4859
}
60+
61+
private static ClassNode dummyReturnType(Statement outputs) {
62+
var cn = new ClassNode(NamedTuple.class);
63+
asDirectives(outputs)
64+
.map(call -> emitName(call))
65+
.filter(name -> name != null)
66+
.forEach((name) -> {
67+
var type = ClassHelper.makeCached(Channel.class);
68+
var fn = new FieldNode(name, Modifier.PUBLIC, type, cn, null);
69+
fn.setDeclaringClass(cn);
70+
cn.addField(fn);
71+
});
72+
return cn;
73+
}
74+
75+
private static String emitName(MethodCallExpression output) {
76+
return Optional.of(output)
77+
.flatMap(call -> Optional.ofNullable(asNamedArgs(call)))
78+
.flatMap(namedArgs ->
79+
namedArgs.stream()
80+
.filter(entry -> "emit".equals(entry.getKeyExpression().getText()))
81+
.findFirst()
82+
)
83+
.flatMap(entry -> Optional.ofNullable(
84+
entry.getValueExpression() instanceof VariableExpression ve ? ve.getName() : null
85+
))
86+
.orElse(null);
87+
}
4988
}

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,24 @@
1515
*/
1616
package nextflow.script.ast;
1717

18+
import java.lang.reflect.Modifier;
19+
import java.util.Optional;
20+
21+
import nextflow.script.types.Channel;
22+
import nextflow.script.types.NamedTuple;
23+
import org.codehaus.groovy.ast.ClassHelper;
1824
import org.codehaus.groovy.ast.ClassNode;
25+
import org.codehaus.groovy.ast.FieldNode;
1926
import org.codehaus.groovy.ast.MethodNode;
2027
import org.codehaus.groovy.ast.Parameter;
28+
import org.codehaus.groovy.ast.expr.Expression;
29+
import org.codehaus.groovy.ast.expr.VariableExpression;
2130
import org.codehaus.groovy.ast.stmt.EmptyStatement;
31+
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
2232
import org.codehaus.groovy.ast.stmt.Statement;
2333

34+
import static nextflow.script.ast.ASTUtils.*;
35+
2436
/**
2537
* A workflow definition.
2638
*
@@ -33,7 +45,7 @@ public class WorkflowNode extends MethodNode {
3345
public final Statement publishers;
3446

3547
public WorkflowNode(String name, Statement takes, Statement main, Statement emits, Statement publishers) {
36-
super(name, 0, null, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
48+
super(name, 0, dummyReturnType(emits), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
3749
this.takes = takes;
3850
this.main = main;
3951
this.emits = emits;
@@ -43,4 +55,30 @@ public WorkflowNode(String name, Statement takes, Statement main, Statement emit
4355
public boolean isEntry() {
4456
return getName() == null;
4557
}
58+
59+
private static ClassNode dummyReturnType(Statement emits) {
60+
var cn = new ClassNode(NamedTuple.class);
61+
asBlockStatements(emits).stream()
62+
.map(stmt -> ((ExpressionStatement) stmt).getExpression())
63+
.map(emit -> emitName(emit))
64+
.filter(name -> name != null)
65+
.forEach((name) -> {
66+
var type = ClassHelper.dynamicType();
67+
var fn = new FieldNode(name, Modifier.PUBLIC, type, cn, null);
68+
fn.setDeclaringClass(cn);
69+
cn.addField(fn);
70+
});
71+
return cn;
72+
}
73+
74+
private static String emitName(Expression emit) {
75+
if( emit instanceof VariableExpression ve ) {
76+
return ve.getName();
77+
}
78+
else if( emit instanceof AssignmentExpression ae ) {
79+
var left = (VariableExpression)ae.getLeftExpression();
80+
return left.getName();
81+
}
82+
return null;
83+
}
4684
}

modules/nf-lang/src/main/java/nextflow/script/control/ResolveVisitor.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,6 @@ protected Expression transformDeclarationExpression(DeclarationExpression de) {
365365
return de;
366366
var result = new DeclarationExpression(left, de.getOperation(), right);
367367
result.setDeclaringClass(de.getDeclaringClass());
368-
result.copyNodeMetaData(de);
369368
return result;
370369
}
371370

modules/nf-lang/src/main/java/nextflow/script/control/ScriptToGroovyVisitor.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.Set;
2020
import java.util.stream.Collectors;
2121

22-
import nextflow.script.ast.ASTNodeMarker;
2322
import nextflow.script.ast.AssignmentExpression;
2423
import nextflow.script.ast.FeatureFlagNode;
2524
import nextflow.script.ast.FunctionNode;
@@ -36,7 +35,6 @@
3635
import org.codehaus.groovy.ast.expr.BinaryExpression;
3736
import org.codehaus.groovy.ast.expr.ClosureExpression;
3837
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
39-
import org.codehaus.groovy.ast.expr.DeclarationExpression;
4038
import org.codehaus.groovy.ast.expr.EmptyExpression;
4139
import org.codehaus.groovy.ast.expr.Expression;
4240
import org.codehaus.groovy.ast.expr.MethodCallExpression;
@@ -404,17 +402,6 @@ private void visitOutputTargets(Statement body) {
404402
}
405403
}
406404

407-
// see: VariableScopeVisitor::visitExpressionStatement()
408-
@Override
409-
public void visitExpressionStatement(ExpressionStatement node) {
410-
var exp = node.getExpression();
411-
if( exp instanceof DeclarationExpression de && de.getNodeMetaData(ASTNodeMarker.IMPLICIT_DECLARATION) != null ) {
412-
var result = new AssignmentExpression(de.getLeftExpression(), de.getRightExpression());
413-
result.setSourcePosition(de);
414-
node.setExpression(result);
415-
}
416-
}
417-
418405
private String getSourceText(Statement node) {
419406
var builder = new StringBuilder();
420407
var colx = node.getColumnNumber();

0 commit comments

Comments
 (0)