From 0743b5a1f216f1afed59dbabb723e589c25e66aa Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Mon, 15 May 2017 22:52:42 +0200 Subject: [PATCH] refactor: MethodTypingContext#isOverriding/isSameSignature/isSubSignature moved to ClassTypingContext (#1299) --- .../filter/OverriddenMethodFilter.java | 8 +- .../reflect/declaration/CtMethodImpl.java | 4 +- .../reference/CtExecutableReferenceImpl.java | 8 +- .../support/visitor/ClassTypingContext.java | 143 ++++++++++ .../support/visitor/MethodTypingContext.java | 246 +++++------------- .../test/ctType/CtTypeParameterTest.java | 10 +- .../spoon/test/generics/GenericsTest.java | 33 ++- 7 files changed, 241 insertions(+), 211 deletions(-) diff --git a/src/main/java/spoon/reflect/visitor/filter/OverriddenMethodFilter.java b/src/main/java/spoon/reflect/visitor/filter/OverriddenMethodFilter.java index 08792413d06..abf586889e4 100644 --- a/src/main/java/spoon/reflect/visitor/filter/OverriddenMethodFilter.java +++ b/src/main/java/spoon/reflect/visitor/filter/OverriddenMethodFilter.java @@ -18,14 +18,14 @@ import spoon.reflect.declaration.CtMethod; import spoon.reflect.visitor.Filter; -import spoon.support.visitor.MethodTypingContext; +import spoon.support.visitor.ClassTypingContext; /** * Gets all overridden method from the method given. */ public class OverriddenMethodFilter implements Filter> { private final CtMethod method; - private final MethodTypingContext context; + private final ClassTypingContext context; private boolean includingSelf = false; /** @@ -36,7 +36,7 @@ public class OverriddenMethodFilter implements Filter> { */ public OverriddenMethodFilter(CtMethod method) { this.method = method; - context = new MethodTypingContext().setMethod(method); + context = new ClassTypingContext(method.getDeclaringType()); } /** @@ -53,6 +53,6 @@ public boolean matches(CtMethod element) { if (method == element) { return this.includingSelf; } - return context.isOverriding(element); + return context.isOverriding(method, element); } } diff --git a/src/main/java/spoon/support/reflect/declaration/CtMethodImpl.java b/src/main/java/spoon/support/reflect/declaration/CtMethodImpl.java index 4ae4c9f071d..2b8feb5dc48 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtMethodImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtMethodImpl.java @@ -26,7 +26,7 @@ import spoon.reflect.declaration.ModifierKind; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.CtVisitor; -import spoon.support.visitor.MethodTypingContext; +import spoon.support.visitor.ClassTypingContext; import java.util.ArrayList; import java.util.EnumSet; @@ -189,7 +189,7 @@ public void replace(CtMethod element) { @Override public boolean isOverriding(CtMethod superMethod) { - return new MethodTypingContext().setMethod(this).isOverriding(superMethod); + return new ClassTypingContext(getDeclaringType()).isOverriding(this, superMethod); } boolean isShadow; diff --git a/src/main/java/spoon/support/reflect/reference/CtExecutableReferenceImpl.java b/src/main/java/spoon/support/reflect/reference/CtExecutableReferenceImpl.java index df0ef18d5ba..262f40a94f9 100644 --- a/src/main/java/spoon/support/reflect/reference/CtExecutableReferenceImpl.java +++ b/src/main/java/spoon/support/reflect/reference/CtExecutableReferenceImpl.java @@ -23,6 +23,7 @@ import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.declaration.ModifierKind; import spoon.reflect.reference.CtActualTypeContainer; import spoon.reflect.reference.CtExecutableReference; @@ -31,7 +32,7 @@ import spoon.reflect.visitor.filter.NameFilter; import spoon.support.reflect.declaration.CtElementImpl; import spoon.support.util.RtHelper; -import spoon.support.visitor.MethodTypingContext; +import spoon.support.visitor.ClassTypingContext; import spoon.support.visitor.SignaturePrinter; import java.lang.reflect.AnnotatedElement; @@ -247,9 +248,8 @@ public boolean isOverriding(CtExecutableReference executable) { } return true; } - if (exec instanceof CtMethod) { - CtMethod method = (CtMethod) exec; - return new MethodTypingContext().setExecutableReference(this).isOverriding(method); + if (exec instanceof CtMethod && thisExec instanceof CtMethod) { + return new ClassTypingContext(((CtTypeMember) thisExec).getDeclaringType()).isOverriding((CtMethod) thisExec, (CtMethod) exec); } //it is not a method. So we can return true only if it is reference to the this executable return exec == getDeclaration(); diff --git a/src/main/java/spoon/support/visitor/ClassTypingContext.java b/src/main/java/spoon/support/visitor/ClassTypingContext.java index a3bc2013183..3255e3cc93f 100644 --- a/src/main/java/spoon/support/visitor/ClassTypingContext.java +++ b/src/main/java/spoon/support/visitor/ClassTypingContext.java @@ -24,6 +24,7 @@ import java.util.Set; import spoon.SpoonException; +import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtFormalTypeDeclarer; @@ -264,6 +265,53 @@ public CtMethod adaptMethod(CtMethod method) { return adaptedMethod; } + /** + * @param thatMethod - to be checked method + * @return true if scope method overrides `thatMethod` + */ + public boolean isOverriding(CtMethod thisMethod, CtMethod thatMethod) { + if (thisMethod == thatMethod) { + //method overrides itself in spoon model + return true; + } + CtType thatDeclType = thatMethod.getDeclaringType(); + CtType thisDeclType = getAdaptationScope(); + if (thatDeclType != thisDeclType) { + if (isSubtypeOf(thatDeclType.getReference()) == false) { + //the declaringType of that method must be superType of this scope type + return false; + } + } + //TODO check method visibility following https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.8.1 + return isSubSignature(thisMethod, thatMethod); + } + + /** + * scope method is subsignature of thatMethod if either + * A) scope method is same signature like thatMethod + * B) scope method is same signature like type erasure of thatMethod + * See https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2 + * + * @param thatMethod - the checked method + * @return true if scope method is subsignature of thatMethod + */ + public boolean isSubSignature(CtMethod thisMethod, CtMethod thatMethod) { + return isSameSignature(thisMethod, thatMethod, true); + } + + /** + * The same signature is the necessary condition for method A overrides method B. + * @param thatExecutable - the checked method + * @return true if this method and `thatMethod` has same signature + */ + public boolean isSameSignature(CtExecutable thisExecutable, CtMethod thatExecutable) { + if ((thatExecutable instanceof CtMethod || thatExecutable instanceof CtConstructor) == false) { + //only method or constructor can have same signature + return false; + } + return isSameSignature(thisExecutable, thatExecutable, false); + } + @Override public ClassTypingContext getEnclosingGenericTypeAdapter() { return enclosingClassTypingContext; @@ -563,4 +611,99 @@ private CtTypeReference adaptTypeForNewMethod(CtTypeReference typeRef) { } return adaptType(typeRef); } + + private boolean isSameSignature(CtExecutable thisMethod, CtExecutable thatMethod, boolean canTypeErasure) { + if (thisMethod == thatMethod) { + return true; + } + ExecutableContext mtc = new ExecutableContext(); + mtc.setClassTypingContext(this); + + if (thisMethod instanceof CtMethod) { + if (thatMethod instanceof CtMethod) { + mtc.setMethod((CtMethod) thisMethod); + } else { + return false; + } + } else if (thisMethod instanceof CtConstructor) { + if (thatMethod instanceof CtConstructor) { + mtc.setConstructor((CtConstructor) thisMethod); + } else { + return false; + } + } else { + //only method or constructor can compare signatures + return false; + } + return mtc.isSameSignatureLikeScopeMethod(thatMethod, canTypeErasure); + } + + private static class ExecutableContext extends MethodTypingContext { + private boolean isSameSignatureLikeScopeMethod(CtExecutable thatExecutable, boolean canTypeErasure) { + //https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2 + CtFormalTypeDeclarer thatDeclarer = (CtFormalTypeDeclarer) thatExecutable; + CtFormalTypeDeclarer thisDeclarer = getAdaptationScope(); + CtExecutable thisExecutable = (CtExecutable) thisDeclarer; + if (thatExecutable.getSimpleName().equals(thisExecutable.getSimpleName()) == false) { + return false; + } + if (thisExecutable.getParameters().size() != thatExecutable.getParameters().size()) { + //the executables has different count of parameters they cannot have same signature + return false; + } + List thisTypeParameters = thisDeclarer.getFormalCtTypeParameters(); + List thatTypeParameters = thatDeclarer.getFormalCtTypeParameters(); + boolean useTypeErasure = false; + if (thisTypeParameters.size() == thatTypeParameters.size()) { + //the methods has same count of formal parameters + //check that formal type parameters are same + if (hasSameMethodFormalTypeParameters((CtFormalTypeDeclarer) thatExecutable) == false) { + return false; + } + } else { + //the methods has different count of formal type parameters. + if (canTypeErasure == false) { + //type erasure is not allowed. So non-generic methods cannot match with generic methods + return false; + } + //non-generic method can override a generic one if type erasure is allowed + if (thisTypeParameters.isEmpty() == false) { + //scope methods has some parameters. It is generic too, it is not a subsignature of that method + return false; + } + //scope method has zero formal type parameters. It is not generic. + useTypeErasure = true; + } + List> thisParameterTypes = getParameterTypes(thisExecutable.getParameters()); + List> thatParameterTypes = getParameterTypes(thatExecutable.getParameters()); + //check that parameters are same after adapting to the same scope + for (int i = 0; i < thisParameterTypes.size(); i++) { + CtTypeReference thisType = thisParameterTypes.get(i); + CtTypeReference thatType = thatParameterTypes.get(i); + if (useTypeErasure) { + if (thatType instanceof CtTypeParameterReference) { + thatType = ((CtTypeParameterReference) thatType).getTypeErasure(); + } + } else { + thatType = adaptType(thatType); + } + if (thatType == null) { + //the type cannot be adapted. + return false; + } + if (thisType.equals(thatType) == false) { + return false; + } + } + return true; + } + + private static List> getParameterTypes(List> params) { + List> types = new ArrayList<>(params.size()); + for (CtParameter param : params) { + types.add(param.getType()); + } + return types; + } + } } diff --git a/src/main/java/spoon/support/visitor/MethodTypingContext.java b/src/main/java/spoon/support/visitor/MethodTypingContext.java index 36936970f8b..20a85ca0b0e 100644 --- a/src/main/java/spoon/support/visitor/MethodTypingContext.java +++ b/src/main/java/spoon/support/visitor/MethodTypingContext.java @@ -18,32 +18,31 @@ import static spoon.support.visitor.ClassTypingContext.getTypeReferences; -import java.util.ArrayList; import java.util.List; import spoon.SpoonException; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtInvocation; 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.CtExecutableReference; import spoon.reflect.reference.CtTypeParameterReference; import spoon.reflect.reference.CtTypeReference; /** - * For the `scopeMethod` and super type hierarchy of it's declaring type, - * it is able to adapt type parameters - * and compare method signatures. + * For the scope method or constructor and super type hierarchy of it's declaring type, + * it is able to adapt type parameters. + * + * https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.4 + * Where two methods or constructors M and N have the same type parameters {@link #hasSameMethodFormalTypeParameters(CtFormalTypeDeclarer)}, + * a type mentioned in N can be adapted to the type parameters of M */ public class MethodTypingContext extends AbstractTypingContext { - private CtExecutable scopeMethod; + private CtFormalTypeDeclarer scopeMethod; private List> actualTypeArguments; private ClassTypingContext classTypingContext; @@ -52,7 +51,7 @@ public MethodTypingContext() { @Override public CtFormalTypeDeclarer getAdaptationScope() { - return (CtFormalTypeDeclarer) scopeMethod; + return scopeMethod; } public MethodTypingContext setMethod(CtMethod scopeMethod) { @@ -101,7 +100,7 @@ public MethodTypingContext setInvocation(CtInvocation invocation) { public MethodTypingContext setExecutableReference(CtExecutableReference execRef) { this.actualTypeArguments = execRef.getActualTypeArguments(); - setScopeMethod(execRef.getExecutableDeclaration()); + setScopeMethod((CtFormalTypeDeclarer) execRef.getExecutableDeclaration()); if (classTypingContext == null) { CtTypeReference declaringTypeRef = execRef.getDeclaringType(); if (declaringTypeRef != null) { @@ -112,188 +111,73 @@ public MethodTypingContext setExecutableReference(CtExecutableReference execR } /** - * @param thatMethod - to be checked method - * @return true if scope method overrides `thatMethod` - */ - public boolean isOverriding(CtMethod thatMethod) { - if (scopeMethod == thatMethod) { - //method overrides itself in spoon model - return true; - } - CtType thatDeclType = thatMethod.getDeclaringType(); - CtType thisDeclType = getScopeMethodDeclaringType(); - if (thatDeclType != thisDeclType) { - if (getEnclosingGenericTypeAdapter().isSubtypeOf(thatDeclType.getReference()) == false) { - return false; - } - } - return isSubSignature(thatMethod); - } - - /** - * scope method is subsignature of thatMethod if either - * A) scope method is same signature like thatMethod - * B) scope method is same signature like type erasure of thatMethod - * See https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2 - * - * @param thatMethod - the checked method - * @return true if scope method is subsignature of thatMethod - */ - public boolean isSubSignature(CtMethod thatMethod) { - return checkSignature(thatMethod, true); - } - - /** - * The same signature is the necessary condition for method A overrides method B. - * @param thatMethod - the checked method - * @return true if this method and `thatMethod` has same signature - */ - public boolean isSameSignature(CtMethod thatMethod) { - return checkSignature(thatMethod, false); - } - - private boolean checkSignature(CtMethod thatMethod, boolean canTypeErasure) { - //https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2 - if (mightBeSameSignature(thatMethod) == false) { - return false; - } - List formalCtTypeParameters = ((CtFormalTypeDeclarer) scopeMethod).getFormalCtTypeParameters(); - List thatTypeParameters = thatMethod.getFormalCtTypeParameters(); - boolean useTypeErasure = false; - if (formalCtTypeParameters.size() == thatTypeParameters.size()) { - //the methods has same count of formal parameters - //check that formal type parameters are same - for (int i = 0; i < formalCtTypeParameters.size(); i++) { - if (isSameMethodFormalTypeParameter(formalCtTypeParameters.get(i), thatTypeParameters.get(i)) == false) { - return false; - } - } - } else { - //the methods has different count of formal type parameters. - if (canTypeErasure == false) { - //type erasure is not allowed. So not generic methods cannot match with generic methods - return false; - } - //non generic method can override a generic one if type erasure is allowed - if (formalCtTypeParameters.isEmpty() == false) { - //scope methods has some parameters. It is generic too, it is not a subsignature of that method - return false; - } - //scope method has zero formal type parameters. It is not generic. - useTypeErasure = true; - } - List> thisParameterTypes = getParameterTypes(scopeMethod.getParameters()); - List> thatParameterTypes = getParameterTypes(thatMethod.getParameters()); - //check that parameters are same after adapted to same scope - for (int i = 0; i < thisParameterTypes.size(); i++) { - CtTypeReference thisType = thisParameterTypes.get(i); - CtTypeReference thatType = thatParameterTypes.get(i); - if (useTypeErasure) { - if (thatType instanceof CtTypeParameterReference) { - thatType = ((CtTypeParameterReference) thatType).getTypeErasure(); - } - } else { - thatType = adaptType(thatType); - } - if (thatType == null) { - //the type cannot be adapted. - return false; - } - if (thisType.equals(thatType) == false) { - return false; - } - } - return true; - } - - /** - * Check if thatMethod might have same signature like scope method. - * Check only attributes, which does not need adapting or type erasure - * @param thatMethod the to be checked method - * @return true if `thatMethod` might have same signature like scope method - */ - private boolean mightBeSameSignature(CtMethod thatMethod) { - if (scopeMethod == thatMethod) { - return true; - } - if ((thatMethod instanceof CtMethod) == false) { - return false; - } - if (thatMethod.getSimpleName().equals(scopeMethod.getSimpleName()) == false) { - return false; - } - if (scopeMethod.getParameters().size() != thatMethod.getParameters().size()) { - //the methods has different count of parameters they cannot have same signature - return false; - } - int nrTypeParamsOfScope = ((CtFormalTypeDeclarer) scopeMethod).getFormalCtTypeParameters().size(); - int nrTypeParamsOfThat = thatMethod.getFormalCtTypeParameters().size(); - if (nrTypeParamsOfThat != nrTypeParamsOfThat) { - //the methods has different count of formal type parameters - if (nrTypeParamsOfScope != 0) { - //they cannot have same signature - return false; - } //the overriding method has 0 parameters, it can have same signature after type erasure - } - return true; - } - - /** - * adapts `typeParam` to the {@link CtTypeReference} + * Adapts `typeParam` to the {@link CtTypeReference} * of scope of this {@link MethodTypingContext} * In can be {@link CtTypeParameterReference} again - depending actual type arguments of this {@link MethodTypingContext}. * - * Note: this method is not checking whether declarer method is overridden by scope method, - * so it it may adapt parameters of potentially override equivalent methods. - * Use {@link #checkSignature(CtMethod, boolean)} to check if method overrides another method - * - * @param superParam to be resolved {@link CtTypeParameter} + * @param typeParam to be resolved {@link CtTypeParameter} * @return {@link CtTypeReference} or {@link CtTypeParameterReference} adapted to scope of this {@link MethodTypingContext} * or null if `typeParam` cannot be adapted to target `scope` */ @Override - protected CtTypeReference adaptTypeParameter(CtTypeParameter superParam) { - CtFormalTypeDeclarer superDeclarer = superParam.getTypeParameterDeclarer(); - if (superDeclarer instanceof CtType) { - return getEnclosingGenericTypeAdapter().adaptType(superParam); - } - if (superDeclarer instanceof CtMethod) { - CtMethod superMethod = (CtMethod) superDeclarer; - /* - * The type parameters of generic executables are same (can be adapted to each other) - * 1) the methods are same or if methods overrides each other - * 2) they are declared on same position - * 3) they have same bound after adapting - * See https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.4 - */ - if (mightBeSameSignature(superMethod) == false) { - //the methods are different. Cannot adapt parameter + protected CtTypeReference adaptTypeParameter(CtTypeParameter typeParam) { + CtFormalTypeDeclarer typeParamDeclarer = typeParam.getTypeParameterDeclarer(); + if (typeParamDeclarer instanceof CtType) { + return getEnclosingGenericTypeAdapter().adaptType(typeParam); + } + //only method to method or constructor to constructor can be adapted + if (typeParamDeclarer instanceof CtMethod) { + if ((scopeMethod instanceof CtMethod) == false) { return null; } - /* - * we do not know 100% if thatMethod overrides scope method, - * but we cannot detect it here, because that check would need adapting of type parameters, - * which would cause StackOverflowError - */ - int superParamPosition = superMethod.getFormalCtTypeParameters().indexOf(superParam); - CtTypeParameter scopeParam = ((CtFormalTypeDeclarer) scopeMethod).getFormalCtTypeParameters().get(superParamPosition); - if (isSameMethodFormalTypeParameter(scopeParam, superParam) == false) { - //the argument cannot be adapted if bounds are not same + } else if (typeParamDeclarer instanceof CtConstructor) { + if ((scopeMethod instanceof CtConstructor) == false) { return null; } - return actualTypeArguments.get(superParamPosition); + } else { + throw new SpoonException("Unexpected type parameter declarer"); + } + //the typeParamDeclarer is method or constructor + /* + * + * Two methods or constructors M and N have the same type parameters if both of the following are true: + * 1) M and N have same number of type parameters (possibly zero). + * 2) Where A1, ..., An are the type parameters of M and B1, ..., Bn are the type parameters of N, let T=[B1:=A1, ..., Bn:=An]. + * Then, for all i (1 ≤ i ≤ n), the bound of Ai is the same type as T applied to the bound of Bi. + */ + if (hasSameMethodFormalTypeParameters(typeParamDeclarer) == false) { + //the methods formal type parameters are different. We cannot adapt such parameters + return null; } - return null; + int typeParamPosition = typeParamDeclarer.getFormalCtTypeParameters().indexOf(typeParam); + return actualTypeArguments.get(typeParamPosition); } /** + * https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.4 + * * Formal type parameters of method are same if * 1) both methods have same number of formal type parameters * 2) bounds of Formal type parameters are after adapting same types * - * This method checks only point (2). Point (1) has to be already checked by caller - * @return if bounds are same after adapting + * @return true if formal type parameters of method are same */ + public boolean hasSameMethodFormalTypeParameters(CtFormalTypeDeclarer typeParamDeclarer) { + List thisTypeParameters = scopeMethod.getFormalCtTypeParameters(); + List thatTypeParameters = typeParamDeclarer.getFormalCtTypeParameters(); + if (thisTypeParameters.size() != thatTypeParameters.size()) { + return false; + } + //the methods has same count of formal parameters + //check that bounds of formal type parameters are same after adapting + for (int i = 0; i < thisTypeParameters.size(); i++) { + if (isSameMethodFormalTypeParameter(thisTypeParameters.get(i), thatTypeParameters.get(i)) == false) { + return false; + } + } + return true; + } + private boolean isSameMethodFormalTypeParameter(CtTypeParameter scopeParam, CtTypeParameter superParam) { CtTypeReference scopeBound = getBound(scopeParam); CtTypeReference superBoundAdapted = adaptType(getBound(superParam)); @@ -311,31 +195,21 @@ private static CtTypeReference getBound(CtTypeParameter typeParam) { return bound; } - private static List> getParameterTypes(List> params) { - List> types = new ArrayList<>(params.size()); - for (CtParameter param : params) { - types.add(param.getType()); - } - return types; - } - - - private CtType getScopeMethodDeclaringType() { if (scopeMethod != null) { - return ((CtTypeMember) scopeMethod).getDeclaringType(); + return scopeMethod.getDeclaringType(); } throw new SpoonException("scopeMethod is not assigned"); } - private void setScopeMethod(CtExecutable executable) { + private void setScopeMethod(CtFormalTypeDeclarer executable) { checkSameTypingContext(classTypingContext, executable); scopeMethod = executable; } - private void checkSameTypingContext(ClassTypingContext ctc, CtExecutable executable) { + private void checkSameTypingContext(ClassTypingContext ctc, CtFormalTypeDeclarer executable) { if (ctc != null && executable != null) { - CtType scope = ((CtTypeMember) executable).getDeclaringType(); + CtType scope = executable.getDeclaringType(); if (scope == null) { throw new SpoonException("Cannot use executable without declaring type as scope of method typing context"); } diff --git a/src/test/java/spoon/test/ctType/CtTypeParameterTest.java b/src/test/java/spoon/test/ctType/CtTypeParameterTest.java index 4d83caa4ec2..b6fed2a7086 100644 --- a/src/test/java/spoon/test/ctType/CtTypeParameterTest.java +++ b/src/test/java/spoon/test/ctType/CtTypeParameterTest.java @@ -152,15 +152,17 @@ public void testTypeSame() throws Exception { //the type parameters of ErasureModelA and ErasureModelA$ModelB are same if they are on the same position. checkIsSame(ctModel.getFormalCtTypeParameters(), ctModelB.getFormalCtTypeParameters(), true); - //the type parameters of ErasureModelA#constructor and ErasureModelA$ModelB constructor are NOT same, because constructors does not override - checkIsSame(ctModelCons.getFormalCtTypeParameters(), ctModelBCons.getFormalCtTypeParameters(), false); + //the type parameters of ErasureModelA#constructor and ErasureModelA$ModelB constructor are same, because constructors has same formal type parameters + //https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.4 + checkIsSame(ctModelCons.getFormalCtTypeParameters(), ctModelBCons.getFormalCtTypeParameters(), true); //the type parameters of ctModel ErasureModelA#method and ErasureModelA$ModelB#method are same if they are on the same position. checkIsSame(ctModelMethod.getFormalCtTypeParameters(), ctModelBMethod.getFormalCtTypeParameters(), true); //the type parameters of ctModel ErasureModelA#constructor and ErasureModelA$ModelB#method are never same, because they have different type of scope (Method!=Constructor) checkIsSame(ctModelCons.getFormalCtTypeParameters(), ctModelBMethod.getFormalCtTypeParameters(), false); - //the type parameters of ctModel ErasureModelA#method and ErasureModelA#method2 are never same, because we can be sure that method does not override method2 - checkIsSame(ctModelMethod.getFormalCtTypeParameters(), ctModelMethod2.getFormalCtTypeParameters(), false); + //the type parameters of ctModel ErasureModelA#method and ErasureModelA#method2 are same, because they have same formal type parameters + //https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.4 + checkIsSame(ctModelMethod.getFormalCtTypeParameters(), ctModelMethod2.getFormalCtTypeParameters(), true); CtClass ctModelC = ctModel.filterChildren(new NameFilter<>("ModelC")).first(); } diff --git a/src/test/java/spoon/test/generics/GenericsTest.java b/src/test/java/spoon/test/generics/GenericsTest.java index 624505b4924..cd6f069908d 100644 --- a/src/test/java/spoon/test/generics/GenericsTest.java +++ b/src/test/java/spoon/test/generics/GenericsTest.java @@ -821,8 +821,6 @@ public void testMethodTypingContext() throws Exception { CtClass ctClassLunch = factory.Class().get(Lunch.class); CtMethod trLunch_eatMe = ctClassLunch.filterChildren(new NameFilter<>("eatMe")).first(); - assertTrue(methodSTH.isOverriding(trLunch_eatMe)); - CtInvocation invokeReserve = factory.Class().get(CelebrationLunch.class) .filterChildren(new TypeFilter<>(CtInvocation.class)) .select((CtInvocation i)->"reserve".equals(i.getExecutable().getSimpleName())) @@ -923,22 +921,35 @@ public void testMethodTypingContextAdaptMethod() throws Exception { //contract: method typing context creates adapted method automatically, which is equal to manually adapted one assertEquals(adaptedLunchEatMe, methodSTH.getAdaptationScope()); + } + + @Test + public void testClassTypingContextMethodSignature() throws Exception { + // core contracts of MethodTypingContext#adaptMethod + Factory factory = build(new File("src/test/java/spoon/test/generics/testclasses")); + CtClass ctClassLunch = factory.Class().get(Lunch.class); + CtClass ctClassWeddingLunch = factory.Class().get(WeddingLunch.class); + + // represents void eatMe(A paramA, B paramB, C paramC){} + CtMethod trLunch_eatMe = ctClassLunch.filterChildren(new NameFilter<>("eatMe")).first(); + // represents void eatMe(M paramA, K paramB, C paramC) CtMethod trWeddingLunch_eatMe = ctClassWeddingLunch.filterChildren(new NameFilter<>("eatMe")).first(); - assertTrue(methodSTH.isOverriding(trLunch_eatMe)); - assertTrue(methodSTH.isOverriding(trWeddingLunch_eatMe)); - assertTrue(methodSTH.isSubSignature(trWeddingLunch_eatMe)); + + ClassTypingContext ctcWeddingLunch = new ClassTypingContext(ctClassWeddingLunch); + + assertTrue(ctcWeddingLunch.isOverriding(trLunch_eatMe, trLunch_eatMe)); + assertTrue(ctcWeddingLunch.isOverriding(trLunch_eatMe, trWeddingLunch_eatMe)); + assertTrue(ctcWeddingLunch.isSubSignature(trLunch_eatMe, trWeddingLunch_eatMe)); - //contract: use method from correct scope and check whether it has same signature like adapted method - methodSTH.setMethod(trWeddingLunch_eatMe); //contract: check that adapting of methods still produces same results, even when scopeMethod is already assigned - assertEquals(adaptedLunchEatMe, ctcWeddingLunch.adaptMethod(trLunch_eatMe)); - assertTrue(methodSTH.isOverriding(trLunch_eatMe)); - assertTrue(methodSTH.isOverriding(trWeddingLunch_eatMe)); - assertTrue(methodSTH.isSubSignature(trWeddingLunch_eatMe)); + assertTrue(ctcWeddingLunch.isOverriding(trWeddingLunch_eatMe, trLunch_eatMe)); + assertTrue(ctcWeddingLunch.isOverriding(trWeddingLunch_eatMe, trWeddingLunch_eatMe)); + assertTrue(ctcWeddingLunch.isSubSignature(trWeddingLunch_eatMe, trWeddingLunch_eatMe)); } + @Test public void testClassContextOnInnerClass() throws Exception { CtClass classBanana = (CtClass)buildClass(Banana.class);