[java-highlighting] same erasure problems migrated

Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 052d3bf8a26c376c7dfc5b1e370e583cf6236138
This commit is contained in:
Tagir Valeev
2025-02-12 13:02:11 +01:00
committed by intellij-monorepo-bot
parent b9afbd19e8
commit 61f7252105
15 changed files with 241 additions and 394 deletions

View File

@@ -217,6 +217,9 @@ method.inheritance.clash.incompatible.return.types={0}; attempting to use incomp
method.inheritance.clash.does.not.throw={0}; overridden method does not throw ''{1}''
method.no.parameter.list=Parameter list expected
method.missing.return.type=Invalid method declaration; return type required
method.generic.same.erasure={0}; both methods have same erasure
method.generic.same.erasure.override={0}; both methods have same erasure, yet neither overrides the other
method.generic.same.erasure.hide={0}; both methods have same erasure, yet neither hides the other
clash.methods.message=''{0}'' clashes with ''{1}''
clash.methods.message.show.classes=''{0}'' in ''{2}'' clashes with ''{1}'' in ''{3}''

View File

@@ -2,6 +2,7 @@
package com.intellij.java.codeserver.highlighting;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
@@ -24,10 +25,13 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static java.util.Objects.*;
import static java.util.Objects.requireNonNull;
final class GenericsChecker {
private final @NotNull JavaErrorVisitor myVisitor;
private final Set<PsiClass> myOverrideEquivalentMethodsVisitedClasses = new HashSet<>();
// stored "clashing signatures" errors for the method (if the key is a PsiModifierList of the method), or the class (if the key is a PsiModifierList of the class)
private final Map<PsiMember, JavaCompilationError<PsiMember, ?>> myOverrideEquivalentMethodsErrors = new HashMap<>();
GenericsChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; }
@@ -773,4 +777,153 @@ final class GenericsChecker {
}
}
}
Map<PsiMember, JavaCompilationError<PsiMember, ?>> computeOverrideEquivalentMethodErrors(@NotNull PsiClass aClass) {
if (myOverrideEquivalentMethodsVisitedClasses.add(aClass)) {
Collection<HierarchicalMethodSignature> signaturesWithSupers = aClass.getVisibleSignatures();
PsiManager manager = aClass.getManager();
Map<MethodSignature, MethodSignatureBackedByPsiMethod> sameErasureMethods =
MethodSignatureUtil.createErasedMethodSignatureMap();
Set<MethodSignature> foundProblems = MethodSignatureUtil.createErasedMethodSignatureSet();
for (HierarchicalMethodSignature signature : signaturesWithSupers) {
JavaCompilationError<PsiMember, ?> error =
checkSameErasureNotSubSignatureInner(signature, manager, aClass, sameErasureMethods);
if (error != null && foundProblems.add(signature)) {
myOverrideEquivalentMethodsErrors.put(error.psi(), error);
}
if (aClass instanceof PsiTypeParameter) {
error = MethodChecker.getMethodIncompatibleReturnType(aClass, signature, signature.getSuperSignatures());
if (error != null) {
myOverrideEquivalentMethodsErrors.put(aClass, error);
}
}
}
}
return myOverrideEquivalentMethodsErrors;
}
private static JavaCompilationError<PsiMember, ?> checkSameErasureNotSubSignatureInner(
@NotNull HierarchicalMethodSignature signature,
@NotNull PsiManager manager,
@NotNull PsiClass aClass,
@NotNull Map<MethodSignature, MethodSignatureBackedByPsiMethod> sameErasureMethods) {
PsiMethod method = signature.getMethod();
JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
if (!facade.getResolveHelper().isAccessible(method, aClass, null)) return null;
MethodSignature signatureToErase = method.getSignature(PsiSubstitutor.EMPTY);
MethodSignatureBackedByPsiMethod sameErasure = sameErasureMethods.get(signatureToErase);
if (sameErasure == null) {
sameErasureMethods.put(signatureToErase, signature);
}
else if (aClass instanceof PsiTypeParameter ||
MethodSignatureUtil.findMethodBySuperMethod(aClass, sameErasure.getMethod(), false) != null ||
!(InheritanceUtil.isInheritorOrSelf(sameErasure.getMethod().getContainingClass(), method.getContainingClass(), true) ||
InheritanceUtil.isInheritorOrSelf(method.getContainingClass(), sameErasure.getMethod().getContainingClass(), true))) {
JavaCompilationError<PsiMember, ?> error = checkSameErasureNotSubSignatureOrSameClass(sameErasure, signature, aClass, method);
if (error != null) return error;
}
List<HierarchicalMethodSignature> supers = signature.getSuperSignatures();
for (HierarchicalMethodSignature superSignature : supers) {
JavaCompilationError<PsiMember, ?> error =
checkSameErasureNotSubSignatureInner(superSignature, manager, aClass, sameErasureMethods);
if (error != null) return error;
if (superSignature.isRaw() && !signature.isRaw()) {
PsiType[] parameterTypes = signature.getParameterTypes();
PsiType[] erasedTypes = superSignature.getErasedParameterTypes();
for (int i = 0; i < erasedTypes.length; i++) {
if (!Comparing.equal(parameterTypes[i], erasedTypes[i])) {
return getSameErasureMessage(method, superSignature.getMethod(), aClass);
}
}
}
}
return null;
}
private static JavaCompilationError<PsiMember, ?> checkSameErasureNotSubSignatureOrSameClass(@NotNull MethodSignatureBackedByPsiMethod signatureToCheck,
@NotNull HierarchicalMethodSignature superSignature,
@NotNull PsiClass aClass,
@NotNull PsiMethod superMethod) {
PsiMethod checkMethod = signatureToCheck.getMethod();
if (superMethod.equals(checkMethod)) return null;
PsiClass checkContainingClass = requireNonNull(checkMethod.getContainingClass());
PsiClass superContainingClass = superMethod.getContainingClass();
boolean checkEqualsSuper = checkContainingClass.equals(superContainingClass);
if (checkMethod.isConstructor()) {
if (!superMethod.isConstructor() || !checkEqualsSuper) return null;
}
else if (superMethod.isConstructor()) return null;
JavaVersionService javaVersionService = JavaVersionService.getInstance();
boolean atLeast17 = javaVersionService.isAtLeast(aClass, JavaSdkVersion.JDK_1_7);
if (checkMethod.hasModifierProperty(PsiModifier.STATIC) && !checkEqualsSuper && !atLeast17) {
return null;
}
if (superMethod.hasModifierProperty(PsiModifier.STATIC) && superContainingClass != null &&
superContainingClass.isInterface() && !checkEqualsSuper &&
PsiUtil.isAvailable(JavaFeature.STATIC_INTERFACE_CALLS, superContainingClass)) {
return null;
}
PsiType retErasure1 = TypeConversionUtil.erasure(checkMethod.getReturnType());
PsiType retErasure2 = TypeConversionUtil.erasure(superMethod.getReturnType());
boolean differentReturnTypeErasure = !Comparing.equal(retErasure1, retErasure2);
if (checkEqualsSuper && atLeast17 && retErasure1 != null && retErasure2 != null) {
differentReturnTypeErasure = !TypeConversionUtil.isAssignable(retErasure1, retErasure2);
}
if (differentReturnTypeErasure &&
!TypeConversionUtil.isVoidType(retErasure1) &&
!TypeConversionUtil.isVoidType(retErasure2) &&
!(checkEqualsSuper && Arrays.equals(superSignature.getParameterTypes(), signatureToCheck.getParameterTypes())) &&
!atLeast17) {
int idx = 0;
PsiType[] erasedTypes = signatureToCheck.getErasedParameterTypes();
boolean erasure = erasedTypes.length > 0;
for (PsiType type : superSignature.getParameterTypes()) {
erasure &= Comparing.equal(type, erasedTypes[idx]);
idx++;
}
if (!erasure) return null;
}
if (!checkEqualsSuper && MethodSignatureUtil.isSubsignature(superSignature, signatureToCheck)) {
return null;
}
if (!javaVersionService.isCompilerVersionAtLeast(aClass, JavaSdkVersion.JDK_1_7)) {
//javac <= 1.6 didn't check transitive overriding rules for interfaces
if (superContainingClass != null &&
!superContainingClass.isInterface() &&
checkContainingClass.isInterface() &&
!aClass.equals(superContainingClass)) {
return null;
}
}
PsiMember anchor = aClass.equals(checkContainingClass) ? checkMethod : aClass;
return getSameErasureMessage(checkMethod, superMethod, anchor);
}
private static JavaCompilationError<PsiMember, ?> getSameErasureMessage(@NotNull PsiMethod method,
@NotNull PsiMethod superMethod,
@NotNull PsiMember anchor) {
return JavaErrorKinds.METHOD_GENERIC_CLASH.create(anchor, new JavaErrorKinds.OverrideClashContext(method, superMethod));
}
void checkTypeParameterOverrideEquivalentMethods(@NotNull PsiClass typeParameter) {
if (typeParameter instanceof PsiTypeParameter && myVisitor.languageLevel().isAtLeast(LanguageLevel.JDK_1_7)) {
PsiReferenceList extendsList = typeParameter.getExtendsList();
if (extendsList.getReferenceElements().length > 1) {
//todo suppress erased methods which come from the same class
var error = computeOverrideEquivalentMethodErrors(typeParameter).get(typeParameter);
if (error != null) {
myVisitor.report(error);
}
}
}
}
}

