mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-05-06 05:10:22 +07:00
[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:
committed by
intellij-monorepo-bot
parent
b9afbd19e8
commit
61f7252105
@@ -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}''
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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}''
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user