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/07/18 18:25:22 UTC

[groovy] branch GROOVY_2_5_X updated (e29c22e3e7 -> c3d1eff730)

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

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


 discard e29c22e3e7 GROOVY-9956, GROOVY-10086, GROOVY-10316, GROOVY-10344: STC: CCE diamond
     new dbd8134488 GROOVY-9956, GROOVY-10086, GROOVY-10316, GROOVY-10344: STC: CCE diamond
     new c3d1eff730 GROOVY-9891: return bound type for wildcard GenericsType -> ClassNode

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (e29c22e3e7)
            \
             N -- N -- N   refs/heads/GROOVY_2_5_X (c3d1eff730)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/codehaus/groovy/ast/GenericsType.java | 10 ++--
 .../codehaus/groovy/ast/tools/GenericsUtils.java   |  5 ++
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 59 +++++++++++++++++++++-
 3 files changed, 66 insertions(+), 8 deletions(-)


[groovy] 01/02: GROOVY-9956, GROOVY-10086, GROOVY-10316, GROOVY-10344: STC: CCE diamond

Posted by em...@apache.org.
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

commit dbd8134488fe877be3351158ef981c294ce54cdd
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Jul 18 11:27:42 2022 -0500

    GROOVY-9956, GROOVY-10086, GROOVY-10316, GROOVY-10344: STC: CCE diamond
---
 .../java/org/codehaus/groovy/ast/GenericsType.java |  98 +++-------
 .../transform/stc/StaticTypeCheckingSupport.java   |  29 +--
 .../transform/stc/StaticTypeCheckingVisitor.java   |  61 ++++--
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 215 ++++++++++++++++++---
 4 files changed, 274 insertions(+), 129 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/GenericsType.java b/src/main/java/org/codehaus/groovy/ast/GenericsType.java
index 18a16da88c..e07a63b225 100644
--- a/src/main/java/org/codehaus/groovy/ast/GenericsType.java
+++ b/src/main/java/org/codehaus/groovy/ast/GenericsType.java
@@ -173,12 +173,8 @@ public class GenericsType extends ASTNode {
     //--------------------------------------------------------------------------
 
     /**
-     * Compares this generics type with the provided class node. If the provided
-     * class node is compatible with the generics specification, returns true.
-     * Otherwise, returns false. The check is complete, meaning that nested
-     * generics are also checked.
-     *
-     * @return if {@code classNode} is or is not compatible with this generics specification
+     * Determines if the provided type is compatible with this specification.
+     * The check is complete, meaning that nested generics are also checked.
      */
     public boolean isCompatibleWith(final ClassNode classNode) {
         GenericsType[] genericsTypes = classNode.getGenericsTypes();
@@ -196,21 +192,24 @@ public class GenericsType extends ASTNode {
                 return name0.equals(getName());
             }
             if (getLowerBound() != null) {
+                // check for "? super T" vs "T"
                 if (name0.equals(getLowerBound().getUnresolvedName())) {
                     return true;
                 }
             } else if (getUpperBounds() != null) {
+                // check for "? extends T" vs "T"
                 if (name0.equals(getUpperBounds()[0].getUnresolvedName())) {
                     return true;
                 }
             }
+            // check for "? extends/super X" vs "T extends/super X"
             return checkGenerics(classNode);
         }
         if (isWildcard() || isPlaceholder()) {
             // if the generics spec is a wildcard or a placeholder then check the bounds
             ClassNode lowerBound = getLowerBound();
             if (lowerBound != null) {
-                // for a lower bound, perform the upper bound checks with reversed arguments
+                // test bound and type in reverse for lower bound vs upper bound
                 if (!implementsInterfaceOrIsSubclassOf(lowerBound, classNode)) {
                     return false;
                 }
@@ -224,19 +223,16 @@ public class GenericsType extends ASTNode {
                         return false;
                     }
                 }
-                // if the provided classnode is a subclass of the upper bound
-                // then check that the generic types supplied by the class node are compatible with
-                // this generics specification
-                // for example, we could have the spec saying List<String> but provided classnode
-                // saying List<Integer>
+                // if the provided type is a subclass of the upper bound(s) then
+                // check that the generic types supplied are compatible with this
+                // for example, this spec could be "Type<X>" but type is "Type<Y>"
                 return checkGenerics(classNode);
             }
-            // if there are no bounds, the generic type is basically Object, and everything is compatible
+            // if there are no bounds, the generic type is basically Object and everything is compatible
             return true;
         }
