Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

review1: feature: CtTypeParameter#getTypeErasure() #1216

Merged
merged 2 commits into from
Apr 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,10 @@ public interface CtTypeInformation {
@DerivedProperty
Collection<CtExecutableReference<?>> getAllExecutables();

/**
* @return the type erasure, which is computed by the java compiler to ensure that no new classes are created for parametrized types so that generics incur no runtime overhead.
* See https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
*/
@DerivedProperty
CtTypeReference<?> getTypeErasure();
}
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,11 @@ public void accept(CtMethod<?> method) {
return Collections.unmodifiableSet(l);
}

@Override
public CtTypeReference<?> getTypeErasure() {
return getReference();
}

boolean isShadow;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,20 @@ public boolean isSubtypeOf(CtTypeReference<?> type) {
return getReference().isSubtypeOf(type);
}

@Override
public CtTypeReference<?> getTypeErasure() {
CtTypeReference<?> boundType = getBound(this);
return boundType.getTypeErasure();
}

private static CtTypeReference<?> getBound(CtTypeParameter typeParam) {
CtTypeReference<?> bound = typeParam.getSuperclass();
if (bound == null) {
bound = typeParam.getFactory().Type().OBJECT;
}
return bound;
}

@Override
@UnsettableProperty
public <M, C extends CtType<Object>> C addMethod(CtMethod<M> method) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ public boolean removeBound(CtTypeReference<?> bound) {
return bounds != CtElementImpl.<CtTypeReference<?>>emptyList() && bounds.remove(bound);
}

@Override
public CtTypeReference<?> getTypeErasure() {
if (bounds == null || bounds.isEmpty()) {
return getFactory().Type().OBJECT;
}
return bounds.get(0).getTypeErasure();
}

