You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2022/07/02 00:08:05 UTC

[groovy] branch GROOVY_4_0_X updated: GROOVY-10651, GROOVY-10671: wildcard implicit bounding

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

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


The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
     new 8f441ba226 GROOVY-10651, GROOVY-10671: wildcard implicit bounding
8f441ba226 is described below

commit 8f441ba226e929c442ce168327d8ce304e3ce058
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Jul 1 04:35:25 2022 +0800

    GROOVY-10651, GROOVY-10671: wildcard implicit bounding
    
    (cherry picked from commit 03886c5fe82cb8664da223f8cd82ada2c2c8b3c7)
---
 .../codehaus/groovy/ast/tools/GenericsUtils.java   | 45 +++++++++++-----
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 63 ++++++++++++++++++++++
 2 files changed, 96 insertions(+), 12 deletions(-)

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 ca7f6306b3..143300ee35 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -129,14 +129,29 @@ public class GenericsUtils {
     }
 
     /**
-     * Generates a wildcard generic type in order to be used for checks against class nodes.
-     * See {@link GenericsType#isCompatibleWith(org.codehaus.groovy.ast.ClassNode)}.
+     * Generates a wildcard generic type with implicit upper and optional lower
+     * bounds. The result provides "?" or "? super T" (no "extends") semantics.
+     */
+    private static GenericsType boundWildcardType(final ClassNode implicit, final ClassNode lower) {
+        ClassNode base = ClassHelper.makeWithoutCaching("?");
+        base.setGenericsPlaceHolder(true);
+        base.setRedirect(implicit);
+
+        GenericsType gt = new GenericsType(base, null, lower);
+        gt.setPlaceholder(false);
+        gt.setWildcard(true);
+        return gt;
+    }
+
+    /**
+     * Generates a wildcard generic type in order to be used for checks against
+     * class nodes. See {@link GenericsType#isCompatibleWith(ClassNode)}.
      *
-     * @param types the type to be used as the wildcard upper bound
-     * @return a wildcard generics type
+     * @param types the type(s) to be used as the wildcard's upper bound
      */
     public static GenericsType buildWildcardType(final ClassNode... types) {
         ClassNode base = ClassHelper.makeWithoutCaching("?");
+
         GenericsType gt = new GenericsType(base, types, null);
         gt.setWildcard(true);
         return gt;
@@ -169,31 +184,37 @@ public class GenericsUtils {
         }
 
         if (!type.isUsingGenerics() || !type.isRedirectNode()) return;
-        GenericsType[] parameterized = type.getGenericsTypes(); int n;
-        if (parameterized == null || (n = parameterized.length) == 0) return;
+        GenericsType[] genericsTypes = type.getGenericsTypes(); int n;
+        if (genericsTypes == null || (n = genericsTypes.length) == 0) return;
 
         // GROOVY-8609, GROOVY-10067, etc.
         if (type.isGenericsPlaceHolder()) {
-            GenericsType gt = parameterized[0];
+            GenericsType gt = genericsTypes[0];
             placeholders.putIfAbsent(new GenericsType.GenericsTypeName(gt.getName()), gt);
             return;
         }
 
         GenericsType[] redirectGenericsTypes = type.redirect().getGenericsTypes();
         if (redirectGenericsTypes == null) {
-            redirectGenericsTypes = parameterized;
+            redirectGenericsTypes = genericsTypes;
         } else if (redirectGenericsTypes.length != n) {
             throw new GroovyBugError("Expected earlier checking to detect generics parameter arity mismatch" +
                     "\nExpected: " + type.getName() + toGenericTypesString(redirectGenericsTypes) +
-                    "\nSupplied: " + type.getName() + toGenericTypesString(parameterized));
+                    "\nSupplied: " + type.getName() + toGenericTypesString(genericsTypes));
         }
 
         List<GenericsType> typeArguments = new ArrayList<>(n);
         for (int i = 0; i < n; i += 1) {
             GenericsType rgt = redirectGenericsTypes[i];
-            if (rgt.isPlaceholder()) {
-                GenericsType typeArgument = parameterized[i];
-                placeholders.computeIfAbsent(new GenericsType.GenericsTypeName(rgt.getName()), name -> {
+            if (rgt.isPlaceholder()) { // type parameter
+                GenericsType typeArgument = genericsTypes[i];
+                placeholders.computeIfAbsent(new GenericsType.GenericsTypeName(rgt.getName()), x -> {
+                    if (typeArgument.isWildcard() && typeArgument.getUpperBounds() == null) {
+                        ClassNode[] implicitBounds = rgt.getUpperBounds();//GROOVY-10651,GROOVY-10671
+                        if (implicitBounds != null && !ClassHelper.isObjectType(implicitBounds[0])) {
+                            return boundWildcardType(implicitBounds[0],typeArgument.getLowerBound());
+                        }
+                    }
                     typeArguments.add(typeArgument);
                     return typeArgument;
                 });
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index db2b86e710..d7af6cbf8e 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -376,6 +376,34 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    void testReturnTypeInferenceWithMethodGenerics1x() {
+        assertScript '''
+            interface Handle {
+                int getCount()
+            }
+            class HandleContainer<H extends Handle> {
+                H handle
+            }
+            interface Input {
+                HandleContainer<? extends Handle> getResult(key)
+            }
+            interface State {
+                def <X extends Handle> HandleContainer<X> getResult(key)
+            }
+
+            void test(Input input, State state) {
+                def container = state.getResult('k') ?: input.getResult('k')
+                Handle handle = container.handle
+                Integer count = handle.count
+                assert count == 1
+            }
+
+            Handle h = {->1}
+            def c = new HandleContainer(handle: h)
+            test({k->c} as Input, {k->c} as State)
+        '''
+    }
+
     // GROOVY-10067
     void testReturnTypeInferenceWithMethodGenerics11() {
         assertScript '''
@@ -2494,6 +2522,29 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-10373
+    void testAssignNullTypeParameterWithUpperBounds2() {
+        assertScript '''
+            class A<I, E extends I>  {
+                void bar(E y) { }
+            }
+            class B<T> {
+            }
+            class C<S extends B<Character>> {
+                A<S, ? extends S> foo() {
+                    (A<S, ? extends S>) null
+                }
+            }
+            class D<I extends B<Character>> {
+                void test() {
+                    C<I> x = (C<I>) null
+                    x?.foo()?.bar(null)
+                }
+            }
+            new D<B<Character>>().test()
+        '''
+    }
+
     void testMethodCallWithArgumentUsingNestedGenerics() {
         assertScript '''
            ThreadLocal<Map<Integer, String>> cachedConfigs = new ThreadLocal<Map<Integer, String>>()
@@ -4773,6 +4824,18 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-10671
+    void testAssertJ() {
+        assertScript '''
+            @Grab('org.assertj:assertj-core:3.23.1')
+            import static org.assertj.core.api.Assertions.assertThat
+
+            def strings = (Collection<String>) ['a','b']
+            assertThat(strings).as('assertion description')
+                .containsExactlyInAnyOrderElementsOf(['a','b'])
+        '''
+    }
+
     // GROOVY-10673
     void testMockito() {
         assertScript '''