View File

@@ -461,6 +461,10 @@ final class JavaErrorVisitor extends JavaElementVisitor {
}
}
}
if (!hasErrorResults() && aClass != null) {
var error = myGenericsChecker.computeOverrideEquivalentMethodErrors(aClass).get(method);
if (error != null) report(error);
}
}
else if (parent instanceof PsiClass aClass) {
if (!hasErrorResults()) myClassChecker.checkDuplicateNestedClass(aClass);
@@ -478,6 +482,10 @@ final class JavaErrorVisitor extends JavaElementVisitor {
}
if (!hasErrorResults()) myClassChecker.checkCyclicInheritance(aClass);
if (!hasErrorResults()) myMethodChecker.checkOverrideEquivalentInheritedMethods(aClass);
if (!hasErrorResults()) {
var error = myGenericsChecker.computeOverrideEquivalentMethodErrors(aClass).get(aClass);
if (error != null) report(error);
}
}
}
@@ -743,6 +751,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults()) myGenericsChecker.checkInterfaceMultipleInheritance(aClass);
if (!hasErrorResults()) myGenericsChecker.checkClassSupersAccessibility(aClass);
if (!hasErrorResults()) myRecordChecker.checkRecordHeader(aClass);
if (!hasErrorResults()) myGenericsChecker.checkTypeParameterOverrideEquivalentMethods(aClass);
}
@Override
@@ -1016,6 +1025,10 @@ final class JavaErrorVisitor extends JavaElementVisitor {
}
if (parent instanceof PsiAnonymousClass psiAnonymousClass && ref.equals(psiAnonymousClass.getBaseClassReference())) {
if (!hasErrorResults()) myGenericsChecker.checkGenericCannotExtendException(psiAnonymousClass);
if (!hasErrorResults()) {
var error = myGenericsChecker.computeOverrideEquivalentMethodErrors(psiAnonymousClass).get(psiAnonymousClass);
if (error != null) report(error);
}
}
if (!hasErrorResults() && resolved instanceof PsiClass psiClass) myExpressionChecker.checkRestrictedIdentifierReference(ref, psiClass);
if (!hasErrorResults()) myExpressionChecker.checkMemberReferencedBeforeConstructorCalled(ref, resolved);

