Skip to content
Permalink
Browse files
GROOVY-10619: STC: handle wildcard for self-referential type parameter
  • Loading branch information
eric-milles committed May 10, 2022
1 parent 86cf83d commit c2512183947e0657298df384bd88f8add6224d98
Showing 2 changed files with 47 additions and 17 deletions.
@@ -1936,31 +1936,44 @@ static ClassNode boundUnboundedWildcards(final ClassNode type) {
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;
}
return gt;
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();
}
}
GenericsType newType = new GenericsType(makeWithoutCaching("?"), upperBounds, lowerBound);
newType.setWildcard(true);
return newType;
}

public static boolean isUnboundedWildcard(final GenericsType gt) {
@@ -4480,6 +4480,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 '''

0 comments on commit c251218

Please sign in to comment.