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

fix: ClassTypingContext resolveTypeParameter of outer parameters #1837

Merged
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
64 changes: 40 additions & 24 deletions src/main/java/spoon/support/visitor/ClassTypingContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -469,36 +469,52 @@ private List<CtTypeReference<?>> resolveTypeParameters(List<CtTypeReference<?>>
CtTypeParameterReference typeParamRef = (CtTypeParameterReference) typeRef;
CtTypeParameter typeParam = typeParamRef.getDeclaration();
CtFormalTypeDeclarer declarer = typeParam.getTypeParameterDeclarer();
if ((declarer instanceof CtType<?>) == false) {
throw new SpoonException("Cannot adapt type parameters of non type scope");
}
CtType<?> typeDeclarer = (CtType<?>) declarer;
List<CtTypeReference<?>> actualTypeArguments = getActualTypeArguments(typeDeclarer.getQualifiedName());
if (actualTypeArguments == null) {
/*
* the actualTypeArguments of this declarer cannot be resolved.
* There is probably a model inconsistency
*/
throw new SpoonException("Cannot resolve " + (result.size() + 1) + ") type parameter <" + typeParamRef.getSimpleName() + "> of declarer " + declarer);
}
if (actualTypeArguments.size() != typeDeclarer.getFormalCtTypeParameters().size()) {
if (actualTypeArguments.isEmpty() == false) {
throw new SpoonException("Unexpected actual type arguments " + actualTypeArguments + " on " + typeDeclarer);
}
/*
* the scope type was delivered as type reference without appropriate type arguments.
* Use references to formal type parameters
*/
actualTypeArguments = getTypeReferences(typeDeclarer.getFormalCtTypeParameters());
typeToArguments.put(typeDeclarer.getQualifiedName(), actualTypeArguments);
}
typeRef = getValue(actualTypeArguments, typeParam, declarer);
typeRef = resolveTypeParameter(declarer, typeParamRef, typeParam, typeRef);
}
result.add(typeRef);
}
return result;
}

private CtTypeReference<?> resolveTypeParameter(CtFormalTypeDeclarer declarer, CtTypeParameterReference typeParamRef, CtTypeParameter typeParam, CtTypeReference<?> typeRef) {
if ((declarer instanceof CtType<?>) == false) {
/*
* The declarer is probably out of the scope of this ClassTypingContext.
* For example outer class or method declares type parameter,
* which is then used as argument in inner class, whose ClassTypingContext we have now
* See GenericsTest#testCannotAdaptTypeOfNonTypeScope.
*
* Use that outer type parameter reference directly without adaptation
*/
return typeRef;
}
CtType<?> typeDeclarer = (CtType<?>) declarer;
List<CtTypeReference<?>> actualTypeArguments = getActualTypeArguments(typeDeclarer.getQualifiedName());
if (actualTypeArguments == null) {
/*
* The declarer is probably out of the scope of this ClassTypingContext.
* For example outer class or method declares type parameter,
* which is then used as argument in inner class, whose ClassTypingContext we have now
* See GenericsTest#testCannotAdaptTypeOfNonTypeScope.
*
* Use that outer type parameter reference directly without adaptation
*/
return typeRef;
}
if (actualTypeArguments.size() != typeDeclarer.getFormalCtTypeParameters().size()) {
if (actualTypeArguments.isEmpty() == false) {
throw new SpoonException("Unexpected actual type arguments " + actualTypeArguments + " on " + typeDeclarer);
}
/*
* the scope type was delivered as type reference without appropriate type arguments.
* Use references to formal type parameters
*/
actualTypeArguments = getTypeReferences(typeDeclarer.getFormalCtTypeParameters());
typeToArguments.put(typeDeclarer.getQualifiedName(), actualTypeArguments);
}
return getValue(actualTypeArguments, typeParam, declarer);
}

private List<CtTypeReference<?>> getActualTypeArguments(String qualifiedName) {
List<CtTypeReference<?>> actualTypeArguments = typeToArguments.get(qualifiedName);
if (actualTypeArguments != null) {
Expand Down
18 changes: 17 additions & 1 deletion src/test/java/spoon/test/generics/GenericsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.junit.Test;
import spoon.Launcher;
import spoon.MavenLauncher;
import spoon.SpoonModelBuilder;
import spoon.compiler.SpoonResourceHelper;
import spoon.reflect.code.BinaryOperatorKind;
Expand All @@ -11,6 +10,7 @@
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtNewClass;
import spoon.reflect.code.CtReturn;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtConstructor;
Expand Down Expand Up @@ -56,6 +56,7 @@
import spoon.test.generics.testclasses.Lunch;
import spoon.test.generics.testclasses.Mole;
import spoon.test.generics.testclasses.Orange;
import spoon.test.generics.testclasses.OuterTypeParameter;
import spoon.test.generics.testclasses.Paella;
import spoon.test.generics.testclasses.Panini;
import spoon.test.generics.testclasses.SameSignature;
Expand Down Expand Up @@ -1403,4 +1404,19 @@ public void testIsGenericTypeEqual() {
MainTest.checkParentConsistency(launcher.getFactory().getModel().getRootPackage());
MainTest.checkParentConsistency(adaptedMethod);
}

@Test
public void testCannotAdaptTypeOfNonTypeScope() throws Exception {
//contract: ClassTypingContext doesn't fail on type parameters, which are defined out of the scope of ClassTypingContext
CtType<?> ctClass = ModelUtils.buildClass(OuterTypeParameter.class);
//the method defines type parameter, which is used in super of local class
CtReturn<?> retStmt = (CtReturn<?>) ctClass.getMethodsByName("method").get(0).getBody().getStatements().get(0);
CtNewClass<?> newClassExpr = (CtNewClass<?>) retStmt.getReturnedExpression();
CtType<?> declaringType = newClassExpr.getAnonymousClass();
CtMethod<?> m1 = declaringType.getMethodsByName("iterator").get(0);
ClassTypingContext c = new ClassTypingContext(declaringType);
//the adaptation of such type parameter keeps that parameter as it is.
assertFalse(c.isOverriding(m1, declaringType.getSuperclass().getTypeDeclaration().getMethodsByName("add").get(0)));
assertTrue(c.isOverriding(m1, declaringType.getSuperclass().getTypeDeclaration().getMethodsByName("iterator").get(0)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package spoon.test.generics.testclasses;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class OuterTypeParameter {
public <T> List<T> method() {
return new ArrayList<T>() {
@Override
public Iterator<T> iterator() {
return super.iterator();
}
};
}
}