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 2022/03/31 01:34:10 UTC

[groovy] branch GROOVY_3_0_X updated: GROOVY-8409: STC: resolve generics contexts separately for return type

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

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


The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
     new d37a593  GROOVY-8409: STC: resolve generics contexts separately for return type
d37a593 is described below

commit d37a59341154bd0d02937621d11ee90b983837c1
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Wed Mar 30 20:31:24 2022 -0500

    GROOVY-8409: STC: resolve generics contexts separately for return type
    
      // Function also uses "T" for its input
      def <T> T m(Function<Type, T> f) {
        T result = f.apply(new Type())
        return result
      }
    
    3_0_X backport
---
 .../transform/stc/StaticTypeCheckingSupport.java   |   2 +-
 .../transform/stc/StaticTypeCheckingVisitor.java   | 156 +++++++++++----------
 src/test/groovy/bugs/Groovy8409Bug.groovy          |  63 ++++-----
 .../groovy/transform/stc/GenericsSTCTest.groovy    |  16 +++
 .../asm/sc/GenericsStaticCompileTest.groovy        |   6 -
 5 files changed, 126 insertions(+), 117 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index e782330..367b8cd 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1787,7 +1787,7 @@ public abstract class StaticTypeCheckingSupport {
         return newSpec;
     }
 
