Skip to content
Permalink
Browse files
GROOVY-10365: type argument/parameter relationships: no type param bound
  • Loading branch information
eric-milles committed May 17, 2022
1 parent 8b44c0a commit cac90d62030685195aa3de1f0001c53b9c1dabe5
Showing 3 changed files with 62 additions and 37 deletions.
@@ -118,6 +118,8 @@ private static String genericsBounds(final ClassNode theType, final Set<String>
private static StringBuilder appendName(final ClassNode theType, final StringBuilder sb) {
if (theType.isArray()) {
appendName(theType.getComponentType(), sb).append("[]");
} else if (theType.isGenericsPlaceHolder()) {
sb.append(theType.getUnresolvedName());
} else if (theType.getOuterClass() != null) {
String parentClassNodeName = theType.getOuterClass().getName();
if (Modifier.isStatic(theType.getModifiers()) || theType.isInterface()) {
@@ -128,7 +130,7 @@ private static StringBuilder appendName(final ClassNode theType, final StringBui
sb.append('.');
sb.append(theType.getName(), parentClassNodeName.length() + 1, theType.getName().length());
} else {
sb.append(theType.isGenericsPlaceHolder() ? theType.getUnresolvedName() : theType.getName());
sb.append(theType.getName());
}
return sb;
}
@@ -40,6 +40,7 @@
import org.codehaus.groovy.runtime.memoize.EvictableCache;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -50,7 +51,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

@@ -142,15 +142,20 @@ public static GenericsType buildWildcardType(final ClassNode... types) {
return gt;
}

/**
* Returns the type parameter/argument relationships of the specified type.
*
* @param type the class node to check
*/
public static Map<GenericsType.GenericsTypeName, GenericsType> extractPlaceholders(final ClassNode type) {
Map<GenericsType.GenericsTypeName, GenericsType> placeholders = new HashMap<>();
extractPlaceholders(type, placeholders);
return placeholders;
}

/**
* For a given classnode, fills in the supplied map with the parameterized
* types it defines.
* Populates the supplied map with the type parameter/argument relationships
* of the specified type.
*
* @param type the class node to check
* @param placeholders the generics type information collector
@@ -164,56 +169,53 @@ public static void extractPlaceholders(final ClassNode type, final Map<GenericsT
}

if (!type.isUsingGenerics() || !type.isRedirectNode()) return;
GenericsType[] parameterized = type.getGenericsTypes();
if (parameterized == null || parameterized.length == 0) return;

Consumer<GenericsType> extractor = (GenericsType gt) -> {
ClassNode lowerBound = gt.getLowerBound();
if (lowerBound != null) {
extractPlaceholders(lowerBound, placeholders);
}
ClassNode[] upperBounds = gt.getUpperBounds();
if (upperBounds != null) {
for (ClassNode upperBound : upperBounds) {
extractPlaceholders(upperBound, placeholders);
}
}
};
GenericsType[] parameterized = type.getGenericsTypes(); int n;
if (parameterized == null || (n = parameterized.length) == 0) return;

// GROOVY-8609, GROOVY-10067
// GROOVY-8609, GROOVY-10067, etc.
if (type.isGenericsPlaceHolder()) {
GenericsType gt = parameterized[0];
placeholders.put(new GenericsType.GenericsTypeName(gt.getName()), gt);
extractor.accept(gt);
placeholders.putIfAbsent(new GenericsType.GenericsTypeName(gt.getName()), gt);
return;
}

GenericsType[] redirectGenericsTypes = type.redirect().getGenericsTypes();
if (redirectGenericsTypes == null) redirectGenericsTypes = parameterized;
else if (redirectGenericsTypes.length != parameterized.length) {
if (redirectGenericsTypes == null) {
redirectGenericsTypes = parameterized;
} 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));
}

List<GenericsType> valueList = new LinkedList<>();
for (int i = 0, n = redirectGenericsTypes.length; i < n; i += 1) {
List<GenericsType> typeArguments = new ArrayList<>(n);
for (int i = 0; i < n; i += 1) {
GenericsType rgt = redirectGenericsTypes[i];
if (rgt.isPlaceholder()) {
GenericsType.GenericsTypeName name = new GenericsType.GenericsTypeName(rgt.getName());
if (!placeholders.containsKey(name)) {
GenericsType value = parameterized[i];
placeholders.put(name, value);
valueList.add(value);
}
GenericsType typeArgument = parameterized[i];
placeholders.computeIfAbsent(new GenericsType.GenericsTypeName(rgt.getName()), name -> {
typeArguments.add(typeArgument);
return typeArgument;
});
}
}

for (GenericsType value : valueList) {
if (value.isWildcard()) {
extractor.accept(value);
} else if (!value.isPlaceholder()) {
extractPlaceholders(value.getType(), placeholders);
// examine non-placeholder type args
for (GenericsType gt : typeArguments) {
if (gt.isWildcard()) {
ClassNode lowerBound = gt.getLowerBound();
if (lowerBound != null) {
extractPlaceholders(lowerBound, placeholders);
} else {
ClassNode[] upperBounds = gt.getUpperBounds();
if (upperBounds != null) {
for (ClassNode upperBound : upperBounds) {
extractPlaceholders(upperBound, placeholders);
}
}
}
} else if (!gt.isPlaceholder()) {
extractPlaceholders(gt.getType(), placeholders);
}
}
}
@@ -3136,6 +3136,27 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
'''
}

// GROOVY-10365
void testCorrectlyBoundedTypeParameterTypeArgument() {
assertScript '''
interface I {
}
class A<T extends Number, Y> implements I {
double m(Integer i) {
i.doubleValue()
}
}
class B<T extends I> {
public int f
double test(A<Float, ? extends T> a) { // "T" is re-purposed
a.m(f) // Cannot call A#m(java.lang.Integer) with arguments [int]
}
}
double result = new B<I>(f:2).test(new A<>())
assert result == 2.0d
'''
}

void testOutOfBoundsByExtendsGenericParameterType() {
shouldFailWithMessages '''
class Foo {

0 comments on commit cac90d6

Please sign in to comment.