Skip to content

Commit 27a86ae

Browse files
Fix for StackOverflowError (#448, #419)
- this is caused by Kotlin class with "recursive" type parameter
1 parent 0611b4b commit 27a86ae

File tree

3 files changed

+54
-23
lines changed

3 files changed

+54
-23
lines changed

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/TypeParser.java

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import java.lang.reflect.Type;
1818
import java.lang.reflect.TypeVariable;
1919
import java.util.Arrays;
20+
import java.util.LinkedHashMap;
2021
import java.util.List;
22+
import java.util.Map;
2123
import java.util.Objects;
2224
import java.util.Optional;
2325
import java.util.stream.Collectors;
@@ -155,7 +157,7 @@ public static boolean isKotlinClass(Class<?> cls) {
155157
public Type getFieldType(Field field) {
156158
final KProperty<?> kProperty = ReflectJvmMapping.getKotlinProperty(field);
157159
if (kProperty != null) {
158-
return getType(kProperty.getReturnType());
160+
return getType(kProperty.getReturnType(), new LinkedHashMap<>());
159161
}
160162
return javaTypeParser.getFieldType(field);
161163
}
@@ -164,7 +166,7 @@ public Type getFieldType(Field field) {
164166
public Type getMethodReturnType(Method method) {
165167
final KFunction<?> kFunction = ReflectJvmMapping.getKotlinFunction(method);
166168
if (kFunction != null) {
167-
return getType(kFunction.getReturnType());
169+
return getType(kFunction.getReturnType(), new LinkedHashMap<>());
168170
} else {
169171
// `method` might be a getter so try to find a corresponding field and pass it to Kotlin reflection
170172
final KClass<?> kClass = JvmClassMappingKt.getKotlinClass(method.getDeclaringClass());
@@ -187,23 +189,25 @@ public List<Type> getMethodParameterTypes(Method method) {
187189
final List<KParameter> kParameters = kFunction.getParameters().stream()
188190
.filter(kParameter -> kParameter.getKind() == KParameter.Kind.VALUE)
189191
.collect(Collectors.toList());
190-
return getTypes(kParameters.stream()
191-
.map(parameter -> parameter.getType())
192-
.collect(Collectors.toList())
192+
return getTypes(
193+
kParameters.stream()
194+
.map(parameter -> parameter.getType())
195+
.collect(Collectors.toList()),
196+
new LinkedHashMap<>()
193197
);
194198
}
195199
return javaTypeParser.getMethodParameterTypes(method);
196200
}
197201

198-
private Type getType(KType kType) {
202+
private Type getType(KType kType, Map<String, JTypeVariable<?>> typeParameters) {
199203
if (kType == null) {
200204
return new JWildcardType();
201205
}
202-
final Type type = getBareType(kType);
206+
final Type type = getBareType(kType, typeParameters);
203207
return new JTypeWithNullability(type, kType.isMarkedNullable());
204208
}
205209

206-
private Type getBareType(KType kType) {
210+
private Type getBareType(KType kType, Map<String, JTypeVariable<?>> typeParameters) {
207211
final KClassifier kClassifier = kType.getClassifier();
208212
if (kClassifier instanceof KClass) {
209213
final KClass<?> kClass = (KClass<?>) kClassifier;
@@ -215,33 +219,41 @@ private Type getBareType(KType kType) {
215219
if (arguments.isEmpty()) {
216220
return javaClass;
217221
} else if (javaClass.isArray()) {
218-
return new JGenericArrayType(getType(arguments.get(0).getType()));
222+
return new JGenericArrayType(getType(arguments.get(0).getType(), typeParameters));
219223
} else {
220224
final List<Type> javaArguments = arguments.stream()
221-
.map(argument -> getType(argument.getType()))
225+
.map(argument -> getType(argument.getType(), typeParameters))
222226
.collect(Collectors.toList());
223227
return Utils.createParameterizedType(javaClass, javaArguments);
224228
}
225229
}
226230
if (kClassifier instanceof KTypeParameter) {
227231
final KTypeParameter kTypeParameter = (KTypeParameter) kClassifier;
228-
final TypeVariable<?> typeVariable = getJavaTypeVariable(kType);
229-
final Type[] bounds = getTypes(kTypeParameter.getUpperBounds()).toArray(new Type[0]);
230-
return new JTypeVariable<>(
231-
typeVariable != null ? typeVariable.getGenericDeclaration() : null,
232-
kTypeParameter.getName(),
233-
bounds,
234-
typeVariable != null ? typeVariable.getAnnotatedBounds() : null,
235-
typeVariable != null ? typeVariable.getAnnotations() : null,
236-
typeVariable != null ? typeVariable.getDeclaredAnnotations() : null
237-
);
232+
final JTypeVariable<?> typeVariableFromMap = typeParameters.get(kTypeParameter.getName());
233+
if (typeVariableFromMap != null) {
234+
return typeVariableFromMap;
235+
} else {
236+
final TypeVariable<?> typeVariable = getJavaTypeVariable(kType);
237+
final JTypeVariable<?> newTypeVariable = new JTypeVariable<>(
238+
typeVariable != null ? typeVariable.getGenericDeclaration() : null,
239+
kTypeParameter.getName(),
240+
/*bounds*/ null,
241+
typeVariable != null ? typeVariable.getAnnotatedBounds() : null,
242+
typeVariable != null ? typeVariable.getAnnotations() : null,
243+
typeVariable != null ? typeVariable.getDeclaredAnnotations() : null
244+
);
245+
typeParameters.put(kTypeParameter.getName(), newTypeVariable);
246+
final Type[] bounds = getTypes(kTypeParameter.getUpperBounds(), typeParameters).toArray(new Type[0]);
247+
newTypeVariable.setBounds(bounds);
248+
return newTypeVariable;
249+
}
238250
}
239251
throw new RuntimeException("Unexpected type: " + kType.toString());
240252
}
241253

242-
private List<Type> getTypes(List<KType> kTypes) {
254+
private List<Type> getTypes(List<KType> kTypes, Map<String, JTypeVariable<?>> typeParameters) {
243255
return kTypes.stream()
244-
.map(kType -> getType(kType))
256+
.map(kType -> getType(kType, typeParameters))
245257
.collect(Collectors.toList());
246258
}
247259

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/type/JTypeVariable.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class JTypeVariable<D extends GenericDeclaration> implements TypeVariable
1313

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

39+
public void setBounds(Type[] bounds) {
40+
this.bounds = bounds;
41+
}
42+
3943
@Override
4044
public D getGenericDeclaration() {
4145
return genericDeclaration;

typescript-generator-core/src/test/java/cz/habarta/typescript/generator/KotlinTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,19 @@ class KotlinTest {
8282
val output = TypeScriptGenerator(settings).generateTypeScript(Input.from(inputClass))
8383
Assert.assertEquals(expected.replace('\'', '"'), output.trim { it <= ' ' })
8484
}
85+
86+
@Test
87+
fun testEnumTypeVariableBound() {
88+
val settings = TestUtils.settings()
89+
val output = TypeScriptGenerator(settings).generateTypeScript(Input.from(A2::class.java))
90+
val errorMessage = "Unexpected output: $output"
91+
Assert.assertTrue(errorMessage, output.contains("interface A2<S>"))
92+
}
93+
94+
private class A2<S> where S : Enum<S> {
95+
fun getData2(): S? {
96+
return null
97+
}
98+
}
99+
85100
}

0 commit comments

Comments
 (0)