-        // last, we could have the spec saying List<String> and a classnode saying List<Integer> so
-        // we must check that generics are compatible
-        return getType().equals(classNode) && compareGenericsWithBound(classNode, type);
+        // not placeholder or wildcard; no covariance allowed for type or bound(s)
+        return classNode.equals(getType()) && compareGenericsWithBound(classNode, getType());
     }
 
     private static boolean implementsInterfaceOrIsSubclassOf(final ClassNode type, final ClassNode superOrInterface) {
@@ -294,13 +290,12 @@ public class GenericsType extends ASTNode {
      * @return true if generics are compatible
      */
     private static boolean compareGenericsWithBound(final ClassNode classNode, final ClassNode bound) {
-        if (classNode == null) {
-            return false;
-        }
-        if (bound.getGenericsTypes() == null || (classNode.getGenericsTypes() == null && classNode.redirect().getGenericsTypes() != null)) {
-            // if the bound is not using generics or the class node is a raw type, there's nothing to compare with
+        if (classNode == null) return false;
+        if (bound.getGenericsTypes() == null
+                || (classNode.getGenericsTypes() == null && classNode.redirect().getGenericsTypes() != null))
+            // if the bound is not using generics or the class node is a raw type, there's nothing to compare
             return true;
-        }
+
         if (!classNode.equals(bound)) {
              // the class nodes are on different types
             // in this situation, we must choose the correct execution path : either the bound
@@ -316,8 +311,10 @@ public class GenericsType extends ASTNode {
                         // class node are not parameterized. This means that we must create a
                         // new class node with the parameterized types that the current class node
                         // has defined.
-                        ClassNode node = GenericsUtils.parameterizeType(classNode, face);
-                        return compareGenericsWithBound(node, bound);
+                        if (face.getGenericsTypes() != null) {
+                            face = GenericsUtils.parameterizeType(classNode, face);
+                        }
+                        return compareGenericsWithBound(face, bound);
                     }
                 }
             }
@@ -333,7 +330,16 @@ public class GenericsType extends ASTNode {
                     if (success) return true;
                 }
             }
-            return compareGenericsWithBound(getParameterizedSuperClass(classNode), bound);
+            if (classNode.equals(ClassHelper.OBJECT_TYPE)) {
+                return false;
+            }
+            ClassNode superClass = classNode.getUnresolvedSuperClass();
+            if (superClass == null) {
+                superClass = ClassHelper.OBJECT_TYPE;
+            } else if (superClass.getGenericsTypes() != null) {
+                superClass = GenericsUtils.parameterizeType(classNode, superClass);
+            }
+            return compareGenericsWithBound(superClass, bound);
         }
 
         GenericsType[] cnTypes = classNode.getGenericsTypes();
@@ -341,7 +347,7 @@ public class GenericsType extends ASTNode {
             cnTypes = classNode.redirect().getGenericsTypes();
         }
         if (cnTypes == null) {
-            // may happen if generic type is Foo<T extends Foo> and classnode is Foo -> Foo
+            // may happen if generic type is Foo<T extends Foo> and ClassNode is Foo -> Foo
             return true;
         }
 
@@ -452,46 +458,6 @@ public class GenericsType extends ASTNode {
         return match;
     }
 
