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/05/07 20:34:15 UTC

[groovy] branch master updated: GROOVY-10075: STC: always re-check extension method receiver/argument(s)

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 308287c  GROOVY-10075: STC: always re-check extension method receiver/argument(s)
308287c is described below

commit 308287cbc3ef8f0d1a32c0ebec8c63e92e838ada
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri May 7 15:22:53 2021 -0500

    GROOVY-10075: STC: always re-check extension method receiver/argument(s)
---
 .../transform/stc/StaticTypeCheckingSupport.java   | 26 +++----
 .../transform/stc/StaticTypeCheckingVisitor.java   |  7 +-
 .../stc/DefaultGroovyMethodsSTCTest.groovy         | 80 +++++++++++++++-------
 .../groovy/runtime/m12n/TestStringExtension.java   | 12 +++-
 4 files changed, 84 insertions(+), 41 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 fa24e5a..de4b243 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1399,25 +1399,21 @@ public abstract class StaticTypeCheckingSupport {
     }
 
     protected static boolean typeCheckMethodsWithGenerics(final ClassNode receiver, final ClassNode[] argumentTypes, final MethodNode candidateMethod) {
-        boolean isExtensionMethod = candidateMethod instanceof ExtensionMethodNode;
-        if (!isExtensionMethod
-                && receiver.isUsingGenerics()
+        if (candidateMethod instanceof ExtensionMethodNode) {
+            ClassNode[] realTypes = new ClassNode[argumentTypes.length + 1];
+            realTypes[0] = receiver; // object expression is implicit argument
+            System.arraycopy(argumentTypes, 0, realTypes, 1, argumentTypes.length);
+            MethodNode realMethod = ((ExtensionMethodNode) candidateMethod).getExtensionMethodNode();
+            return typeCheckMethodsWithGenerics(realMethod.getDeclaringClass(), realTypes, realMethod, true);
+        }
+
+        if (receiver.isUsingGenerics()
                 && receiver.equals(CLASS_Type)
                 && !candidateMethod.getDeclaringClass().equals(CLASS_Type)) {
             return typeCheckMethodsWithGenerics(receiver.getGenericsTypes()[0].getType(), argumentTypes, candidateMethod);
         }
-        // both candidate method and receiver have generic information so a check is possible
-        GenericsType[] genericsTypes = candidateMethod.getGenericsTypes();
-        boolean methodUsesGenerics = (genericsTypes != null && genericsTypes.length > 0);
-        if (isExtensionMethod && methodUsesGenerics) {
-            ClassNode[] dgmArgs = new ClassNode[argumentTypes.length + 1];
-            dgmArgs[0] = receiver;
-            System.arraycopy(argumentTypes, 0, dgmArgs, 1, argumentTypes.length);
-            MethodNode extensionMethodNode = ((ExtensionMethodNode) candidateMethod).getExtensionMethodNode();
-            return typeCheckMethodsWithGenerics(extensionMethodNode.getDeclaringClass(), dgmArgs, extensionMethodNode, true);
-        } else {
-            return typeCheckMethodsWithGenerics(receiver, argumentTypes, candidateMethod, false);
-        }
+
+        return typeCheckMethodsWithGenerics(receiver, argumentTypes, candidateMethod, false);
     }
 
     private static boolean typeCheckMethodsWithGenerics(final ClassNode receiver, final ClassNode[] argumentTypes, final MethodNode candidateMethod, final boolean isExtensionMethod) {
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 67e0093..171b963 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -1611,6 +1611,11 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
                 for (MethodNode method : findDGMMethodsByNameAndArguments(getSourceUnit().getClassLoader(), dgmReceiver, "is" + capName, ClassNode.EMPTY_ARRAY)) {
                     if (Boolean_TYPE.equals(getWrapper(method.getReturnType()))) methods.add(method);
                 }
+                if (isUsingGenericsOrIsArrayUsingGenerics(dgmReceiver)) {
+                    methods.removeIf(method -> // GROOVY-10075: "List<Integer>" vs "List<String>"
+                        !typeCheckMethodsWithGenerics(dgmReceiver, ClassNode.EMPTY_ARRAY, method)
+                    );
+                }
                 if (!methods.isEmpty()) {
                     List<MethodNode> bestMethods = chooseBestMethod(dgmReceiver, methods, ClassNode.EMPTY_ARRAY);
                     if (bestMethods.size() == 1) {
@@ -5357,7 +5362,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             // connect E:T from source to E:Type from target
             for (GenericsType placeholder : aNode.getGenericsTypes()) {
                 for (Map.Entry<GenericsTypeName, GenericsType> e : source.entrySet()) {
-                    if (e.getValue().getName().equals(placeholder.getName())) {
+                    if (e.getValue() == placeholder) {
                         Optional.ofNullable(target.get(e.getKey()))
                             // skip "f(g())" for "f(T<String>)" and "<U extends Number> U g()"
                             .filter(gt -> isAssignableTo(gt.getType(), placeholder.getType()))
diff --git a/src/test/groovy/transform/stc/DefaultGroovyMethodsSTCTest.groovy b/src/test/groovy/transform/stc/DefaultGroovyMethodsSTCTest.groovy
index cf4c8f6..6192bba 100644
--- a/src/test/groovy/transform/stc/DefaultGroovyMethodsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/DefaultGroovyMethodsSTCTest.groovy
@@ -24,37 +24,37 @@ package groovy.transform.stc
 class DefaultGroovyMethodsSTCTest extends StaticTypeCheckingTestCase {
 
     void testEach() {
-        assertScript """
+        assertScript '''
             ['a','b'].each { // DGM#each(Object, Closure)
                 println it // DGM#println(Object,Object)
             }
-        """
+        '''
 
-        assertScript """
+        assertScript '''
             ['a','b'].eachWithIndex { it, i ->// DGM#eachWithIndex(Object, Closure)
                 println it // DGM#println(Object,Object)
             }
-        """
+        '''
     }
 
     void testStringToInteger() {
-        assertScript """
-        String name = "123"
-        name.toInteger() // toInteger() is defined by DGM
-        """
+        assertScript '''
+            String name = "123"
+            name.toInteger() // toInteger() is defined by DGM
+        '''
     }
 
     void testVariousAssignmentsThenToInteger() {
-        assertScript """
-         class A {
-          void foo() {}
-         }
-        def name = new A()
-        name.foo()
-        name = 1
-        name = '123'
-        name.toInteger() // toInteger() is defined by DGM
-        """
+        assertScript '''
+            class A {
+                void foo() {}
+            }
+            def name = new A()
+            name.foo()
+            name = 1
+            name = '123'
+            name.toInteger() // toInteger() is defined by DGM
+        '''
     }
 
     void testMethodsOnPrimitiveTypes() {
@@ -76,14 +76,48 @@ class DefaultGroovyMethodsSTCTest extends StaticTypeCheckingTestCase {
     }
 
     // GROOVY-5568
-    void testDGMMethodAsProperty() {
+    void testPropertySemantics1() {
         assertScript '''
-            String foo(InputStream input) {
-                input.text
+            String test(InputStream input) {
+                input.text // IOGroovyMethods#getText(InputStream)
             }
-            def text = new ByteArrayInputStream('foo'.getBytes())
-            assert foo(text) == 'foo'
+            assert test(new ByteArrayInputStream('foo'.bytes)) == 'foo'
         '''
+
+        assertScript '''
+            def chars = new StringBuilder('foo').chars // StringGroovyMethods#getChars(CharSequence)
+            assert chars == new char[] {'f','o','o'}
+        '''
+
+        assertScript '''
+            def a = Character.valueOf((char) 'a')
+            assert a.letter // DefaultGroovyMethods#isLetter(Character)
+        '''
+    }
+
+    // GROOVY-10075
+    void testPropertySemantics2() {
+        // see org.codehaus.groovy.runtime.m12n.TestStringExtension
+
+        assertScript '''
+            List<String> strings = ['x','y','z']
+            assert strings.getSequence() == 'x'
+            assert strings.getString() == 'x'
+          //assert strings.sequence == 'x'
+          //assert strings.string == 'x'
+        '''
+
+        shouldFailWithMessages '''
+            List<Number> numbers = [1, 2, 3]
+            numbers.getSequence()
+            numbers.getString()
+            numbers.sequence
+            numbers.string
+        ''',
+        'Cannot call <CS extends java.lang.CharSequence> java.util.ArrayList#getSequence() with arguments []',
+        'Cannot call java.util.ArrayList#getString() with arguments []',
+        'No such property: sequence for class: java.util.ArrayList',
+        'No such property: string for class: java.util.ArrayList'
     }
 
     // GROOVY-5584
diff --git a/src/test/org/codehaus/groovy/runtime/m12n/TestStringExtension.java b/src/test/org/codehaus/groovy/runtime/m12n/TestStringExtension.java
index fdd5e9f..fbbe8c2 100644
--- a/src/test/org/codehaus/groovy/runtime/m12n/TestStringExtension.java
+++ b/src/test/org/codehaus/groovy/runtime/m12n/TestStringExtension.java
@@ -19,8 +19,16 @@
 package org.codehaus.groovy.runtime.m12n;
 
 public class TestStringExtension {
+
     public static String reverseToUpperCase(String self) {
-        StringBuilder sb = new StringBuilder(self.toUpperCase());
-        return sb.reverse().toString();
+        return new StringBuilder(self.toUpperCase()).reverse().toString();
+    }
+
+    public static String getString(java.util.List<String> self) {
+        return self.get(0);
+    }
+
+    public static <CS extends CharSequence> CharSequence getSequence(java.util.List<CS> self) {
+        return self.get(0);
     }
 }