Skip to content

Commit

Permalink
feat: add support for analyzing whether a type reference is generic o…
Browse files Browse the repository at this point in the history
…r not (method isGeneric) (#1562)
  • Loading branch information
monperrus authored and pvojtechovsky committed Sep 29, 2017
1 parent 82e2fe9 commit c6bb2ec
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 13 deletions.
1 change: 0 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ Spoon Roadmap
Short-term, long term and crazy ideas about Spoon

* Model
* support for analyzing bound vs unbound type references (`List<T>` vs `List<String>`)
* build model of binary code using a decompiler
* Improves usage of generics on the model (See https://github.com/INRIA/spoon/issues/583#issue-148728790)
* Add an embedded DSL / builder mechanism (see https://github.com/INRIA/spoon/pull/741)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ public interface CtTypeInformation {
boolean isAnnotationType();

/**
* Returns true if this element is a generics (eg "T") and false if it is an actual type (eg 'Book" or "String")
* Returns true if it refers to a type parameter (ie not a concrete class, eg "T foo"). It can refer to it directly (eg T), or indirectly (eg List&lt;T&gt;, or Set&lt;List&lt;T&gt;&gt;).
*/
@DerivedProperty
boolean isGenerics();

/**
Expand Down
1 change: 1 addition & 0 deletions src/main/java/spoon/reflect/reference/CtTypeReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,5 @@ public interface CtTypeReference<T> extends CtReference, CtActualTypeContainer,
@DerivedProperty
CtTypeParameter getTypeParameterDeclaration();


}
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@ public <T extends CtTypeParameterReference> T setUpper(boolean upper) {
return (T) this;
}

@Override
public boolean isGenerics() {
return true;
}

@Override
public boolean isPrimitive() {
return false;
Expand Down Expand Up @@ -251,4 +246,15 @@ public boolean isSubtypeOf(CtTypeReference<?> type) {
public CtTypeParameterReference clone() {
return (CtTypeParameterReference) super.clone();
}

@Override
public boolean isGenerics() {
if (getDeclaration() instanceof CtTypeParameter) {
return true;
}
if (getBoundingType() != null && getBoundingType().isGenerics()) {
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -622,11 +622,6 @@ public boolean isEnum() {
}
}

@Override
public boolean isGenerics() {
return false;
}

@Override
public boolean canAccess(CtTypeReference<?> type) {
try {
Expand Down Expand Up @@ -824,6 +819,19 @@ public CtTypeParameter getTypeParameterDeclaration() {
return null;
}

@Override
public boolean isGenerics() {
if (getDeclaration() instanceof CtTypeParameter) {
return true;
}
for (CtTypeReference ref : getActualTypeArguments()) {
if (ref.isGenerics()) {
return true;
}
}
return false;
}

private CtTypeParameter findTypeParamDeclarationByPosition(CtFormalTypeDeclarer type, int position) {
return type.getFormalCtTypeParameters().get(position);
}
Expand Down
175 changes: 174 additions & 1 deletion src/test/java/spoon/test/generics/GenericsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import spoon.reflect.declaration.CtTypeMember;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.reflect.declaration.CtTypedElement;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtReference;
import spoon.reflect.reference.CtTypeParameterReference;
Expand Down Expand Up @@ -59,7 +60,9 @@
import java.io.File;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
Expand Down Expand Up @@ -599,7 +602,10 @@ public void testIsGenericsMethod() throws Exception {

CtTypeReference ctTypeReference = aTacos.getSuperInterfaces().toArray(new CtTypeReference[aTacos.getSuperInterfaces().size()])[0];
assertFalse(aTacos.isGenerics());
assertFalse(ctTypeReference.isGenerics());

// this is a generic type reference spoon.test.generics.testclasses.ITacos<V>
assertEquals("spoon.test.generics.testclasses.ITacos<V>", ctTypeReference.toString());
assertTrue(ctTypeReference.isGenerics());
}
@Test
public void testTypeParameterReferenceAsActualTypeArgument() throws Exception {
Expand Down Expand Up @@ -655,6 +661,173 @@ public void testGenericTypeReference() throws Exception {
}
}
@Test
public void testisGeneric() throws Exception {
Factory factory = build(new File("src/test/java/spoon/test/generics/testclasses"));

/*
// this code has been used to generate the list of assertEquals below,
// and then each assertEquals was verified
Set<String> s = new HashSet<>();
factory.getModel().getElements(new TypeFilter<CtTypeReference>(CtTypeReference.class) {
@Override
public boolean matches(CtTypeReference element) {
return super.matches(element) && element.getParent() instanceof CtVariable;
}
}).forEach(x -> {
String simpleName = ((CtVariable) x.getParent()).getSimpleName();
if (!s.contains(simpleName)) {
System.out.println("\t\t// "+x.toString());
System.out.println("\t\tCtTypeReference<?> "+simpleName+"Ref = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, \"" + simpleName+ "\")).first(CtVariable.class).getType();");
System.out.println("\t\tassertEquals("+x.isGeneric() + ", " + simpleName + "Ref.isGeneric());");
System.out.println();
}
s.add(simpleName);
});
*/

// T
CtTypeReference<?> var1Ref = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "var1")).first(CtVariable.class).getType();
assertEquals(true, var1Ref.isGenerics());

// spoon.test.generics.testclasses.rxjava.Subscriber<? super T>
CtTypeReference<?> sRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "s")).first(CtVariable.class).getType();
assertEquals(true, sRef.isGenerics());