View File

@@ -3,6 +3,7 @@ package com.intellij.java.codeserver.highlighting;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext;
import com.intellij.openapi.project.Project;
@@ -284,10 +285,20 @@ final class MethodChecker {
void checkMethodIncompatibleReturnType(@NotNull PsiMember anchor, @NotNull MethodSignatureBackedByPsiMethod methodSignature,
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures) {
JavaCompilationError<?, ?> error = getMethodIncompatibleReturnType(anchor, methodSignature, superMethodSignatures);
if (error != null) {
myVisitor.report(error);
}
}
@Nullable
static JavaCompilationError<PsiMember, ?> getMethodIncompatibleReturnType(@NotNull PsiMember anchor,
@NotNull MethodSignatureBackedByPsiMethod methodSignature,
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures) {
PsiMethod method = methodSignature.getMethod();
PsiType returnType = methodSignature.getSubstitutor().substitute(method.getReturnType());
PsiClass aClass = method.getContainingClass();
if (aClass == null) return;
if (aClass == null) return null;
for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) {
PsiMethod superMethod = superMethodSignature.getMethod();
PsiType declaredReturnType = superMethod.getReturnType();
@@ -296,24 +307,27 @@ final class MethodChecker {
if (returnType == null || superReturnType == null || method == superMethod) continue;
PsiClass superClass = superMethod.getContainingClass();
if (superClass == null) continue;
checkSuperMethodSignature(anchor, superMethod, superMethodSignature, superReturnType, method, methodSignature, returnType);
JavaCompilationError<PsiMember, ?> error =
getSuperMethodSignatureError(anchor, superMethod, superMethodSignature, superReturnType, method, methodSignature, returnType);
if (error != null) return error;
}
return null;
}
private void checkSuperMethodSignature(@NotNull PsiMember anchor,
@NotNull PsiMethod superMethod,
@NotNull MethodSignatureBackedByPsiMethod superMethodSignature,
@NotNull PsiType superReturnType,
@NotNull PsiMethod method,
@NotNull MethodSignatureBackedByPsiMethod methodSignature,
@NotNull PsiType returnType) {
private static @Nullable JavaCompilationError<PsiMember, ?> getSuperMethodSignatureError(@NotNull PsiMember anchor,
@NotNull PsiMethod superMethod,
@NotNull MethodSignatureBackedByPsiMethod superMethodSignature,
@NotNull PsiType superReturnType,
@NotNull PsiMethod method,
@NotNull MethodSignatureBackedByPsiMethod methodSignature,
@NotNull PsiType returnType) {
PsiClass superContainingClass = superMethod.getContainingClass();
if (superContainingClass != null &&
CommonClassNames.JAVA_LANG_OBJECT.equals(superContainingClass.getQualifiedName()) &&
!superMethod.hasModifierProperty(PsiModifier.PUBLIC)) {
PsiClass containingClass = method.getContainingClass();
if (containingClass != null && containingClass.isInterface() && !superContainingClass.isInterface()) {
return;
return null;
}
}
@@ -332,17 +346,17 @@ final class MethodChecker {
substitutedSuperReturnType = TypeConversionUtil.erasure(superMethodSignature.getSubstitutor().substitute(superReturnType));
}
if (returnType.equals(substitutedSuperReturnType)) return;
if (returnType.equals(substitutedSuperReturnType)) return null;
if (!(returnType instanceof PsiPrimitiveType) && substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType) {
if (hasGenerics && LambdaUtil.performWithSubstitutedParameterBounds(
methodSignature.getTypeParameters(), methodSignature.getSubstitutor(),
() -> TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType))) {
return;
return null;
}
}
myVisitor.report(JavaErrorKinds.METHOD_INHERITANCE_CLASH_INCOMPATIBLE_RETURN_TYPES.create(
anchor, new JavaErrorKinds.IncompatibleOverrideReturnTypeContext(method, returnType, superMethod, substitutedSuperReturnType)));
return JavaErrorKinds.METHOD_INHERITANCE_CLASH_INCOMPATIBLE_RETURN_TYPES.create(
anchor, new JavaErrorKinds.IncompatibleOverrideReturnTypeContext(method, returnType, superMethod, substitutedSuperReturnType));
}
private void checkInterfaceInheritedMethodsReturnTypes(@NotNull PsiClass aClass,

View File

@@ -21,15 +21,13 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
final class JavaErrorFormatUtil {
static @NotNull @NlsContexts.DetailedDescription String formatClashMethodMessage(@NotNull PsiMethod method1, @NotNull PsiMethod method2, boolean showContainingClasses) {
if (showContainingClasses) {
PsiClass class1 = method1.getContainingClass();
PsiClass class2 = method2.getContainingClass();
if (class1 != null && class2 != null) {
return JavaCompilationErrorBundle.message("clash.methods.message.show.classes",
formatMethod(method1), formatMethod(method2),
formatClass(class1), formatClass(class2));
}
static @NotNull @NlsContexts.DetailedDescription String formatClashMethodMessage(@NotNull PsiMethod method1, @NotNull PsiMethod method2) {
PsiClass class1 = method1.getContainingClass();
PsiClass class2 = method2.getContainingClass();
if (class1 != null && class2 != null && !class1.isEquivalentTo(class2)) {
return JavaCompilationErrorBundle.message("clash.methods.message.show.classes",
formatMethod(method1), formatMethod(method2),
formatClass(class1), formatClass(class2));
}
return JavaCompilationErrorBundle.message("clash.methods.message", formatMethod(method1), formatMethod(method2));
}

View File

@@ -24,10 +24,7 @@ import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.*;
import com.intellij.refactoring.util.RefactoringChangeUtil;
import com.intellij.util.VisibilityUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import org.jetbrains.annotations.*;
import java.util.Collection;
import java.util.List;
@@ -659,14 +656,25 @@ public final class JavaErrorKinds {
})
.withRawDescription((psi, ctx) -> message(
"method.inheritance.weaker.privileges",
formatClashMethodMessage(ctx.method(), ctx.superMethod(), true),
ctx.clashMessage(),
VisibilityUtil.toPresentableText(PsiUtil.getAccessModifier(PsiUtil.getAccessLevel(ctx.method().getModifierList()))),
VisibilityUtil.toPresentableText(PsiUtil.getAccessModifier(PsiUtil.getAccessLevel(ctx.superMethod().getModifierList())))));
public static final Parameterized<PsiMember, OverrideClashContext> METHOD_GENERIC_CLASH =
parameterized(PsiMember.class, OverrideClashContext.class, "method.generic.same.erasure")
.withRange((member, ctx) -> getMemberDeclarationTextRange(member))
.withRawDescription((member, ctx) -> {
@NonNls String key = ctx.sameClass() ? "method.generic.same.erasure" :
ctx.method().hasModifierProperty(PsiModifier.STATIC) ?
"method.generic.same.erasure.hide" :
"method.generic.same.erasure.override";
return message(key, ctx.clashMessage());
});
public static final Parameterized<PsiClass, @NotNull OverrideClashContext> METHOD_INHERITANCE_CLASH_UNRELATED_RETURN_TYPES =
parameterized(PsiClass.class, OverrideClashContext.class, "method.inheritance.clash.unrelated.return.types")
.withRange((cls, ctx) -> getClassDeclarationTextRange(cls))
.withRawDescription((cls, ctx) -> message("method.inheritance.clash.unrelated.return.types",
formatClashMethodMessage(ctx.superMethod(), ctx.method(), true)));
formatClashMethodMessage(ctx.superMethod(), ctx.method())));
public static final Parameterized<PsiMember, @NotNull IncompatibleOverrideReturnTypeContext>
METHOD_INHERITANCE_CLASH_INCOMPATIBLE_RETURN_TYPES =
parameterized(PsiMember.class, IncompatibleOverrideReturnTypeContext.class, "method.inheritance.clash.incompatible.return.types")
@@ -686,7 +694,7 @@ public final class JavaErrorKinds {
return getMemberDeclarationTextRange(psi);
})
.withRawDescription((cls, ctx) -> message("method.inheritance.clash.incompatible.return.types",
formatClashMethodMessage(ctx.method(), ctx.superMethod(), true)));
formatClashMethodMessage(ctx.method(), ctx.superMethod())));
public static final Parameterized<PsiMember, @NotNull IncompatibleOverrideExceptionContext>
METHOD_INHERITANCE_CLASH_DOES_NOT_THROW =
parameterized(PsiMember.class, IncompatibleOverrideExceptionContext.class, "method.inheritance.clash.does.not.throw")
@@ -694,7 +702,7 @@ public final class JavaErrorKinds {
ctx.exceptionReference() != null ? ctx.exceptionReference().getTextRange().shiftLeft(psi.getTextRange().getStartOffset()) :
getMemberDeclarationTextRange(psi))
.withRawDescription((cls, ctx) -> message("method.inheritance.clash.does.not.throw",
formatClashMethodMessage(ctx.method(), ctx.superMethod(), true),
formatClashMethodMessage(ctx.method(), ctx.superMethod()),
formatType(ctx.exceptionType())));
public static final Parameterized<PsiMethod, String> METHOD_MISSING_RETURN_TYPE =
parameterized(PsiMethod.class, String.class, "method.missing.return.type")
@@ -1481,6 +1489,15 @@ public final class JavaErrorKinds {
}
public record OverrideClashContext(@NotNull PsiMethod method, @NotNull PsiMethod superMethod) {
boolean sameClass() {
PsiClass cls1 = method.getContainingClass();
PsiClass cls2 = superMethod.getContainingClass();
return cls1 != null && cls2 != null && cls1.isEquivalentTo(cls2);
}
@NotNull @Nls String clashMessage() {
return formatClashMethodMessage(method, superMethod);
}
}
public record InheritTypeClashContext(@NotNull PsiClass superClass, @Nullable PsiType type1, @Nullable PsiType type2) {}

