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/12 23:40:50 UTC

[groovy] branch GROOVY_3_0_X updated: GROOVY-10482: STC: resolve type parameter of ` T m()` in call args

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 ff3ef86  GROOVY-10482: STC: resolve type parameter of `<T> T m()` in call args
ff3ef86 is described below

commit ff3ef86865870868f5d77ac9466aff7c5a024e8b
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sat Mar 12 17:22:26 2022 -0600

    GROOVY-10482: STC: resolve type parameter of `<T> T m()` in call args
    
    3_0_X backport
---
 .../transform/stc/StaticTypeCheckingVisitor.java   | 98 +++++++++++-----------
 .../groovy/transform/stc/ClosuresSTCTest.groovy    | 32 +++++++
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 31 +++++++
 3 files changed, 114 insertions(+), 47 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 9194c46..4e3a82c 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -209,6 +209,8 @@ import static org.codehaus.groovy.ast.tools.WideningCategories.isLongCategory;
 import static org.codehaus.groovy.ast.tools.WideningCategories.isNumberCategory;
 import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound;
 import static org.codehaus.groovy.classgen.AsmClassGenerator.MINIMUM_BYTECODE_VERSION;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.init;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.last;
 import static org.codehaus.groovy.syntax.Types.ASSIGN;
 import static org.codehaus.groovy.syntax.Types.COMPARE_EQUAL;
 import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL;
@@ -2225,48 +2227,47 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
 
     @Override
     public void visitConstructorCallExpression(final ConstructorCallExpression call) {
-        if (extension.beforeMethodCall(call)) {
-            extension.afterMethodCall(call);
-            return;
-        }
-        ClassNode receiver;
-        if (call.isThisCall()) {
-            receiver = makeThis();
-        } else if (call.isSuperCall()) {
-            receiver = makeSuper();
-        } else {
-            receiver = call.getType();
-        }
-        Expression arguments = call.getArguments();
-        ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(arguments);
-
-        checkForbiddenSpreadArgument(argumentList);
-        visitMethodCallArguments(receiver, argumentList, false, null);
+        if (!extension.beforeMethodCall(call)) {
+            ClassNode receiver;
+            if (call.isThisCall()) {
+                receiver = makeThis();
+            } else if (call.isSuperCall()) {
+                receiver = makeSuper();
+            } else {
+                receiver = call.getType();
+            }
+            Expression arguments = call.getArguments();
+            ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(arguments);
 
-        ClassNode[] args = getArgumentTypes(argumentList);
+            checkForbiddenSpreadArgument(argumentList);
+            visitMethodCallArguments(receiver, argumentList, false, null);
+            final ClassNode[] argumentTypes = getArgumentTypes(argumentList);
 
-        MethodNode node;
-        if (looksLikeNamedArgConstructor(receiver, args)
-                && findMethod(receiver, "<init>", DefaultGroovyMethods.init(args)).size() == 1
-                && findMethod(receiver, "<init>", args).isEmpty()) {
-            // bean-style constructor
-            node = typeCheckMapConstructor(call, receiver, arguments);
-            if (node != null) {
-                storeTargetMethod(call, node);
-                extension.afterMethodCall(call);
-                return;
-            }
-        }
-        node = findMethodOrFail(call, receiver, "<init>", args);
-        if (node != null) {
-            if (looksLikeNamedArgConstructor(receiver, args) && node.getParameters().length + 1 == args.length) {
-                node = typeCheckMapConstructor(call, receiver, arguments);
+            MethodNode ctor;
+            if (looksLikeNamedArgConstructor(receiver, argumentTypes)
+                    && findMethod(receiver, "<init>", argumentTypes).isEmpty()
+                    && findMethod(receiver, "<init>", init(argumentTypes)).size() == 1) {
+                ctor = typeCheckMapConstructor(call, receiver, arguments);
             } else {
-                typeCheckMethodsWithGenericsOrFail(receiver, args, node, call);
+                ctor = findMethodOrFail(call, receiver, "<init>", argumentTypes);
+                if (ctor != null) {
+                    Parameter[] parameters = ctor.getParameters();
+                    if (looksLikeNamedArgConstructor(receiver, argumentTypes)
+                            && parameters.length == argumentTypes.length - 1) {
+                        ctor = typeCheckMapConstructor(call, receiver, arguments);
+                    } else {
+                        if (receiver.getGenericsTypes() != null) { // GROOVY-8961, GROOVY-9734, GROOVY-9915, GROOVY-10482, et al.
+                            Map<GenericsTypeName, GenericsType> context = extractPlaceHolders(null, receiver, ctor.getDeclaringClass());
+                            parameters = Arrays.stream(parameters).map(p -> new Parameter(applyGenericsContext(context, p.getType()), p.getName())).toArray(Parameter[]::new);
+                        }
+                        resolvePlaceholdersFromImplicitTypeHints(argumentTypes, argumentList, parameters);
+                        typeCheckMethodsWithGenericsOrFail(receiver, argumentTypes, ctor, call);
+                        visitMethodCallArguments(receiver, argumentList, true, ctor);
+                    }
+                }
             }
-            if (node != null) {
-                storeTargetMethod(call, node);
-                visitMethodCallArguments(receiver, argumentList, true, node);
+            if (ctor != null) {
+                storeTargetMethod(call, ctor);
             }
         }
 
@@ -2642,7 +2643,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
                 if (!mn.isEmpty()) {
                     if (mn.size() == 1) {
                         // GROOVY-8961, GROOVY-9734, GROOVY-9915
-                        resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0));
+                        resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0).getParameters());
                         typeCheckMethodsWithGenericsOrFail(currentReceiver.getType(), args, mn.get(0), call);
                     }
                     chosenReceiver = currentReceiver;
@@ -3543,7 +3544,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
                             }
                         }
                         // GROOVY-8961, GROOVY-9734
