Skip to content

Commit 1ee948b

Browse files
committed
Improve scope checking for operators and operator closures
Signed-off-by: Ben Sherman <[email protected]>
1 parent 32a17d6 commit 1ee948b

File tree

2 files changed

+41
-24
lines changed

2 files changed

+41
-24
lines changed

modules/compiler/src/main/java/script/control/VariableScopeChecker.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ private Variable findDslVariable(ClassNode cn, String name, ASTNode node) {
184184
while( cn != null ) {
185185
for( var mn : cn.getMethods() ) {
186186
// processes, workflows, and operators can be accessed as variables, e.g. with pipes
187-
if( isCallableVariable(mn) && name.equals(mn.getName()) ) {
187+
if( isDataflowMethod(mn) && name.equals(mn.getName()) ) {
188188
return wrapMethodAsVariable(mn, name);
189189
}
190190
// built-in variables are methods annotated as @Constant
@@ -209,15 +209,15 @@ private Variable findDslVariable(ClassNode cn, String name, ASTNode node) {
209209
return null;
210210
}
211211

212-
private boolean isCallableVariable(MethodNode mn) {
213-
if( mn instanceof ProcessNode || mn instanceof WorkflowNode )
214-
return true;
215-
if( findAnnotation(mn, Operator.class).isPresent() )
216-
return true;
217-
return false;
212+
public static boolean isDataflowMethod(MethodNode mn) {
213+
return mn instanceof ProcessNode || mn instanceof WorkflowNode || isOperator(mn);
218214
}
219215

220-
private PropertyNode wrapMethodAsVariable(MethodNode mn, String name) {
216+
public static boolean isOperator(MethodNode mn) {
217+
return findAnnotation(mn, Operator.class).isPresent();
218+
}
219+
220+
private static PropertyNode wrapMethodAsVariable(MethodNode mn, String name) {
221221
var cn = mn.getDeclaringClass();
222222
var fn = new FieldNode(name, mn.getModifiers() & 0xF, mn.getReturnType(), cn, null);
223223
fn.setHasNoRealSourcePosition(true);

modules/compiler/src/main/java/script/control/VariableScopeVisitor.java

+33-16
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ public void declare() {
108108

109109
private void declareInclude(IncludeNode node) {
110110
for( var module : node.modules ) {
111+
if( module.getTarget() == null )
112+
continue;
111113
var name = module.getNameOrAlias();
112114
var otherInclude = vsc.getInclude(name);
113115
if( otherInclude != null )
@@ -312,9 +314,9 @@ private MethodCallExpression checkDirective(Statement node, String typeLabel, bo
312314
return null;
313315
}
314316
var name = call.getMethodAsString();
315-
var defNode = vsc.findDslFunction(name, call.getMethod());
316-
if( defNode != null )
317-
call.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, defNode);
317+
var mn = vsc.findDslFunction(name, call.getMethod());
318+
if( mn != null )
319+
call.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, mn);
318320
else
319321
vsc.addError("Invalid " + typeLabel + " `" + name + "`", node);
320322
return call;
@@ -459,8 +461,6 @@ private boolean declareAssignedVariable(VariableExpression ve) {
459461
if( variable != null ) {
460462
if( isDslVariable(variable) )
461463
vsc.addError("Built-in variable cannot be re-assigned", ve);
462-
else
463-
checkExternalWriteInAsyncClosure(ve, variable);
464464
ve.setAccessedVariable(variable);
465465
return false;
466466
}
@@ -523,9 +523,8 @@ private void checkExternalWriteInAsyncClosure(VariableExpression target, Variabl
523523
return;
524524
var scope = currentClosure.getVariableScope();
525525
var name = variable.getName();
526-
// TODO: should apply only to operator closures
527-
if( scope.isReferencedLocalVariable(name) && scope.getDeclaredVariable(name) == null )
528-
vsc.addParanoidWarning("Mutating an external variable in an operator closure may lead to a race condition", target, "External variable declared here", (ASTNode) variable);
526+
if( inOperatorCall && scope.isReferencedLocalVariable(name) && scope.getDeclaredVariable(name) == null )
527+
sourceUnit.addWarning("Mutating an external variable in an operator closure can lead to a race condition", target);
529528
}
530529

531530
// expressions
@@ -537,6 +536,8 @@ private void checkExternalWriteInAsyncClosure(VariableExpression target, Variabl
537536
"while"
538537
);
539538

539+
private boolean inOperatorCall;
540+
540541
@Override
541542
public void visitMethodCallExpression(MethodCallExpression node) {
542543
var target = checkSetAssignment(node);
@@ -546,7 +547,15 @@ public void visitMethodCallExpression(MethodCallExpression node) {
546547
return;
547548
}
548549
checkMethodCall(node);
550+
var ioc = inOperatorCall;
551+
inOperatorCall = isOperatorCall(node);
549552
super.visitMethodCallExpression(node);
553+
inOperatorCall = ioc;
554+
}
555+
556+
private static boolean isOperatorCall(MethodCallExpression node) {
557+
return node.getNodeMetaData(ASTNodeMarker.METHOD_TARGET) instanceof MethodNode mn
558+
&& VariableScopeChecker.isOperator(mn);
550559
}
551560

552561
/**
@@ -568,30 +577,38 @@ private void checkMethodCall(MethodCallExpression node) {
568577
if( !node.isImplicitThis() )
569578
return;
570579
var name = node.getMethodAsString();
571-
var defNode = vsc.findDslFunction(name, node);
572-
if( defNode != null ) {
573-
if( defNode instanceof ProcessNode || defNode instanceof WorkflowNode )
574-
checkProcessOrWorkflowCall(node, defNode);
575-
node.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, defNode);
580+
var mn = vsc.findDslFunction(name, node);
581+
if( mn != null ) {
582+
if( VariableScopeChecker.isDataflowMethod(mn) )
583+
checkDataflowMethod(node, mn);
584+
node.putNodeMetaData(ASTNodeMarker.METHOD_TARGET, mn);
576585
}
577586
else if( !KEYWORDS.contains(name) ) {
578587
vsc.addError("`" + name + "` is not defined", node.getMethod());
579588
}
580589
}
581590

582-
private void checkProcessOrWorkflowCall(MethodCallExpression node, MethodNode defNode) {
591+
private void checkDataflowMethod(MethodCallExpression node, MethodNode mn) {
583592
if( !(currentDefinition instanceof WorkflowNode) ) {
584-
var type = defNode instanceof ProcessNode ? "Processes" : "Workflows";
593+
var type = dataflowMethodType(mn);
585594
vsc.addError(type + " can only be called from a workflow", node);
586595
return;
587596
}
588597
if( currentClosure != null ) {
589-
var type = defNode instanceof ProcessNode ? "Processes" : "Workflows";
598+
var type = dataflowMethodType(mn);
590599
vsc.addError(type + " cannot be called from within a closure", node);
591600
return;
592601
}
593602
}
594603

604+
private static String dataflowMethodType(MethodNode mn) {
605+
if( mn instanceof ProcessNode )
606+
return "Processes";
607+
if( mn instanceof WorkflowNode )
608+
return "Workflows";
609+
return "Operators";
610+
}
611+
595612
@Override
596613
public void visitDeclarationExpression(DeclarationExpression node) {
597614
visit(node.getRightExpression());

0 commit comments

Comments
 (0)