[
https://issues.apache.org/jira/browse/LANG-1836?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
William Degrange updated LANG-1836:
-----------------------------------
Description:
*Description*
{{TypeUtils.containsTypeVariables(Type)}} returns {{false}} for a
{{ParameterizedType}} whose owner type contains a type variable. The
{{ParameterizedType}} branch only checks {{{}getActualTypeArguments(){}}};
{{getOwnerType()}} is never inspected.
This contradicts the Javadoc ("Tests, recursively, whether any of the type
parameters associated with {{type}} are bound to variables"): the type
parameters of the owner are part of the type's parameterization, and
{{unrollVariables}} already handles the owner explicitly.
*Steps to reproduce*
{code:java}
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import org.apache.commons.lang3.reflect.TypeUtils;public class
ContainsTypeVariablesOwnerRepro {
static class Outer<T> {
class Inner {}
Inner inner; // generic type is Outer<T>.Inner
}
public static void main(String[] args) throws Exception {
Type fromReflection =
Outer.class.getDeclaredField("inner").getGenericType();
System.out.println(TypeUtils.containsTypeVariables(fromReflection)); //
false, expected true
TypeVariable<?> t = Outer.class.getTypeParameters()[0];
Type built =
TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(Outer.class, t),
Outer.Inner.class);
System.out.println(TypeUtils.containsTypeVariables(built)); // false,
expected true
// Consequence: unrollVariables short-circuits and does not substitute T
System.out.println(TypeUtils.unrollVariables(Collections.singletonMap(t,
String.class), fromReflection));
// prints Outer<T>.Inner, expected Outer<String>.Inner
}
}{code}
*Actual:* {{{}false{}}}, {{{}false{}}}, {{Outer<T>.Inner}}
*Expected:* {{{}true{}}}, {{{}true{}}}, {{Outer<String>.Inner}}
*Suggested fix*
Recurse into the owner type, but only when it is not a raw {{{}Class{}}}. For a
static nested type such as {{{}Map.Entry<String, Integer>{}}}, the owner is the
raw {{{}Map.class{}}}, and the {{Class}} branch would return {{true}} because
{{Map}} declares type parameters:
{code:java}
if (type instanceof ParameterizedType) {
final ParameterizedType parameterizedType = (ParameterizedType) type;
for (final Type arg : parameterizedType.getActualTypeArguments()) {
if (containsTypeVariables(arg)) {
return true;
}
}
final Type ownerType = parameterizedType.getOwnerType();
return ownerType != null && !(ownerType instanceof Class<?>) &&
containsTypeVariables(ownerType);
} {code}
Suggested regression tests:
* {{Outer<T>.Inner}} → {{true}}
* {{Outer<String>.Inner}} → {{false}}
* {{Map.Entry<String, Integer>}} → {{false}} (the owner is the raw
{{{}Map.class{}}})
was:
*Description*
{{TypeUtils.containsTypeVariables(Type)}} returns {{false}} for a
{{ParameterizedType}} whose owner type contains a type variable. The
{{ParameterizedType}} branch only checks {{{}getActualTypeArguments(){}}};
{{getOwnerType()}} is never inspected.
This contradicts the Javadoc ("Tests, recursively, whether any of the type
parameters associated with {{type}} are bound to variables"): the type
parameters of the owner are part of the type's parameterization, and
{{unrollVariables}} already handles the owner explicitly.
*Steps to reproduce*
{code:java}
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import org.apache.commons.lang3.reflect.TypeUtils;public class
ContainsTypeVariablesOwnerRepro { static class Outer<T> {
class Inner {}
Inner inner; // generic type is Outer<T>.Inner
} public static void main(String[] args) throws Exception {
Type fromReflection =
Outer.class.getDeclaredField("inner").getGenericType();
System.out.println(TypeUtils.containsTypeVariables(fromReflection)); //
false, expected true TypeVariable<?> t =
Outer.class.getTypeParameters()[0];
Type built =
TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(Outer.class, t),
Outer.Inner.class);
System.out.println(TypeUtils.containsTypeVariables(built)); // false,
expected true // Consequence: unrollVariables short-circuits and does
not substitute T
System.out.println(TypeUtils.unrollVariables(Collections.singletonMap(t,
String.class), fromReflection));
// prints Outer<T>.Inner, expected Outer<String>.Inner
}
}{code}
*Actual:* {{{}false{}}}, {{{}false{}}}, {{Outer<T>.Inner}}
*Expected:* {{{}true{}}}, {{{}true{}}}, {{Outer<String>.Inner}}
*Suggested fix*
Recurse into the owner type, but only when it is not a raw {{{}Class{}}}. For a
static nested type such as {{{}Map.Entry<String, Integer>{}}}, the owner is the
raw {{{}Map.class{}}}, and the {{Class}} branch would return {{true}} because
{{Map}} declares type parameters:
{code:java}
if (type instanceof ParameterizedType) {
final ParameterizedType parameterizedType = (ParameterizedType) type;
for (final Type arg : parameterizedType.getActualTypeArguments()) {
if (containsTypeVariables(arg)) {
return true;
}
}
final Type ownerType = parameterizedType.getOwnerType();
return ownerType != null && !(ownerType instanceof Class<?>) &&
containsTypeVariables(ownerType);
} {code}
Suggested regression tests:
* {{Outer<T>.Inner}} → {{true}}
* {{Outer<String>.Inner}} → {{false}}
* {{Map.Entry<String, Integer>}} → {{false}} (the owner is the raw
{{{}Map.class{}}})
> TypeUtils.containsTypeVariables(Type) ignores the owner type of a
> ParameterizedType
> -----------------------------------------------------------------------------------
>
> Key: LANG-1836
> URL: https://issues.apache.org/jira/browse/LANG-1836
> Project: Commons Lang
> Issue Type: Bug
> Affects Versions: 3.21.0
> Reporter: William Degrange
> Priority: Major
>
> *Description*
> {{TypeUtils.containsTypeVariables(Type)}} returns {{false}} for a
> {{ParameterizedType}} whose owner type contains a type variable. The
> {{ParameterizedType}} branch only checks {{{}getActualTypeArguments(){}}};
> {{getOwnerType()}} is never inspected.
> This contradicts the Javadoc ("Tests, recursively, whether any of the type
> parameters associated with {{type}} are bound to variables"): the type
> parameters of the owner are part of the type's parameterization, and
> {{unrollVariables}} already handles the owner explicitly.
> *Steps to reproduce*
> {code:java}
> import java.lang.reflect.Type;
> import java.lang.reflect.TypeVariable;
> import java.util.Collections;
> import org.apache.commons.lang3.reflect.TypeUtils;public class
> ContainsTypeVariablesOwnerRepro {
> static class Outer<T> {
> class Inner {}
> Inner inner; // generic type is Outer<T>.Inner
> }
> public static void main(String[] args) throws Exception {
> Type fromReflection =
> Outer.class.getDeclaredField("inner").getGenericType();
> System.out.println(TypeUtils.containsTypeVariables(fromReflection));
> // false, expected true
> TypeVariable<?> t = Outer.class.getTypeParameters()[0];
> Type built =
> TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(Outer.class, t),
> Outer.Inner.class);
> System.out.println(TypeUtils.containsTypeVariables(built)); // false,
> expected true
>
> // Consequence: unrollVariables short-circuits and does not
> substitute T
>
> System.out.println(TypeUtils.unrollVariables(Collections.singletonMap(t,
> String.class), fromReflection));
> // prints Outer<T>.Inner, expected Outer<String>.Inner
> }
> }{code}
> *Actual:* {{{}false{}}}, {{{}false{}}}, {{Outer<T>.Inner}}
> *Expected:* {{{}true{}}}, {{{}true{}}}, {{Outer<String>.Inner}}
> *Suggested fix*
> Recurse into the owner type, but only when it is not a raw {{{}Class{}}}. For
> a static nested type such as {{{}Map.Entry<String, Integer>{}}}, the owner is
> the raw {{{}Map.class{}}}, and the {{Class}} branch would return {{true}}
> because {{Map}} declares type parameters:
> {code:java}
> if (type instanceof ParameterizedType) {
> final ParameterizedType parameterizedType = (ParameterizedType) type;
> for (final Type arg : parameterizedType.getActualTypeArguments()) {
> if (containsTypeVariables(arg)) {
> return true;
> }
> }
> final Type ownerType = parameterizedType.getOwnerType();
> return ownerType != null && !(ownerType instanceof Class<?>) &&
> containsTypeVariables(ownerType);
> } {code}
>
> Suggested regression tests:
> * {{Outer<T>.Inner}} → {{true}}
> * {{Outer<String>.Inner}} → {{false}}
> * {{Map.Entry<String, Integer>}} → {{false}} (the owner is the raw
> {{{}Map.class{}}})
--
This message was sent by Atlassian Jira
(v8.20.10#820010)