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 2023/10/24 15:29:52 UTC

[groovy] 01/03: GROOVY-10055, GROOVY-10619: STC: wildcard for self-referential type vars

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

commit 02f50576d3a69690bdff524cb87b411cf978cfc9
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Oct 24 09:37:05 2023 -0500

    GROOVY-10055, GROOVY-10619: STC: wildcard for self-referential type vars
    
    3_0_X backport
---
 .../transform/stc/StaticTypeCheckingSupport.java   | 45 ++++++++++++++--------
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 17 ++++++++
 2 files changed, 46 insertions(+), 16 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 059a1bf9a6..c5d5e0755b 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1957,31 +1957,44 @@ public abstract class StaticTypeCheckingSupport {
         if (type.isArray()) {
             return boundUnboundedWildcards(type.getComponentType()).makeArray();
         }
-        ClassNode target = type.redirect();
-        if (target == null || type == target || !isUsingGenericsOrIsArrayUsingGenerics(target)) return type;
+        ClassNode redirect = type.redirect();
+        if (redirect == null || redirect == type || !isUsingGenericsOrIsArrayUsingGenerics(redirect)) {
+            return type;
+        }
         ClassNode newType = type.getPlainNodeReference();
         newType.setGenericsPlaceHolder(type.isGenericsPlaceHolder());
-        newType.setGenericsTypes(boundUnboundedWildcards(type.getGenericsTypes(), target.getGenericsTypes()));
+        newType.setGenericsTypes(boundUnboundedWildcards(type.getGenericsTypes(), redirect.getGenericsTypes()));
         return newType;
     }
 
-    private static GenericsType[] boundUnboundedWildcards(final GenericsType[] usage, final GenericsType[] declaration) {
-        GenericsType[] newGts = new GenericsType[usage.length];
-        for (int i = 0, n = usage.length; i < n; i += 1) {
-            newGts[i] = boundUnboundedWildcard(usage[i], declaration[i]);
+    private static GenericsType[] boundUnboundedWildcards(final GenericsType[] actual, final GenericsType[] declared) {
+        int n = actual.length; GenericsType[] newTypes = new GenericsType[n];
+        for (int i = 0; i < n; i += 1) {
+            newTypes[i] = boundUnboundedWildcard(actual[i], declared[i]);
         }
-        return newGts;
+        return newTypes;
     }
 
-    private static GenericsType boundUnboundedWildcard(final GenericsType gt, final GenericsType spec) {
-        if (isUnboundedWildcard(gt)) {
-            ClassNode base = makeWithoutCaching("?");
-            // The bounds on the declared type are at least as good as the ones on an unbounded wildcard, since it has none!
-            GenericsType newGt = new GenericsType(base, spec.getUpperBounds(), spec.getLowerBound());
-            newGt.setWildcard(true);
-            return newGt;
+    private static GenericsType boundUnboundedWildcard(final GenericsType actual, final GenericsType declared) {
+        if (!isUnboundedWildcard(actual)) return actual;
+        ClassNode   lowerBound = declared.getLowerBound();
+        ClassNode[] upperBounds = declared.getUpperBounds();
+        if (lowerBound != null) {
+            assert upperBounds == null;
+        } else if (upperBounds == null) {
+            upperBounds = new ClassNode[]{OBJECT_TYPE};
+        } else if (declared.isPlaceholder()) {
+            upperBounds = upperBounds.clone();
+            for (int i = 0, n = upperBounds.length; i < n; i += 1) {
+                // GROOVY-10055, GROOVY-10619: purge self references
+                if (GenericsUtils.extractPlaceholders(upperBounds[i])
+                    .containsKey(new GenericsTypeName(declared.getName())))
+                        upperBounds[i] = upperBounds[i].getPlainNodeReference();
+            }
         }
-        return gt;
+        GenericsType newType = new GenericsType(makeWithoutCaching("?"), upperBounds, lowerBound);
+        newType.setWildcard(true);
+        return newType;
     }
 
     public static boolean isUnboundedWildcard(final GenericsType gt) {
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 5e6a8cfb4f..7cd9ae7492 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -5362,6 +5362,23 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-10619
+    void testSelfReferentialTypeParameter5() {
+        assertScript '''
+            @Grab('org.springframework.boot:spring-boot-starter-webflux:2.6.7')
+            import static org.springframework.http.MediaType.APPLICATION_JSON
+            import org.springframework.core.ParameterizedTypeReference
+
+            List<BigDecimal> test(org.springframework.web.reactive.function.client.WebClient web) {
+                def response = web.get()
+                    .uri('/something?id={id}', 'id').accept(APPLICATION_JSON).retrieve()
+                    .toEntity(new ParameterizedTypeReference<List<BigDecimal>>() {})
+                    .block(java.time.Duration.ofSeconds(2))
+                return response.getBody() ?: []
+            }
+        '''
+    }
+
     // GROOVY-7804
     void testParameterlessClosureToGenericSAMTypeArgumentCoercion() {
         assertScript '''