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/09/12 14:36:38 UTC

[groovy] branch GROOVY_2_5_X updated: GROOVY-10116, GROOVY-10337: STC: honor explicit ctor call type arguments

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

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


The following commit(s) were added to refs/heads/GROOVY_2_5_X by this push:
     new 7dcba88d89 GROOVY-10116, GROOVY-10337: STC: honor explicit ctor call type arguments
7dcba88d89 is described below

commit 7dcba88d89891d6f3896e34a05a6b4f4a30645ba
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Sep 12 08:13:48 2022 -0500

    GROOVY-10116, GROOVY-10337: STC: honor explicit ctor call type arguments
    
    2_5_X backport
---
 .../transform/stc/StaticTypeCheckingSupport.java   |  83 +++++-----
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 167 +++++++++++++++++++--
 2 files changed, 194 insertions(+), 56 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 cbd3a2bf63..d16a76116a 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -71,7 +71,6 @@ import java.util.UUID;
 import java.util.WeakHashMap;
 import java.util.regex.Matcher;
 
-import static java.lang.Math.min;
 import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
 import static org.apache.groovy.ast.tools.ExpressionUtils.isNullConstant;
 import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE;
@@ -449,7 +448,7 @@ public abstract class StaticTypeCheckingSupport {
         ClassNode elementType = arrayType.getComponentType();
         ClassNode argumentType = argumentTypes[argumentTypes.length - 1];
         if (isNumberType(elementType) && isNumberType(argumentType) && !getWrapper(elementType).equals(getWrapper(argumentType))) return -1;
-        return isAssignableTo(argumentType, elementType) ? min(getDistance(argumentType, arrayType), getDistance(argumentType, elementType)) : -1;
+        return isAssignableTo(argumentType, elementType) ? Math.min(getDistance(argumentType, arrayType), getDistance(argumentType, elementType)) : -1;
     }
 
     /**
@@ -475,10 +474,10 @@ public abstract class StaticTypeCheckingSupport {
             ClassNode sourceComponent = type.getComponentType(), targetComponent = toBeAssignedTo.getComponentType();
             return (isPrimitiveType(sourceComponent) == isPrimitiveType(targetComponent)) && isAssignableTo(sourceComponent, targetComponent);
         }
-        if (type.isDerivedFrom(GSTRING_TYPE) && STRING_TYPE.equals(toBeAssignedTo)) {
+        if (type.isDerivedFrom(GSTRING_TYPE) && toBeAssignedTo.equals(STRING_TYPE)) {
             return true;
         }
-        if (toBeAssignedTo.isDerivedFrom(GSTRING_TYPE) && STRING_TYPE.equals(type)) {
+        if (type.equals(STRING_TYPE) && toBeAssignedTo.isDerivedFrom(GSTRING_TYPE)) {
             return true;
         }
         if (implementsInterfaceOrIsSubclassOf(type, toBeAssignedTo)) {
@@ -1477,44 +1476,44 @@ public abstract class StaticTypeCheckingSupport {
         return typeCheckMethodsWithGenerics(receiver, arguments, candidateMethod, false);
     }
 
-    private static boolean typeCheckMethodsWithGenerics(ClassNode receiver, ClassNode[] arguments, MethodNode candidateMethod, boolean isExtensionMethod) {
-        boolean failure = false;
+    private static boolean typeCheckMethodsWithGenerics(final ClassNode receiver, final ClassNode[] arguments, final MethodNode candidateMethod, final boolean isExtensionMethod) {
+        Parameter[] parameters = candidateMethod.getParameters();
+        if (parameters.length == 0 || parameters.length > arguments.length) {
+            // this is a limitation that must be removed in a future version
+            // we cannot check generic type arguments if there are default parameters!
+            return true;
+        }
 
         // correct receiver for inner class
         // we assume the receiver is an instance of the declaring class of the
         // candidate method, but findMethod returns also outer class methods
         // for that receiver. For now we skip receiver based checks in that case
         // TODO: correct generics for when receiver is to be skipped
-        boolean skipBecauseOfInnerClassNotReceiver = isOuterClassOf(receiver, candidateMethod.getDeclaringClass());
-
-        Parameter[] parameters = candidateMethod.getParameters();
-        Map<GenericsTypeName, GenericsType> classGTs;
-        if (skipBecauseOfInnerClassNotReceiver) {
-            classGTs = Collections.EMPTY_MAP;
-        } else {
-            classGTs = GenericsUtils.extractPlaceholders(receiver);
-        }
-        if (parameters.length > arguments.length || parameters.length == 0) {
-            // this is a limitation that must be removed in a future version
-            // we cannot check generic type arguments if there are default parameters!
-            return true;
-        }
+        boolean skipBecauseOfInnerClassNotReceiver = !implementsInterfaceOrIsSubclassOf(receiver, candidateMethod.getDeclaringClass());
 
         // we have here different generics contexts we have to deal with.
         // There is firstly the context given through the class, and the method.
         // The method context may hide generics given through the class, but use
         // the non-hidden ones.
-        Map<GenericsTypeName, GenericsType> resolvedMethodGenerics = new HashMap<GenericsTypeName, GenericsType>();
-        if (!skipBecauseOfInnerClassNotReceiver) {
+        Map<GenericsTypeName, GenericsType> classGTs;
+        Map<GenericsTypeName, GenericsType> resolvedMethodGenerics = new HashMap<>();
+        if (skipBecauseOfInnerClassNotReceiver) {
+            classGTs = Collections.emptyMap();
+        } else {
+            classGTs = GenericsUtils.extractPlaceholders(receiver);
             addMethodLevelDeclaredGenerics(candidateMethod, resolvedMethodGenerics);
+            // remove hidden generics
+            for (GenericsTypeName key : resolvedMethodGenerics.keySet()) {
+                classGTs.remove(key);
+            }
+            // use the remaining information to refine given generics
+            applyGenericsConnections(classGTs, resolvedMethodGenerics);
         }
-        // so first we remove hidden generics
-        for (GenericsTypeName key : resolvedMethodGenerics.keySet()) classGTs.remove(key);
-        // then we use the remaining information to refine the given generics
-        applyGenericsConnections(classGTs, resolvedMethodGenerics);
-        // and then start our checks with the receiver
+
+        boolean failure = false;
+        // start checks with the receiver
         if (!skipBecauseOfInnerClassNotReceiver) {
-            failure |= inferenceCheck(Collections.EMPTY_SET, resolvedMethodGenerics, candidateMethod.getDeclaringClass(), receiver, false);
+            failure = inferenceCheck(Collections.EMPTY_SET, resolvedMethodGenerics, candidateMethod.getDeclaringClass(), receiver, false);
         }
         // the outside context parts till now define placeholder we are not allowed to
         // generalize, thus we save that for later use...
@@ -1522,26 +1521,20 @@ public abstract class StaticTypeCheckingSupport {
         // first parameter. While we normally allow generalization for the first
         // parameter, in case of an extension method we must not.
         Set<GenericsTypeName> fixedGenericsPlaceHolders = extractResolvedPlaceHolders(resolvedMethodGenerics);
+        if ("<init>".equals(candidateMethod.getName())) {
+            fixedGenericsPlaceHolders.addAll(resolvedMethodGenerics.keySet());
+        }
+        for (int i = 0, n = arguments.length, nthParameter = parameters.length - 1; i < n; i += 1) {
+            ClassNode argumentType = arguments[i];
+            ClassNode parameterType = parameters[Math.min(i, nthParameter)].getOriginType();
+            failure |= inferenceCheck(fixedGenericsPlaceHolders, resolvedMethodGenerics, parameterType, argumentType, i >= nthParameter);
 
-        for (int i = 0; i < arguments.length; i++) {
-            int pindex = min(i, parameters.length - 1);
-            ClassNode wrappedArgument = arguments[i];
-            ClassNode type = parameters[pindex].getOriginType();
-
-            failure |= inferenceCheck(fixedGenericsPlaceHolders, resolvedMethodGenerics, type, wrappedArgument, i >= parameters.length - 1);
-
-            // set real fixed generics for extension methods
-            if (isExtensionMethod && i == 0)
+            if (i == 0 && isExtensionMethod) // set real fixed generics for extension methods
                 fixedGenericsPlaceHolders = extractResolvedPlaceHolders(resolvedMethodGenerics);
         }
         return !failure;
     }
 
-    private static boolean isOuterClassOf(ClassNode receiver, ClassNode type) {
-        if (implementsInterfaceOrIsSubclassOf(receiver, type)) return false;
-        return true;
-    }
-
     private static Set<GenericsTypeName> extractResolvedPlaceHolders(Map<GenericsTypeName, GenericsType> resolvedMethodGenerics) {
         if (resolvedMethodGenerics.isEmpty()) return Collections.EMPTY_SET;
         Set<GenericsTypeName> result = new HashSet<GenericsTypeName>();
@@ -1571,6 +1564,8 @@ public abstract class StaticTypeCheckingSupport {
         extractGenericsConnections(connections, wrappedArgument, type);
         // each found connection must comply with already found connections
         boolean failure = !compatibleConnections(connections, resolvedMethodGenerics, fixedGenericsPlaceHolders);
+
+        connections.keySet().removeAll(fixedGenericsPlaceHolders); // GROOVY-10337
         // and then apply the found information to refine the method level
         // information. This way the method level information slowly turns
         // into information for the callsite
@@ -1686,8 +1681,8 @@ public abstract class StaticTypeCheckingSupport {
                         ClassNode replacementType = extractType(newValue);
                         if (oldValue.isCompatibleWith(replacementType)) {
                             entry.setValue(newValue);
-                            if (newValue.isPlaceholder()) {
-                                checkForMorePlaceholders = checkForMorePlaceholders || !equalIncludingGenerics(oldValue, newValue);
+                            if (!checkForMorePlaceholders && newValue.isPlaceholder()) {
+                                checkForMorePlaceholders = !equalIncludingGenerics(oldValue, newValue);
                             }
                         }
                     }
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index e98192c633..cc6a225b0e 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -2348,7 +2348,62 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    @NotYetImplemented // GROOVY-5692, GROOVY-10006
     void testCompatibleArgumentsForPlaceholders1() {
+        assertScript '''
+            def <T> T test(T one, T two) { }
+            def result = test(1,"II")
+        '''
+        assertScript '''
+            def <T> T test(T one, T two, T three) { }
+            def result = test(1,"II",Class)
+        '''
+        assertScript '''
+            def <T extends Number> T test(T one, T two) { }
+            def result = test(1L,2G)
+        '''
+    }
+
+    @NotYetImplemented // GROOVY-5692
+    void testCompatibleArgumentsForPlaceholders2() {
+        assertScript '''
+            def <T> boolean test(T one, List<T> many) { }
+            test(1,["II","III"])
+        '''
+    }
+
+    // GROOVY-10220
+    void testCompatibleArgumentsForPlaceholders3() {
+        assertScript '''
+            class C<S, T extends Number> {
+            }
+            class D<T> {
+                C<? extends T, Integer> f
+                D(C<? extends T, Integer> p) {
+                    f = p
+                }
+            }
+
+            assert new D<String>(null).f == null
+        '''
+    }
+
+    // GROOVY-10235
+    void testCompatibleArgumentsForPlaceholders4() {
+        assertScript '''
+            import static java.util.concurrent.ConcurrentHashMap.newKeySet
+
+            void accept(Collection<Integer> integers) {
+                assert integers
+            }
+
+            Set<Integer> integers = newKeySet()
+            integers.add(42)
+            accept(integers)
+        '''
+    }
+
+    void testCompatibleArgumentsForPlaceholders5() {
         assertScript '''
             def <X> X foo(X x) {
             }
@@ -2362,7 +2417,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
     }
 
     // GROOVY-10482
-    void testCompatibleArgumentsForPlaceholders2() {
+    void testCompatibleArgumentsForPlaceholders6() {
         ['def', 'public', 'static', '@Deprecated'].each {
             assertScript """
                 class Foo<X> {
@@ -2379,13 +2434,100 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         }
     }
 
+    // GROOVY-10499
+    void testCompatibleArgumentsForPlaceholders7() {
+        ['?', 'Y', '? extends Y'].each {
+            assertScript """
+                class Foo<X> {
+                    Foo(X x) {
+                    }
+                }
+                class Bar<Y> {
+                    Bar(Foo<${it}> foo, Y y) {
+                    }
+                    Y baz(Y y) {
+                    }
+                }
+                def <Z> void test(Z z = null) {
+                    new Bar<>(new Foo<Z>(z), z).baz(z) // Cannot find matching method Bar#baz(Z)
+                }
+                test()
+            """
+        }
+    }
+
+    // GROOVY-10100
+    void testCompatibleArgumentsForPlaceholders8() {
+        assertScript '''
+            import java.util.function.Function
+
+            class C<T> {
+                T m(Object... args) {
+                }
+            }
+            def <T extends Number> void test() {
+                C<T> c = new C<>()
+                Function<String[], T> f = c.&m
+                T t = f.apply(["string"] as String[]) // Cannot assign value of type java.lang.String[] to variable of type T
+            }
+            test()
+        '''
+    }
+
+    // GROOVY-10115
+    void testCompatibleArgumentsForPlaceholders9() {
+        assertScript '''
+            class C<X, T extends X> {
+                T t
+                C(T t) {
+                    this.t = t
+                }
+            }
+            new C(null)
+        '''
+    }
+
+    // GROOVY-10116
+    void testCompatibleArgumentsForPlaceholders10() {
+        assertScript '''
+            class Foo<X,T> {
+                Foo(Bar<X,T> bar) {
+                }
+            }
+            class Bar<X,T> {
+            }
+            class Baz<T> {
+                void test() {
+                    Foo<T,Long> x = new Foo<T,Long>(new Bar<T,Long>()) // Cannot call Foo#<init>(Bar<T,Long>) with arguments [Bar<T,Long>]
+                }
+            }
+            new Baz().test()
+        '''
+    }
+
+    @NotYetImplemented // GROOVY-10153
+    void testCompatibleArgumentsForPlaceholders11() {
+        ['A', 'B', 'C'].each { T ->
+            assertScript """
+                class A {}
+                class B extends A {}
+                class C extends B {}
+                class Foo<T extends A> {}
+
+                Foo<? super C> foo = new Foo<$T>()
+                //  ^ lower bound is C (explicit); upper bound is A (implicit)
+            """
+        }
+    }
+
     void testIncompatibleGenericsForTwoArguments() {
         shouldFailWithMessages '''
             public <T> void printEqual(T arg1, T arg2) {
                 println arg1 == arg2
             }
             printEqual(1, 'foo')
-        ''', '#printEqual(T, T) with arguments [int, java.lang.String]'
+        ''',
+        '#printEqual(T, T) with arguments [int, java.lang.String]'
     }
 
     void testIncompatibleGenericsForTwoArgumentsUsingEmbeddedPlaceholder() {
@@ -2394,11 +2536,12 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 println arg1 == arg2
             }
             printEqual(1, ['foo'])
-        ''', '#printEqual(T, java.util.List <T>) with arguments [int, java.util.List <java.lang.String>]'
+        ''',
+        '#printEqual(T, java.util.List <T>) with arguments [int, java.util.List <java.lang.String>]'
     }
 
-    // GROOVY-9902
-    void testIncompatibleArgumentsForGenericArgument_IncludingDelegation() {
+    // GROOVY-9902: incomplete generics should not stop type checking
+    void testIncompatibleArgumentsForPlaceholders3() {
         shouldFailWithMessages '''
             class Holder<Unknown> {
                 TypedProperty<Number, Unknown> numberProperty = prop(Number)
@@ -2494,7 +2637,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             assert twoIntegers.getTop() == 2
 
             def oneIntegerAgain = stack.push(1).push(2).pop()
-            assert oneIntegerAgain.getTop() == 1 // BOOM!!!!
+            assert oneIntegerAgain.getTop() == 1 // Cannot find matching method IStack#getTop()
         '''
     }
 
@@ -2511,14 +2654,14 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
     // GROOVY-5735
     void testCorrespondingParameterType() {
         assertScript '''
-        public <T> void someMethod (java.lang.Class<T> clazz, T object) {}
+            public <T> void someMethod (java.lang.Class<T> clazz, T object) {}
 
-        void method() {
-            List<String> list = null
-            someMethod(java.util.List.class, list)
-        }
+            void method() {
+                List<String> list = null
+                someMethod(java.util.List.class, list)
+            }
 
-        method()
+            method()
         '''
     }