Skip to content

Commit

Permalink
Fix for StackOverflowError (#448, #419)
Browse files Browse the repository at this point in the history
- this is caused by Kotlin class with "recursive" type parameter
  • Loading branch information
vojtechhabarta committed Jan 13, 2020
1 parent 0611b4b commit 27a86ae
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -155,7 +157,7 @@ public static boolean isKotlinClass(Class<?> cls) {
public Type getFieldType(Field field) {
final KProperty<?> kProperty = ReflectJvmMapping.getKotlinProperty(field);
if (kProperty != null) {
return getType(kProperty.getReturnType());
return getType(kProperty.getReturnType(), new LinkedHashMap<>());
}
return javaTypeParser.getFieldType(field);
}
Expand All @@ -164,7 +166,7 @@ public Type getFieldType(Field field) {
public Type getMethodReturnType(Method method) {
final KFunction<?> kFunction = ReflectJvmMapping.getKotlinFunction(method);
if (kFunction != null) {
return getType(kFunction.getReturnType());
return getType(kFunction.getReturnType(), new LinkedHashMap<>());
} else {
// `method` might be a getter so try to find a corresponding field and pass it to Kotlin reflection
final KClass<?> kClass = JvmClassMappingKt.getKotlinClass(method.getDeclaringClass());
Expand All @@ -187,23 +189,25 @@ public List<Type> getMethodParameterTypes(Method method) {
final List<KParameter> kParameters = kFunction.getParameters().stream()
.filter(kParameter -> kParameter.getKind() == KParameter.Kind.VALUE)
.collect(Collectors.toList());
return getTypes(kParameters.stream()
.map(parameter -> parameter.getType())
.collect(Collectors.toList())
return getTypes(
kParameters.stream()
.map(parameter -> parameter.getType())
.collect(Collectors.toList()),
new LinkedHashMap<>()
);
}
return javaTypeParser.getMethodParameterTypes(method);
}

private Type getType(KType kType) {
private Type getType(KType kType, Map<String, JTypeVariable<?>> typeParameters) {
if (kType == null) {
return new JWildcardType();
}
final Type type = getBareType(kType);
final Type type = getBareType(kType, typeParameters);
return new JTypeWithNullability(type, kType.isMarkedNullable());
}

private Type getBareType(KType kType) {
private Type getBareType(KType kType, Map<String, JTypeVariable<?>> typeParameters) {
final KClassifier kClassifier = kType.getClassifier();
if (kClassifier instanceof KClass) {
final KClass<?> kClass = (KClass<?>) kClassifier;
Expand All @@ -215,33 +219,41 @@ private Type getBareType(KType kType) {
if (arguments.isEmpty()) {
return javaClass;
} else if (javaClass.isArray()) {
return new JGenericArrayType(getType(arguments.get(0).getType()));
return new JGenericArrayType(getType(arguments.get(0).getType(), typeParameters));
} else {
final List<Type> javaArguments = arguments.stream()
.map(argument -> getType(argument.getType()))
.map(argument -> getType(argument.getType(), typeParameters))
.collect(Collectors.toList());
return Utils.createParameterizedType(javaClass, javaArguments);
}
}
if (kClassifier instanceof KTypeParameter) {
final KTypeParameter kTypeParameter = (KTypeParameter) kClassifier;
final TypeVariable<?> typeVariable = getJavaTypeVariable(kType);
final Type[] bounds = getTypes(kTypeParameter.getUpperBounds()).toArray(new Type[0]);
return new JTypeVariable<>(
typeVariable != null ? typeVariable.getGenericDeclaration() : null,
kTypeParameter.getName(),
bounds,
typeVariable != null ? typeVariable.getAnnotatedBounds() : null,
typeVariable != null ? typeVariable.getAnnotations() : null,
typeVariable != null ? typeVariable.getDeclaredAnnotations() : null
);
final JTypeVariable<?> typeVariableFromMap = typeParameters.get(kTypeParameter.getName());
if (typeVariableFromMap != null) {
return typeVariableFromMap;
} else {
final TypeVariable<?> typeVariable = getJavaTypeVariable(kType);
final JTypeVariable<?> newTypeVariable = new JTypeVariable<>(
typeVariable != null ? typeVariable.getGenericDeclaration() : null,
kTypeParameter.getName(),
/*bounds*/ null,
typeVariable != null ? typeVariable.getAnnotatedBounds() : null,
typeVariable != null ? typeVariable.getAnnotations() : null,
typeVariable != null ? typeVariable.getDeclaredAnnotations() : null
);
typeParameters.put(kTypeParameter.getName(), newTypeVariable);
final Type[] bounds = getTypes(kTypeParameter.getUpperBounds(), typeParameters).toArray(new Type[0]);
newTypeVariable.setBounds(bounds);
return newTypeVariable;
}
}
throw new RuntimeException("Unexpected type: " + kType.toString());
}

private List<Type> getTypes(List<KType> kTypes) {
private List<Type> getTypes(List<KType> kTypes, Map<String, JTypeVariable<?>> typeParameters) {
return kTypes.stream()
.map(kType -> getType(kType))
.map(kType -> getType(kType, typeParameters))
.collect(Collectors.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class JTypeVariable<D extends GenericDeclaration> implements TypeVariable

private final D genericDeclaration; // should not be null but for Kotlin KTypeParameter we don't have it
private final String name;
private final Type[] bounds;
private Type[] bounds;
private final AnnotatedType[] annotatedBounds;
private final Annotation[] annotations;
private final Annotation[] declaredAnnotations;
Expand All @@ -36,6 +36,10 @@ public Type[] getBounds() {
return bounds;
}

public void setBounds(Type[] bounds) {
this.bounds = bounds;
}

@Override
public D getGenericDeclaration() {
return genericDeclaration;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,19 @@ class KotlinTest {
val output = TypeScriptGenerator(settings).generateTypeScript(Input.from(inputClass))
Assert.assertEquals(expected.replace('\'', '"'), output.trim { it <= ' ' })
}

@Test
fun testEnumTypeVariableBound() {
val settings = TestUtils.settings()
val output = TypeScriptGenerator(settings).generateTypeScript(Input.from(A2::class.java))
val errorMessage = "Unexpected output: $output"
Assert.assertTrue(errorMessage, output.contains("interface A2<S>"))
}

private class A2<S> where S : Enum<S> {
fun getData2(): S? {
return null
}
}

}

0 comments on commit 27a86ae

Please sign in to comment.