Skip to content

Commit

Permalink
Remove TypeToken check for type variable
Browse files Browse the repository at this point in the history
As mentioned in review comments, there are cases during serialization where
usage of the type variable is not so problematic (but still not ideal).
  • Loading branch information
Marcono1234 committed Feb 16, 2022
1 parent 446bf92 commit cba0307
Show file tree
Hide file tree
Showing 2 changed files with 7 additions and 89 deletions.
40 changes: 7 additions & 33 deletions gson/src/main/java/com/google/gson/reflect/TypeToken.java
Expand Up @@ -22,7 +22,6 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -38,6 +37,12 @@
* <p>
* {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
*
* <p>Capturing a type variable as type argument of a {@code TypeToken} should
* be avoided. Due to type erasure the runtime type of a type variable is not
* available to Gson and therefore it cannot provide the functionality one
* might expect, which gives a false sense of type-safety at compilation time
* and can lead to an unexpected {@code ClassCastException} at runtime.
*
* @author Bob Lee
* @author Sven Mawson
* @author Jesse Wilson
Expand All @@ -54,13 +59,6 @@ public class TypeToken<T> {
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
* parameter in the anonymous class's type hierarchy so we can reconstitute it
* at runtime despite erasure.
*
* <p>Because {@code TypeToken} is mainly intended for usage with Gson
* (and not other libraries) using a type variable as part of the type
* argument for {@code TypeToken} is not allowed. Due to type erasure the
* runtime type of a type variable is not available to Gson and therefore
* it cannot provide the functionality the user might expect, which would
* give a false sense of type-safety.
*/
@SuppressWarnings("unchecked")
protected TypeToken() {
Expand Down Expand Up @@ -89,9 +87,7 @@ private Type getTypeTokenTypeArgument() {
if (superclass instanceof ParameterizedType) {
ParameterizedType parameterized = (ParameterizedType) superclass;
if (parameterized.getRawType() == TypeToken.class) {
Type typeArgument = $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
verifyNoTypeVariable(typeArgument);
return typeArgument;
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
}
// Check for raw TypeToken as superclass
Expand All @@ -104,28 +100,6 @@ else if (superclass == TypeToken.class) {
throw new IllegalStateException("Must only create direct subclasses of TypeToken");
}

private static void verifyNoTypeVariable(Type type) {
if (type instanceof TypeVariable) {
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
throw new IllegalArgumentException("TypeToken type argument must not contain a type variable; captured type variable "
+ typeVariable.getName() + " declared by " + typeVariable.getGenericDeclaration());
} else if (type instanceof GenericArrayType) {
verifyNoTypeVariable(((GenericArrayType) type).getGenericComponentType());
} else if (type instanceof ParameterizedType) {
for (Type typeArgument : ((ParameterizedType) type).getActualTypeArguments()) {
verifyNoTypeVariable(typeArgument);
}
} else if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
for (Type bound : wildcardType.getLowerBounds()) {
verifyNoTypeVariable(bound);
}
for (Type bound : wildcardType.getUpperBounds()) {
verifyNoTypeVariable(bound);
}
}
}

/**
* Returns the raw (non-generic) type for this type.
*/
Expand Down
56 changes: 0 additions & 56 deletions gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java
Expand Up @@ -146,62 +146,6 @@ class SubSubTypeToken2 extends SubTypeToken<Integer> {}
}
}

/**
* TypeToken type argument must not contain a type variable because, due to
* type erasure, at runtime only the bound of the type variable is available
* which is likely not what the user wanted.
*
* <p>Note that type variables are allowed for the methods calling {@code TypeToken(Type)}
* because there the return type is {@code TypeToken<?>} which does not give
* a false sense of type-safety.
*/
public <T> void testTypeTokenTypeVariable() {
try {
new TypeToken<T>() {};
fail();
} catch (IllegalArgumentException expected) {
assertEquals("TypeToken type argument must not contain a type variable; captured type variable T "
+ "declared by public void com.google.gson.reflect.TypeTokenTest.testTypeTokenTypeVariable()",
expected.getMessage());
}

try {
new TypeToken<List<List<T>>>() {};
fail();
} catch (IllegalArgumentException expected) {
assertEquals("TypeToken type argument must not contain a type variable; captured type variable T "
+ "declared by public void com.google.gson.reflect.TypeTokenTest.testTypeTokenTypeVariable()",
expected.getMessage());
}

try {
new TypeToken<List<? extends List<T>>>() {};
fail();
} catch (IllegalArgumentException expected) {
assertEquals("TypeToken type argument must not contain a type variable; captured type variable T "
+ "declared by public void com.google.gson.reflect.TypeTokenTest.testTypeTokenTypeVariable()",
expected.getMessage());
}

try {
new TypeToken<List<? super List<T>>>() {};
fail();
} catch (IllegalArgumentException expected) {
assertEquals("TypeToken type argument must not contain a type variable; captured type variable T "
+ "declared by public void com.google.gson.reflect.TypeTokenTest.testTypeTokenTypeVariable()",
expected.getMessage());
}

try {
new TypeToken<List<T>[]>() {};
fail();
} catch (IllegalArgumentException expected) {
assertEquals("TypeToken type argument must not contain a type variable; captured type variable T "
+ "declared by public void com.google.gson.reflect.TypeTokenTest.testTypeTokenTypeVariable()",
expected.getMessage());
}
}

@SuppressWarnings("rawtypes")
public void testTypeTokenRaw() {
try {
Expand Down

0 comments on commit cba0307

Please sign in to comment.