// spoon.test.generics.testclasses.rxjava.Try<java.util.Optional<java.lang.Object>>
CtTypeReference<?> notificationRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "notification")).first(CtVariable.class).getType();
assertEquals(false, notificationRef.isGenerics());

// java.util.function.Function<? super spoon.test.generics.testclasses.rxjava.Observable<spoon.test.generics.testclasses.rxjava.Try<java.util.Optional<java.lang.Object>>>, ? extends spoon.test.generics.testclasses.rxjava.Publisher<?>>
CtTypeReference<?> managerRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "manager")).first(CtVariable.class).getType();
assertEquals(false, managerRef.isGenerics());

// spoon.test.generics.testclasses.rxjava.BehaviorSubject<spoon.test.generics.testclasses.rxjava.Try<java.util.Optional<java.lang.Object>>>
CtTypeReference<?> subjectRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "subject")).first(CtVariable.class).getType();
assertEquals(false, subjectRef.isGenerics());

// spoon.test.generics.testclasses.rxjava.PublisherRedo.RedoSubscriber<T>
CtTypeReference<?> parentRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "parent")).first(CtVariable.class).getType();
assertEquals(true, parentRef.isGenerics());

// spoon.test.generics.testclasses.rxjava.Publisher<?>
CtTypeReference<?> actionRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "action")).first(CtVariable.class).getType();
assertEquals(false, actionRef.isGenerics());

// spoon.test.generics.testclasses.rxjava.ToNotificationSubscriber
CtTypeReference<?> trucRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "truc")).first(CtVariable.class).getType();
assertEquals(false, trucRef.isGenerics());

// java.util.function.Consumer<? super spoon.test.generics.testclasses.rxjava.Try<java.util.Optional<java.lang.Object>>>
CtTypeReference<?> consumerRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "consumer")).first(CtVariable.class).getType();
assertEquals(false, consumerRef.isGenerics());

// S
CtTypeReference<?> sectionRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "section")).first(CtVariable.class).getType();
assertEquals(true, sectionRef.isGenerics());

// X
CtTypeReference<?> paramARef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "paramA")).first(CtVariable.class).getType();
assertEquals(true, paramARef.isGenerics());

// spoon.test.generics.testclasses.Tacos
CtTypeReference<?> paramBRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "paramB")).first(CtVariable.class).getType();
assertEquals(false, paramBRef.isGenerics());

// C
CtTypeReference<?> paramCRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "paramC")).first(CtVariable.class).getType();
assertEquals(true, paramCRef.isGenerics());

// R
CtTypeReference<?> cookRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "cook")).first(CtVariable.class).getType();
assertEquals(true, cookRef.isGenerics());

// spoon.test.generics.testclasses.CelebrationLunch<java.lang.Integer, java.lang.Long, java.lang.Double>
CtTypeReference<?> clRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "cl")).first(CtVariable.class).getType();
assertEquals(false, clRef.isGenerics());

// spoon.test.generics.testclasses.CelebrationLunch<java.lang.Integer, java.lang.Long, java.lang.Double>.WeddingLunch<spoon.test.generics.testclasses.Mole>
CtTypeReference<?> disgustRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "disgust")).first(CtVariable.class).getType();
assertEquals(false, disgustRef.isGenerics());

