From 6f6a4cd22a275ddfba062090aaf9401e96d313da Mon Sep 17 00:00:00 2001 From: Ben Sherman Date: Sat, 2 Aug 2025 10:35:13 -0500 Subject: [PATCH] Check for duplicate process/workflow calls in strict syntax Signed-off-by: Ben Sherman --- .../script/control/VariableScopeVisitor.java | 14 +++++++ .../script/control/ScriptResolveTest.groovy | 40 +++++++++++++++++++ 2 files changed, 54 insertions(+) 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 d46da88e28..b1d5ef5b76 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 @@ -16,7 +16,9 @@ package nextflow.script.control; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; import nextflow.script.ast.ASTNodeMarker; import nextflow.script.ast.AssignmentExpression; @@ -168,12 +170,15 @@ public void visitFeatureFlag(FeatureFlagNode node) { } } + private Set workflowComponents; + private boolean inWorkflowEmit; @Override public void visitWorkflow(WorkflowNode node) { vsc.pushScope(node.isEntry() ? EntryWorkflowDsl.class : WorkflowDsl.class); currentDefinition = node; + workflowComponents = new HashSet(); node.setVariableScope(currentScope()); declareWorkflowInputs(node.takes); @@ -185,6 +190,7 @@ public void visitWorkflow(WorkflowNode node) { visitWorkflowOutputs(node.emits, "emit"); visitWorkflowOutputs(node.publishers, "output"); + workflowComponents = null; currentDefinition = null; vsc.popScope(); } @@ -590,6 +596,14 @@ private void checkDataflowMethod(MethodCallExpression node, MethodNode mn) { vsc.addError(type + " cannot be called from within a closure", node); return; } + if( mn instanceof ProcessNode || mn instanceof WorkflowNode ) { + var name = node.getMethodAsString(); + if( workflowComponents.contains(name) ) { + var type = mn instanceof ProcessNode ? "Process" : "Workflow"; + vsc.addError(type + " `" + name + "` cannot be called more than once in the same workflow -- use an alias instead", node); + } + workflowComponents.add(name); + } } private static String dataflowMethodType(MethodNode mn) { 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..fe10d255dd 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 @@ -256,6 +256,28 @@ class ScriptResolveTest extends Specification { errors[0].getStartLine() == 10 errors[0].getStartColumn() == 9 errors[0].getOriginalMessage() == 'Processes cannot be called from within a closure' + + when: + errors = check( + '''\ + process hello { + script: + """ + echo hello + """ + } + + workflow { + hello() + hello() + } + ''' + ) + then: + errors.size() == 1 + errors[0].getStartLine() == 10 + errors[0].getStartColumn() == 5 + errors[0].getOriginalMessage() == 'Process `hello` cannot be called more than once in the same workflow -- use an alias instead' } def 'should report an error for an invalid workflow invocation' () { @@ -294,6 +316,24 @@ class ScriptResolveTest extends Specification { errors[0].getStartLine() == 6 errors[0].getStartColumn() == 9 errors[0].getOriginalMessage() == 'Workflows cannot be called from within a closure' + + when: + errors = check( + '''\ + workflow hello { + } + + workflow { + hello() + hello() + } + ''' + ) + then: + errors.size() == 1 + errors[0].getStartLine() == 6 + errors[0].getStartColumn() == 5 + errors[0].getOriginalMessage() == 'Workflow `hello` cannot be called more than once in the same workflow -- use an alias instead' } }