You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2026/10/15 20:03:57 UTC
(commons-lang) 02/04: Follow up: Fix TypeUtils.isAssignable rejecting Class to Class> assignments (#1820)
This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
commit 110683aa672b874d7ccecc1b1e89baf7774dd3c4
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Thu Oct 15 15:42:22 2026 -0400
Follow up: Fix TypeUtils.isAssignable rejecting Class<X> to Class<?>
assignments
(#1820)
Fix nested type substitution in TypeUtils assignability.
Substitute inherited generic array components and preserve external type
variables nested in wildcard bounds when traversing parent types. Remove
the redundant assignment-map snapshot and correct the Javadoc.
Add regression coverage for array substitution and upper and lower
wildcard bounds.
---
.../apache/commons/lang3/reflect/TypeUtils.java | 122 ++++++++-------
.../commons/lang3/reflect/TypeUtilsTest.java | 164 ++++++++++++---------
2 files changed, 168 insertions(+), 118 deletions(-)
diff --git a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
index f56db9e4e..173d99c33 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
@@ -863,7 +863,7 @@ private static Map<TypeVariable<?>, Type> getTypeArguments(final ParameterizedTy
final ParameterizedType parameterizedParentType = (ParameterizedType) parentType;
final Type[] parentTypeArgs = parameterizedParentType.getActualTypeArguments().clone();
for (int i = 0; i < parentTypeArgs.length; i++) {
- final Type unrolled = unrollVariables(typeVarAssigns, parentTypeArgs[i]);
+ final Type unrolled = unrollVariables(typeVarAssigns, parentTypeArgs[i], new HashSet<>(), newIdentitySet(), true);
if (unrolled != null) {
parentTypeArgs[i] = unrolled;
}
@@ -1093,7 +1093,7 @@ private static boolean isAssignable(final Type type, final GenericArrayType toGe
* @param toParameterizedType The target parameterized type.
* @param typeVarAssigns A map with type variables.
* @param allowUnchecked Whether unchecked conversions are allowed.
- * @return {@code true} if {@code type} is assignable to {@code toType}.
+ * @return {@code true} if {@code type} is assignable to {@code toParameterizedType}.
*/
private static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType, final Map<TypeVariable<?>, Type> typeVarAssigns,
final boolean allowUnchecked) {
@@ -1141,13 +1141,8 @@ private static boolean isAssignable(final Type type, final ParameterizedType toP
return false;
}
final Map<TypeVariable<?>, Type> fromAssignments = getTypeArguments(fromType);
- // Preserve externally declared variables when substituting the parent type.
- for (final Type argument : new HashMap<>(fromAssignments).values()) {
- if (argument instanceof TypeVariable<?>) {
- fromAssignments.putIfAbsent((TypeVariable<?>) argument, argument);
- }
- }
- final Type parent = unrollVariables(fromAssignments, getClosestParentType(fromClass, toClass));
+ // Preserve unmapped variables, including those nested in wildcard bounds, when substituting the parent type.
+ final Type parent = unrollVariables(fromAssignments, getClosestParentType(fromClass, toClass), new HashSet<>(), newIdentitySet(), true);
return isAssignable(parent, toParameterizedType, typeVarAssigns, false);
}
}
@@ -1204,36 +1199,6 @@ && isAssignableToClassWildcard(fromTypeArg, (WildcardType) toTypeArg, typeVarAss
return true;
}
- /**
- * Tests whether the type argument {@code fromTypeArg} of a {@code Class} parameterized type satisfies the wildcard type
- * argument of a target {@code Class} parameterized type. Unchecked conversion does not apply inside a type argument,
- * including when checking the upper or lower bounds of a wildcard.
- *
- * @param fromTypeArg the subject type argument.
- * @param wildcardType the target wildcard type argument.
- * @param typeVarAssigns a map with type variables.
- * @return {@code true} if {@code fromTypeArg} is contained by {@code wildcardType}.
- */
- private static boolean isAssignableToClassWildcard(final Type fromTypeArg, final WildcardType wildcardType,
- final Map<TypeVariable<?>, Type> typeVarAssigns) {
- return fromTypeArg != null && isAssignable(fromTypeArg, wildcardType, typeVarAssigns, false);
- }
-
- /**
- * Tests whether {@code type} is a raw generic class or a non-static member of a raw generic class.
- *
- * @param type the type to test.
- * @return {@code true} if {@code type} is a raw generic class.
- */
- private static boolean isRawGenericClass(final Type type) {
- if (!(type instanceof Class<?>)) {
- return false;
- }
- final Class<?> cls = (Class<?>) type;
- return cls.getTypeParameters().length > 0
- || !Modifier.isStatic(cls.getModifiers()) && isRawGenericClass(cls.getDeclaringClass());
- }
-
/**
* Tests if the subject type may be implicitly cast to the target type following the Java generics rules. If both types are {@link Class} objects, the
* method returns the result of {@link ClassUtils#isAssignable(Class, Class)}.
@@ -1403,6 +1368,21 @@ private static boolean isAssignable(final Type type, final WildcardType toWildca
return true;
}
+ /**
+ * Tests whether the type argument {@code fromTypeArg} of a {@code Class} parameterized type satisfies the wildcard type
+ * argument of a target {@code Class} parameterized type. Unchecked conversion does not apply inside a type argument,
+ * including when checking the upper or lower bounds of a wildcard.
+ *
+ * @param fromTypeArg the subject type argument.
+ * @param wildcardType the target wildcard type argument.
+ * @param typeVarAssigns a map with type variables.
+ * @return {@code true} if {@code fromTypeArg} is contained by {@code wildcardType}.
+ */
+ private static boolean isAssignableToClassWildcard(final Type fromTypeArg, final WildcardType wildcardType,
+ final Map<TypeVariable<?>, Type> typeVarAssigns) {
+ return fromTypeArg != null && isAssignable(fromTypeArg, wildcardType, typeVarAssigns, false);
+ }
+
/**
* Tests whether the class contains a cyclical reference in the qualified name of a class. If any of the type parameters of A class is extending X class
* which is in scope of A class, then it forms cycle.
@@ -1435,6 +1415,21 @@ public static boolean isInstance(final Object value, final Type type) {
return value == null ? !(type instanceof Class<?>) || !((Class<?>) type).isPrimitive() : isAssignable(value.getClass(), type, null);
}
+ /**
+ * Tests whether {@code type} is a raw generic class or a non-static member of a raw generic class.
+ *
+ * @param type the type to test.
+ * @return {@code true} if {@code type} is a raw generic class.
+ */
+ private static boolean isRawGenericClass(final Type type) {
+ if (!(type instanceof Class<?>)) {
+ return false;
+ }
+ final Class<?> cls = (Class<?>) type;
+ return cls.getTypeParameters().length > 0
+ || !Modifier.isStatic(cls.getModifiers()) && isRawGenericClass(cls.getDeclaringClass());
+ }
+
/**
* Tests whether two type arrays hold the very same types, compared by identity so that no {@code equals()} or {@code hashCode()} of a custom {@link Type}
* is called.
@@ -1827,17 +1822,19 @@ private static String typeVariableToString(final TypeVariable<?> typeVariable) {
/**
* Unrolls variables in a type bounds array, preserving cycle state.
*
- * @param typeArguments assignments {@link Map}.
- * @param bounds in which to expand variables.
- * @param visited set of visited type variables for cycle detection.
- * @param unrolling identity-based types currently being unrolled to prevent recursion cycles.
+ * @param typeArguments assignments {@link Map}.
+ * @param bounds in which to expand variables.
+ * @param visited set of visited type variables for cycle detection.
+ * @param unrolling identity-based types currently being unrolled to prevent recursion cycles.
+ * @param preserveUnmapped whether to preserve variables without assignments.
* @return {@code bounds} with any variables reassigned.
*/
- private static Type[] unrollBounds(final Map<TypeVariable<?>, Type> typeArguments, final Type[] bounds, final Set<TypeVariable<?>> visited, final Set<Type> unrolling) {
+ private static Type[] unrollBounds(final Map<TypeVariable<?>, Type> typeArguments, final Type[] bounds, final Set<TypeVariable<?>> visited, final Set<Type> unrolling,
+ final boolean preserveUnmapped) {
Type[] result = bounds;
int i = 0;
for (; i < result.length; i++) {
- final Type unrolled = unrollVariables(typeArguments, result[i], visited, unrolling);
+ final Type unrolled = unrollVariables(typeArguments, result[i], visited, unrolling, preserveUnmapped);
if (unrolled == null) {
result = ArrayUtils.remove(result, i--);
} else {
@@ -1869,6 +1866,9 @@ private static Type unrollVariableAssignments(TypeVariable<?> typeVariable, fina
/**
* Gets a type representing {@code type} with variable assignments "unrolled."
*
+ * <p>Substitutes variables in parameterized types, wildcard bounds, and generic array components. If an array component resolves to a class, the result
+ * is the corresponding array class.</p>
+ *
* @param typeArguments as from {@link TypeUtils#getTypeArguments(Type, Class)}.
* @param type The type to unroll variable assignments for.
* @return Type.
@@ -1878,22 +1878,39 @@ public static Type unrollVariables(Map<TypeVariable<?>, Type> typeArguments, fin
if (typeArguments == null) {
typeArguments = Collections.emptyMap();
}
- return unrollVariables(typeArguments, type, new HashSet<>(), newIdentitySet());
+ return unrollVariables(typeArguments, type, new HashSet<>(), newIdentitySet(), false);
}
- private static Type unrollVariables(final Map<TypeVariable<?>, Type> typeArguments, final Type type, final Set<TypeVariable<?>> visited, final Set<Type> unrolling) {
+ private static Type unrollVariables(final Map<TypeVariable<?>, Type> typeArguments, final Type type, final Set<TypeVariable<?>> visited, final Set<Type> unrolling,
+ final boolean preserveUnmapped) {
if (type instanceof TypeVariable<?>) {
final TypeVariable<?> var = (TypeVariable<?>) type;
if (!visited.add(var)) {
return var;
}
try {
- return unrollVariables(typeArguments, typeArguments.get(type), visited, unrolling);
+ final Type assignment = typeArguments.get(type);
+ return assignment == null && preserveUnmapped ? var : unrollVariables(typeArguments, assignment, visited, unrolling, preserveUnmapped);
} finally {
// Only guard against cycles: the same variable may legitimately occur more than once in a type.
visited.remove(var);
}
}
+ if (type instanceof GenericArrayType) {
+ if (!unrolling.add(type)) {
+ return type;
+ }
+ try {
+ final Type component = ((GenericArrayType) type).getGenericComponentType();
+ final Type unrolled = unrollVariables(typeArguments, component, visited, unrolling, preserveUnmapped);
+ if (unrolled == null || unrolled == component) {
+ return type;
+ }
+ return unrolled instanceof Class<?> ? Array.newInstance((Class<?>) unrolled, 0).getClass() : genericArrayType(unrolled);
+ } finally {
+ unrolling.remove(type);
+ }
+ }
if (type instanceof ParameterizedType) {
final ParameterizedType p = (ParameterizedType) type;
if (hasCyclicOwnerChain(p) || !unrolling.add(p)) {
@@ -1904,14 +1921,15 @@ private static Type unrollVariables(final Map<TypeVariable<?>, Type> typeArgumen
final Type[] unrolledArgs = args.clone();
boolean changed = false;
for (int i = 0; i < args.length; i++) {
- final Type unrolled = unrollVariables(typeArguments, args[i], visited, unrolling);
+ final Type unrolled = unrollVariables(typeArguments, args[i], visited, unrolling, preserveUnmapped);
if (unrolled != null && unrolled != args[i]) {
unrolledArgs[i] = unrolled;
changed = true;
}
}
final Type owner = p.getOwnerType();
- final Type unrolledOwner = owner != null && !(owner instanceof Class<?>) ? unrollVariables(typeArguments, owner, visited, unrolling) : owner;
+ final Type unrolledOwner = owner != null && !(owner instanceof Class<?>)
+ ? unrollVariables(typeArguments, owner, visited, unrolling, preserveUnmapped) : owner;
if (!changed && unrolledOwner == owner) {
return p;
}
@@ -1928,8 +1946,8 @@ private static Type unrollVariables(final Map<TypeVariable<?>, Type> typeArgumen
try {
final Type[] upperBounds = wild.getUpperBounds();
final Type[] lowerBounds = wild.getLowerBounds();
- final Type[] unrolledUpperBounds = unrollBounds(typeArguments, upperBounds.clone(), visited, unrolling);
- final Type[] unrolledLowerBounds = unrollBounds(typeArguments, lowerBounds.clone(), visited, unrolling);
+ final Type[] unrolledUpperBounds = unrollBounds(typeArguments, upperBounds.clone(), visited, unrolling, preserveUnmapped);
+ final Type[] unrolledLowerBounds = unrollBounds(typeArguments, lowerBounds.clone(), visited, unrolling, preserveUnmapped);
if (isSameTypes(upperBounds, unrolledUpperBounds) && isSameTypes(lowerBounds, unrolledLowerBounds)) {
return wild;
}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
index f68f0175d..284ddddea 100644
--- a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
@@ -225,15 +225,19 @@ public interface And<K, V> extends This<Number, Number> {
// empty
}
- public static class ClassWithSuperClassWithGenericType extends ArrayList<Object> {
+ private static class ArrayValues<T> extends ArrayList<T[]> {
private static final long serialVersionUID = 1L;
-
- public static <U> Iterable<U> methodWithGenericReturnType() {
- return null;
- }
}
private static class ClassWildcardTypes<T extends List, U, V extends List<String>, W extends U> {
+ Class<ArrayValues<String>> arrayValues;
+ Class<ArrayValues<U>> variableArrayValues;
+ Class<? extends List<String[]>> extendsStringArrays;
+ Class<? extends List<U[]>> extendsVariableArrays;
+ Class<ArrayList<? extends U>> upperVariableValues;
+ Class<ArrayList<? super U>> lowerVariableValues;
+ Class<? extends List<? extends U>> extendsUpperVariableValues;
+ Class<? extends List<? super U>> extendsLowerVariableValues;
Class<List<String>> strings;
Class<List> rawList;
Class<ArrayList<String>> stringArrayList;
@@ -276,6 +280,14 @@ private static class ClassWildcardTypes<T extends List, U, V extends List<String
Class<? super W> superDependentVariable;
}
+ public static class ClassWithSuperClassWithGenericType extends ArrayList<Object> {
+ private static final long serialVersionUID = 1L;
+
+ public static <U> Iterable<U> methodWithGenericReturnType() {
+ return null;
+ }
+ }
+
/**
* A custom {@link ParameterizedType} with a structural {@code hashCode()}, whose owner and type arguments can be set after construction so that tests can
* create cycles.
@@ -331,6 +343,14 @@ public String toString() {
}
}
+ private static class GenericNumberOwner<T> {
+ class Member implements TestIF<Number> {
+ }
+
+ static class StaticMember implements TestIF<Number> {
+ }
+ }
+
static class LexOrdering<T> extends MyOrdering<Iterable<T>> implements Serializable {
private static final long serialVersionUID = 1L;
}
@@ -363,6 +383,9 @@ static class MyNonTransientException extends MyException {
static class MyOrdering<T> implements MyComparator<T> {
}
+ private static class NumberTestImpl implements TestIF<Number> {
+ }
+
public class Other<T> implements This<String, T> {
// empty
}
@@ -394,17 +417,6 @@ class Deep<V> {
Map<T, T> map;
}
- private static class GenericNumberOwner<T> {
- class Member implements TestIF<Number> {
- }
-
- static class StaticMember implements TestIF<Number> {
- }
- }
-
- private static class NumberTestImpl implements TestIF<Number> {
- }
-
private static class ParameterizedRawTestImpl<T> extends TestImpl {
}
@@ -474,6 +486,13 @@ public static <T extends Comparable<? extends T>> T stub3() {
static Stream<Arguments> testIsAssignable_ClassWildcardBounds() {
return Stream.of(
+ Arguments.of("arrayValues", "extendsStringArrays", true),
+ Arguments.of("variableArrayValues", "extendsVariableArrays", true),
+ Arguments.of("arrayValues", "extendsVariableArrays", false),
+ Arguments.of("upperVariableValues", "extendsUpperVariableValues", true),
+ Arguments.of("lowerVariableValues", "extendsLowerVariableValues", true),
+ Arguments.of("upperVariableValues", "extendsLowerVariableValues", false),
+ Arguments.of("lowerVariableValues", "extendsUpperVariableValues", false),
Arguments.of("extendsRawList", "extendsStrings", false),
Arguments.of("extendsRawList", "extendsUnknownList", false),
Arguments.of("rawBound", "extendsStrings", false),
@@ -1195,6 +1214,35 @@ void testIsArrayTypeClasses() {
assertFalse(TypeUtils.isArrayType(String.class));
}
+ @ParameterizedTest(name = "{0} -> {1}: {2}")
+ @MethodSource
+ void testIsAssignable_ClassWildcardBounds(final String fromField, final String toField, final boolean expected) throws NoSuchFieldException {
+ final Type from = ClassWildcardTypes.class.getDeclaredField(fromField).getGenericType();
+ final Type to = ClassWildcardTypes.class.getDeclaredField(toField).getGenericType();
+ assertEquals(expected, TypeUtils.isAssignable(from, to), () -> from + " -> " + to);
+ }
+
+ @Test
+ void testIsAssignable_ClassWithBoundedWildcardObject() {
+ final Type to = TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().withUpperBounds(Object.class).build());
+ assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, String.class), to),
+ "Class<String> should be assignable to Class<? extends Object>");
+ }
+
+ @Test
+ void testIsAssignable_ClassWithLowerBoundedWildcard() {
+ final Type to = TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().withLowerBounds(Integer.class).build());
+ assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, Number.class), to),
+ "Class<Number> should be assignable to Class<? super Integer>");
+ assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().withLowerBounds(Number.class).build()), to),
+ "Class<? super Number> should be assignable to Class<? super Integer>");
+ assertFalse(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, String.class), to),
+ "Class<String> should not be assignable to Class<? super Integer>");
+ assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, List.class),
+ TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().withLowerBounds(TypeUtils.parameterize(List.class, String.class)).build())),
+ "Class<List> should be assignable to Class<? super List<String>>");
+ }
+
@Test
void testIsAssignable_ClassWithParameterizedType1() {
final ParameterizedType topre1 = TypeUtils.parameterize(TestIF.class, TypeUtils.wildcardType().build());
@@ -1219,6 +1267,18 @@ void testIsAssignable_ClassWithParameterizedType3() {
assertFalse(TypeUtils.isAssignable(from3, to3), "Class<TestImpl2> should not be assignable to Class<? extends TestIF<Number>>");
}
+ @Test
+ void testIsAssignable_ClassWithParameterizedWildcardBound() {
+ final Type to = TypeUtils.parameterize(Class.class,
+ TypeUtils.wildcardType().withUpperBounds(TypeUtils.parameterize(Collection.class, TypeUtils.wildcardType().build())).build());
+ assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, TypeUtils.parameterize(List.class, String.class)), to),
+ "Class<List<String>> should be assignable to Class<? extends Collection<?>>");
+ assertFalse(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, TypeUtils.parameterize(List.class, String.class)),
+ TypeUtils.parameterize(Class.class, TypeUtils.wildcardType()
+ .withUpperBounds(TypeUtils.parameterize(Collection.class, Integer.class)).build())),
+ "Class<List<String>> should not be assignable to Class<? extends Collection<Integer>>");
+ }
+
@Test
void testIsAssignable_ClassWithUnboundedWildcard() {
final Type to = TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().build());
@@ -1235,13 +1295,6 @@ void testIsAssignable_ClassWithUnboundedWildcard() {
"Class<?> should not be assignable to Class<Integer>");
}
- @Test
- void testIsAssignable_ClassWithBoundedWildcardObject() {
- final Type to = TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().withUpperBounds(Object.class).build());
- assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, String.class), to),
- "Class<String> should be assignable to Class<? extends Object>");
- }
-
@Test
void testIsAssignable_ClassWithUpperBoundedWildcard() {
final Type to = TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().withUpperBounds(Number.class).build());
@@ -1253,49 +1306,6 @@ void testIsAssignable_ClassWithUpperBoundedWildcard() {
"Class<String> should not be assignable to Class<? extends Number>");
}
- @Test
- void testIsAssignable_ClassWithLowerBoundedWildcard() {
- final Type to = TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().withLowerBounds(Integer.class).build());
- assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, Number.class), to),
- "Class<Number> should be assignable to Class<? super Integer>");
- assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().withLowerBounds(Number.class).build()), to),
- "Class<? super Number> should be assignable to Class<? super Integer>");
- assertFalse(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, String.class), to),
- "Class<String> should not be assignable to Class<? super Integer>");
- assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, List.class),
- TypeUtils.parameterize(Class.class, TypeUtils.wildcardType().withLowerBounds(TypeUtils.parameterize(List.class, String.class)).build())),
- "Class<List> should be assignable to Class<? super List<String>>");
- }
-
- @Test
- void testIsAssignable_ClassWithParameterizedWildcardBound() {
- final Type to = TypeUtils.parameterize(Class.class,
- TypeUtils.wildcardType().withUpperBounds(TypeUtils.parameterize(Collection.class, TypeUtils.wildcardType().build())).build());
- assertTrue(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, TypeUtils.parameterize(List.class, String.class)), to),
- "Class<List<String>> should be assignable to Class<? extends Collection<?>>");
- assertFalse(TypeUtils.isAssignable(TypeUtils.parameterize(Class.class, TypeUtils.parameterize(List.class, String.class)),
- TypeUtils.parameterize(Class.class, TypeUtils.wildcardType()
- .withUpperBounds(TypeUtils.parameterize(Collection.class, Integer.class)).build())),
- "Class<List<String>> should not be assignable to Class<? extends Collection<Integer>>");
- }
-
- @ParameterizedTest(name = "{0} -> {1}: {2}")
- @MethodSource
- void testIsAssignable_ClassWildcardBounds(final String fromField, final String toField, final boolean expected) throws NoSuchFieldException {
- final Type from = ClassWildcardTypes.class.getDeclaredField(fromField).getGenericType();
- final Type to = ClassWildcardTypes.class.getDeclaredField(toField).getGenericType();
- assertEquals(expected, TypeUtils.isAssignable(from, to), () -> from + " -> " + to);
- }
-
- @Test
- void testIsAssignable_UnrelatedTypeVariables() {
- final TypeVariable<?>[] variables = ClassWildcardTypes.class.getTypeParameters();
- assertFalse(TypeUtils.isAssignable(variables[0], variables[1]));
- assertFalse(TypeUtils.isAssignable(variables[1], variables[0]));
- assertFalse(TypeUtils.isAssignable(variables[1], variables[3]));
- assertTrue(TypeUtils.isAssignable(variables[3], variables[1]));
- }
-
@Test
public void testIsAssignable_HiddenMapLogic() {
class Container<T> {
@@ -1315,6 +1325,15 @@ class Container<T> {
assertFalse(result);
}
+ @Test
+ void testIsAssignable_UnrelatedTypeVariables() {
+ final TypeVariable<?>[] variables = ClassWildcardTypes.class.getTypeParameters();
+ assertFalse(TypeUtils.isAssignable(variables[0], variables[1]));
+ assertFalse(TypeUtils.isAssignable(variables[1], variables[0]));
+ assertFalse(TypeUtils.isAssignable(variables[1], variables[3]));
+ assertTrue(TypeUtils.isAssignable(variables[3], variables[1]));
+ }
+
@Test
void testIsAssignableClasses() {
assertTrue(TypeUtils.isAssignable(char.class, double.class));
@@ -1987,6 +2006,19 @@ void testUnrollVariablesCyclicOwnerChainFromEitherType() {
assertSame(b, TypeUtils.unrollVariables(mapping, b));
}
+ @Test
+ void testUnrollVariablesGenericArray() {
+ final TypeVariable<?> variable = ArrayValues.class.getTypeParameters()[0];
+ final GenericArrayType array = TypeUtils.genericArrayType(variable);
+ assertSame(array, TypeUtils.unrollVariables(Collections.emptyMap(), array));
+ assertSame(array, TypeUtils.unrollVariables(Collections.singletonMap(variable, variable), array));
+ assertEquals(String[].class, TypeUtils.unrollVariables(Collections.singletonMap(variable, String.class), array));
+ final Type list = TypeUtils.parameterize(List.class, String.class);
+ assertEquals(TypeUtils.genericArrayType(list), TypeUtils.unrollVariables(Collections.singletonMap(variable, list), array));
+ assertEquals(String[][].class,
+ TypeUtils.unrollVariables(Collections.singletonMap(variable, String.class), TypeUtils.genericArrayType(array)));
+ }
+
@Test
void testUnrollVariablesMultiLevelOwnerType() throws NoSuchFieldException {
final TypeVariable<?> t = Outer.class.getTypeParameters()[0];