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/25 09:19:22 UTC

[groovy] branch master updated: GROOVY-10053: apply object expression generics to ref method return type

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2415668  GROOVY-10053: apply object expression generics to ref method return type
2415668 is described below

commit 24156683dde9064bfa644c0ef4976b401504eb13
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sun Apr 25 04:16:28 2021 -0500

    GROOVY-10053: apply object expression generics to ref method return type
---
 .../transform/stc/StaticTypeCheckingVisitor.java   | 46 ++++++++++++----------
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 19 ++++++++-
 .../asm/sc/GenericsStaticCompileTest.groovy        | 18 +++++++++
 3 files changed, 61 insertions(+), 22 deletions(-)

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 ece5068..b265462 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -2453,9 +2453,10 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             List<Receiver<String>> receivers = new ArrayList<>();
             addReceivers(receivers, makeOwnerList(expression.getExpression()), false);
 
+            ClassNode receiverType = null;
             List<MethodNode> candidates = EMPTY_METHODNODE_LIST;
             for (Receiver<String> currentReceiver : receivers) {
-                ClassNode receiverType = wrapTypeIfNecessary(currentReceiver.getType());
+                receiverType = wrapTypeIfNecessary(currentReceiver.getType());
 
                 candidates = findMethodsWithGenerated(receiverType, nameText);
                 if (isBeingCompiled(receiverType)) candidates.addAll(GROOVY_OBJECT_TYPE.getMethods(nameText));
@@ -2475,10 +2476,11 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             }
 
             if (!candidates.isEmpty()) {
-                candidates.stream().map(MethodNode::getReturnType)
-                        .reduce(WideningCategories::lowestUpperBound)
-                        .filter(returnType -> !returnType.equals(OBJECT_TYPE))
-                        .ifPresent(returnType -> storeType(expression, wrapClosureType(returnType)));
+                Map<GenericsTypeName, GenericsType> gts = GenericsUtils.extractPlaceholders(receiverType);
+                candidates.stream().map(candidate -> applyGenericsContext(gts, candidate.getReturnType()))
+                        .reduce(WideningCategories::lowestUpperBound).ifPresent(returnType -> {
+                            storeType(expression, wrapClosureType(returnType));
+                        });
                 expression.putNodeMetaData(MethodNode.class, candidates);
             } else if (!(expression instanceof MethodReferenceExpression)) {
                 ClassNode type = wrapTypeIfNecessary(getType(expression.getExpression()));
@@ -3688,12 +3690,12 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             }
         }
 
-        visitMethodCallArguments(receiver, new ArgumentListExpression(newArgumentExpressionList), true, selectedMethod);
+        visitMethodCallArguments(receiver, args(newArgumentExpressionList), true, selectedMethod);
 
-        for (Integer methodReferenceParamIndex : methodReferenceParamIndexList) {
-            LambdaExpression lambdaExpression = (LambdaExpression) newArgumentExpressionList.get(methodReferenceParamIndex);
-            ClassNode[] argumentTypes = lambdaExpression.getNodeMetaData(CLOSURE_ARGUMENTS);
-            argumentExpressions.get(methodReferenceParamIndex).putNodeMetaData(CLOSURE_ARGUMENTS, argumentTypes);
+        for (int index : methodReferenceParamIndexList) {
+            Expression lambdaExpression = newArgumentExpressionList.get(index);
+            Expression methodReferenceExpression = argumentExpressions.get(index);
+            methodReferenceExpression.putNodeMetaData(CLOSURE_ARGUMENTS, lambdaExpression.getNodeMetaData(CLOSURE_ARGUMENTS));
         }
     }
 
@@ -5442,15 +5444,17 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         ClassNode[] paramTypes = samSignature.get();
         return options.stream().filter((MethodNode option) -> {
             ClassNode[] types = collateMethodReferenceParameterTypes(source, option);
-            if (types.length == paramTypes.length) {
-                for (int i = 0, n = types.length; i < n; i += 1) {
-                    if (!types[i].isGenericsPlaceHolder() && !isAssignableTo(types[i], paramTypes[i])) {
-                        return false;
-                    }
+            final int n = types.length;
+            if (n != paramTypes.length) {
+                return false;
+            }
+            for (int i = 0; i < n; i += 1) {
+                // param type represents incoming argument type
+                if (!isAssignableTo(paramTypes[i], types[i])) {
+                    return false;
                 }
-                return true;
             }
-            return false;
+            return true;
         }).findFirst().orElse(null); // TODO: order matches by param distance
     }
 
@@ -5477,9 +5481,9 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
      * Converts a closure type to the appropriate SAM type, which is used to
      * infer return type generics.
      *
-     * @param closureType the inferred type of a closure {@code Closure<Type>}
-     * @param samType     the type into which the closure is coerced into
-     * @return SAM type augmented using information from the argument expression
+     * @param expression closure, lambda, pointer or reference
+     * @param closureType the inferred type of {@code expression}
+     * @return {@code samType} augmented by argument expression information
      */
     private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType, final Map<GenericsTypeName, GenericsType> placeholders) {
         // use the generics information from Closure to further specify the type
@@ -5588,7 +5592,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         return declaringClass;
     }
 
-    private Map<GenericsTypeName, GenericsType> resolvePlaceHoldersFromDeclaration(final ClassNode receiver, final ClassNode declaration, final MethodNode method, final boolean isStaticTarget) {
+    private static Map<GenericsTypeName, GenericsType> resolvePlaceHoldersFromDeclaration(final ClassNode receiver, final ClassNode declaration, final MethodNode method, final boolean isStaticTarget) {
         Map<GenericsTypeName, GenericsType> resolvedPlaceholders;
         if (isStaticTarget && CLASS_Type.equals(receiver) &&
                 receiver.isUsingGenerics() &&
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 885c00b..944b1f8 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -236,7 +236,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    // GROOVY-10049
+    // GROOVY-10049, GROOVY-10053
     void testReturnTypeInferenceWithMethodGenerics9() {
         assertScript '''
             def <X> Set<X> f(Class<X> x) {
@@ -250,6 +250,23 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             assert result == [ 42 ]
         '''
 
+        ['n -> (N) n', '{ n -> (N) n }'].each { cast ->
+            assertScript """
+                Set<Number> f() {
+                    Collections.<Number>singleton(42)
+                }
+                def <N extends Number> Set<N> g(Class<N> t) {
+                    Set<N> result = new HashSet<>()
+                    f().stream().filter(n -> t.isInstance(n))
+                        .<N>map($cast).forEach(n -> result.add(n))
+                    return result
+                }
+
+                def result = g(Integer)
+                assert result == [42] as Set
+            """
+        }
+
         assertScript '''
             def <T> String test(Iterable<T> iterable) {
                 Iterator<T> it = iterable.iterator()
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 4a599b2..d8c10e6 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy
@@ -24,4 +24,22 @@ import groovy.transform.stc.GenericsSTCTest
  * Unit tests for static compilation : generics.
  */
 class GenericsStaticCompileTest extends GenericsSTCTest implements StaticCompilationTestSupport {
+
+    // GROOVY-10053
+    void testReturnTypeInferenceWithMethodGenericsSC() {
+        assertScript """
+            Set<Number> f() {
+                Collections.<Number>singleton(42)
+            }
+            def <N extends Number> Set<N> g(Class<N> t) {
+                Set<N> result = new HashSet<>()
+                f().stream().filter(n -> t.isInstance(n))
+                    .<N>map(t::cast).forEach(n -> result.add(n))
+                return result
+            }
+
+            def result = g(Integer)
+            assert result == [42] as Set
+        """
+    }
 }