From 8edd7a4cf53e60e470173a0123e058c9eb28152c Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Fri, 11 Dec 2015 13:36:32 +0100 Subject: [PATCH] method hierarchy: don't merge methods with return types which are not substitutable (IDEA-57393) --- .../psi/util/MethodSignatureUtil.java | 32 +++++++++++++++++ .../psi/impl/PsiSuperMethodImplUtil.java | 36 +++++++++++++++++-- ...tableForSameOverrideEquivalentMethods.java | 8 +++++ .../daemon/GenericsHighlightingTest.java | 4 +++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/ReturnTypeSubstitutableForSameOverrideEquivalentMethods.java diff --git a/java/java-psi-api/src/com/intellij/psi/util/MethodSignatureUtil.java b/java/java-psi-api/src/com/intellij/psi/util/MethodSignatureUtil.java index 2d5510de7cea..a7c3ca7b3ffd 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/MethodSignatureUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/MethodSignatureUtil.java @@ -392,4 +392,36 @@ public class MethodSignatureUtil { } return true; } + + + /** + * 8.4.5 Method Result :: return type substitutable + */ + public static boolean isReturnTypeSubstitutable(MethodSignature d1, MethodSignature d2, PsiType r1, PsiType r2) { + //If R1 is void then R2 is void. + if (PsiType.VOID.equals(r1)) { + return PsiType.VOID.equals(r2); + } + + //If R1 is a primitive type then R2 is identical to R1. + if (r1 instanceof PsiPrimitiveType) { + return r1.equals(r2); + } + + if (r1 instanceof PsiClassType && r2 != null) { + + //R1, adapted to the type parameters of d2 (§8.4.4), is a subtype of R2. + final PsiSubstitutor adaptingSubstitutor = getSuperMethodSignatureSubstitutor(d1, d2); + if (adaptingSubstitutor != null && r2.isAssignableFrom(adaptingSubstitutor.substitute(r1))) { + return true; + } + + //d1 does not have the same signature as d2 (§8.4.2), and R1 = |R2|. + if (!areSignaturesEqual(d1, d2)) { + return r1.equals(TypeConversionUtil.erasure(r2)); + } + } + + return Comparing.equal(r1, r2); + } } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiSuperMethodImplUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiSuperMethodImplUtil.java index 639d62dcf3c2..a18e2c959ed3 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiSuperMethodImplUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiSuperMethodImplUtil.java @@ -31,6 +31,8 @@ import com.intellij.psi.util.*; import com.intellij.util.*; import com.intellij.util.containers.ConcurrentFactoryMap; import com.intellij.util.containers.FactoryMap; +import com.intellij.util.containers.hash.EqualityPolicy; +import com.intellij.util.containers.hash.LinkedHashMap; import gnu.trove.THashMap; import gnu.trove.THashSet; import gnu.trove.TObjectHashingStrategy; @@ -135,7 +137,37 @@ public class PsiSuperMethodImplUtil { boolean isInRawContext, GlobalSearchScope resolveScope) { ProgressManager.checkCanceled(); - Map result = new LinkedHashMap(); + Map result = new LinkedHashMap( + new EqualityPolicy() { + @Override + public int getHashCode(MethodSignature object) { + return object.hashCode(); + } + + @Override + public boolean isEqual(MethodSignature o1, MethodSignature o2) { + if (o1.equals(o2)) { + final PsiMethod method1 = ((MethodSignatureBackedByPsiMethod)o1).getMethod(); + final PsiType returnType1 = method1.getReturnType(); + final PsiMethod method2 = ((MethodSignatureBackedByPsiMethod)o2).getMethod(); + final PsiType returnType2 = method2.getReturnType(); + if (method1.hasModifierProperty(PsiModifier.STATIC) || method2.hasModifierProperty(PsiModifier.STATIC)) { + return true; + } + + if (MethodSignatureUtil.isReturnTypeSubstitutable(o1, o2, returnType1, returnType2)) { + return true; + } + + final PsiClass containingClass1 = method1.getContainingClass(); + final PsiClass containingClass2 = method2.getContainingClass(); + if (containingClass1 != null && containingClass2 != null) { + return containingClass1.isAnnotationType() || containingClass2.isAnnotationType(); + } + } + return false; + } + }); final Map> sameParameterErasureMethods = new THashMap>(MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY); Map map = new THashMap(new TObjectHashingStrategy() { @@ -443,4 +475,4 @@ public class PsiSuperMethodImplUtil { } return false; } -} +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/ReturnTypeSubstitutableForSameOverrideEquivalentMethods.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/ReturnTypeSubstitutableForSameOverrideEquivalentMethods.java new file mode 100644 index 000000000000..e5bb658da886 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/ReturnTypeSubstitutableForSameOverrideEquivalentMethods.java @@ -0,0 +1,8 @@ +interface I { + int foo(T x); + void foo(S x); +} + +class A implements I{ + public void foo(Throwable x) { } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/GenericsHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/GenericsHighlightingTest.java index c76685b7ff25..6ecf4443439b 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/GenericsHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/GenericsHighlightingTest.java @@ -573,4 +573,8 @@ public class GenericsHighlightingTest extends LightDaemonAnalyzerTestCase { configureFromFileText("Collections.java", StringUtil.convertLineSeparators(StringUtil.replace(text, "package java.util;", "package java.utilx; import java.util.*;"))); doTestConfiguredFile(false, false, null); } + + public void testReturnTypeSubstitutableForSameOverrideEquivalentMethods() throws Exception { + doTest(LanguageLevel.JDK_1_7, JavaSdkVersion.JDK_1_7, false); + } }