Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
monperrus committed Mar 30, 2017
1 parent a215b2e commit 96c68bf
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 27 deletions.
3 changes: 1 addition & 2 deletions src/main/java/spoon/reflect/factory/TypeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,7 @@ public CtTypeParameterReference createReference(CtTypeParameter type) {
ref.addAnnotation(ctAnnotation.clone());
}
ref.setSimpleName(type.getSimpleName());
//TypeParameter reference without parent is unusable. It lost information about it's declarer
ref.setParent(type);

return ref;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
*/
package spoon.support.reflect.reference;

import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtFormalTypeDeclarer;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.reflect.reference.CtActualTypeContainer;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtIntersectionTypeReference;
import spoon.reflect.reference.CtTypeParameterReference;
import spoon.reflect.reference.CtTypeReference;
Expand Down Expand Up @@ -171,20 +175,43 @@ public CtTypeParameter getDeclaration() {
if (!isParentInitialized()) {
return null;
}
return getRecursiveDeclaration(this);
}

private CtTypeParameter getRecursiveDeclaration(CtElement element) {
final CtFormalTypeDeclarer formalTypeDeclarer = element.getParent(CtFormalTypeDeclarer.class);
if (formalTypeDeclarer == null) {
return null;
// case #1: we're a type of a method parameter, a local variable, ...
// the strategy is to look in the parents
// collecting all formal type declarers of the hierarchy
CtElement e = this;
while ((e = e.getParent(CtFormalTypeDeclarer.class)) != null) {
CtTypeParameter result = findTypeParamDeclaration((CtFormalTypeDeclarer) e, this.getSimpleName());
if (result != null) {
return result;
}
}
for (CtTypeParameter typeParameter : formalTypeDeclarer.getFormalCtTypeParameters()) {
if (simplename.equals(typeParameter.getSimpleName())) {
return typeParameter;

CtElement parent = this.getParent();

// case 2: this is an actual type argument of a type reference eg List<E>
if (parent instanceof CtTypeReference) {
CtType t = ((CtTypeReference) parent).getTypeDeclaration();
return findTypeParamDeclaration(t, this.getSimpleName());
}

// case 3: this is an actual type argument of a method/constructor reference
if (parent instanceof CtExecutableReference) {
CtExecutable<?> exec = ((CtExecutableReference<?>) parent).getExecutableDeclaration();
if (exec instanceof CtMethod || exec instanceof CtConstructor) {
return findTypeParamDeclaration((CtFormalTypeDeclarer) exec, this.getSimpleName());
}
}
return getRecursiveDeclaration(formalTypeDeclarer);
return null;
}

private CtTypeParameter findTypeParamDeclaration(CtFormalTypeDeclarer type, String refName) {
for (CtTypeParameter typeParam : type.getFormalCtTypeParameters()) {
if (typeParam.getSimpleName().equals(refName)) {
return typeParam;
}
}
return null;
}

@Override
Expand Down
63 changes: 48 additions & 15 deletions src/test/java/spoon/test/generics/GenericsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,32 @@ public void testTypeParameterReference() throws Exception {

@Test
public void testTypeParameterDeclarer() throws Exception {
// contract: one can navigate to the declarer of a type parameter
// contract: one can lookup the declarer of a type parameter if it is in appropriate context (the declararer is in the parent hierarchy)
CtClass<?> classThatDefinesANewTypeArgument = build("spoon.test.generics", "ClassThatDefinesANewTypeArgument");
CtTypeParameter typeParam = classThatDefinesANewTypeArgument.getFormalCtTypeParameters().get(0);
assertEquals("T", classThatDefinesANewTypeArgument.getFormalCtTypeParameters().get(0).getSimpleName());
assertSame(classThatDefinesANewTypeArgument, typeParam.getTypeParameterDeclarer());
assertSame(typeParam, typeParam.getReference().getDeclaration());
CtTypeParameterReference typeParamReference = typeParam.getReference();

// creating an appropriate context
CtMethod m = classThatDefinesANewTypeArgument.getFactory().createMethod();
m.setParent(classThatDefinesANewTypeArgument);
// setting the return type of the method
m.setType(typeParamReference);
classThatDefinesANewTypeArgument.addMethod(m);

// the final assertions
assertSame(typeParam, typeParamReference.getDeclaration());
assertSame(classThatDefinesANewTypeArgument, typeParamReference.getDeclaration().getParent());

// now testing that the getDeclaration of a type parameter is actually a dynamic lookup
CtClass<?> c2 = classThatDefinesANewTypeArgument.clone();
c2.addMethod(m);
assertSame(c2, typeParamReference.getDeclaration().getParent());
// even if we rename it
typeParamReference.setSimpleName("R"); // renaming the reference
c2.getFormalCtTypeParameters().get(0).setSimpleName("R"); // renaming the declaration
assertSame(c2, typeParamReference.getDeclaration().getParent());
}

@Test
Expand Down Expand Up @@ -560,33 +581,45 @@ public void testIsGenericsMethod() throws Exception {
}
@Test
public void testTypeParameterReferenceAsActualTypeArgument() throws Exception {
CtType<Tacos> aTacos = buildNoClasspath(Tacos.class).Type().get(Tacos.class);
CtType<Tacos> aTacos = buildNoClasspath(ClassThatDefinesANewTypeArgument.class).Type().get(ClassThatDefinesANewTypeArgument.class);

CtTypeReference<?> typeRef = aTacos.getReference();

assertSame(aTacos, typeRef.getDeclaration());

CtTypeParameter typeParam = aTacos.getFormalCtTypeParameters().get(0);
CtTypeParameterReference typeParamRef = typeParam.getReference();

//typeParamRef is able to resolve it's typeParam - OK
assertSame(typeParam, typeParamRef.getDeclaration());
//the reference to parent is used by legacy
//CtTypeParameterReferenceImpl#getRecursiveDeclaration
//to detect declaration - it is probably wrong approach
assertSame(typeParam, typeParamRef.getParent());

//this assignment changes parent of typeParamRef to typeRef


// a tyoe parameter ref with no context cannot be resolved
assertSame(null, typeParamRef.getDeclaration());
// by default a typeParamRef is not in a tree
assertEquals(false, typeParamRef.isParentInitialized());

assertEquals("spoon.test.generics.ClassThatDefinesANewTypeArgument", typeRef.toString());

// creating a reference to "ClassThatDefinesANewTypeArgument<T>"
//this assignment changes parent of typeParamRef to TYPEREF
typeRef.addActualTypeArgument(typeParamRef);

assertEquals("spoon.test.generics.ClassThatDefinesANewTypeArgument<T>", typeRef.toString());

// this does not change the declaration
assertSame(aTacos, typeRef.getDeclaration());
//stored typeParamRef is same like the added one, no clone - OK
assertSame(typeParamRef, typeRef.getActualTypeArguments().get(0));
//typeParamRef has got new parent
assertSame(typeRef, typeParamRef.getParent());
//and therefore it lost the link to declaring CtFormalTypeDeclarer,
//so this assertion FAILS

assertSame(typeParam, typeParamRef.getDeclaration());
}
@Test
public void testGenericTypeReference() throws Exception {

// contract: the parameter includingFormalTypeParameter of createReference enables one to also create actual type arguments

CtType<Tacos> aTacos = buildNoClasspath(Tacos.class).Type().get(Tacos.class);
//this returns a type reference with unitialized actual type arguments.
//this returns a type reference with uninitialized actual type arguments.
// CtTypeReference<?> genericTypeRef = aTacos.getReference();
CtTypeReference<?> genericTypeRef = aTacos.getFactory().Type().createReference(aTacos, true);

Expand Down

0 comments on commit 96c68bf

Please sign in to comment.