// L
CtTypeReference<?> paramRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "param")).first(CtVariable.class).getType();
assertEquals(true, paramRef.isGenerics());

// spoon.reflect.declaration.CtType<? extends spoon.reflect.declaration.CtNamedElement>
CtTypeReference<?> targetTypeRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "targetType")).first(CtVariable.class).getType();
assertEquals(false, targetTypeRef.isGenerics());

// spoon.reflect.declaration.CtType<?>
CtTypeReference<?> somethingRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "something")).first(CtVariable.class).getType();
assertEquals(false, somethingRef.isGenerics());

// int
CtTypeReference<?> iRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "i")).first(CtVariable.class).getType();
assertEquals(false, iRef.isGenerics());

// T
CtTypeReference<?> biduleRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "bidule")).first(CtVariable.class).getType();
assertEquals(true, biduleRef.isGenerics());

// Cook<java.lang.String>
CtTypeReference<?> aClassRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "aClass")).first(CtVariable.class).getType();
assertEquals(false, aClassRef.isGenerics());

// java.util.List<java.util.List<M>>
CtTypeReference<?> list2mRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "list2m")).first(CtVariable.class).getType();
assertEquals(true, list2mRef.isGenerics());

// spoon.test.generics.testclasses.Panini.Subscriber<? extends java.lang.Long>
CtTypeReference<?> tRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "t")).first(CtVariable.class).getType();
assertEquals(false, tRef.isGenerics());

// spoon.test.generics.testclasses.Spaghetti<B>.Tester
CtTypeReference<?> testerRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "tester")).first(CtVariable.class).getType();
assertEquals(false, testerRef.isGenerics());

// spoon.test.generics.testclasses.Spaghetti<B>.Tester
CtTypeReference<?> tester1Ref = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "tester1")).first(CtVariable.class).getType();
assertEquals(false, tester1Ref.isGenerics());

// spoon.test.generics.testclasses.Spaghetti<B>.That<java.lang.String, java.lang.String>
CtTypeReference<?> fieldRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "field")).first(CtVariable.class).getType();
assertEquals(false, fieldRef.isGenerics());

// spoon.test.generics.testclasses.Spaghetti<java.lang.String>.That<java.lang.String, java.lang.String>
CtTypeReference<?> field1Ref = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "field1")).first(CtVariable.class).getType();
assertEquals(false, field1Ref.isGenerics());

// spoon.test.generics.testclasses.Spaghetti<java.lang.Number>.That<java.lang.String, java.lang.String>
CtTypeReference<?> field2Ref = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "field2")).first(CtVariable.class).getType();
assertEquals(false, field2Ref.isGenerics());

// spoon.test.generics.testclasses.Tacos<K, java.lang.String>.Burritos<K, V>
CtTypeReference<?> burritosRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "burritos")).first(CtVariable.class).getType();
assertEquals(true, burritosRef.isGenerics());

// int
CtTypeReference<?> nbTacosRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "nbTacos")).first(CtVariable.class).getType();
assertEquals(false, nbTacosRef.isGenerics());

// java.util.List<java.lang.String>
CtTypeReference<?> lRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "l")).first(CtVariable.class).getType();
assertEquals(false, lRef.isGenerics());

// java.util.List
CtTypeReference<?> l2Ref = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "l2")).first(CtVariable.class).getType();
assertEquals(false, l2Ref.isGenerics());

// java.util.List<?>
CtTypeReference<?> l3Ref = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "l3")).first(CtVariable.class).getType();
assertEquals(false, l3Ref.isGenerics());

// T
CtTypeReference<?> anObjectRef = factory.getModel().getRootPackage().filterChildren(new NamedElementFilter(CtVariable.class, "anObject")).first(CtVariable.class).getType();
assertEquals(true, anObjectRef.isGenerics());

}
@Test
public void testCtTypeReference_getSuperclass() throws Exception {
Factory factory = build(new File("src/test/java/spoon/test/generics/testclasses"));
CtClass<?> ctClassCelebrationLunch = factory.Class().get(CelebrationLunch.class);
Expand Down

0 comments on commit c6bb2ec

Please sign in to comment.