-                        resolvePlaceholdersFromImplicitTypeHints(args, argumentList, directMethodCallCandidate);
+                        resolvePlaceholdersFromImplicitTypeHints(args, argumentList, directMethodCallCandidate.getParameters());
                         if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, directMethodCallCandidate, call)) {
                             returnType = adjustWithTraits(directMethodCallCandidate, chosenReceiver.getType(), args, returnType);
 
@@ -4059,7 +4060,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
 
     private static boolean notReturningBlock(final Statement statement) {
         return statement.isEmpty() || !(statement instanceof BlockStatement)
-            || !(DefaultGroovyMethods.last(((BlockStatement) statement).getStatements()) instanceof ReturnStatement);
+            || !(last(((BlockStatement) statement).getStatements()) instanceof ReturnStatement);
     }
 
     @Override
@@ -5306,12 +5307,12 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
      * argument is {@code List<T>} without explicit type arguments. Knowning the
      * method target of "m", {@code T} could be resolved.
      */
-    private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final MethodNode inferredMethod) {
+    private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final Parameter[] parameterArray) {
         for (int i = 0, n = actuals.length; i < n; i += 1) {
-            // check for method call with known target
             Expression a = argumentList.getExpression(i);
-            if (!(a instanceof MethodCallExpression)) continue;
-            if (((MethodCallExpression) a).isUsingGenerics()) continue;
+            // check for method call without type arguments, with a known target
+            if (!(a instanceof MethodCall) || (a instanceof MethodCallExpression
+                    && ((MethodCallExpression) a).isUsingGenerics())) continue;
             MethodNode aNode = a.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
             if (aNode == null || aNode.getGenericsTypes() == null) continue;
 
@@ -5319,8 +5320,8 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             ClassNode at = actuals[i];
             if (!GenericsUtils.hasUnresolvedGenerics(at)) continue;
 
-            int np = inferredMethod.getParameters().length;
-            Parameter p = inferredMethod.getParameters()[Math.min(i, np - 1)];
+            int np = parameterArray.length;
+            Parameter p = parameterArray[Math.min(i, np - 1)];
 
             ClassNode pt = p.getOriginType();
             if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType();
@@ -5331,6 +5332,9 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             Map<GenericsTypeName, GenericsType> source = GenericsUtils.extractPlaceholders(at);
             Map<GenericsTypeName, GenericsType> target = GenericsUtils.extractPlaceholders(pt);
 
+            if (at.isGenericsPlaceHolder()) // GROOVY-10482: call argument via "def <T> T m()"
+                target.put(new GenericsTypeName(at.getUnresolvedName()), pt.asGenericsType());
+
             // connect E:T from source to E:Type from target
             for (GenericsType placeholder : aNode.getGenericsTypes()) {
                 for (Map.Entry<GenericsTypeName, GenericsType> e : source.entrySet()) {
diff --git a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
index 510d07b..a910068 100644
--- a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
@@ -813,4 +813,36 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
             """
         }
     }
+
+    void testTypeCheckingOfClosureInMapConstructorCalls() {
+        shouldFailWithMessages '''
+            class Foo {
+                Number bar
+                Object baz
+            }
+
+            new Foo(bar: 123, baz: { ->
+                int i = Integer
+            })
+        ''',
+        'Cannot assign value of type java.lang.Class <java.lang.Integer> to variable of type int'
+
+        shouldFailWithMessages '''
+            import groovy.transform.NamedParam
+            import groovy.transform.NamedParams
+
+            class Foo {
+                Foo(@NamedParams([
+                    @NamedParam(value='bar',type=Number)
+                ]) Map<String,?> named, Object positional) {
+                }
+            }
+
+            new Foo(bar: Number, { ->
+                int i = Integer
+            })
+        ''',
+        'Cannot assign value of type java.lang.Class <java.lang.Integer> to variable of type int',
+        "named param 'bar' has type 'java.lang.Class <java.lang.Number>' but expected 'java.lang.Number'"
+    }
 }
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index c16b875..0a5a761 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -1455,6 +1455,37 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    void testCompatibleArgumentsForPlaceholders1() {
+        assertScript '''
+            def <X> X foo(X x) {
+            }
+            def <Y> Y bar() {
+            }
+            def <Z> void baz() {
+                this.<Z>foo(bar())
+            }
+            this.<String>baz()
+        '''
+    }
+
+    // GROOVY-10482
+    void testCompatibleArgumentsForPlaceholders2() {
+        ['', 'def', 'public', 'static', '@Deprecated'].each {
+            assertScript """
+                class Foo<X> {
+                    Foo(X x) {
+                    }
+                }
+                $it <Y> Y bar() {
+                }
+                $it <Z> void baz() {
+                    new Foo<Z>(bar()) // Cannot call Foo#<init>(Z) with arguments [#Y]
+                }
+                this.<String>baz()
+            """
+        }
+    }
+
     void testIncompatibleGenericsForTwoArguments() {
         shouldFailWithMessages '''
             public <T> void printEqual(T arg1, T arg2) {