@Override
public CtIntersectionTypeReference<T> clone() {
return (CtIntersectionTypeReference<T>) super.clone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ public CtType<Object> getTypeDeclaration() {
return getDeclaration();
}

@Override
public CtTypeReference<?> getTypeErasure() {
return getDeclaration().getTypeErasure();
}

@Override
public CtTypeParameterReference clone() {
return (CtTypeParameterReference) super.clone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -833,4 +833,14 @@ public CtTypeParameter getTypeParameterDeclaration() {
private CtTypeParameter findTypeParamDeclarationByPosition(CtFormalTypeDeclarer type, int position) {
return type.getFormalCtTypeParameters().get(position);
}

@Override
public CtTypeReference<?> getTypeErasure() {
if (getActualTypeArguments().isEmpty()) {
return this;
}
CtTypeReference<?> erasedRef = clone();
erasedRef.getActualTypeArguments().clear();
return erasedRef;
}
}
131 changes: 131 additions & 0 deletions src/test/java/spoon/test/ctType/CtTypeParameterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package spoon.test.ctType;

import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

import org.junit.Test;

import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtFormalTypeDeclarer;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeMember;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.reflect.reference.CtTypeParameterReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.filter.NameFilter;
import spoon.test.ctType.testclasses.ErasureModelA;
import spoon.testing.utils.ModelUtils;

import static org.junit.Assert.*;

public class CtTypeParameterTest {

@Test
public void testTypeErasure() throws Exception {
//contract: the type erasure computed by getTypeErasure is same as the one computed by the Java compiler
CtClass<?> ctModel = (CtClass<?>) ModelUtils.buildClass(ErasureModelA.class);
//visit all methods of type ctModel
//visit all inner types and their methods recursively `getTypeErasure` returns expected value
//for each formal type parameter or method parameter check if
checkType(ctModel);
}

private void checkType(CtType<?> type) throws NoSuchFieldException, SecurityException {
List<CtTypeParameter> formalTypeParameters = type.getFormalCtTypeParameters();
for (CtTypeParameter ctTypeParameter : formalTypeParameters) {
checkTypeParamErasureOfType(ctTypeParameter, type.getActualClass());
}

for (CtTypeMember member : type.getTypeMembers()) {
if (member instanceof CtFormalTypeDeclarer) {
CtFormalTypeDeclarer ftDecl = (CtFormalTypeDeclarer) member;
formalTypeParameters = ftDecl.getFormalCtTypeParameters();
if (member instanceof CtExecutable<?>) {
CtExecutable<?> exec = (CtExecutable<?>) member;
for (CtTypeParameter ctTypeParameter : formalTypeParameters) {
checkTypeParamErasureOfExecutable(ctTypeParameter);
}
for (CtParameter<?> param : exec.getParameters()) {
checkParameterErasureOfExecutable(param);
}
} else if (member instanceof CtType<?>) {
CtType<?> nestedType = (CtType<?>) member;
// recursive call for nested type
checkType(nestedType);
}
}
}
}

private void checkTypeParamErasureOfType(CtTypeParameter typeParam, Class<?> clazz) throws NoSuchFieldException, SecurityException {
Field field = clazz.getDeclaredField("param"+typeParam.getSimpleName());
assertEquals("TypeErasure of type param "+getTypeParamIdentification(typeParam), field.getType().getName(), typeParam.getTypeErasure().getQualifiedName());
}

private void checkTypeParamErasureOfExecutable(CtTypeParameter typeParam) throws NoSuchFieldException, SecurityException {
CtExecutable<?> exec = (CtExecutable<?>) typeParam.getParent();
CtParameter<?> param = exec.filterChildren(new NameFilter<>("param"+typeParam.getSimpleName())).first();
assertNotNull("Missing param"+typeParam.getSimpleName() + " in "+ exec.getSignature(), param);
int paramIdx = exec.getParameters().indexOf(param);
Class declClass = exec.getParent(CtType.class).getActualClass();
Executable declExec;
if (exec instanceof CtConstructor) {
declExec = declClass.getDeclaredConstructors()[0];
} else {
declExec = getMethodByName(declClass, exec.getSimpleName());
}
Class<?> paramType = declExec.getParameterTypes()[paramIdx];
// contract the type erasure given with Java reflection is the same as the one computed by spoon
assertEquals("TypeErasure of executable param "+getTypeParamIdentification(typeParam), paramType.getName(), typeParam.getTypeErasure().toString());
}

private void checkParameterErasureOfExecutable(CtParameter<?> param) {
CtExecutable<?> exec = param.getParent();
CtTypeReference<?> typeErasure = param.getType().getTypeErasure();
int paramIdx = exec.getParameters().indexOf(param);
Class declClass = exec.getParent(CtType.class).getActualClass();
Executable declExec;
if (exec instanceof CtConstructor) {
declExec = declClass.getDeclaredConstructors()[0];
} else {
declExec = getMethodByName(declClass, exec.getSimpleName());
}
Class<?> paramType = declExec.getParameterTypes()[paramIdx];
assertEquals(0, typeErasure.getActualTypeArguments().size());
// contract the type erasure of the method parameter given with Java reflection is the same as the one computed by spoon
assertEquals("TypeErasure of executable "+exec.getSignature()+" parameter "+param.getSimpleName(), paramType.getName(), typeErasure.getQualifiedName());
}


private Executable getMethodByName(Class declClass, String simpleName) {
for (Method method : declClass.getDeclaredMethods()) {
if(method.getName().equals(simpleName)) {
return method;
}
}
fail("Method "+simpleName+" not found in "+declClass.getName());
return null;
}

private String getTypeParamIdentification(CtTypeParameter typeParam) {
String result = "<"+typeParam.getSimpleName()+">";
CtFormalTypeDeclarer l_decl = typeParam.getParent(CtFormalTypeDeclarer.class);
if (l_decl instanceof CtType) {
return ((CtType) l_decl).getQualifiedName()+result;
}
if (l_decl instanceof CtExecutable) {
CtExecutable exec = (CtExecutable) l_decl;
if (exec instanceof CtMethod) {
result=exec.getSignature()+result;
}
return exec.getParent(CtType.class).getQualifiedName()+"#"+result;
}
throw new AssertionError();
}
}
56 changes: 56 additions & 0 deletions src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package spoon.test.ctType.testclasses;

import java.io.Serializable;
import java.util.List;

public class ErasureModelA<A, B extends Exception, C extends B, D extends List<B>> {

A paramA;
B paramB;
C paramC;
D paramD;

public <I, J extends C> ErasureModelA(I paramI, J paramJ, D paramD) {
}

public <I, J extends C> void method(I paramI, J paramJ, D paramD) {
}

public <I, J extends C> void method2(I paramI, J paramJ, D paramD) {
}

public <I, J extends C, K extends ErasureModelA<A,B,C,D>&Serializable> void method3(I paramI, J paramJ, D paramD, K paramK) {
}

public <I> void wildCardMethod(I paramI, ErasureModelA<? extends I, B, C, D> extendsI) {
}

// simple case
public void list(List<Object> x, List<List<Object>> y, List<String> z) {
}

static class ModelB<A2,B2 extends Exception, C2 extends B2, D2 extends List<B2>> extends ErasureModelA<A2,B2,C2,D2> {
A2 paramA2;
B2 paramB2;
C2 paramC2;
D2 paramD2;

public <I, J extends C2> ModelB(I paramI, J paramJ, D2 paramD2) {
super(paramI, paramJ, paramD2);
}

@Override
public <I, J extends C2> void method(I paramI, J paramJ, D2 paramD2) {
}
}

static class ModelC extends ErasureModelA<Integer, RuntimeException, IllegalArgumentException, List<RuntimeException>> {

public ModelC(Float paramI, IllegalArgumentException paramJ, ModelC paramK) {
super(paramI, paramJ, null);
}

public void method(Float paramI, IllegalArgumentException paramJ, ModelC paramK) {
}
}
}