Skip to content

Commit

Permalink
GROOVY-5881, GROOVY-6324: STC: implicit ".call" for property expression
Browse files Browse the repository at this point in the history
GROOVY-5705, GROOVY-11366: STC: implicit ".call" for variable expression
  • Loading branch information
eric-milles committed Sep 13, 2024
1 parent dcfe41d commit 1955413
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,20 +59,15 @@ Expression transformMethodCallExpression(final MethodCallExpression mce) {
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);
Expand All @@ -90,15 +83,6 @@ private static boolean isIsExtension(final MethodNode node) {
((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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -3649,46 +3648,13 @@ public void visitMethodCallExpression(final MethodCallExpression call) {
// 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);
Expand Down Expand Up @@ -3761,6 +3727,19 @@ && getType(fieldNode).equals(CLOSURE_TYPE)
}
}
}
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()) {
Expand Down
24 changes: 24 additions & 0 deletions src/test/groovy/transform/stc/ClosuresSTCTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,30 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
'''
}

// 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 }
Expand Down

0 comments on commit 1955413

Please sign in to comment.