GROOVY-5881, GROOVY-6324: STC: implicit ".call" for property expression
GROOVY-5705, GROOVY-11366: STC: implicit ".call" for variable expression
diff --git a/src/main/java/org/codehaus/groovy/transform/sc/transformers/MethodCallExpressionTransformer.java b/src/main/java/org/codehaus/groovy/transform/sc/transformers/MethodCallExpressionTransformer.java
index d24c325..2f74882 100644
--- a/src/main/java/org/codehaus/groovy/transform/sc/transformers/MethodCallExpressionTransformer.java
+++ b/src/main/java/org/codehaus/groovy/transform/sc/transformers/MethodCallExpressionTransformer.java
@@ -28,10 +28,8 @@
import org.codehaus.groovy.classgen.asm.MopWriter;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.transform.stc.ExtensionMethodNode;
-import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor;
import org.codehaus.groovy.transform.stc.StaticTypesMarker;
-import static org.apache.groovy.ast.tools.ClassNodeUtils.getField;
import static org.codehaus.groovy.classgen.AsmClassGenerator.argumentSize;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
@@ -61,20 +59,15 @@
return transformMethodCallExpression(transformToMopSuperCall((ClassNode) superCallReceiver, mce));
}
- if (isCallOnClosure(mce)) {
- var field = getField(scTransformer.getClassNode(), mce.getMethodAsString());
- if (field != null) {
- var closureFieldCall = new MethodCallExpression(
- new VariableExpression(field),
- "call",
- scTransformer.transform(arguments));
- // implicit-this "field(args)" expression has no place for safe, spread-safe, or type arguments
- closureFieldCall.setImplicitThis(false);
- closureFieldCall.setMethodTarget(mce.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET));
- closureFieldCall.setSourcePosition(mce);
- closureFieldCall.copyNodeMetaData(mce);
- return closureFieldCall;
- }
+ Expression callable = mce.getNodeMetaData("callable property");
+ if (callable != null) {
+ var callableCall = new MethodCallExpression(callable, "call", scTransformer.transform(arguments));
+ // "callable(args)" expression has no place for safe, spread-safe or type arguments
+ callableCall.setImplicitThis(false);
+ callableCall.setMethodTarget(mce.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET));
+ callableCall.setSourcePosition(mce);
+ callableCall.copyNodeMetaData(mce);
+ return callableCall;
}
return scTransformer.superTransform(mce);
@@ -90,15 +83,6 @@
((ExtensionMethodNode) node).getExtensionMethodNode().getDeclaringClass().getName());
}
- private static boolean isCallOnClosure(final MethodCallExpression expr) {
- MethodNode target = expr.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET);
- return expr.isImplicitThis()
- && !"call".equals(expr.getMethodAsString())
- && (target == StaticTypeCheckingVisitor.CLOSURE_CALL_VARGS
- || target == StaticTypeCheckingVisitor.CLOSURE_CALL_NO_ARG
- || target == StaticTypeCheckingVisitor.CLOSURE_CALL_ONE_ARG);
- }
-
private static MethodCallExpression transformToMopSuperCall(final ClassNode superType, final MethodCallExpression expr) {
MethodNode mn = expr.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET);
String mopName = MopWriter.getMopMethodName(mn, false);
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index b7c5675..3a6d96d 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -145,7 +145,6 @@
import java.util.stream.Collectors;
import java.util.stream.IntStream;
-import static org.apache.groovy.ast.tools.ClassNodeUtils.getField;
import static org.apache.groovy.ast.tools.MethodNodeUtils.withDefaultArgumentMethods;
import static org.apache.groovy.util.BeanUtils.capitalize;
import static org.apache.groovy.util.BeanUtils.decapitalize;
@@ -3649,46 +3648,13 @@
// visit functional arguments *after* target method selection
visitMethodCallArguments(receiver, argumentList, false, null);
- boolean isThisObjectExpression = isThisExpression(objectExpression);
- boolean isCallOnClosure = false;
- FieldNode fieldNode = null;
- switch (name) {
- case "call":
- case "doCall":
- if (!isThisObjectExpression) {
- isCallOnClosure = receiver.equals(CLOSURE_TYPE);
- }
- default:
- if (isThisObjectExpression) {
- // GROOVY-5705, GROOVY-11366: "this.x(...)" could refer to field
- if (!typeCheckingContext.isInStaticContext) {
- fieldNode = getField(receiver, name);
- } else {
- fieldNode = getField(receiver, name, FieldNode::isStatic);
- }
- if (fieldNode != null
- && getType(fieldNode).equals(CLOSURE_TYPE)
- && !receiver.hasPossibleMethod(name, callArguments)) {
- isCallOnClosure = true;
- }
- }
- }
-
try {
+ boolean isThisObjectExpression = isThisExpression(objectExpression);
ClassNode[] args = getArgumentTypes(argumentList);
boolean functorsVisited = false;
- if (isCallOnClosure) {
- if (fieldNode != null) {
- GenericsType[] genericsTypes = getType(fieldNode).getGenericsTypes();
- if (genericsTypes != null) {
- Parameter[] parameters = fieldNode.getNodeMetaData(CLOSURE_ARGUMENTS);
- if (parameters != null) {
- typeCheckClosureCall(callArguments, args, parameters);
- }
- ClassNode closureReturnType = genericsTypes[0].getType();
- storeType(call, closureReturnType);
- }
- } else if (objectExpression instanceof VariableExpression) {
+ if (!isThisObjectExpression && receiver.equals(CLOSURE_TYPE)
+ && (name.equals("call") || name.equals("doCall"))) {
+ if (objectExpression instanceof VariableExpression) {
Variable variable = findTargetVariable((VariableExpression) objectExpression);
if (variable instanceof ASTNode) {
Parameter[] parameters = ((ASTNode) variable).getNodeMetaData(CLOSURE_ARGUMENTS);
@@ -3761,6 +3727,19 @@
}
}
}
+ if (mn.isEmpty() && !name.equals("call")) {
+ // GROOVY-5705, GROOVY-5881, GROOVY-6324, GROOVY-11366: closure property
+ var property = propX(objectExpression, call.getMethod(), call.isSafe());
+ property.setImplicitThis(call.isImplicitThis());
+ if (existsProperty(property, true)
+ && getType(property).equals(CLOSURE_TYPE)) {
+ chosenReceiver = Receiver.make(getType(property));
+ call.putNodeMetaData("callable property", property);
+ List<Expression> list = argumentList.getExpressions();
+ int nArgs = list.stream().noneMatch(e -> e instanceof SpreadExpression) ? list.size() : -1;
+ mn = List.of(nArgs == 0 ? CLOSURE_CALL_NO_ARG : nArgs == 1 ? CLOSURE_CALL_ONE_ARG : CLOSURE_CALL_VARGS);
+ }
+ }
if (mn.isEmpty()) {
mn = extension.handleMissingMethod(receiver, name, argumentList, args, call);
if (mn == null || mn.isEmpty()) {
diff --git a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
index ddc8066..a789332 100644
--- a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
@@ -229,6 +229,30 @@
'''
}
+ // GROOVY-5881
+ void testCallClosure21() {
+ assertScript '''
+ Map<Integer, Closure<Integer>> m = [1: { int i -> i }, 2: Closure.IDENTITY]
+ int result = 0
+ for (e in m) {
+ def c = e.value
+ def x = c(e.key)
+ assert x == e.key
+ result += e.value(e.key)
+ }
+ assert result == 3
+ '''
+ }
+
+ // GROOVY-6324
+ void testCallClosure22() {
+ assertScript '''
+ class Car { Closure<String> model }
+ def c = new Car(model: {->'Tesla'})
+ assert c.model() == 'Tesla'
+ '''
+ }
+
void testClosureReturnTypeInference1() {
assertScript '''
def c = { int a, int b -> return a + b }