View File

@@ -9,30 +9,20 @@ import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.JavaVersionService;
import com.intellij.openapi.roots.FileIndexFacade;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiClassImplUtil;
import com.intellij.psi.impl.light.LightRecordMethod;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.*;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.MessageFormat;
import java.util.*;
import java.util.function.Consumer;
public final class GenericsHighlightUtil {
private static final Logger LOG = Logger.getInstance(GenericsHighlightUtil.class);
@@ -134,34 +124,6 @@ public final class GenericsHighlightUtil {
return expectedType;
}
static void computeOverrideEquivalentMethodErrors(@NotNull PsiClass aClass,
@NotNull Set<? super PsiClass> overrideEquivalentMethodsVisitedClasses,
@NotNull Map<PsiMember, HighlightInfo.Builder> overrideEquivalentMethodsErrors) {
if (overrideEquivalentMethodsVisitedClasses.add(aClass)) {
Collection<HierarchicalMethodSignature> signaturesWithSupers = aClass.getVisibleSignatures();
PsiManager manager = aClass.getManager();
Map<MethodSignature, MethodSignatureBackedByPsiMethod> sameErasureMethods =
MethodSignatureUtil.createErasedMethodSignatureMap();
Set<MethodSignature> foundProblems = MethodSignatureUtil.createErasedMethodSignatureSet();
for (HierarchicalMethodSignature signature : signaturesWithSupers) {
Pair<PsiMember, HighlightInfo.Builder> pair = checkSameErasureNotSubSignatureInner(signature, manager, aClass, sameErasureMethods);
if (pair != null && foundProblems.add(signature)) {
overrideEquivalentMethodsErrors.put(pair.getFirst(), pair.getSecond());
}
if (aClass instanceof PsiTypeParameter) {
HighlightInfo.Builder info =
checkMethodIncompatibleReturnType(signature, signature.getSuperSignatures(), true,
HighlightNamesUtil.getClassDeclarationTextRange(aClass)
);
if (info != null) {
overrideEquivalentMethodsErrors.put(aClass, info);
}
}
}
}
}
/**
* @param skipMethodInSelf pass false to check if method in {@code aClass} can be deleted
*
@@ -288,129 +250,6 @@ public final class GenericsHighlightUtil {
return null;
}
private static Pair<PsiMember, HighlightInfo.Builder> checkSameErasureNotSubSignatureInner(@NotNull HierarchicalMethodSignature signature,
@NotNull PsiManager manager,
@NotNull PsiClass aClass,
@NotNull Map<MethodSignature, MethodSignatureBackedByPsiMethod> sameErasureMethods) {
PsiMethod method = signature.getMethod();
JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
if (!facade.getResolveHelper().isAccessible(method, aClass, null)) return null;
MethodSignature signatureToErase = method.getSignature(PsiSubstitutor.EMPTY);
MethodSignatureBackedByPsiMethod sameErasure = sameErasureMethods.get(signatureToErase);
if (sameErasure == null) {
sameErasureMethods.put(signatureToErase, signature);
}
else if (aClass instanceof PsiTypeParameter ||
MethodSignatureUtil.findMethodBySuperMethod(aClass, sameErasure.getMethod(), false) != null ||
!(InheritanceUtil.isInheritorOrSelf(sameErasure.getMethod().getContainingClass(), method.getContainingClass(), true) ||
InheritanceUtil.isInheritorOrSelf(method.getContainingClass(), sameErasure.getMethod().getContainingClass(), true))) {
Pair<PsiMember, HighlightInfo.Builder> pair = checkSameErasureNotSubSignatureOrSameClass(sameErasure, signature, aClass, method);
if (pair != null) return pair;
}
List<HierarchicalMethodSignature> supers = signature.getSuperSignatures();
for (HierarchicalMethodSignature superSignature : supers) {
Pair<PsiMember, HighlightInfo.Builder> pair = checkSameErasureNotSubSignatureInner(superSignature, manager, aClass, sameErasureMethods);
if (pair != null) return pair;
if (superSignature.isRaw() && !signature.isRaw()) {
PsiType[] parameterTypes = signature.getParameterTypes();
PsiType[] erasedTypes = superSignature.getErasedParameterTypes();
for (int i = 0; i < erasedTypes.length; i++) {
if (!Comparing.equal(parameterTypes[i], erasedTypes[i])) {
return Pair.create(aClass, getSameErasureMessage(false, method, superSignature.getMethod(),
HighlightNamesUtil.getClassDeclarationTextRange(aClass)));
}
}
}
}
return null;
}
private static Pair<PsiMember, HighlightInfo.Builder> checkSameErasureNotSubSignatureOrSameClass(@NotNull MethodSignatureBackedByPsiMethod signatureToCheck,
@NotNull HierarchicalMethodSignature superSignature,
@NotNull PsiClass aClass,
@NotNull PsiMethod superMethod) {
PsiMethod checkMethod = signatureToCheck.getMethod();
if (superMethod.equals(checkMethod)) return null;
PsiClass checkContainingClass = checkMethod.getContainingClass();
LOG.assertTrue(checkContainingClass != null);
PsiClass superContainingClass = superMethod.getContainingClass();
boolean checkEqualsSuper = checkContainingClass.equals(superContainingClass);
if (checkMethod.isConstructor()) {
if (!superMethod.isConstructor() || !checkEqualsSuper) return null;
}
else if (superMethod.isConstructor()) return null;
JavaVersionService javaVersionService = JavaVersionService.getInstance();
boolean atLeast17 = javaVersionService.isAtLeast(aClass, JavaSdkVersion.JDK_1_7);
if (checkMethod.hasModifierProperty(PsiModifier.STATIC) && !checkEqualsSuper && !atLeast17) {
return null;
}
if (superMethod.hasModifierProperty(PsiModifier.STATIC) && superContainingClass != null &&
superContainingClass.isInterface() && !checkEqualsSuper &&
PsiUtil.isAvailable(JavaFeature.STATIC_INTERFACE_CALLS, superContainingClass)) {
return null;
}
PsiType retErasure1 = TypeConversionUtil.erasure(checkMethod.getReturnType());
PsiType retErasure2 = TypeConversionUtil.erasure(superMethod.getReturnType());
boolean differentReturnTypeErasure = !Comparing.equal(retErasure1, retErasure2);
if (checkEqualsSuper && atLeast17 && retErasure1 != null && retErasure2 != null) {
differentReturnTypeErasure = !TypeConversionUtil.isAssignable(retErasure1, retErasure2);
}
if (differentReturnTypeErasure &&
!TypeConversionUtil.isVoidType(retErasure1) &&
!TypeConversionUtil.isVoidType(retErasure2) &&
!(checkEqualsSuper && Arrays.equals(superSignature.getParameterTypes(), signatureToCheck.getParameterTypes())) &&
!atLeast17) {
int idx = 0;
PsiType[] erasedTypes = signatureToCheck.getErasedParameterTypes();
boolean erasure = erasedTypes.length > 0;
for (PsiType type : superSignature.getParameterTypes()) {
erasure &= Comparing.equal(type, erasedTypes[idx]);
idx++;
}
if (!erasure) return null;
}
if (!checkEqualsSuper && MethodSignatureUtil.isSubsignature(superSignature, signatureToCheck)) {
return null;
}
if (!javaVersionService.isCompilerVersionAtLeast(aClass, JavaSdkVersion.JDK_1_7)) {
//javac <= 1.6 didn't check transitive overriding rules for interfaces
if (superContainingClass != null && !superContainingClass.isInterface() && checkContainingClass.isInterface() && !aClass.equals(superContainingClass)) return null;
}
if (aClass.equals(checkContainingClass)) {
boolean sameClass = aClass.equals(superContainingClass);
return Pair.create(checkMethod, getSameErasureMessage(sameClass, checkMethod, superMethod, HighlightNamesUtil.getMethodDeclarationTextRange(checkMethod)));
}
else {
return Pair.create(aClass, getSameErasureMessage(false, checkMethod, superMethod, HighlightNamesUtil.getClassDeclarationTextRange(aClass)));
}
}
private static HighlightInfo.Builder getSameErasureMessage(boolean sameClass, @NotNull PsiMethod method, @NotNull PsiMethod superMethod,
@NotNull TextRange textRange) {
@NonNls String key = sameClass ? "generics.methods.have.same.erasure" :
method.hasModifierProperty(PsiModifier.STATIC) ?
"generics.methods.have.same.erasure.hide" :
"generics.methods.have.same.erasure.override";
String description = JavaErrorBundle.message(key, createClashMethodMessage(method, superMethod, !sameClass));
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description);
if (!(method instanceof SyntheticElement)) {
IntentionAction action = QuickFixFactory.getInstance().createSameErasureButDifferentMethodsFix(method, superMethod);
info.registerFix(action, null, null, null, null);
}
return info;
}
static HighlightInfo.Builder checkMemberSignatureTypesAccessibility(@NotNull PsiReferenceExpression ref) {
String message = null;
@@ -507,151 +346,4 @@ public final class GenericsHighlightUtil {
return null;
}
static void checkTypeParameterOverrideEquivalentMethods(@NotNull PsiClass typeParameter, @NotNull LanguageLevel level,
@NotNull Consumer<? super HighlightInfo.Builder> errorSink,
@NotNull Set<? super PsiClass> overrideEquivalentMethodsVisitedClasses,
@NotNull Map<PsiMember, HighlightInfo.Builder> overrideEquivalentMethodsErrors) {
if (typeParameter instanceof PsiTypeParameter && level.isAtLeast(LanguageLevel.JDK_1_7)) {
PsiReferenceList extendsList = typeParameter.getExtendsList();
if (extendsList.getReferenceElements().length > 1) {
//todo suppress erased methods which come from the same class
computeOverrideEquivalentMethodErrors(typeParameter, overrideEquivalentMethodsVisitedClasses, overrideEquivalentMethodsErrors);
errorSink.accept(overrideEquivalentMethodsErrors.get(typeParameter));
}
}
}
private static @NotNull @NlsContexts.DetailedDescription String createClashMethodMessage(@NotNull PsiMethod method1,
@NotNull PsiMethod method2,
boolean showContainingClasses) {
if (showContainingClasses) {
PsiClass class1 = method1.getContainingClass();
PsiClass class2 = method2.getContainingClass();
if (class1 != null && class2 != null) {
return JavaErrorBundle.message("clash.methods.message.show.classes",
JavaHighlightUtil.formatMethod(method1),
JavaHighlightUtil.formatMethod(method2),
HighlightUtil.formatClass(class1),
HighlightUtil.formatClass(class2));
}
}
return JavaErrorBundle.message("clash.methods.message",
JavaHighlightUtil.formatMethod(method1),
JavaHighlightUtil.formatMethod(method2));
}
private static HighlightInfo.Builder checkMethodIncompatibleReturnType(@NotNull MethodSignatureBackedByPsiMethod methodSignature,
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures,
boolean includeRealPositionInfo,
@Nullable TextRange textRange) {
PsiMethod method = methodSignature.getMethod();
PsiType returnType = methodSignature.getSubstitutor().substitute(method.getReturnType());
PsiClass aClass = method.getContainingClass();
if (aClass == null) return null;
for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) {
PsiMethod superMethod = superMethodSignature.getMethod();
PsiType declaredReturnType = superMethod.getReturnType();
PsiType superReturnType = declaredReturnType;
if (superMethodSignature.isRaw()) superReturnType = TypeConversionUtil.erasure(declaredReturnType);
if (returnType == null || superReturnType == null || method == superMethod) continue;
PsiClass superClass = superMethod.getContainingClass();
if (superClass == null) continue;
if (textRange == null && includeRealPositionInfo) {
PsiTypeElement typeElement = method.getReturnTypeElement();
if (typeElement != null) {
textRange = typeElement.getTextRange();
}
}
if (textRange == null) {
textRange = TextRange.EMPTY_RANGE;
}
HighlightInfo.Builder info = checkSuperMethodSignature(
superMethod, superMethodSignature, superReturnType, method, methodSignature, returnType,
textRange, PsiUtil.getLanguageLevel(aClass));
if (info != null) {
return info;
}
}
return null;
}
private static HighlightInfo.Builder checkSuperMethodSignature(@NotNull PsiMethod superMethod,
@NotNull MethodSignatureBackedByPsiMethod superMethodSignature,
@NotNull PsiType superReturnType,
@NotNull PsiMethod method,
@NotNull MethodSignatureBackedByPsiMethod methodSignature,
@NotNull PsiType returnType,
@NotNull TextRange range,
@NotNull LanguageLevel languageLevel) {
PsiClass superContainingClass = superMethod.getContainingClass();
if (superContainingClass != null &&
CommonClassNames.JAVA_LANG_OBJECT.equals(superContainingClass.getQualifiedName()) &&
!superMethod.hasModifierProperty(PsiModifier.PUBLIC)) {
PsiClass containingClass = method.getContainingClass();
if (containingClass != null && containingClass.isInterface() && !superContainingClass.isInterface()) {
return null;
}
}
PsiType substitutedSuperReturnType;
boolean hasGenerics = JavaFeature.GENERICS.isSufficient(languageLevel);
if (hasGenerics && !superMethodSignature.isRaw() && superMethodSignature.equals(methodSignature)) { //see 8.4.5
PsiSubstitutor unifyingSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature,
superMethodSignature);
substitutedSuperReturnType = unifyingSubstitutor == null
? superReturnType
: unifyingSubstitutor.substitute(superReturnType);
}
else {
substitutedSuperReturnType = TypeConversionUtil.erasure(superMethodSignature.getSubstitutor().substitute(superReturnType));
}
if (returnType.equals(substitutedSuperReturnType)) return null;
if (!(returnType instanceof PsiPrimitiveType) && substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType) {
if (hasGenerics && LambdaUtil.performWithSubstitutedParameterBounds(methodSignature.getTypeParameters(),
methodSignature.getSubstitutor(),
() -> TypeConversionUtil.isAssignable(substitutedSuperReturnType,
returnType))) {
return null;
}
}
return createIncompatibleReturnTypeMessage(method, superMethod, substitutedSuperReturnType, returnType,
JavaErrorBundle.message("incompatible.return.type"), range
);
}
private static HighlightInfo.@NotNull Builder createIncompatibleReturnTypeMessage(@NotNull PsiMethod method,
@NotNull PsiMethod superMethod,
@NotNull PsiType substitutedSuperReturnType,
@NotNull PsiType returnType,
@NotNull @Nls String detailMessage,
@NotNull TextRange textRange) {
String description = MessageFormat.format("{0}; {1}", createClashMethodMessage(method, superMethod, true), detailMessage);
HighlightInfo.Builder errorResult =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description);
if (method instanceof LightRecordMethod recordMethod) {
for (IntentionAction fix :
HighlightFixUtil.getChangeVariableTypeFixes(recordMethod.getRecordComponent(), substitutedSuperReturnType)) {
errorResult.registerFix(fix, null, null, null, null);
}
}
else {
IntentionAction action = QuickFixFactory.getInstance().createMethodReturnFix(method, substitutedSuperReturnType, false);
errorResult.registerFix(action, null, null, null, null);
}
IntentionAction action1 = QuickFixFactory.getInstance().createSuperMethodReturnFix(superMethod, returnType);
errorResult.registerFix(action1, null, null, null, null);
PsiClass returnClass = PsiUtil.resolveClassInClassTypeOnly(returnType);
if (returnClass != null && substitutedSuperReturnType instanceof PsiClassType) {
IntentionAction action =
QuickFixFactory.getInstance().createChangeParameterClassFix(returnClass, (PsiClassType)substitutedSuperReturnType);
errorResult.registerFix(action, null, null, null, null);
}
return errorResult;
}
}

View File

@@ -38,10 +38,7 @@ import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.*;
@@ -64,9 +61,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
private final @NotNull Consumer<? super HighlightInfo.Builder> myErrorSink = builder -> add(builder);
private final Set<PsiClass> myOverrideEquivalentMethodsVisitedClasses = new HashSet<>();
// stored "clashing signatures" errors for the method (if the key is a PsiModifierList of the method), or the class (if the key is a PsiModifierList of the class)
private final Map<PsiMember, HighlightInfo.Builder> myOverrideEquivalentMethodsErrors = new HashMap<>();
private boolean myHasError; // true if myHolder.add() was called with HighlightInfo of >=ERROR severity. On each .visit(PsiElement) call this flag is reset. Useful to determine whether the error was already reported while visiting this PsiElement.
protected HighlightVisitorImpl() {
@@ -130,8 +124,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
myFile = null;
myHolder = null;
myCollector = null;
myOverrideEquivalentMethodsVisitedClasses.clear();
myOverrideEquivalentMethodsErrors.clear();
}
return true;
@@ -227,13 +219,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
}
@Override
public void visitClass(@NotNull PsiClass aClass) {
super.visitClass(aClass);
if (aClass instanceof PsiSyntheticClass) return;
if (!hasErrorResults()) GenericsHighlightUtil.checkTypeParameterOverrideEquivalentMethods(aClass, myLanguageLevel, myErrorSink, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
}
@Override
public void visitIdentifier(@NotNull PsiIdentifier identifier) {
PsiElement parent = identifier.getParent();
@@ -261,39 +246,11 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!hasErrorResults()) add(ModuleHighlightUtil.checkModuleReference(statement));
}
@Override
public void visitModifierList(@NotNull PsiModifierList list) {
super.visitModifierList(list);
PsiElement parent = list.getParent();
if (parent instanceof PsiMethod method) {
PsiClass aClass = method.getContainingClass();
if (!hasErrorResults() && aClass != null) {
GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(aClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
myErrorSink.accept(myOverrideEquivalentMethodsErrors.get(method));
}
}
else if (parent instanceof PsiClass aClass) {
if (!hasErrorResults()) {
GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(aClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
myErrorSink.accept(myOverrideEquivalentMethodsErrors.get(aClass));
}
}
}
private JavaResolveResult doVisitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) {
private void doVisitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) {
JavaResolveResult result = resolveOptimised(ref, myFile);
if (result == null) return null;
PsiElement parent = ref.getParent();
add(HighlightUtil.checkReference(ref, result));
if (parent instanceof PsiAnonymousClass psiAnonymousClass && ref.equals(psiAnonymousClass.getBaseClassReference())) {
GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(psiAnonymousClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
myErrorSink.accept(myOverrideEquivalentMethodsErrors.get(psiAnonymousClass));
if (result != null) {
add(HighlightUtil.checkReference(ref, result));
}
return result;
}
static @Nullable JavaResolveResult resolveOptimised(@NotNull PsiJavaCodeReferenceElement ref, @NotNull PsiFile containingFile) {

View File

@@ -285,6 +285,9 @@ final class JavaErrorFixProvider {
sink.accept(addModifierFix(aClass, PsiModifier.ABSTRACT));
}
});
fix(METHOD_GENERIC_CLASH, error ->
error.context().method() instanceof SyntheticElement ?
null : myFactory.createSameErasureButDifferentMethodsFix(error.context().method(), error.context().superMethod()));
}
private void createExceptionFixes() {

View File

@@ -7,9 +7,6 @@ annotation.unknown.method=Cannot find @interface method ''{0}()''
annotation.not.applicable=''@{0}'' not applicable to {1}
annotation.annotation.type.expected=Annotation type expected
generics.wrong.number.of.type.arguments=Wrong number of type arguments: {0}; required: {1}
generics.methods.have.same.erasure={0}; both methods have same erasure
generics.methods.have.same.erasure.override={0}; both methods have same erasure, yet neither overrides the other
generics.methods.have.same.erasure.hide={0}; both methods have same erasure, yet neither hides the other
generics.type.argument.cannot.be.of.primitive.type=Type argument cannot be of primitive type
generics.unchecked.assignment=Unchecked assignment: ''{0}'' to ''{1}''
generics.unchecked.cast=Unchecked cast: ''{0}'' to ''{1}''

View File

@@ -6,7 +6,7 @@ class A {
}
interface I {
<error descr="'with(Class<T>)' clashes with 'with(Class<Long>)'; both methods have same erasure"><T> T with(Class<T> aClass)</error>;
<error descr="'with(Class<T>)' clashes with 'with(Class<Long>)'; both methods have same erasure"><T> T with(Class<T> aClass);</error>
long with(Class<Long> aClass);
}
}

View File

@@ -52,7 +52,7 @@ class Test {
}*/
abstract class D<T extends Throwable & Runnable> {
<error descr="'foo(T, D<? extends Runnable>)' clashes with 'foo(T, D<? extends Throwable>)'; both methods have same erasure">abstract <T extends Serializable & Comparable<?>> void foo(T x, D<? extends Runnable> y)</error>;
<error descr="'foo(T, D<? extends Runnable>)' clashes with 'foo(T, D<? extends Throwable>)'; both methods have same erasure">abstract <T extends Serializable & Comparable<?>> void foo(T x, D<? extends Runnable> y);</error>
abstract <T extends Serializable & Comparable<?>> void foo(T x, D<? extends Throwable> y);
}

View File

@@ -7,7 +7,7 @@ interface Matcher<T> {
}
interface ArgumentConstraintPhrases {
<error descr="'with(Matcher<T>)' clashes with 'with(Matcher<Boolean>)'; both methods have same erasure"><T> T with(Matcher<T> matcher)</error>;
<error descr="'with(Matcher<T>)' clashes with 'with(Matcher<Boolean>)'; both methods have same erasure"><T> T with(Matcher<T> matcher);</error>
boolean with(Matcher<Boolean> matcher);
byte with(Matcher<Byte> matcher);
short with(Matcher<Short> matcher);

View File

@@ -52,7 +52,7 @@ class Test {
}*/
abstract class D<T extends Throwable & Runnable> {
<error descr="'foo(T, D<? extends Runnable>)' clashes with 'foo(T, D<? extends Throwable>)'; both methods have same erasure">abstract <T extends Serializable & Comparable<?>> void foo(T x, D<? extends Runnable> y)</error>;
<error descr="'foo(T, D<? extends Runnable>)' clashes with 'foo(T, D<? extends Throwable>)'; both methods have same erasure">abstract <T extends Serializable & Comparable<?>> void foo(T x, D<? extends Runnable> y);</error>
abstract <T extends Serializable & Comparable<?>> void foo(T x, D<? extends Throwable> y);
}

View File

@@ -7,7 +7,7 @@ interface Matcher<T> {
}
interface ArgumentConstraintPhrases {
<error descr="'with(Matcher<T>)' clashes with 'with(Matcher<Boolean>)'; both methods have same erasure"><T> T with(Matcher<T> matcher)</error>;
<error descr="'with(Matcher<T>)' clashes with 'with(Matcher<Boolean>)'; both methods have same erasure"><T> T with(Matcher<T> matcher);</error>
boolean with(Matcher<Boolean> matcher);
byte with(Matcher<Byte> matcher);
short with(Matcher<Short> matcher);