Skip to content

Commit

Permalink
feature: support for better analysis of generic type arguments (TypeR…
Browse files Browse the repository at this point in the history
…eference#getTypeParameterReference() and TypeFactory#createReference(type,includingFormalTypeParams)) (#1237)
  • Loading branch information
pvojtechovsky authored and monperrus committed Apr 3, 2017
1 parent 43c7e52 commit 5e7ee76
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 15 deletions.
17 changes: 15 additions & 2 deletions src/main/java/spoon/reflect/factory/TypeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -316,15 +316,29 @@ public <T> CtTypeReference<T> createReference(Class<T> type) {
* Create a reference to a simple type
*/
public <T> CtTypeReference<T> createReference(CtType<T> type) {
return createReference(type, false);
}

/**
* @param includingFormalTypeParameter if true then references to formal type parameters
* are added as actual type arguments of returned {@link CtTypeReference}
*/
public <T> CtTypeReference<T> createReference(CtType<T> type, boolean includingFormalTypeParameter) {
CtTypeReference<T> ref = factory.Core().createTypeReference();

if (type.getDeclaringType() != null) {
ref.setDeclaringType(createReference(type.getDeclaringType()));
ref.setDeclaringType(createReference(type.getDeclaringType(), includingFormalTypeParameter));
} else if (type.getPackage() != null) {
ref.setPackage(factory.Package().createReference(type.getPackage()));
}

ref.setSimpleName(type.getSimpleName());

if (includingFormalTypeParameter) {
for (CtTypeParameter formalTypeParam : type.getFormalCtTypeParameters()) {
ref.addActualTypeArgument(formalTypeParam.getReference());
}
}
return ref;
}

Expand All @@ -342,7 +356,6 @@ 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 @@ -72,7 +72,11 @@ public interface CtTypeParameterReference extends CtTypeReference<Object> {
*/
<T extends CtTypeParameterReference> T setBoundingType(CtTypeReference<?> superType);

// overriding the return type
/**
* Returns the {@link CtTypeParameter}, a {@link CtTypeParameter}, that declares the type parameter
* referenced or <code>null</code> if the reference is not in a context where such type parameter is declared.
* See also {@link #getTypeParameterDeclaration()} which has a different semantic.
*/
@Override
@DerivedProperty
CtTypeParameter getDeclaration();
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/spoon/reflect/reference/CtTypeReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import spoon.reflect.declaration.CtShadowable;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeInformation;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.support.DerivedProperty;
import spoon.support.SpoonClassNotFoundException;

Expand Down Expand Up @@ -181,4 +182,25 @@ public interface CtTypeReference<T> extends CtReference, CtActualTypeContainer,
*/
@DerivedProperty
CtTypeReference<?> getAccessType();

/**
* If this type reference is used as a type argument (see {@link #getActualTypeArguments()}), returns the type parameter declaration in the target type, returns null otherwise.
*
* In the following example, getTypeParameterDeclaration of "String" returns the type parameter definition "X".
* <pre>
* class Dog&lt;X&gt;{}
* Dog&lt;String&gt;var = ...;
* </pre>
**
* In this other example, getTypeParameterDeclaration of T in Dog&lt;T&gt; returns the type parameter definition "X" (while {@link #getDeclaration()} returns the "T" of Cat).
* <pre>
* class Dog&lt;X&gt;{}
* class Cat&lt;T&gt; {
* Dog&lt;T&gt; dog;
* }
* </pre>
*/
@DerivedProperty
CtTypeParameter getTypeParameterDeclaration();

}
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,27 @@ 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;
return null;
}

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

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
import spoon.Launcher;
import spoon.SpoonException;
import spoon.reflect.declaration.CtClass;
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.CtPackage;
import spoon.reflect.declaration.CtShadowable;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.reference.CtActualTypeContainer;
import spoon.reflect.reference.CtArrayTypeReference;
Expand Down Expand Up @@ -801,4 +807,30 @@ public <E extends CtShadowable> E setShadow(boolean isShadow) {
public CtTypeReference<T> clone() {
return (CtTypeReference<T>) super.clone();
}

@Override
public CtTypeParameter getTypeParameterDeclaration() {

CtElement parent = this.getParent();

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

// case 2: 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 findTypeParamDeclarationByPosition((CtFormalTypeDeclarer) exec, ((CtTypeReference) parent).getActualTypeArguments().indexOf(this));
}
}

return null;
}

private CtTypeParameter findTypeParamDeclarationByPosition(CtFormalTypeDeclarer type, int position) {
return type.getFormalCtTypeParameters().get(position);
}
}
82 changes: 80 additions & 2 deletions src/test/java/spoon/test/generics/GenericsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,34 @@ 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();
assertSame(typeParam, typeParamReference.getDeclaration());

// 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 @@ -558,4 +581,59 @@ public void testIsGenericsMethod() throws Exception {
assertFalse(aTacos.isGenerics());
assertFalse(ctTypeReference.isGenerics());
}
@Test
public void testTypeParameterReferenceAsActualTypeArgument() throws Exception {
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();
assertSame(typeParam, typeParamRef.getDeclaration());

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());

// null because without context
assertEquals(null, typeParamRef.getDeclaration());
assertEquals(typeParam, typeParamRef.getTypeParameterDeclaration());
typeParamRef.setSimpleName("Y");
assertEquals(typeParam, typeParamRef.getTypeParameterDeclaration());
}
@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 uninitialized actual type arguments.
// CtTypeReference<?> genericTypeRef = aTacos.getReference();
CtTypeReference<?> genericTypeRef = aTacos.getFactory().Type().createReference(aTacos, true);

assertTrue(genericTypeRef.getActualTypeArguments().size()>0);
assertEquals(aTacos.getFormalCtTypeParameters().size(), genericTypeRef.getActualTypeArguments().size());
for(int i=0; i<aTacos.getFormalCtTypeParameters().size(); i++) {
assertSame("TypeParameter reference idx="+i+" is different", aTacos.getFormalCtTypeParameters().get(i), genericTypeRef.getActualTypeArguments().get(i).getTypeParameterDeclaration());

// contract: getTypeParameterDeclaration goes back to the declaration, eevn without context
assertSame(aTacos.getFormalCtTypeParameters().get(i), genericTypeRef.getActualTypeArguments().get(i).getTypeParameterDeclaration());

}


}
}

0 comments on commit 5e7ee76

Please sign in to comment.