You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2021/04/04 17:51:00 UTC

[groovy] 01/01: GROOVY-9971: STC: support "Closure c = { -> return "$v" }"

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY-9971
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 05c160b327dfa7f2489c44e85532121acc930f0e
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sun Apr 4 12:50:38 2021 -0500

    GROOVY-9971: STC: support "Closure<String> c = { -> return "$v" }"
    
    - convert GString to String at the point of return
---
 .../groovy/classgen/asm/ClosureWriter.java         | 31 ++++++++++++----------
 .../transform/stc/StaticTypeCheckingVisitor.java   | 25 +++++++++++++----
 .../groovy/transform/stc/ClosuresSTCTest.groovy    | 15 +++++++++++
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 24 +----------------
 4 files changed, 53 insertions(+), 42 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java
index 13e1370..607d6e3 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java
@@ -38,6 +38,7 @@ import org.codehaus.groovy.ast.expr.FieldExpression;
 import org.codehaus.groovy.ast.expr.TupleExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.tools.GenericsUtils;
 import org.codehaus.groovy.classgen.AsmClassGenerator;
 import org.codehaus.groovy.classgen.Verifier;
 import org.objectweb.asm.MethodVisitor;
@@ -57,6 +58,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_RETURN_TYPE;
 import static org.objectweb.asm.Opcodes.ACC_FINAL;
 import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
 import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
