Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions src/main/java/nextflow/script/control/TypeCheckingVisitorEx.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@
import nextflow.script.types.Channel;
import nextflow.script.types.ParamsMap;
import nextflow.script.types.Record;
import nextflow.script.types.TaskConfig;
import nextflow.script.types.Tuple;
import nextflow.script.types.TypesEx;
import nextflow.script.types.Value;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CodeVisitorSupport;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
Expand Down Expand Up @@ -98,6 +100,7 @@ public class TypeCheckingVisitorEx extends ScriptVisitorSupport {
private static final ClassNode PATH_TYPE = ClassHelper.makeCached(Path.class);
private static final ClassNode PARAMS_TYPE = ClassHelper.makeCached(ParamsMap.class);
private static final ClassNode RECORD_TYPE = ClassHelper.makeCached(Record.class);
private static final ClassNode TASK_CONFIG_TYPE = ClassHelper.makeCached(TaskConfig.class);
private static final ClassNode TUPLE_TYPE = ClassHelper.makeCached(Tuple.class);
private static final ClassNode VALUE_TYPE = ClassHelper.makeCached(Value.class);

Expand Down Expand Up @@ -228,12 +231,15 @@ public void visitProcessV2(ProcessNodeV2 node) {
visitProcessDirectives(node.directives);
visit(node.stagers);
if( !(node.when instanceof EmptyExpression) )
addError("Process `when` section is not supported with static typing -- use conditinal logic in the calling workflow instead", node.when);
addError("Process `when` section is not supported with static typing -- use conditional logic in the calling workflow instead", node.when);
visit(node.when);
visit(node.exec);
visit(node.stub);
visit(node.outputs);
visitProcessTopics(node.topics);
if( !"exec".equals(node.type) )
checkTaskScriptProperties(node.exec);
checkTaskScriptProperties(node.stub);
}

private void visitProcessDirectives(Statement block) {
Expand Down Expand Up @@ -261,6 +267,37 @@ private void visitProcessTopics(Statement block) {
}
}

/**
* Report an error for each use of a restricted task property (e.g.
* `task.hash`, `task.workDir`) in the process script section.
*
* These properties depend on the task hash, which in turn is computed
* from the process script, so using them in the script creates a
* circular dependency.
*
* @param block
*/
private void checkTaskScriptProperties(Statement block) {
if( block != null )
block.visit(new TaskScriptPropertyVisitor());
}

private class TaskScriptPropertyVisitor extends CodeVisitorSupport {

private static final List<String> RESTRICTED_TASK_PROPERTIES = List.of("hash", "workDir");

@Override
public void visitPropertyExpression(PropertyExpression node) {
super.visitPropertyExpression(node);
var property = node.getPropertyAsString();
if( !RESTRICTED_TASK_PROPERTIES.contains(property) )
return;
if( !TASK_CONFIG_TYPE.equals(getType(node.getObjectExpression())) )
return;
addError("`task." + property + "` is not defined in the process `script:` section", node);
}
}

@Override
public void visitFunction(FunctionNode node) {
// visit parameters and code
Expand Down Expand Up @@ -388,7 +425,7 @@ private void applyTupleAssignment(TupleExpression target, ClassNode sourceType)
addError("Tuple assignment is not compatible with type " + TypesEx.getName(sourceType), target);
return;
}

var vars = target.getExpressions();
var gts = sourceType.getGenericsTypes();
if( gts == null )
Expand Down
73 changes: 73 additions & 0 deletions src/test/groovy/nextflow/script/types/TypeCheckingTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,79 @@ class TypeCheckingTest extends Specification {
)
}

def 'should report error for task.hash and task.workDir in the process script' () {
when:
def errors = getErrors(
'''\
nextflow.enable.types = true

process hello {
script:
"echo ${task.hash} > ${task.workDir}/out.txt"
}
'''
)
then:
errors.size() == 2
errors[0].getStartLine() == 5
errors[0].getOriginalMessage() == "`task.hash` is not defined in the process `script:` section"
errors[1].getStartLine() == 5
errors[1].getOriginalMessage() == "`task.workDir` is not defined in the process `script:` section"
}

def 'should report error for task.hash and task.workDir in the process stub' () {
when:
def errors = getErrors(
'''\
nextflow.enable.types = true

process hello {
script:
'echo hello'

stub:
"echo ${task.hash} > ${task.workDir}/out.txt"
}
'''
)
then:
errors.size() == 2
errors[0].getStartLine() == 8
errors[0].getOriginalMessage() == "`task.hash` is not defined in the process `script:` section"
errors[1].getStartLine() == 8
errors[1].getOriginalMessage() == "`task.workDir` is not defined in the process `script:` section"
}

def 'should allow other task properties in the process script' () {
expect:
check(
'''\
nextflow.enable.types = true

process hello {
script:
"echo ${task.cpus} ${task.process} ${task.attempt}"
}
''',
null
)
}

def 'should allow task.hash and task.workDir in the process exec section' () {
expect:
check(
'''\
nextflow.enable.types = true

process hello {
exec:
println "${task.hash} ${task.workDir}"
}
''',
null
)
}

def 'should resolve record type' () {
when:
def exp = parseExpression(
Expand Down
Loading