Skip to content

Commit 3bdf386

Browse files
authored
refactor: implement check for recursive type bound without using Object (#3865)
1 parent af828a2 commit 3bdf386

File tree

5 files changed

+65
-38
lines changed

5 files changed

+65
-38
lines changed

src/main/java/spoon/support/visitor/java/JavaReflectionTreeBuilder.java

+47-37
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ public void addConstructor(CtConstructor<?> ctConstructor) {
142142
@Override
143143
public void addTypeReference(CtRole role, CtTypeReference<?> typeReference) {
144144
switch (role) {
145-
case SUPER_TYPE:
146-
ctClass.setSuperclass(typeReference);
147-
return;
145+
case SUPER_TYPE:
146+
ctClass.setSuperclass(typeReference);
147+
return;
148148
}
149149
super.addTypeReference(role, typeReference);
150150
}
@@ -291,8 +291,7 @@ public void visitField(Field field) {
291291
Set<ModifierKind> modifiers = RtHelper.getModifiers(field.getModifiers());
292292
if (modifiers.contains(ModifierKind.STATIC)
293293
&& modifiers.contains(ModifierKind.PUBLIC)
294-
&& (field.getType().isPrimitive() || String.class.isAssignableFrom(field.getType()))
295-
) {
294+
&& (field.getType().isPrimitive() || String.class.isAssignableFrom(field.getType()))) {
296295
CtLiteral<Object> defaultExpression = factory.createLiteral(field.get(null));
297296
ctField.setDefaultExpression(defaultExpression);
298297
}
@@ -356,13 +355,13 @@ public <T extends GenericDeclaration> void visitTypeParameter(TypeVariable<T> pa
356355
@Override
357356
public void addTypeReference(CtRole role, CtTypeReference<?> typeReference) {
358357
switch (role) {
359-
case SUPER_TYPE:
360-
if (typeParameter.getSuperclass() != null) {
361-
typeParameter.setSuperclass(typeParameter.getFactory().createIntersectionTypeReferenceWithBounds(Arrays.asList(typeParameter.getSuperclass(), typeReference)));
362-
} else {
363-
typeParameter.setSuperclass(typeReference);
364-
}
365-
return;
358+
case SUPER_TYPE:
359+
if (typeParameter.getSuperclass() != null) {
360+
typeParameter.setSuperclass(typeParameter.getFactory().createIntersectionTypeReferenceWithBounds(Arrays.asList(typeParameter.getSuperclass(), typeReference)));
361+
} else {
362+
typeParameter.setSuperclass(typeReference);
363+
}
364+
return;
366365
}
367366
super.addTypeReference(role, typeReference);
368367
}
@@ -379,13 +378,6 @@ public <T extends GenericDeclaration> void visitTypeParameterReference(CtRole ro
379378
typeParameterReference.setSimpleName(parameter.getName());
380379

381380
RuntimeBuilderContext runtimeBuilderContext = new TypeReferenceRuntimeBuilderContext(parameter, typeParameterReference);
382-
if (contexts.contains(runtimeBuilderContext)) {
383-
// we are in the case of a loop
384-
exit();
385-
enter(new TypeReferenceRuntimeBuilderContext(Object.class, factory.Type().OBJECT));
386-
return;
387-
}
388-
389381
GenericDeclaration genericDeclaration = parameter.getGenericDeclaration();
390382
for (RuntimeBuilderContext context : contexts) {
391383
CtTypeParameter typeParameter = context.getTypeParameter(genericDeclaration, parameter.getName());
@@ -404,9 +396,14 @@ public <T extends GenericDeclaration> void visitTypeParameterReference(CtRole ro
404396

405397
@Override
406398
public void visitTypeReference(CtRole role, ParameterizedType type) {
399+
Type[] typeArguments = type.getActualTypeArguments();
400+
if (role == CtRole.SUPER_TYPE && typeArguments.length > 0) {
401+
if (hasProcessedRecursiveBound(typeArguments)) {
402+
return;
403+
}
404+
}
407405
final CtTypeReference<?> ctTypeReference = factory.Core().createTypeReference();
408406
ctTypeReference.setSimpleName(((Class) type.getRawType()).getSimpleName());
409-
410407
RuntimeBuilderContext context = new TypeReferenceRuntimeBuilderContext(type, ctTypeReference) {
411408

412409
@Override
@@ -418,27 +415,26 @@ public void addType(CtType<?> aType) {
418415

419416
enter(context);
420417
super.visitTypeReference(role, type);
421-
422-
// in case of a loop we have replaced a context:
423-
// we do not want to addTypeName then
424-
// and we have to rely on the instance reference to check that
425-
boolean contextStillExisting = false;
426-
for (RuntimeBuilderContext context1 : contexts) {
427-
contextStillExisting = contextStillExisting || (context1 == context);
428-
}
429418
exit();
430419

431-
if (contextStillExisting) {
432-
contexts.peek().addTypeReference(role, ctTypeReference);
433-
}
420+
contexts.peek().addTypeReference(role, ctTypeReference);
434421
}
435422

436423
@Override
437424
public void visitTypeReference(CtRole role, WildcardType type) {
438425
final CtWildcardReference wildcard = factory.Core().createWildcardReference();
439-
//looks like type.getUpperBounds() always returns single value array with Object.class
426+
//type.getUpperBounds() returns at least a single value array with Object.class
440427
//so we cannot distinguish between <? extends Object> and <?>, which must be upper==true too!
441-
wildcard.setUpper((type.getLowerBounds() != null && type.getLowerBounds().length > 0) == false);
428+
wildcard.setUpper((type.getLowerBounds().length > 0) == false);
429+
430+
if (!type.getUpperBounds()[0].equals(Object.class)) {
431+
if (hasProcessedRecursiveBound(type.getUpperBounds())) {
432+
return;
433+
}
434+
}
435+
if (hasProcessedRecursiveBound(type.getLowerBounds())) {
436+
return;
437+
}
442438

443439
enter(new TypeReferenceRuntimeBuilderContext(type, wildcard));
444440
super.visitTypeReference(role, type);
@@ -447,7 +443,21 @@ public void visitTypeReference(CtRole role, WildcardType type) {
447443
contexts.peek().addTypeReference(role, wildcard);
448444
}
449445

450-
446+
// check if a type parameter that is bounded by some expression involving itself has already been processed
447+
private boolean hasProcessedRecursiveBound(Type[] types) {
448+
for (Type type : types) {
449+
if (type instanceof TypeVariable) {
450+
TypeVariable t = (TypeVariable) type;
451+
final CtTypeParameterReference typeParameterReference = factory.Core().createTypeParameterReference();
452+
typeParameterReference.setSimpleName(t.getName());
453+
RuntimeBuilderContext runtimeBuilderContext = new TypeReferenceRuntimeBuilderContext(t, typeParameterReference);
454+
if (contexts.contains(runtimeBuilderContext)) {
455+
return true;
456+
}
457+
}
458+
}
459+
return false;
460+
}
451461

452462
@Override
453463
public <T> void visitArrayReference(CtRole role, final Type typeArray) {
@@ -457,9 +467,9 @@ public <T> void visitArrayReference(CtRole role, final Type typeArray) {
457467
@Override
458468
public void addTypeReference(CtRole role, CtTypeReference<?> typeReference) {
459469
switch (role) {
460-
case DECLARING_TYPE:
461-
arrayTypeReference.setDeclaringType(typeReference);
462-
return;
470+
case DECLARING_TYPE:
471+
arrayTypeReference.setDeclaringType(typeReference);
472+
return;
463473
}
464474
arrayTypeReference.setComponentType(typeReference);
465475
}

src/main/java/spoon/support/visitor/java/JavaReflectionVisitorImpl.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ public void visitTypeReference(CtRole role, ParameterizedType type) {
461461

462462
@Override
463463
public void visitTypeReference(CtRole role, WildcardType type) {
464-
if (type.getUpperBounds() != null && type.getUpperBounds().length > 0 && !type.getUpperBounds()[0].equals(Object.class)) {
464+
if (!type.getUpperBounds()[0].equals(Object.class)) {
465465
for (Type upper : type.getUpperBounds()) {
466466
visitTypeReference(CtRole.BOUNDING_TYPE, upper);
467467
}

src/test/java/spoon/test/generics/GenericsTest.java

+9
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,15 @@ public void testBugCommonCollection() throws Exception {
335335
}
336336
}
337337

338+
@Test
339+
public void testRecursiveUpperBound() throws Exception {
340+
// contract: wildcards with a recursive upper bound are properly parsed with runtime reflection
341+
CtClass<?> type = build("spoon.test.generics.testclasses3", "UpperBoundedWildcardUser");
342+
CtField field = type.getElements(new TypeFilter<>(CtField.class)).get(0);
343+
assertTrue(field.getType().getTypeDeclaration().toString()
344+
.startsWith("public class UpperBoundedWildcard<Q extends java.lang.Comparable<? extends Q>>"));
345+
}
346+
338347
@Test
339348
public void testInstanceOfMapEntryGeneric() throws Exception {
340349
CtClass<?> type = build("spoon.test.generics.testclasses3", "InstanceOfMapEntryGeneric");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package spoon.test.generics.testclasses3;
2+
3+
public class UpperBoundedWildcard<Q extends Comparable<? extends Q>> { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package spoon.test.generics.testclasses3;
2+
3+
public class UpperBoundedWildcardUser {
4+
UpperBoundedWildcard x;
5+
}

0 commit comments

Comments
 (0)