-    private static GenericsType[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final GenericsType[] gts) {
+    static GenericsType[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final GenericsType[] gts) {
         if (gts == null || spec == null || spec.isEmpty()) return gts;
         GenericsType[] newGTs = new GenericsType[gts.length];
         for (int i = 0, n = gts.length; i < n; i += 1) {
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 f65bbbc..fcfc80f 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -5223,43 +5223,43 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
     }
 
     /**
-     * If a method call returns a parameterized type, then we can perform additional inference on the
-     * return type, so that the type gets actual type parameters. For example, the method
-     * Arrays.asList(T...) is generified with type T which can be deduced from actual type
-     * arguments.
+     * If a method call returns a parameterized type, then perform additional
+     * inference on the return type, so that the type gets actual type arguments.
+     * For example, the method {@code Arrays.asList(T...)} is parameterized with
+     * {@code T}, which can be deduced type arguments or call arguments.
      *
-     * @param method    the method node
-     * @param arguments the method call arguments
-     * @return parameterized, infered, class node
+     * @param method            the method node
+     * @param arguments         the method call arguments
+     * @param receiver          the object expression type
      */
     protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final MethodNode method, final Expression arguments) {
         return inferReturnTypeGenerics(receiver, method, arguments, null);
     }
 
     /**
-     * If a method call returns a parameterized type, then we can perform additional inference on the
-     * return type, so that the type gets actual type parameters. For example, the method
-     * Arrays.asList(T...) is generified with type T which can be deduced from actual type
-     * arguments.
+     * If a method call returns a parameterized type, then perform additional
+     * inference on the return type, so that the type gets actual type arguments.
+     * For example, the method {@code Arrays.asList(T...)} is parameterized with
+     * {@code T}, which can be deduced type arguments or call arguments.
      *
      * @param method            the method node
      * @param arguments         the method call arguments
-     * @param explicitTypeHints explicit type hints as found for example in Collections.&lt;String&gt;emptyList()
-     * @return parameterized, infered, class node
+     * @param receiver          the object expression type
+     * @param explicitTypeHints type arguments (optional), for example {@code Collections.<String>emptyList()}
      */
     protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final MethodNode method, final Expression arguments, final GenericsType[] explicitTypeHints) {
-        ClassNode returnType = method.getReturnType();
-        if (getGenericsWithoutArray(returnType) == null) {
+        ClassNode returnType = method instanceof ConstructorNode ? method.getDeclaringClass() : method.getReturnType();
+        if (!GenericsUtils.hasUnresolvedGenerics(returnType)) {
+            // GROOVY-7538: replace "Type<?>" with "Type<? extends/super X>" for any "Type<T extends/super X>"
+            if (getGenericsWithoutArray(returnType) != null) returnType = boundUnboundedWildcards(returnType);
+
             return returnType;
         }
+
         if (method instanceof ExtensionMethodNode) {
-            // check if the placeholder corresponds to the placeholder of the first parameter
-            ExtensionMethodNode emn = (ExtensionMethodNode) method;
-            MethodNode dgm = emn.getExtensionMethodNode();
             ArgumentListExpression args = new ArgumentListExpression();
             VariableExpression vexp = varX("$self", receiver);
             args.addExpression(vexp);
-            vexp.setNodeMetaData(ExtensionMethodDeclaringClass.class, emn.getDeclaringClass());
             if (arguments instanceof ArgumentListExpression) {
                 for (Expression argument : (ArgumentListExpression) arguments) {
                     args.addExpression(argument);
@@ -5267,70 +5267,78 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             } else {
                 args.addExpression(arguments);
             }
-            return inferReturnTypeGenerics(receiver, dgm, args, explicitTypeHints);
+            vexp.setNodeMetaData(ExtensionMethodDeclaringClass.class, method.getDeclaringClass());
+            return inferReturnTypeGenerics(receiver, ((ExtensionMethodNode) method).getExtensionMethodNode(), args, explicitTypeHints);
         }
-        Map<GenericsTypeName, GenericsType> resolvedPlaceholders = resolvePlaceHoldersFromDeclaration(receiver, getDeclaringClass(method, arguments), method, method.isStatic());
-        resolvePlaceholdersFromExplicitTypeHints(method, explicitTypeHints, resolvedPlaceholders);
-        if (resolvedPlaceholders.isEmpty()) {
-            return boundUnboundedWildcards(returnType);
-        }
-        Map<GenericsTypeName, GenericsType> placeholdersFromContext = extractGenericsParameterMapOfThis(typeCheckingContext);
-        applyGenericsConnections(placeholdersFromContext, resolvedPlaceholders);
-
-        // then resolve receivers from method arguments
-        List<Expression> expressions = InvocationWriter.makeArgumentList(arguments).getExpressions();
-        Parameter[] parameters = method.getParameters();
-        boolean isVargs = isVargs(parameters);
-        int paramLength = parameters.length;
-        if (expressions.size() >= paramLength) {
-            for (int i = 0; i < paramLength; i += 1) {
-                if (isNullConstant(expressions.get(i)))
-                    continue; // GROOVY-9984: skip null
-                boolean lastArg = (i == paramLength - 1);
-                ClassNode paramType = parameters[i].getType();
-                ClassNode argumentType = getDeclaredOrInferredType(expressions.get(i));
-                while (paramType.isArray() && argumentType.isArray()) {
-                    paramType = paramType.getComponentType();
-                    argumentType = argumentType.getComponentType();
-                }
-                if (isUsingGenericsOrIsArrayUsingGenerics(paramType)) {
-                    if (argumentType.isDerivedFrom(CLOSURE_TYPE)) {
-                        MethodNode sam = findSAM(paramType);
-                        if (sam != null) { // implicit closure coercion in action!
-                            argumentType = !paramType.isUsingGenerics() ? paramType
-                                    : convertClosureTypeToSAMType(expressions.get(i), argumentType, sam, paramType,
-                                            applyGenericsContextToParameterClass(resolvedPlaceholders, paramType));
-                        }
-                    }
-                    if (isVargs && lastArg && paramType.isArray() && !argumentType.isArray()) {
-                        paramType = paramType.getComponentType();
-                    }
-                    argumentType = wrapTypeIfNecessary(argumentType);
 
-                    Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
-                    extractGenericsConnections(connections, argumentType, paramType);
-                    extractGenericsConnectionsForSuperClassAndInterfaces(resolvedPlaceholders, connections);
+        Map<GenericsTypeName, GenericsType> context = method.isStatic() || method instanceof ConstructorNode
+                                            ? null : extractPlaceHolders(null, receiver, getDeclaringClass(method, arguments));
+        GenericsType[] methodGenericTypes = method instanceof ConstructorNode ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes());
+
+        // 1) resolve type parameters of method
+
+        if (methodGenericTypes != null) {
+            Map<GenericsTypeName, GenericsType> resolvedPlaceholders = new HashMap<>();
+            for (GenericsType gt : methodGenericTypes) resolvedPlaceholders.put(new GenericsTypeName(gt.getName()), gt);
+
+            Parameter[] parameters = method.getParameters();
+            final int  nParameters = parameters.length;
 
-                    applyGenericsConnections(connections, resolvedPlaceholders);
-                    applyGenericsConnections(placeholdersFromContext, resolvedPlaceholders);
+            if (explicitTypeHints != null) { // resolve type parameters from type arguments
+                int n = methodGenericTypes.length;
+                if (n == explicitTypeHints.length) {
+                    for (int i = 0; i < n; i += 1) {
+                        resolvedPlaceholders.put(new GenericsTypeName(methodGenericTypes[i].getName()), explicitTypeHints[i]);
+                    }
                 }
-            }
-        }
+            } else if (nParameters > 0) { // resolve type parameters from call arguments
+                List<Expression> expressions = InvocationWriter.makeArgumentList(arguments).getExpressions();
+                boolean isVargs = isVargs(parameters);
+                if (expressions.size() >= nParameters) {
+                    for (int i = 0; i < nParameters; i += 1) {
+                        if (isNullConstant(expressions.get(i)))
+                            continue; // GROOVY-9984: skip null
+                        boolean lastArg = (i == nParameters - 1);
+                        ClassNode paramType = parameters[i].getType();
+                        ClassNode argumentType = getDeclaredOrInferredType(expressions.get(i));
+                        while (paramType.isArray() && argumentType.isArray()) {
+                            paramType = paramType.getComponentType();
+                            argumentType = argumentType.getComponentType();
+                        }
+                        if (isUsingGenericsOrIsArrayUsingGenerics(paramType)) {
+                            if (argumentType.isDerivedFrom(CLOSURE_TYPE)) {
+                                MethodNode sam = findSAM(paramType);
+                                if (sam != null) { // implicit closure coercion in action!
+                                    argumentType = !paramType.isUsingGenerics() ? paramType
+                                            : convertClosureTypeToSAMType(expressions.get(i), argumentType, sam, paramType,
+                                                    applyGenericsContextToParameterClass(resolvedPlaceholders, paramType));
+                                }
+                            }
+                            if (isVargs && lastArg && paramType.isArray() && !argumentType.isArray()) {
+                                paramType = paramType.getComponentType();
+                            }
 
-        return applyGenericsContext(resolvedPlaceholders, returnType);
-    }
+                            Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
+                            extractGenericsConnections(connections, wrapTypeIfNecessary(argumentType), paramType);
+                            extractGenericsConnectionsForSuperClassAndInterfaces(resolvedPlaceholders, connections);
 
-    private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode method, final GenericsType[] explicitTypeHints, final Map<GenericsTypeName, GenericsType> resolvedPlaceholders) {
-        if (explicitTypeHints != null) {
-            GenericsType[] methodGenericTypes = method.getGenericsTypes();
-            if (methodGenericTypes != null && methodGenericTypes.length == explicitTypeHints.length) {
-                for (int i = 0, n = methodGenericTypes.length; i < n; i += 1) {
-                    GenericsType methodGenericType = methodGenericTypes[i];
-                    GenericsType explicitTypeHint = explicitTypeHints[i];
-                    resolvedPlaceholders.put(new GenericsTypeName(methodGenericType.getName()), explicitTypeHint);
+                            applyGenericsConnections(connections, resolvedPlaceholders);
+                        }
+                    }
                 }
             }
+            returnType = applyGenericsContext(resolvedPlaceholders, returnType);
         }
+
+        // 2) resolve type parameters of method's enclosing context
+
+        if (context != null) returnType = applyGenericsContext(context, returnType);
+
+        // 3) resolve bounds of type parameters from calling context
+
+        returnType = applyGenericsContext(extractGenericsParameterMapOfThis(typeCheckingContext), returnType);
+
+        return returnType;
     }
 
     /**
diff --git a/src/test/groovy/bugs/Groovy8409Bug.groovy b/src/test/groovy/bugs/Groovy8409Bug.groovy
index d122ae5..a24fce2 100644
--- a/src/test/groovy/bugs/Groovy8409Bug.groovy
+++ b/src/test/groovy/bugs/Groovy8409Bug.groovy
@@ -18,52 +18,43 @@
  */
 package groovy.bugs
 
-import gls.CompilableTestSupport
-import groovy.test.NotYetImplemented
+import org.junit.Test
 
-class Groovy8409Bug extends CompilableTestSupport {
-    @NotYetImplemented
+import static groovy.test.GroovyAssert.assertScript
+
+final class Groovy8409Bug {
+
+    @Test
     void test() {
         assertScript '''
-        import groovy.transform.CompileStatic
-        import java.util.function.BiFunction
-
-        @CompileStatic
-        class Groovy8409Bug {
-        
-            static <T> T actionWrapperT(BiFunction<Date, URL, T> action) {
-                T result = action.apply(new Date(), new URL('http://www.example.com'))
-                // do something else here
-                return result
+            @groovy.transform.CompileStatic
+            class Groovy8409Bug {
+                static <T> T actionWrapperT(java.util.function.BiFunction<Date, URL, T> action) {
+                    T result = action.apply(new Date(), new URL('http://www.example.com'))
+                    // do something else here
+                    return result
+                }
+                static void main(String[] args) {
+                    Groovy8409Bug t = actionWrapperT { Date date, URL url -> new Groovy8409Bug() }
+                }
             }
-        
-            static void main(String[] args) {
-                Groovy8409Bug t = actionWrapperT { Date date, URL url -> new Groovy8409Bug() }
-            }
-        
-        }
         '''
     }
 
+    @Test
     void testWorkaround() {
         assertScript '''
-        import groovy.transform.CompileStatic
-        import java.util.function.BiFunction
-
-        @CompileStatic
-        class Groovy8409Bug {
-        
-            static <X> X actionWrapperT(BiFunction<Date, URL, X> action) {
-                X result = action.apply(new Date(), new URL('http://www.example.com'))
-                // do something else here
-                return result
-            }
-        
-            static void main(String[] args) {
-                Groovy8409Bug t = actionWrapperT { Date date, URL url -> new Groovy8409Bug() }
+            @groovy.transform.CompileStatic
+            class Groovy8409Bug {
+                static <X> X actionWrapperT(java.util.function.BiFunction<Date, URL, X> action) {
+                    X result = action.apply(new Date(), new URL('http://www.example.com'))
+                    // do something else here
+                    return result
+                }
+                static void main(String[] args) {
+                    Groovy8409Bug t = actionWrapperT { Date date, URL url -> new Groovy8409Bug() }
+                }
             }
-        
-        }
         '''
     }
 }
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 2ad5bfb..1515770 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -3101,6 +3101,22 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-10557
+    void testReturnTypeInferenceWithClosure2() {
+        assertScript '''
+            class C {
+                def <T> T m(java.util.function.Function<Reader,T> f)  {
+                    new StringReader("").withCloseable { reader ->
+                        f.apply(reader)
+                    }
+                }
+            }
+            Object result = new C().m { it.text.empty }
+            //                          ^^ StringReader
+            assert result == Boolean.TRUE
+        '''
+    }
+
     // GROOVY-6129
     void testShouldNotThrowNPE() {
         assertScript '''
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy
index 3a667c5..4a599b2 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy
@@ -18,16 +18,10 @@
  */
 package org.codehaus.groovy.classgen.asm.sc
 
-import groovy.test.NotYetImplemented
 import groovy.transform.stc.GenericsSTCTest
 
 /**
  * Unit tests for static compilation : generics.
  */
 class GenericsStaticCompileTest extends GenericsSTCTest implements StaticCompilationTestSupport {
-
-    @Override @NotYetImplemented // GROOVY-8409
-    void testReturnTypeInferenceWithMethodGenerics14() {
-        super.testReturnTypeInferenceWithMethodGenerics14()
-    }
 }