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 }