-    /**
-     * If you have a class which extends a class using generics, returns the superclass with parameterized types. For
-     * example, if you have:
-     * <code>class MyList&lt;T&gt; extends LinkedList&lt;T&gt;
-     * def list = new MyList&lt;String&gt;
-     * </code>
-     * then the parameterized superclass for MyList&lt;String&gt; is LinkedList&lt;String&gt;
-     * @param classNode the class for which we want to return the parameterized superclass
-     * @return the parameterized superclass
-     */
-    private static ClassNode getParameterizedSuperClass(final ClassNode classNode) {
-        if (ClassHelper.OBJECT_TYPE.equals(classNode)) return null;
-        ClassNode superClass = classNode.getUnresolvedSuperClass();
-        if (superClass == null) return ClassHelper.OBJECT_TYPE;
-
-        if (!classNode.isUsingGenerics() || !superClass.isUsingGenerics()) {
-            return superClass;
-        }
-
-        GenericsType[] genericsTypes = classNode.getGenericsTypes();
-        GenericsType[] redirectGenericTypes = classNode.redirect().getGenericsTypes();
-        superClass = superClass.getPlainNodeReference();
-        if (genericsTypes == null || redirectGenericTypes == null || superClass.getGenericsTypes() == null) {
-            return superClass;
-        }
-        for (int i = 0, genericsTypesLength = genericsTypes.length; i < genericsTypesLength; i += 1) {
-            if (redirectGenericTypes[i].isPlaceholder()) {
-                GenericsType genericsType = genericsTypes[i];
-                GenericsType[] superGenericTypes = superClass.getGenericsTypes();
-                for (int j = 0, superGenericTypesLength = superGenericTypes.length; j < superGenericTypesLength; j += 1) {
-                    GenericsType superGenericType = superGenericTypes[j];
-                    if (superGenericType.isPlaceholder() && superGenericType.getName().equals(redirectGenericTypes[i].getName())) {
-                        superGenericTypes[j] = genericsType;
-                    }
-                }
-            }
-        }
-        return superClass;
-    }
-
     //--------------------------------------------------------------------------
 
     public static class GenericsTypeName {
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 634f685083..13c56c71ea 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -16,7 +16,6 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-
 package org.codehaus.groovy.transform.stc;
 
 import org.codehaus.groovy.GroovyBugError;
@@ -66,7 +65,6 @@ import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.UUID;
@@ -159,13 +157,15 @@ import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED;
 import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED_EQUAL;
 
 /**
- * Static support methods for {@link StaticTypeCheckingVisitor}.
+ * Support methods for {@link StaticTypeCheckingVisitor}.
  */
 public abstract class StaticTypeCheckingSupport {
-    protected static final ClassNode Collection_TYPE = makeWithoutCaching(Collection.class);
-    protected static final ClassNode Deprecated_TYPE = makeWithoutCaching(Deprecated.class);
+
     protected static final ClassNode Matcher_TYPE = makeWithoutCaching(Matcher.class);
     protected static final ClassNode ArrayList_TYPE = makeWithoutCaching(ArrayList.class);
+    protected static final ClassNode Collection_TYPE = makeWithoutCaching(Collection.class);
+    protected static final ClassNode Deprecated_TYPE = makeWithoutCaching(Deprecated.class);
+
     protected static final ExtensionMethodCache EXTENSION_METHOD_CACHE = new ExtensionMethodCache();
     protected static final Map<ClassNode, Integer> NUMBER_TYPES = Collections.unmodifiableMap(
             new HashMap<ClassNode, Integer>() {
@@ -1539,7 +1539,7 @@ public abstract class StaticTypeCheckingSupport {
     private static Set<GenericsTypeName> extractResolvedPlaceHolders(Map<GenericsTypeName, GenericsType> resolvedMethodGenerics) {
         if (resolvedMethodGenerics.isEmpty()) return Collections.EMPTY_SET;
         Set<GenericsTypeName> result = new HashSet<GenericsTypeName>();
-        for (Entry<GenericsTypeName, GenericsType> entry : resolvedMethodGenerics.entrySet()) {
+        for (Map.Entry<GenericsTypeName, GenericsType> entry : resolvedMethodGenerics.entrySet()) {
             GenericsType value = entry.getValue();
             if (value.isPlaceholder()) continue;
             result.add(entry.getKey());
@@ -1592,7 +1592,7 @@ public abstract class StaticTypeCheckingSupport {
     }
 
     private static boolean compatibleConnections(Map<GenericsTypeName, GenericsType> connections, Map<GenericsTypeName, GenericsType> resolvedMethodGenerics, Set<GenericsTypeName> fixedGenericsPlaceHolders) {
-        for (Entry<GenericsTypeName, GenericsType> entry : connections.entrySet()) {
+        for (Map.Entry<GenericsTypeName, GenericsType> entry : connections.entrySet()) {
             GenericsType resolved = resolvedMethodGenerics.get(entry.getKey());
             if (resolved == null) continue;
             GenericsType connection = entry.getValue();
@@ -1639,7 +1639,7 @@ public abstract class StaticTypeCheckingSupport {
     }
 
     private static void addMissingEntries(Map<GenericsTypeName, GenericsType> connections, Map<GenericsTypeName, GenericsType> resolved) {
-        for (Entry<GenericsTypeName, GenericsType> entry : connections.entrySet()) {
+        for (Map.Entry<GenericsTypeName, GenericsType> entry : connections.entrySet()) {
             if (resolved.containsKey(entry.getKey())) continue;
             GenericsType gt = entry.getValue();
             ClassNode cn = gt.getType();
@@ -1981,19 +1981,6 @@ public abstract class StaticTypeCheckingSupport {
         return genericsType.getType();
     }
 
-    private static void applyContextGenerics(Map<GenericsTypeName, GenericsType> resolvedPlaceholders, Map<GenericsTypeName, GenericsType> placeholdersFromContext) {
-        if (placeholdersFromContext == null) return;
-        for (Entry<GenericsTypeName, GenericsType> entry : resolvedPlaceholders.entrySet()) {
-            GenericsType gt = entry.getValue();
-            if (gt.isPlaceholder()) {
-                GenericsTypeName name = new GenericsTypeName(gt.getName());
-                GenericsType outer = placeholdersFromContext.get(name);
-                if (outer == null) continue;
-                entry.setValue(outer);
-            }
-        }
-    }
-
     private static Map<GenericsTypeName, GenericsType> getGenericsParameterMapOfThis(ClassNode cn) {
         if (cn == null) return null;
         Map<GenericsTypeName, GenericsType> map = null;
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 ab98570249..6173a262ef 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -1058,19 +1058,39 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
     }
 
     protected void inferDiamondType(final ConstructorCallExpression cce, final ClassNode lType) {
-        ClassNode cceType = cce.getType();
+        ClassNode cceType = cce.getType(), inferredType = lType;
         // check if constructor call expression makes use of the diamond operator
         if (cceType.getGenericsTypes() != null && cceType.getGenericsTypes().length == 0) {
-            ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(cce.getArguments());
-            if (argumentListExpression.getExpressions().isEmpty()) {
-                adjustGenerics(lType, cceType);
-            } else {
-                ClassNode type = getType(argumentListExpression.getExpression(0));
+            ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(cce.getArguments());
+            ConstructorNode constructor = cce.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET);
+            if (constructor != null && !argumentList.getExpressions().isEmpty()) {
+                ClassNode type = GenericsUtils.parameterizeType(cceType, cceType);
+                type = inferReturnTypeGenerics(type, constructor, argumentList);
                 if (type.isUsingGenerics()) {
-                    adjustGenerics(type, cceType);
+                    // GROOVY-6232, GROOVY-9956: if cce not assignment compatible, process target as additional type witness
+                    if (checkCompatibleAssignmentTypes(lType, type, cce) && !GenericsUtils.buildWildcardType(lType).isCompatibleWith(type)) {
+                        // allow covariance of each type parameter, but maintain semantics for nested generics
+
+                        ClassNode pType = GenericsUtils.parameterizeType(lType, type);
+                        GenericsType[] lhs = pType.getGenericsTypes(), rhs = type.getGenericsTypes();
+                        if (lhs == null || rhs == null || lhs.length != rhs.length) throw new GroovyBugError(
+                                "Parameterization failed: " + prettyPrintType(pType) + " ~ " + prettyPrintType(type));
+
+                        boolean allMatch = true;
+                        for (int i = 0, n = lhs.length; i < n && allMatch; i += 1) {
+                            if (!GenericsUtils.buildWildcardType(getCombinedBoundType(lhs[i])).isCompatibleWith(getCombinedBoundType(rhs[i]))) {
+                                allMatch = false;
+                            }
+                        }
+                        if (allMatch) type = pType; // lType proved to be a viable type witness
+                    }
+                    inferredType = type;
                 }
             }
-            // store inferred type on CCE
+            if (inferredType.isGenericsPlaceHolder()) // GROOVY-10344: "T t = new C<>()"
+                inferredType = getCombinedBoundType(inferredType.getGenericsTypes()[0]);
+
+            adjustGenerics(inferredType, cceType);
             storeType(cce, cceType);
         }
     }
@@ -5209,9 +5229,20 @@ 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 Parameter[] parameterArray) {
-        for (int i = 0, n = actuals.length; i < n; i += 1) {
+    private void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final Parameter[] parameterArray) {
+        int np = parameterArray.length;
+        for (int i = 0, n = actuals.length; i < n && np > 0; i += 1) {
             Expression a = argumentList.getExpression(i);
+            Parameter p = parameterArray[Math.min(i, np - 1)];
+
+            ClassNode at = actuals[i], pt = p.getOriginType();
+            if (!isUsingGenericsOrIsArrayUsingGenerics(pt)) continue;
+            if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType();
+
+            if (a instanceof ConstructorCallExpression) {
+                inferDiamondType((ConstructorCallExpression) a, pt); // GROOVY-10086, GROOVY-10316
+            }
+
             // check for method call without type arguments, with a known target
             if (!(a instanceof MethodCall) || (a instanceof MethodCallExpression
                     && ((MethodCallExpression) a).isUsingGenerics())) continue;
@@ -5219,14 +5250,12 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             if (aNode == null || aNode.getGenericsTypes() == null) continue;
 
             // and unknown generics
-            ClassNode at = actuals[i];
             if (!GenericsUtils.hasUnresolvedGenerics(at)) continue;
 
-            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();
+            while (!at.equals(pt) && !at.equals(OBJECT_TYPE)) {
+                ClassNode sc = GenericsUtils.getSuperClass(at, pt);
+                at = applyGenericsContext(GenericsUtils.extractPlaceholders(at), sc);
+            }
 
             // try to resolve placeholder(s) in argument type using parameter type
 
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 13bc2ff199..f76b57c149 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -41,21 +41,24 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
     void testDeclaration3() {
         shouldFailWithMessages '''
             Map<String,String> obj = new HashMap<String,Integer>()
-        ''', 'Incompatible generic argument types. Cannot assign java.util.HashMap <String, Integer> to: java.util.Map <String, String>'
+        ''',
+        'Incompatible generic argument types. Cannot assign java.util.HashMap <String, Integer> to: java.util.Map <String, String>'
     }
 
     // GROOVY-10010: check beyond first wildcard
     void testDeclaration4() {
         shouldFailWithMessages '''
             Map<? extends CharSequence,String> obj = new HashMap<String,Integer>()
-        ''', 'Incompatible generic argument types. Cannot assign java.util.HashMap <String, Integer> to: java.util.Map <? extends java.lang.CharSequence, String>'
+        ''',
+        'Incompatible generic argument types. Cannot assign java.util.HashMap <String, Integer> to: java.util.Map <? extends java.lang.CharSequence, String>'
     }
 
     void testAddOnList() {
         shouldFailWithMessages '''
             List<String> list = []
             list.add(1)
-        ''', "[Static type checking] - Cannot find matching method java.util.List#add(int)"
+        ''',
+        'Cannot find matching method java.util.List#add(int)'
     }
 
     void testAddOnList2() {
@@ -81,7 +84,8 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             List<String> list = []
             list << 1
-        ''', 'Cannot call <T> java.util.List <String>#leftShift(T) with arguments [int]'
+        ''',
+        'Cannot call <T> java.util.List <String>#leftShift(T) with arguments [int]'
     }
 
     void testAddOnList2UsingLeftShift() {
@@ -129,14 +133,16 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             List<Integer> list = new LinkedList<>()
             list.add 'Hello'
-        ''', 'Cannot find matching method java.util.LinkedList#add(java.lang.String). Please check if the declared type is correct and if the method exists.'
+        ''',
+        'Cannot find matching method java.util.LinkedList#add(java.lang.String). Please check if the declared type is correct and if the method exists.'
     }
 
     void testAddOnListWithDiamondAndWrongTypeUsingLeftShift() {
         shouldFailWithMessages '''
             List<Integer> list = new LinkedList<>()
             list << 'Hello'
-        ''', 'Cannot call <T> java.util.LinkedList <java.lang.Integer>#leftShift(T) with arguments [java.lang.String]'
+        ''',
+        'Cannot call <T> java.util.LinkedList <java.lang.Integer>#leftShift(T) with arguments [java.lang.String]'
     }
 
     void testAddOnListWithDiamondAndNullUsingLeftShift() {
@@ -281,26 +287,114 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
 
     void testDiamondInferrenceFromConstructor1() {
         assertScript '''
-            Set< Long > s2 = new HashSet<>()
+            class Foo<U> {
+                U method() { }
+            }
+            Foo<Integer> foo = new Foo<>()
+            Integer result = foo.method()
         '''
     }
 
-    void testDiamondInferrenceFromConstructorWithoutAssignment() {
+    void testDiamondInferrenceFromConstructor2() {
         assertScript '''
-            new HashSet<>(Arrays.asList(0L,0L));
+            Set<Long> set = new HashSet<>()
         '''
     }
 
-    void testDiamondInferrenceFromConstructor2() {
-        shouldFailWithMessages '''
-            Set< Number > s3 = new HashSet<>(Arrays.asList(0L,0L));
-        ''', 'Cannot assign java.util.HashSet <java.lang.Long> to: java.util.Set <Number>'
+    void testDiamondInferrenceFromConstructor3() {
+        assertScript '''
+            new HashSet<>(Arrays.asList(0L))
+        '''
+
+        assertScript '''
+            Set<Number> set = new HashSet<>(Arrays.asList(0L))
+        '''
+
+        // not diamond inference, but tests compatible assignment
+        assertScript '''
+            Set<Number> set = new HashSet<Number>(Arrays.asList(0L))
+        '''
+
+        assertScript '''
+            Set<? super Number> set = new HashSet<>(Arrays.asList(0L))
+        '''
+
+        assertScript '''
+            Set<? extends Number> set = new HashSet<>(Arrays.asList(0L))
+        '''
     }
 
-    void testDiamondInferrenceFromConstructor3() {
+    // GROOVY-7419
+    void testDiamondInferrenceFromConstructor4() {
+        assertScript '''
+            Map<Thread.State, Object> map = new EnumMap<>(Thread.State)
+            assert map.size() == 0
+            assert map.isEmpty()
+        '''
+    }
+
+    // GROOVY-9956
+    void testDiamondInferrenceFromConstructor8() {
+        assertScript '''
+            @groovy.transform.TupleConstructor(defaults=false)
+            class C<T> {
+                T p
+            }
+            interface I { }
+            class D implements I { }
+
+            C<I> ci = new C<I>(new D())
+            ci = new C<>(new D()) // infers C<D> on RHS
+        '''
+    }
+
+    void testDiamondInferrenceFromConstructor9() {
         assertScript '''
-            Set<Number> s4 = new HashSet<Number>(Arrays.asList(0L,0L))
+            abstract class A<X> { }
+            @groovy.transform.TupleConstructor(defaults=false)
+            class C<T> extends A<T> {
+                T p
+            }
+            interface I { }
+            class D implements I { }
+
+            A<I> ai = new C<>(new D())
         '''
+
+        shouldFailWithMessages '''
+            abstract class A<X> { }
+            @groovy.transform.TupleConstructor(defaults=false)
+            class C<T> extends A<T> {
+                T p
+            }
+            interface I { }
+            class D implements I { }
+
+            A<String> ax = new C<>(new D())
+        ''',
+        'Incompatible generic argument types. Cannot assign C <D> to: A <String>'
+
+        shouldFailWithMessages '''
+            Set<List<String>> strings = new HashSet<>([new ArrayList<Number>()])
+        ''',
+        'Incompatible generic argument types. Cannot assign java.util.HashSet <java.util.ArrayList> to: java.util.Set <List>'
+    }
+
+    // GROOVY-10086
+    void testDiamondInferrenceFromConstructor12() {
+        shouldFailWithMessages '''
+            @groovy.transform.TupleConstructor(defaults=false)
+            class C<T> {
+                T p
+            }
+            class D {
+            }
+            void m(int i, C<D>... zeroOrMore) {
+                D d = zeroOrMore[0].p
+            }
+            m(0, new C<>(1))
+        ''',
+        'Cannot call', 'm(int, C <D>[]) with arguments [int, C <java.lang.Integer>]'
     }
 
     // GROOVY-10324
@@ -314,6 +408,86 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-10310
+    void testDiamondInferrenceFromConstructor21() {
+        assertScript '''
+            @groovy.transform.TupleConstructor
+            class A<T> {
+                T f
+            }
+            class B<T> {
+            }
+            def <T> A<T> m(T t, B<? extends T> b_of_t) {
+                new A<>(t)
+            }
+            def x = 'x'
+            m(x, new B<>()) // Cannot call m(T,B<? extends T>) with arguments...
+        '''
+    }
+
+    // GROOVY-10344
+    void testDiamondInferrenceFromConstructor22() {
+        assertScript '''
+            class C<X,Y> {
+            }
+            def <T extends C<? extends Number, ? extends Number>> T m(T t) {
+                return t
+            }
+            def x = m(new C<>())
+            assert x instanceof C
+        '''
+    }
+
+    @NotYetImplemented // GROOVY-10230
+    void testDiamondInferrenceFromConstructor23() {
+        assertScript '''
+            class A {
+                def <T extends C<Number,Number>> T m(T t) {
+                    return t
+                }
+            }
+            class B extends A {
+                @Override
+                def <T extends C<Number,Number>> T m(T t) {
+                    T x = null; super.m(true ? t : x)
+                }
+            }
+            class C<X,Y> {
+            }
+            def x = new B().m(new C<>())
+            assert x instanceof C
+        '''
+    }
+
+    // GROOVY-10351
+    void testDiamondInferrenceFromConstructor24() {
+        assertScript '''
+            class C<T> {
+                C(T one, D<T,? extends T> two) {
+                }
+            }
+            class D<U,V> {
+            }
+            D<Integer,? extends Integer> d_of_i_and_i = null
+            C<Integer> c_of_i = new C<>(1,d_of_i_and_i) // 3 witnesses for T
+        '''
+    }
+
+    // GROOVY-10368
+    void testDiamondInferrenceFromConstructor25() {
+        ['T', 'T extends Number', 'T extends Object'].each {
+            assertScript """
+                class C<$it> {
+                    C(p) {
+                    }
+                }
+                void m(C<Integer> c) {
+                }
+                m(new C<>(null)) // Cannot call m(C<Integer>) with arguments [C<# extends Number>]
+            """
+        }
+    }
+
     // GROOVY-10367
     void testDiamondInferrenceFromConstructor26() {
         assertScript '''
@@ -2056,17 +2230,6 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    // GROOVY-6232
-    void testDiamond() {
-        assertScript '''
-            class Foo<T>{  Foo(T a, T b){} }
-            def bar() {
-                Foo<Object> f = new Foo<>("a",new Object())
-            }
-            bar()
-        '''
-    }
-
     // GROOVY-6233
     void testConstructorArgumentsAgainstGenerics() {
         shouldFailWithMessages '''


[groovy] 02/02: GROOVY-9891: return bound type for wildcard GenericsType -> ClassNode

Posted by em...@apache.org.
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

commit c3d1eff73093e2ee127d8bd0d98cc823b4111b2d
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Jul 18 13:09:00 2022 -0500

    GROOVY-9891: return bound type for wildcard GenericsType -> ClassNode
---
 .../codehaus/groovy/ast/tools/GenericsUtils.java   |  5 ++
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 59 +++++++++++++++++++++-
 2 files changed, 63 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
index 086c98d2cc..3bf6b32ad7 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -417,6 +417,11 @@ public class GenericsUtils {
         if (type.isPlaceholder()) {
             String name = type.getName();
             ret = genericsSpec.get(name);
+        } else if (type.isWildcard()) { // GROOVY-9891
+            ret = type.getLowerBound(); // use lower or upper
+            if (ret == null && type.getUpperBounds() != null) {
+                ret = type.getUpperBounds()[0]; // ? supports 1
+            }
         }
         if (ret == null) ret = type.getType();
         return ret;
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index f76b57c149..1f3f814b16 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -2950,8 +2950,10 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             }
             null
         '''
+    }
 
-        // GROOVY-9821
+    // GROOVY-9821
+    void testBoundedReturnTypeChecking2() {
         File parentDir = createTempDir()
         config.with {
             targetDirectory = createTempDir()
@@ -2985,6 +2987,61 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         }
     }
 
+    // GROOVY-9891
+    void testBoundedReturnTypeChecking3() {
+        assertScript '''
+            class Pogo {
+                Map<String, ? extends Number> map
+            }
+            def test(Pogo p) {
+                Collection<? extends Number> c = p.map.values()
+                Iterable<? extends Number> i = p.map.values()
+                i.iterator().next()
+            }
+            def n = test(new Pogo(map:[x:1,y:2.3]))
+            assert n == 1
+        '''
+
+        //
+
+        File parentDir = createTempDir()
+        config.with {
+            targetDirectory = createTempDir()
+            jointCompilationOptions = [stubDir: createTempDir()]
+        }
+        try {
+            def a = new File(parentDir, 'Pojo.java')
+            a.write '''
+                import java.util.Map;
+                class Pojo {
+                    Map<String, ? extends Number> getMap() {
+                        return map;
+                    }
+                    void setMap(Map<String, ? extends Number> map) {
+                        this.map = map;
+                    }
+                    private Map<String, ? extends Number> map = null;
+                }
+            '''
+            def b = new File(parentDir, 'Script.groovy')
+            b.write '''
+                void test(Pojo p) {
+                    Collection<? extends Number> c = p.map.values()
+                    Iterable<? extends Number> i = p.map.values()
+                }
+            '''
+
+            def loader = new GroovyClassLoader(this.class.classLoader)
+            def cu = new JavaAwareCompilationUnit(config, loader)
+            cu.addSources(a, b)
+            cu.compile()
+        } finally {
+            parentDir.deleteDir()
+            config.targetDirectory.deleteDir()
+            config.jointCompilationOptions.stubDir.deleteDir()
+        }
+    }
+
     // GROOVY-10234
     void testSelfReferentialTypeParameter() {
         File parentDir = createTempDir()