@@ -188,7 +190,6 @@ public class ClosureWriter {
         ClassNode classNode = controller.getClassNode();
         ClassNode outerClass = controller.getOutermostClass();
         String name = genClosureClassName();
-        boolean staticMethodOrInStaticClass = controller.isStaticMethod() || classNode.isStaticClass();
 
         Parameter[] parameters = expression.getParameters();
         if (parameters == null) {
@@ -204,20 +205,22 @@ public class ClosureWriter {
         Parameter[] localVariableParams = getClosureSharedVariables(expression);
         removeInitialValues(localVariableParams);
 
+        // GROOVY-9971: closure return type is mapped to Groovy cast by classgen
+        ClassNode returnType = expression.getNodeMetaData(INFERRED_RETURN_TYPE);
+        if (returnType == null) returnType = ClassHelper.OBJECT_TYPE; // not STC or unknown path
+        else if (returnType.isPrimaryClassNode()) returnType = returnType.getPlainNodeReference();
+        else if (ClassHelper.isPrimitiveType(returnType)) returnType = ClassHelper.getWrapper(returnType);
+        else if (GenericsUtils.hasUnresolvedGenerics(returnType)) returnType = GenericsUtils.nonGeneric(returnType);
+
         InnerClassNode answer = new InnerClassNode(classNode, name, modifiers, ClassHelper.CLOSURE_TYPE.getPlainNodeReference());
         answer.setEnclosingMethod(controller.getMethodNode());
+        answer.setScriptBody(controller.isInScriptBody());
+        answer.setSourcePosition(expression);
+        answer.setStaticClass(controller.isStaticMethod() || classNode.isStaticClass());
         answer.setSynthetic(true);
         answer.setUsingGenerics(outerClass.isUsingGenerics());
-        answer.setSourcePosition(expression);
 
-        if (staticMethodOrInStaticClass) {
-            answer.setStaticClass(true);
-        }
-        if (controller.isInScriptBody()) {
-            answer.setScriptBody(true);
-        }
-        MethodNode method =
-                answer.addMethod("doCall", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, parameters, ClassNode.EMPTY_ARRAY, expression.getCode());
+        MethodNode method = answer.addMethod("doCall", ACC_PUBLIC, returnType, parameters, ClassNode.EMPTY_ARRAY, expression.getCode());
         method.setSourcePosition(expression);
 
         VariableScope varScope = expression.getVariableScope();
@@ -229,14 +232,14 @@ public class ClosureWriter {
         }
         if (parameters.length > 1
                 || (parameters.length == 1
-                && parameters[0].getType() != null
-                && parameters[0].getType() != ClassHelper.OBJECT_TYPE
-                && !ClassHelper.OBJECT_TYPE.equals(parameters[0].getType().getComponentType()))) {
+                    && parameters[0].getType() != null
+                    && !ClassHelper.OBJECT_TYPE.equals(parameters[0].getType())
+                    && !ClassHelper.OBJECT_TYPE.equals(parameters[0].getType().getComponentType()))) {
             // let's add a typesafe call method
             MethodNode call = new MethodNode(
                     "call",
                     ACC_PUBLIC,
-                    ClassHelper.OBJECT_TYPE,
+                    returnType,
                     parameters,
                     ClassNode.EMPTY_ARRAY,
                     returnS(callThisX("doCall", args(parameters))));
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 b9fa745..bacb1fd 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -266,6 +266,7 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isBitO
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isBoolIntrinsicOp;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isCompareToBoolean;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isGStringOrGStringStringLUB;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isOperationInGroup;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isParameterizedWithGStringOrGStringString;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isParameterizedWithString;
@@ -2216,7 +2217,14 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
     }
 
     protected void addClosureReturnType(final ClassNode returnType) {
-        typeCheckingContext.getEnclosingClosure().addReturnType(returnType);
+        TypeCheckingContext.EnclosingClosure enclosingClosure = typeCheckingContext.getEnclosingClosure();
+        if (isGStringOrGStringStringLUB(returnType)
+                && STRING_TYPE.equals(getInferredReturnType(enclosingClosure.getClosureExpression()))) {
+            // GROOVY-9971: convert GString to String at the point of return
+            enclosingClosure.addReturnType(STRING_TYPE);
+        } else {
+            enclosingClosure.addReturnType(returnType);
+        }
     }
 
     @Override
@@ -2745,14 +2753,21 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             if (visitClosures && expression instanceof ClosureExpression
                     || !visitClosures && !(expression instanceof ClosureExpression)) {
                 if (i < params.length && visitClosures) {
-                    Parameter param = params[i];
-                    checkClosureWithDelegatesTo(receiver, selectedMethod, newArgs, params, expression, param);
+                    Parameter target = params[i];
+                    ClassNode targetType = target.getType();
+                    ClosureExpression source = (ClosureExpression) expression;
+                    checkClosureWithDelegatesTo(receiver, selectedMethod, newArgs, params, source, target);
                     if (selectedMethod instanceof ExtensionMethodNode) {
                         if (i > 0) {
-                            inferClosureParameterTypes(receiver, arguments, (ClosureExpression) expression, param, selectedMethod);
+                            inferClosureParameterTypes(receiver, arguments, source, target, selectedMethod);
                         }
                     } else {
-                        inferClosureParameterTypes(receiver, newArgs, (ClosureExpression) expression, param, selectedMethod);
+                        inferClosureParameterTypes(receiver, newArgs, source, target, selectedMethod);
+                    }
+                    if (isFunctionalInterface(targetType)) {
+                        storeInferredReturnType(source, GenericsUtils.parameterizeSAM(targetType).getV2());
+                    } else if (isClosureWithType(targetType)) {
+                        storeInferredReturnType(source, getCombinedBoundType(targetType.getGenericsTypes()[0]));
                     }
                 }
                 expression.visit(this);
diff --git a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
index 287465b..b707e77 100644
--- a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
@@ -117,6 +117,21 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-9971
+    void testClosureReturnTypeInference6() {
+        assertScript '''
+            def m(Closure<String> c) {
+                c.call()
+            }
+            final x = 123
+            Closure<String> c = { -> "x=$x" }
+            String type = c.call().class.name
+            assert type == 'java.lang.String'
+            type = (m { -> "x=$x" }).class.name
+            assert type == 'java.lang.String' // not GStringImpl
+        '''
+    }
+
     // GROOVY-5145
     void testCollect() {
         assertScript '''
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 638d14c..1a7751b 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -1186,7 +1186,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
     }
 
     // GROOVY-5559: related behaviour
-    void testGStringVsString1() {
+    void testGStringVsString() {
         assertScript '''
             int i = 1
             @ASTTest(phase=INSTRUCTION_SELECTION, value={
@@ -1197,28 +1197,6 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    // GROOVY-9971
-    void testGStringVsString2() {
-        shouldFailWithMessages '''
-            class C {
-                String apply(Closure<String> code) {
-                    code.call()
-                }
-
-                void test() {
-                    int i = 1
-                    assert apply { "i=$i" } == 'i=1'
-                    assert apply { "i=$i".toString() } == 'i=1'
-                    assert apply { false ? 'abc' : "i=$i" } == 'i=1'
-                    assert apply { true  ? 'abc' : "i=$i".toString() } == 'abc'
-                }
-            }
-            new C().test()
-        ''',
-        'Cannot find matching method C#apply(groovy.lang.Closure <groovy.lang.GString>)',
-        'Cannot find matching method C#apply(groovy.lang.Closure <java.io.Serializable>)'
-    }
-
     // GROOVY-5594
     void testMapEntryUsingPropertyNotation() {
         assertScript '''