mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] Overridden method problems -> MethodChecker
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 2d04cceca2e5984f725590f69edc2ef2641619b0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
bff363ddcb
commit
811dafd743
@@ -136,6 +136,9 @@ method.instance.overrides.static=Instance method ''{0}'' in ''{1}'' cannot overr
|
||||
method.static.overrides.instance=Static method ''{0}'' in ''{1}'' cannot override instance method ''{2}'' in ''{3}''
|
||||
method.overrides.final=''{0}'' cannot override ''{1}'' in ''{2}''; overridden method is final
|
||||
method.inheritance.weaker.privileges={0}; attempting to assign weaker access privileges (''{1}''); was ''{2}''
|
||||
method.inheritance.clash.unrelated.return.types={0}; methods have unrelated return types
|
||||
method.inheritance.clash.incompatible.return.types={0}; attempting to use incompatible return type
|
||||
method.inheritance.clash.does.not.throw={0}; overridden method does not throw ''{1}''
|
||||
clash.methods.message=''{0}'' clashes with ''{1}''
|
||||
clash.methods.message.show.classes=''{0}'' in ''{2}'' clashes with ''{1}'' in ''{3}''
|
||||
|
||||
|
||||
+18
@@ -14,6 +14,7 @@ import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
|
||||
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl;
|
||||
import com.intellij.psi.infos.MethodCandidateInfo;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
@@ -21,6 +22,7 @@ import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -214,6 +216,21 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
if (!hasErrorResults()) myMethodChecker.checkMethodCanHaveBody(method);
|
||||
if (!hasErrorResults()) myMethodChecker.checkMethodMustHaveBody(method);
|
||||
if (!hasErrorResults()) myMethodChecker.checkStaticMethodOverride(method);
|
||||
MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY);
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if (!method.isConstructor()) {
|
||||
List<HierarchicalMethodSignature> superMethodSignatures = method.getHierarchicalMethodSignature().getSuperSignatures();
|
||||
if (!superMethodSignatures.isEmpty()) {
|
||||
if (!method.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
if (!hasErrorResults()) myMethodChecker.checkMethodWeakerPrivileges(method, methodSignature, superMethodSignatures);
|
||||
if (!hasErrorResults()) myMethodChecker.checkMethodOverridesFinal(methodSignature, superMethodSignatures);
|
||||
}
|
||||
if (!hasErrorResults()) myMethodChecker.checkMethodIncompatibleReturnType(method, methodSignature, superMethodSignatures);
|
||||
if (aClass != null && !hasErrorResults()) {
|
||||
myMethodChecker.checkMethodIncompatibleThrows(method, methodSignature, superMethodSignatures, aClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (parent instanceof PsiClass aClass) {
|
||||
if (!hasErrorResults()) myClassChecker.checkDuplicateNestedClass(aClass);
|
||||
@@ -230,6 +247,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
//myErrorSink.accept(myOverrideEquivalentMethodsErrors.get(aClass));
|
||||
}
|
||||
if (!hasErrorResults()) myClassChecker.checkCyclicInheritance(aClass);
|
||||
if (!hasErrorResults()) myMethodChecker.checkOverrideEquivalentInheritedMethods(aClass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+269
-7
@@ -1,10 +1,13 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
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.JavaErrorKinds;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.IncompleteModelUtil;
|
||||
import com.intellij.psi.impl.PsiSuperMethodImplUtil;
|
||||
@@ -12,10 +15,9 @@ import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.containers.MostlySingularMultiMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
final class MethodChecker {
|
||||
private final @NotNull JavaErrorVisitor myVisitor;
|
||||
@@ -137,13 +139,13 @@ final class MethodChecker {
|
||||
boolean isSuperMethodStatic = superModifierList.hasModifierProperty(PsiModifier.STATIC);
|
||||
if (isMethodStatic != isSuperMethodStatic) {
|
||||
var errorKind = isMethodStatic ? JavaErrorKinds.METHOD_STATIC_OVERRIDES_INSTANCE : JavaErrorKinds.METHOD_INSTANCE_OVERRIDES_STATIC;
|
||||
myVisitor.report(errorKind.create(method, superMethod));
|
||||
myVisitor.report(errorKind.create(method, new JavaErrorKinds.OverrideClashContext(method, superMethod)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMethodStatic) {
|
||||
if (superClass.isInterface()) return;
|
||||
checkIsWeaker(method, superMethod);
|
||||
checkIsWeaker(method, method, superMethod);
|
||||
if (!myVisitor.hasErrorResults()) checkSuperMethodIsFinal(method, superMethod);
|
||||
}
|
||||
}
|
||||
@@ -155,15 +157,275 @@ final class MethodChecker {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIsWeaker(@NotNull PsiMethod method, @NotNull PsiMethod superMethod) {
|
||||
private void checkIsWeaker(@NotNull PsiMember anchor, @NotNull PsiMethod method, @NotNull PsiMethod superMethod) {
|
||||
PsiModifierList modifierList = method.getModifierList();
|
||||
int accessLevel = PsiUtil.getAccessLevel(modifierList);
|
||||
int superAccessLevel = PsiUtil.getAccessLevel(superMethod.getModifierList());
|
||||
if (accessLevel < superAccessLevel) {
|
||||
myVisitor.report(JavaErrorKinds.METHOD_INHERITANCE_WEAKER_PRIVILEGES.create(method, superMethod));
|
||||
myVisitor.report(JavaErrorKinds.METHOD_INHERITANCE_WEAKER_PRIVILEGES.create(
|
||||
anchor, new JavaErrorKinds.OverrideClashContext(method, superMethod)));
|
||||
}
|
||||
}
|
||||
|
||||
void checkOverrideEquivalentInheritedMethods(@NotNull PsiClass aClass) {
|
||||
Collection<HierarchicalMethodSignature> visibleSignatures = aClass.getVisibleSignatures();
|
||||
if (aClass.getImplementsListTypes().length == 0 && aClass.getExtendsListTypes().length == 0) {
|
||||
// optimization: do not analyze unrelated methods from Object: in case of no inheritance they can't conflict
|
||||
return;
|
||||
}
|
||||
PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper();
|
||||
|
||||
for (HierarchicalMethodSignature signature : visibleSignatures) {
|
||||
PsiMethod method = signature.getMethod();
|
||||
if (!resolveHelper.isAccessible(method, aClass, null)) continue;
|
||||
List<HierarchicalMethodSignature> superSignatures = signature.getSuperSignatures();
|
||||
|
||||
boolean allAbstracts = method.hasModifierProperty(PsiModifier.ABSTRACT);
|
||||
PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null || aClass.equals(containingClass)) continue; //to be checked at method level
|
||||
|
||||
if (aClass.isInterface() && !containingClass.isInterface()) continue;
|
||||
if (allAbstracts) {
|
||||
superSignatures = new ArrayList<>(superSignatures);
|
||||
superSignatures.add(0, signature);
|
||||
checkInterfaceInheritedMethodsReturnTypes(aClass, superSignatures);
|
||||
}
|
||||
else {
|
||||
checkMethodIncompatibleReturnType(aClass, signature, superSignatures);
|
||||
}
|
||||
|
||||
if (method.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
//jsl 8, chapter 9.4.1
|
||||
//chapter 8.4.8.2 speaks about a class that "declares or inherits a static method",
|
||||
// at the same time the rule from chapter 9.4.1 speaks only about an interface that "declares a static method"
|
||||
//There is no point to add java version check, because static methods in interfaces are allowed from java 8 too.
|
||||
(!aClass.isInterface() ||
|
||||
aClass.getManager().areElementsEquivalent(aClass, method.getContainingClass()))) {
|
||||
for (HierarchicalMethodSignature superSignature : superSignatures) {
|
||||
PsiMethod superMethod = superSignature.getMethod();
|
||||
if (!superMethod.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
myVisitor.report(JavaErrorKinds.METHOD_STATIC_OVERRIDES_INSTANCE.create(
|
||||
aClass, new JavaErrorKinds.OverrideClashContext(method, superMethod)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!myVisitor.hasErrorResults()) {
|
||||
checkMethodIncompatibleThrows(aClass, signature, superSignatures, aClass);
|
||||
}
|
||||
|
||||
if (!myVisitor.hasErrorResults()) {
|
||||
checkMethodWeakerPrivileges(aClass, signature, superSignatures);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkMethodWeakerPrivileges(@NotNull PsiMember anchor,
|
||||
@NotNull MethodSignatureBackedByPsiMethod methodSignature,
|
||||
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures) {
|
||||
PsiMethod method = methodSignature.getMethod();
|
||||
PsiModifierList modifierList = method.getModifierList();
|
||||
if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) return;
|
||||
for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) {
|
||||
PsiMethod superMethod = superMethodSignature.getMethod();
|
||||
if (method.hasModifierProperty(PsiModifier.ABSTRACT) && !MethodSignatureUtil.isSuperMethod(superMethod, method)) continue;
|
||||
if (!PsiUtil.isAccessible(myVisitor.project(), superMethod, method, null)) continue;
|
||||
if (anchor instanceof PsiClass && MethodSignatureUtil.isSuperMethod(superMethod, method)) continue;
|
||||
checkIsWeaker(anchor, method, superMethod);
|
||||
}
|
||||
}
|
||||
|
||||
void checkMethodIncompatibleThrows(@NotNull PsiMember anchor,
|
||||
@NotNull MethodSignatureBackedByPsiMethod methodSignature,
|
||||
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures,
|
||||
@NotNull PsiClass analyzedClass) {
|
||||
PsiMethod method = methodSignature.getMethod();
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null) return;
|
||||
PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(aClass, analyzedClass, PsiSubstitutor.EMPTY);
|
||||
PsiClassType[] exceptions = method.getThrowsList().getReferencedTypes();
|
||||
PsiJavaCodeReferenceElement[] referenceElements = anchor == method ? method.getThrowsList().getReferenceElements() : null;
|
||||
List<PsiJavaCodeReferenceElement> exceptionContexts = new ArrayList<>();
|
||||
List<PsiClassType> checkedExceptions = new ArrayList<>();
|
||||
for (int i = 0; i < exceptions.length; i++) {
|
||||
PsiClassType exception = exceptions[i];
|
||||
if (!ExceptionUtil.isUncheckedException(exception)) {
|
||||
checkedExceptions.add(exception);
|
||||
if (referenceElements != null && i < referenceElements.length) {
|
||||
PsiJavaCodeReferenceElement exceptionRef = referenceElements[i];
|
||||
exceptionContexts.add(exceptionRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) {
|
||||
PsiMethod superMethod = superMethodSignature.getMethod();
|
||||
int index = getExtraExceptionNum(methodSignature, superMethodSignature, checkedExceptions, superSubstitutor);
|
||||
if (index != -1) {
|
||||
if (aClass.isInterface()) {
|
||||
PsiClass superContainingClass = superMethod.getContainingClass();
|
||||
if (superContainingClass != null && !superContainingClass.isInterface()) continue;
|
||||
if (superContainingClass != null && !aClass.isInheritor(superContainingClass, true)) continue;
|
||||
}
|
||||
PsiClassType exception = checkedExceptions.get(index);
|
||||
myVisitor.report(JavaErrorKinds.METHOD_INHERITANCE_CLASH_DOES_NOT_THROW.create(
|
||||
anchor,
|
||||
new JavaErrorKinds.IncompatibleOverrideExceptionContext(method, superMethod, exception,
|
||||
exceptionContexts.isEmpty() ? null : exceptionContexts.get(index))));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkMethodIncompatibleReturnType(@NotNull PsiMember anchorClass, @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;
|
||||
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;
|
||||
checkSuperMethodSignature(
|
||||
anchorClass, superMethod, superMethodSignature, superReturnType, method, methodSignature, returnType);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSuperMethodSignature(@NotNull PsiMember anchorClass,
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
PsiType substitutedSuperReturnType;
|
||||
// Important: we should use the language level of the file where the method is declared,
|
||||
// not the language level of the current file, so myVisitor.isApplicable() doesn't work here.
|
||||
boolean hasGenerics = PsiUtil.isAvailable(JavaFeature.GENERICS, method);
|
||||
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;
|
||||
if (!(returnType instanceof PsiPrimitiveType) && substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType) {
|
||||
if (hasGenerics && LambdaUtil.performWithSubstitutedParameterBounds(
|
||||
methodSignature.getTypeParameters(), methodSignature.getSubstitutor(),
|
||||
() -> TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
myVisitor.report(JavaErrorKinds.METHOD_INHERITANCE_CLASH_INCOMPATIBLE_RETURN_TYPES.create(
|
||||
anchorClass, new JavaErrorKinds.IncompatibleOverrideReturnTypeContext(method, returnType, superMethod, substitutedSuperReturnType)));
|
||||
}
|
||||
|
||||
private void checkInterfaceInheritedMethodsReturnTypes(@NotNull PsiClass aClass,
|
||||
@NotNull List<HierarchicalMethodSignature> superMethodSignatures) {
|
||||
if (superMethodSignatures.size() < 2) return;
|
||||
MethodSignatureBackedByPsiMethod[] returnTypeSubstitutable = {superMethodSignatures.get(0)};
|
||||
for (int i = 1; i < superMethodSignatures.size(); i++) {
|
||||
PsiMethod currentMethod = returnTypeSubstitutable[0].getMethod();
|
||||
PsiType currentType = returnTypeSubstitutable[0].getSubstitutor().substitute(currentMethod.getReturnType());
|
||||
|
||||
MethodSignatureBackedByPsiMethod otherSuperSignature = superMethodSignatures.get(i);
|
||||
PsiMethod otherSuperMethod = otherSuperSignature.getMethod();
|
||||
PsiSubstitutor otherSubstitutor = otherSuperSignature.getSubstitutor();
|
||||
PsiType otherSuperReturnType = otherSubstitutor.substitute(otherSuperMethod.getReturnType());
|
||||
PsiSubstitutor unifyingSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(returnTypeSubstitutable[0],
|
||||
otherSuperSignature);
|
||||
if (unifyingSubstitutor != null) {
|
||||
otherSuperReturnType = unifyingSubstitutor.substitute(otherSuperReturnType);
|
||||
currentType = unifyingSubstitutor.substitute(currentType);
|
||||
}
|
||||
|
||||
if (otherSuperReturnType == null || currentType == null || otherSuperReturnType.equals(currentType)) continue;
|
||||
PsiType otherReturnType = otherSuperReturnType;
|
||||
PsiType curType = currentType;
|
||||
LambdaUtil.performWithSubstitutedParameterBounds(otherSuperMethod.getTypeParameters(), otherSubstitutor, () -> {
|
||||
if (myVisitor.languageLevel().isAtLeast(LanguageLevel.JDK_1_5)) {
|
||||
//http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.8 Example 8.1.5-3
|
||||
if (!(otherReturnType instanceof PsiPrimitiveType || curType instanceof PsiPrimitiveType)) {
|
||||
if (otherReturnType.isAssignableFrom(curType)) return null;
|
||||
if (curType.isAssignableFrom(otherReturnType)) {
|
||||
returnTypeSubstitutable[0] = otherSuperSignature;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (otherSuperMethod.getTypeParameters().length > 0 && JavaGenericsUtil.isRawToGeneric(otherReturnType, curType)) return null;
|
||||
}
|
||||
myVisitor.report(JavaErrorKinds.METHOD_INHERITANCE_CLASH_UNRELATED_RETURN_TYPES
|
||||
.create(aClass, new JavaErrorKinds.OverrideClashContext(currentMethod, otherSuperMethod)));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void checkMethodOverridesFinal(@NotNull MethodSignatureBackedByPsiMethod methodSignature,
|
||||
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures) {
|
||||
PsiMethod method = methodSignature.getMethod();
|
||||
for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) {
|
||||
PsiMethod superMethod = superMethodSignature.getMethod();
|
||||
checkSuperMethodIsFinal(method, superMethod);
|
||||
if (myVisitor.hasErrorResults()) return;
|
||||
}
|
||||
}
|
||||
|
||||
// return number of exception which was not declared in super method or -1
|
||||
private static int getExtraExceptionNum(@NotNull MethodSignature methodSignature,
|
||||
@NotNull MethodSignatureBackedByPsiMethod superSignature,
|
||||
@NotNull List<? extends PsiClassType> checkedExceptions,
|
||||
@NotNull PsiSubstitutor substitutorForDerivedClass) {
|
||||
PsiMethod superMethod = superSignature.getMethod();
|
||||
PsiSubstitutor substitutorForMethod = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature, superSignature);
|
||||
for (int i = 0; i < checkedExceptions.size(); i++) {
|
||||
PsiClassType checkedEx = checkedExceptions.get(i);
|
||||
PsiType substituted =
|
||||
substitutorForMethod == null ? TypeConversionUtil.erasure(checkedEx) : substitutorForMethod.substitute(checkedEx);
|
||||
PsiType exception = substitutorForDerivedClass.substitute(substituted);
|
||||
if (!isMethodThrows(superMethod, substitutorForMethod, exception, substitutorForDerivedClass)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static boolean isMethodThrows(@NotNull PsiMethod method,
|
||||
@Nullable PsiSubstitutor substitutorForMethod,
|
||||
@NotNull PsiType exception,
|
||||
@NotNull PsiSubstitutor substitutorForDerivedClass) {
|
||||
PsiClassType[] thrownExceptions = method.getThrowsList().getReferencedTypes();
|
||||
for (PsiClassType thrownException1 : thrownExceptions) {
|
||||
PsiType thrownException =
|
||||
substitutorForMethod != null ? substitutorForMethod.substitute(thrownException1) : TypeConversionUtil.erasure(thrownException1);
|
||||
thrownException = substitutorForDerivedClass.substitute(thrownException);
|
||||
if (TypeConversionUtil.isAssignable(thrownException, exception)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isEnumSyntheticMethod(@NotNull MethodSignature methodSignature, @NotNull Project project) {
|
||||
if (methodSignature.equals(ourValuesEnumSyntheticMethod)) return true;
|
||||
PsiType javaLangString = PsiType.getJavaLangString(PsiManager.getInstance(project), GlobalSearchScope.allScope(project));
|
||||
|
||||
+78
-33
@@ -372,22 +372,22 @@ public final class JavaErrorKinds {
|
||||
error(PsiMethod.class, "method.should.have.body").withRange(JavaErrorFormatUtil::getMethodDeclarationTextRange);
|
||||
public static final Simple<PsiMethod> METHOD_SHOULD_HAVE_BODY_OR_ABSTRACT =
|
||||
error(PsiMethod.class, "method.should.have.body.or.abstract").withRange(JavaErrorFormatUtil::getMethodDeclarationTextRange);
|
||||
public static final Parameterized<PsiMethod, PsiMethod> METHOD_STATIC_OVERRIDES_INSTANCE =
|
||||
parameterized(PsiMethod.class, PsiMethod.class, "method.static.overrides.instance")
|
||||
.withRange((method, superMethod) -> getMethodDeclarationTextRange(method))
|
||||
.withRawDescription((method, superMethod) -> message("method.static.overrides.instance",
|
||||
formatMethod(method),
|
||||
formatClass(requireNonNull(method.getContainingClass())),
|
||||
formatMethod(superMethod),
|
||||
formatClass(requireNonNull(superMethod.getContainingClass()))));
|
||||
public static final Parameterized<PsiMethod, PsiMethod> METHOD_INSTANCE_OVERRIDES_STATIC =
|
||||
parameterized(PsiMethod.class, PsiMethod.class, "method.instance.overrides.static")
|
||||
.withRange((method, superMethod) -> getMethodDeclarationTextRange(method))
|
||||
.withRawDescription((method, superMethod) -> message("method.instance.overrides.static",
|
||||
formatMethod(method),
|
||||
formatClass(requireNonNull(method.getContainingClass())),
|
||||
formatMethod(superMethod),
|
||||
formatClass(requireNonNull(superMethod.getContainingClass()))));
|
||||
public static final Parameterized<PsiMember, @NotNull OverrideClashContext> METHOD_STATIC_OVERRIDES_INSTANCE =
|
||||
parameterized(PsiMember.class, OverrideClashContext.class, "method.static.overrides.instance")
|
||||
.withRange((member, ctx) -> getMemberDeclarationTextRange(member))
|
||||
.withRawDescription((method, ctx) -> message("method.static.overrides.instance",
|
||||
formatMethod(ctx.method()),
|
||||
formatClass(requireNonNull(ctx.method().getContainingClass())),
|
||||
formatMethod(ctx.superMethod()),
|
||||
formatClass(requireNonNull(ctx.superMethod().getContainingClass()))));
|
||||
public static final Parameterized<PsiMember, @NotNull OverrideClashContext> METHOD_INSTANCE_OVERRIDES_STATIC =
|
||||
parameterized(PsiMember.class, OverrideClashContext.class, "method.instance.overrides.static")
|
||||
.withRange((method, ctx) -> getMemberDeclarationTextRange(method))
|
||||
.withRawDescription((method, ctx) -> message("method.instance.overrides.static",
|
||||
formatMethod(ctx.method()),
|
||||
formatClass(requireNonNull(ctx.method().getContainingClass())),
|
||||
formatMethod(ctx.superMethod()),
|
||||
formatClass(requireNonNull(ctx.superMethod().getContainingClass()))));
|
||||
public static final Parameterized<PsiMethod, PsiMethod> METHOD_OVERRIDES_FINAL =
|
||||
parameterized(PsiMethod.class, PsiMethod.class, "method.overrides.final")
|
||||
.withRange((method, superMethod) -> getMethodDeclarationTextRange(method))
|
||||
@@ -398,26 +398,56 @@ public final class JavaErrorKinds {
|
||||
formatMethod(superMethod),
|
||||
superClass != null ? formatClass(superClass) : "<unknown>");
|
||||
});
|
||||
public static final Parameterized<PsiMethod, PsiMethod> METHOD_INHERITANCE_WEAKER_PRIVILEGES =
|
||||
parameterized(PsiMethod.class, PsiMethod.class, "method.inheritance.weaker.privileges")
|
||||
.withRange((method, superMethod) -> {
|
||||
PsiModifierList modifierList = method.getModifierList();
|
||||
PsiElement keyword = PsiUtil.findModifierInList(modifierList, PsiUtil.getAccessModifier(PsiUtil.getAccessLevel(modifierList)));
|
||||
if (keyword != null) {
|
||||
return keyword.getTextRange().shiftLeft(method.getTextRange().getStartOffset());
|
||||
public static final Parameterized<PsiMember, @NotNull OverrideClashContext> METHOD_INHERITANCE_WEAKER_PRIVILEGES =
|
||||
parameterized(PsiMember.class, OverrideClashContext.class, "method.inheritance.weaker.privileges")
|
||||
.withRange((psi, ctx) -> {
|
||||
if (psi instanceof PsiMethod method) {
|
||||
PsiModifierList modifierList = method.getModifierList();
|
||||
PsiElement keyword = PsiUtil.findModifierInList(modifierList, PsiUtil.getAccessModifier(PsiUtil.getAccessLevel(modifierList)));
|
||||
if (keyword != null) {
|
||||
return keyword.getTextRange().shiftLeft(method.getTextRange().getStartOffset());
|
||||
}
|
||||
// in case of package-private or some crazy third-party plugin where some access modifier implied even if it's absent
|
||||
PsiIdentifier identifier = method.getNameIdentifier();
|
||||
if (identifier != null) {
|
||||
return identifier.getTextRangeInParent();
|
||||
}
|
||||
}
|
||||
// in case of package-private or some crazy third-party plugin where some access modifier implied even if it's absent
|
||||
PsiIdentifier identifier = method.getNameIdentifier();
|
||||
if (identifier != null) {
|
||||
return identifier.getTextRangeInParent();
|
||||
}
|
||||
return getMethodDeclarationTextRange(method);
|
||||
return getMemberDeclarationTextRange(psi);
|
||||
})
|
||||
.withRawDescription((method, superMethod) -> message(
|
||||
.withRawDescription((psi, ctx) -> message(
|
||||
"method.inheritance.weaker.privileges",
|
||||
formatClashMethodMessage(method, superMethod, true),
|
||||
VisibilityUtil.toPresentableText(PsiUtil.getAccessModifier(PsiUtil.getAccessLevel(method.getModifierList()))),
|
||||
VisibilityUtil.toPresentableText(PsiUtil.getAccessModifier(PsiUtil.getAccessLevel(superMethod.getModifierList())))));
|
||||
formatClashMethodMessage(ctx.method(), ctx.superMethod(), true),
|
||||
VisibilityUtil.toPresentableText(PsiUtil.getAccessModifier(PsiUtil.getAccessLevel(ctx.method().getModifierList()))),
|
||||
VisibilityUtil.toPresentableText(PsiUtil.getAccessModifier(PsiUtil.getAccessLevel(ctx.superMethod().getModifierList())))));
|
||||
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)));
|
||||
public static final Parameterized<PsiMember, @NotNull IncompatibleOverrideReturnTypeContext>
|
||||
METHOD_INHERITANCE_CLASH_INCOMPATIBLE_RETURN_TYPES =
|
||||
parameterized(PsiMember.class, IncompatibleOverrideReturnTypeContext.class, "method.inheritance.clash.incompatible.return.types")
|
||||
.withRange((psi, ctx) -> {
|
||||
if (psi instanceof PsiMethod method) {
|
||||
PsiTypeElement returnTypeElement = method.getReturnTypeElement();
|
||||
if (returnTypeElement != null) {
|
||||
return returnTypeElement.getTextRangeInParent();
|
||||
}
|
||||
}
|
||||
return getMemberDeclarationTextRange(psi);
|
||||
})
|
||||
.withRawDescription((cls, ctx) -> message("method.inheritance.clash.incompatible.return.types",
|
||||
formatClashMethodMessage(ctx.method(), ctx.superMethod(), true)));
|
||||
public static final Parameterized<PsiMember, @NotNull IncompatibleOverrideExceptionContext>
|
||||
METHOD_INHERITANCE_CLASH_DOES_NOT_THROW =
|
||||
parameterized(PsiMember.class, IncompatibleOverrideExceptionContext.class, "method.inheritance.clash.does.not.throw")
|
||||
.withRange((psi, ctx) ->
|
||||
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),
|
||||
formatType(ctx.exceptionType())));
|
||||
|
||||
public static final Parameterized<PsiMember, AmbiguousImplicitConstructorCallContext> CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL =
|
||||
parameterized(PsiMember.class, AmbiguousImplicitConstructorCallContext.class, "constructor.ambiguous.implicit.call")
|
||||
@@ -554,5 +584,20 @@ public final class JavaErrorKinds {
|
||||
return message("constructor.ambiguous.implicit.call", m1, m2);
|
||||
}
|
||||
}
|
||||
|
||||
public record OverrideClashContext(@NotNull PsiMethod method, @NotNull PsiMethod superMethod) {
|
||||
}
|
||||
|
||||
public record IncompatibleOverrideReturnTypeContext(@NotNull PsiMethod method,
|
||||
@NotNull PsiType methodReturnType,
|
||||
@NotNull PsiMethod superMethod,
|
||||
@NotNull PsiType superMethodReturnType) {
|
||||
}
|
||||
|
||||
public record IncompatibleOverrideExceptionContext(@NotNull PsiMethod method,
|
||||
@NotNull PsiMethod superMethod,
|
||||
@NotNull PsiClassType exceptionType,
|
||||
@Nullable PsiJavaCodeReferenceElement exceptionReference) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-328
@@ -16,7 +16,6 @@ import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.lang.jvm.JvmModifier;
|
||||
import com.intellij.lang.jvm.actions.JvmElementActionFactories;
|
||||
import com.intellij.lang.jvm.actions.MemberRequestsKt;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsUtil;
|
||||
import com.intellij.openapi.project.IndexNotReadyException;
|
||||
@@ -57,7 +56,6 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class HighlightMethodUtil {
|
||||
private static final Logger LOG = Logger.getInstance(HighlightMethodUtil.class);
|
||||
|
||||
private HighlightMethodUtil() { }
|
||||
|
||||
@@ -79,70 +77,6 @@ public final class HighlightMethodUtil {
|
||||
JavaHighlightUtil.formatMethod(method2));
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkMethodWeakerPrivileges(@NotNull MethodSignatureBackedByPsiMethod methodSignature,
|
||||
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures,
|
||||
boolean includeRealPositionInfo,
|
||||
@NotNull PsiFile containingFile, @Nullable Ref<? super String> description) {
|
||||
PsiMethod method = methodSignature.getMethod();
|
||||
PsiModifierList modifierList = method.getModifierList();
|
||||
if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) return null;
|
||||
for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) {
|
||||
PsiMethod superMethod = superMethodSignature.getMethod();
|
||||
if (method.hasModifierProperty(PsiModifier.ABSTRACT) && !MethodSignatureUtil.isSuperMethod(superMethod, method)) continue;
|
||||
if (!PsiUtil.isAccessible(containingFile.getProject(), superMethod, method, null)) continue;
|
||||
if (!includeRealPositionInfo && MethodSignatureUtil.isSuperMethod(superMethod, method)) continue;
|
||||
HighlightInfo.Builder info = isWeaker(method, superMethod, includeRealPositionInfo, description);
|
||||
if (info != null) return info;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HighlightInfo.Builder isWeaker(@NotNull PsiMethod method,
|
||||
@NotNull PsiMethod superMethod,
|
||||
boolean includeRealPositionInfo, @Nullable Ref<? super String> descriptionH) {
|
||||
PsiModifierList modifierList = method.getModifierList();
|
||||
int accessLevel = PsiUtil.getAccessLevel(modifierList);
|
||||
String accessModifier = PsiUtil.getAccessModifier(accessLevel);
|
||||
int superAccessLevel = PsiUtil.getAccessLevel(superMethod.getModifierList());
|
||||
if (accessLevel < superAccessLevel) {
|
||||
String description = JavaErrorBundle.message("weaker.privileges",
|
||||
createClashMethodMessage(method, superMethod, true),
|
||||
VisibilityUtil.toPresentableText(accessModifier),
|
||||
PsiUtil.getAccessModifier(superAccessLevel));
|
||||
if (descriptionH != null) {
|
||||
descriptionH.set(description);
|
||||
}
|
||||
TextRange textRange = TextRange.EMPTY_RANGE;
|
||||
if (includeRealPositionInfo) {
|
||||
PsiElement keyword = PsiUtil.findModifierInList(modifierList, accessModifier);
|
||||
if (keyword != null) {
|
||||
textRange = keyword.getTextRange();
|
||||
}
|
||||
else {
|
||||
// in case of package-private or some crazy third-party plugin where some access modifier implied even if it's absent
|
||||
PsiIdentifier identifier = method.getNameIdentifier();
|
||||
if (identifier != null) {
|
||||
textRange = identifier.getTextRange();
|
||||
}
|
||||
}
|
||||
}
|
||||
HighlightInfo.Builder info =
|
||||
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description);
|
||||
IntentionAction action = QuickFixFactory.getInstance().createChangeModifierFix();
|
||||
info.registerFix(action, null, null, null, null);
|
||||
return info;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static HighlightInfo.Builder checkMethodIncompatibleReturnType(@NotNull MethodSignatureBackedByPsiMethod methodSignature,
|
||||
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures,
|
||||
boolean includeRealPositionInfo,
|
||||
@Nullable Ref<? super String> description) {
|
||||
return checkMethodIncompatibleReturnType(methodSignature, superMethodSignatures, includeRealPositionInfo, null, description);
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkMethodIncompatibleReturnType(@NotNull MethodSignatureBackedByPsiMethod methodSignature,
|
||||
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures,
|
||||
@@ -171,7 +105,7 @@ public final class HighlightMethodUtil {
|
||||
}
|
||||
HighlightInfo.Builder info = checkSuperMethodSignature(
|
||||
superMethod, superMethodSignature, superReturnType, method, methodSignature, returnType,
|
||||
JavaErrorBundle.message("incompatible.return.type"), textRange, PsiUtil.getLanguageLevel(aClass), description);
|
||||
textRange, PsiUtil.getLanguageLevel(aClass), description);
|
||||
if (info != null) {
|
||||
return info;
|
||||
}
|
||||
@@ -186,7 +120,6 @@ public final class HighlightMethodUtil {
|
||||
@NotNull PsiMethod method,
|
||||
@NotNull MethodSignatureBackedByPsiMethod methodSignature,
|
||||
@NotNull PsiType returnType,
|
||||
@NotNull @Nls String detailMessage,
|
||||
@NotNull TextRange range,
|
||||
@NotNull LanguageLevel languageLevel,
|
||||
@Nullable Ref<? super String> description) {
|
||||
@@ -222,7 +155,8 @@ public final class HighlightMethodUtil {
|
||||
}
|
||||
}
|
||||
|
||||
return createIncompatibleReturnTypeMessage(method, superMethod, substitutedSuperReturnType, returnType, detailMessage, range,
|
||||
return createIncompatibleReturnTypeMessage(method, superMethod, substitutedSuperReturnType, returnType,
|
||||
JavaErrorBundle.message("incompatible.return.type"), range,
|
||||
description);
|
||||
}
|
||||
|
||||
@@ -261,133 +195,6 @@ public final class HighlightMethodUtil {
|
||||
}
|
||||
|
||||
|
||||
static HighlightInfo.Builder checkMethodOverridesFinal(@NotNull MethodSignatureBackedByPsiMethod methodSignature,
|
||||
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures) {
|
||||
PsiMethod method = methodSignature.getMethod();
|
||||
for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) {
|
||||
PsiMethod superMethod = superMethodSignature.getMethod();
|
||||
HighlightInfo.Builder info = checkSuperMethodIsFinal(method, superMethod);
|
||||
if (info != null) return info;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HighlightInfo.Builder checkSuperMethodIsFinal(@NotNull PsiMethod method, @NotNull PsiMethod superMethod) {
|
||||
// strange things happen when super method is from Object and method from interface
|
||||
if (superMethod.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
PsiClass superClass = superMethod.getContainingClass();
|
||||
String description = JavaErrorBundle.message("final.method.override",
|
||||
JavaHighlightUtil.formatMethod(method),
|
||||
JavaHighlightUtil.formatMethod(superMethod),
|
||||
superClass != null ? HighlightUtil.formatClass(superClass) : "<unknown>");
|
||||
TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method);
|
||||
HighlightInfo.Builder errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description);
|
||||
QuickFixAction.registerQuickFixActions(errorResult, null, JvmElementActionFactories.createModifierActions(superMethod, MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false)));
|
||||
return errorResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkMethodIncompatibleThrows(@NotNull MethodSignatureBackedByPsiMethod methodSignature,
|
||||
@NotNull List<? extends HierarchicalMethodSignature> superMethodSignatures,
|
||||
boolean includeRealPositionInfo,
|
||||
@NotNull PsiClass analyzedClass, @Nullable Ref<? super String> descriptionH) {
|
||||
PsiMethod method = methodSignature.getMethod();
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null) return null;
|
||||
PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(aClass, analyzedClass, PsiSubstitutor.EMPTY);
|
||||
PsiClassType[] exceptions = method.getThrowsList().getReferencedTypes();
|
||||
PsiJavaCodeReferenceElement[] referenceElements;
|
||||
List<PsiElement> exceptionContexts;
|
||||
if (includeRealPositionInfo) {
|
||||
exceptionContexts = new ArrayList<>();
|
||||
referenceElements = method.getThrowsList().getReferenceElements();
|
||||
}
|
||||
else {
|
||||
exceptionContexts = null;
|
||||
referenceElements = null;
|
||||
}
|
||||
List<PsiClassType> checkedExceptions = new ArrayList<>();
|
||||
for (int i = 0; i < exceptions.length; i++) {
|
||||
PsiClassType exception = exceptions[i];
|
||||
if (exception == null) {
|
||||
LOG.error("throws: " + method.getThrowsList().getText() + "; method: " + method);
|
||||
}
|
||||
else if (!ExceptionUtil.isUncheckedException(exception)) {
|
||||
checkedExceptions.add(exception);
|
||||
if (includeRealPositionInfo && i < referenceElements.length) {
|
||||
PsiJavaCodeReferenceElement exceptionRef = referenceElements[i];
|
||||
exceptionContexts.add(exceptionRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) {
|
||||
PsiMethod superMethod = superMethodSignature.getMethod();
|
||||
int index = getExtraExceptionNum(methodSignature, superMethodSignature, checkedExceptions, superSubstitutor);
|
||||
if (index != -1) {
|
||||
if (aClass.isInterface()) {
|
||||
PsiClass superContainingClass = superMethod.getContainingClass();
|
||||
if (superContainingClass != null && !superContainingClass.isInterface()) continue;
|
||||
if (superContainingClass != null && !aClass.isInheritor(superContainingClass, true)) continue;
|
||||
}
|
||||
PsiClassType exception = checkedExceptions.get(index);
|
||||
String description = JavaErrorBundle.message("overridden.method.does.not.throw",
|
||||
createClashMethodMessage(method, superMethod, true),
|
||||
JavaHighlightUtil.formatType(exception));
|
||||
if (descriptionH != null) {
|
||||
descriptionH.set(description);
|
||||
}
|
||||
TextRange textRange;
|
||||
if (includeRealPositionInfo) {
|
||||
PsiElement exceptionContext = exceptionContexts.get(index);
|
||||
textRange = exceptionContext.getTextRange();
|
||||
}
|
||||
else {
|
||||
textRange = TextRange.EMPTY_RANGE;
|
||||
}
|
||||
HighlightInfo.Builder errorResult =
|
||||
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description);
|
||||
IntentionAction action1 = QuickFixFactory.getInstance().createMethodThrowsFix(method, exception, false, false);
|
||||
errorResult.registerFix(action1, null, null, null, null);
|
||||
IntentionAction action = QuickFixFactory.getInstance().createMethodThrowsFix(superMethod, exception, true, true);
|
||||
errorResult.registerFix(action, null, null, null, null);
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// return number of exception which was not declared in super method or -1
|
||||
private static int getExtraExceptionNum(@NotNull MethodSignature methodSignature,
|
||||
@NotNull MethodSignatureBackedByPsiMethod superSignature,
|
||||
@NotNull List<? extends PsiClassType> checkedExceptions,
|
||||
@NotNull PsiSubstitutor substitutorForDerivedClass) {
|
||||
PsiMethod superMethod = superSignature.getMethod();
|
||||
PsiSubstitutor substitutorForMethod = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature, superSignature);
|
||||
for (int i = 0; i < checkedExceptions.size(); i++) {
|
||||
PsiClassType checkedEx = checkedExceptions.get(i);
|
||||
PsiType substituted = substitutorForMethod == null ? TypeConversionUtil.erasure(checkedEx) : substitutorForMethod.substitute(checkedEx);
|
||||
PsiType exception = substitutorForDerivedClass.substitute(substituted);
|
||||
if (!isMethodThrows(superMethod, substitutorForMethod, exception, substitutorForDerivedClass)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static boolean isMethodThrows(@NotNull PsiMethod method,
|
||||
@Nullable PsiSubstitutor substitutorForMethod,
|
||||
@NotNull PsiType exception,
|
||||
@NotNull PsiSubstitutor substitutorForDerivedClass) {
|
||||
PsiClassType[] thrownExceptions = method.getThrowsList().getReferencedTypes();
|
||||
for (PsiClassType thrownException1 : thrownExceptions) {
|
||||
PsiType thrownException = substitutorForMethod != null ? substitutorForMethod.substitute(thrownException1) : TypeConversionUtil.erasure(thrownException1);
|
||||
thrownException = substitutorForDerivedClass.substitute(thrownException);
|
||||
if (TypeConversionUtil.isAssignable(thrownException, exception)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void checkMethodCall(@NotNull PsiMethodCallExpression methodCall,
|
||||
@NotNull PsiResolveHelper resolveHelper,
|
||||
@NotNull LanguageLevel languageLevel,
|
||||
@@ -1382,138 +1189,6 @@ public final class HighlightMethodUtil {
|
||||
}
|
||||
|
||||
|
||||
private static String checkInterfaceInheritedMethodsReturnTypesDescription(@NotNull List<? extends MethodSignatureBackedByPsiMethod> superMethodSignatures,
|
||||
@NotNull LanguageLevel languageLevel) {
|
||||
if (superMethodSignatures.size() < 2) return null;
|
||||
MethodSignatureBackedByPsiMethod[] returnTypeSubstitutable = {superMethodSignatures.get(0)};
|
||||
for (int i = 1; i < superMethodSignatures.size(); i++) {
|
||||
PsiMethod currentMethod = returnTypeSubstitutable[0].getMethod();
|
||||
PsiType currentType = returnTypeSubstitutable[0].getSubstitutor().substitute(currentMethod.getReturnType());
|
||||
|
||||
MethodSignatureBackedByPsiMethod otherSuperSignature = superMethodSignatures.get(i);
|
||||
PsiMethod otherSuperMethod = otherSuperSignature.getMethod();
|
||||
PsiSubstitutor otherSubstitutor = otherSuperSignature.getSubstitutor();
|
||||
PsiType otherSuperReturnType = otherSubstitutor.substitute(otherSuperMethod.getReturnType());
|
||||
PsiSubstitutor unifyingSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(returnTypeSubstitutable[0],
|
||||
otherSuperSignature);
|
||||
if (unifyingSubstitutor != null) {
|
||||
otherSuperReturnType = unifyingSubstitutor.substitute(otherSuperReturnType);
|
||||
currentType = unifyingSubstitutor.substitute(currentType);
|
||||
}
|
||||
|
||||
if (otherSuperReturnType == null || currentType == null || otherSuperReturnType.equals(currentType)) continue;
|
||||
PsiType otherReturnType = otherSuperReturnType;
|
||||
PsiType curType = currentType;
|
||||
String info =
|
||||
LambdaUtil.performWithSubstitutedParameterBounds(otherSuperMethod.getTypeParameters(), otherSubstitutor, () -> {
|
||||
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_5)) {
|
||||
//http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.8 Example 8.1.5-3
|
||||
if (!(otherReturnType instanceof PsiPrimitiveType || curType instanceof PsiPrimitiveType)) {
|
||||
if (otherReturnType.isAssignableFrom(curType)) return null;
|
||||
if (curType.isAssignableFrom(otherReturnType)) {
|
||||
returnTypeSubstitutable[0] = otherSuperSignature;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (otherSuperMethod.getTypeParameters().length > 0 && JavaGenericsUtil.isRawToGeneric(otherReturnType, curType)) return null;
|
||||
}
|
||||
return MessageFormat.format("{0}; {1}", createClashMethodMessage(otherSuperMethod, currentMethod, true), JavaErrorBundle.message("unrelated.overriding.methods.return.types"));
|
||||
});
|
||||
if (info != null) return info;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkOverrideEquivalentInheritedMethods(@NotNull PsiClass aClass,
|
||||
@NotNull PsiFile containingFile,
|
||||
@NotNull LanguageLevel languageLevel) {
|
||||
Collection<HierarchicalMethodSignature> visibleSignatures = aClass.getVisibleSignatures();
|
||||
if (aClass.getImplementsListTypes().length == 0 && aClass.getExtendsListTypes().length == 0) {
|
||||
// optimization: do not analyze unrelated methods from Object: in case of no inheritance they can't conflict
|
||||
return null;
|
||||
}
|
||||
PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper();
|
||||
|
||||
String description = null;
|
||||
boolean appendImplementMethodFix = true;
|
||||
Ultimate:
|
||||
for (HierarchicalMethodSignature signature : visibleSignatures) {
|
||||
PsiMethod method = signature.getMethod();
|
||||
if (!resolveHelper.isAccessible(method, aClass, null)) continue;
|
||||
List<HierarchicalMethodSignature> superSignatures = signature.getSuperSignatures();
|
||||
|
||||
boolean allAbstracts = method.hasModifierProperty(PsiModifier.ABSTRACT);
|
||||
PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null || aClass.equals(containingClass)) continue; //to be checked at method level
|
||||
|
||||
if (aClass.isInterface() && !containingClass.isInterface()) continue;
|
||||
String error;
|
||||
if (allAbstracts) {
|
||||
superSignatures = new ArrayList<>(superSignatures);
|
||||
superSignatures.add(0, signature);
|
||||
error = checkInterfaceInheritedMethodsReturnTypesDescription(superSignatures, languageLevel);
|
||||
}
|
||||
else {
|
||||
Ref<String> descriptionH = new Ref<>();
|
||||
checkMethodIncompatibleReturnType(signature, superSignatures, false, descriptionH);
|
||||
error = descriptionH.get();
|
||||
}
|
||||
if (error != null) {
|
||||
description = error;
|
||||
}
|
||||
|
||||
if (method.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
//jsl 8, chapter 9.4.1
|
||||
//chapter 8.4.8.2 speaks about a class that "declares or inherits a static method",
|
||||
// at the same time the rule from chapter 9.4.1 speaks only about an interface that "declares a static method"
|
||||
//There is no point to add java version check, because static methods in interfaces are allowed from java 8 too.
|
||||
(!aClass.isInterface() ||
|
||||
aClass.getManager().areElementsEquivalent(aClass, method.getContainingClass()))) {
|
||||
for (HierarchicalMethodSignature superSignature : superSignatures) {
|
||||
PsiMethod superMethod = superSignature.getMethod();
|
||||
if (!superMethod.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
PsiClass superClass = superMethod.getContainingClass();
|
||||
description = JavaErrorBundle.message("static.method.cannot.override.instance.method",
|
||||
JavaHighlightUtil.formatMethod(method),
|
||||
HighlightUtil.formatClass(containingClass),
|
||||
JavaHighlightUtil.formatMethod(superMethod),
|
||||
superClass != null ? HighlightUtil.formatClass(superClass) : "<unknown>");
|
||||
appendImplementMethodFix = false;
|
||||
break Ultimate;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (description == null) {
|
||||
Ref<@Nls String> descriptionH = new Ref<>();
|
||||
checkMethodIncompatibleThrows(signature, superSignatures, false, aClass, descriptionH);
|
||||
description = descriptionH.get();
|
||||
}
|
||||
|
||||
if (description == null) {
|
||||
Ref<@Nls String> descriptionH = new Ref<>();
|
||||
checkMethodWeakerPrivileges(signature, superSignatures, false, containingFile, descriptionH);
|
||||
description = descriptionH.get();
|
||||
}
|
||||
|
||||
if (description != null) break;
|
||||
}
|
||||
|
||||
|
||||
if (description != null) {
|
||||
// show error info at the class level
|
||||
TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
|
||||
HighlightInfo.Builder highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description);
|
||||
if (appendImplementMethodFix) {
|
||||
IntentionAction action = QuickFixFactory.getInstance().createImplementMethodsFix(aClass);
|
||||
highlightInfo.registerFix(action, null, null, null, null);
|
||||
}
|
||||
return highlightInfo;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkConstructorHandleSuperClassExceptions(@NotNull PsiMethod method) {
|
||||
if (!method.isConstructor()) {
|
||||
return null;
|
||||
|
||||
+3
-31
@@ -61,7 +61,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -869,43 +868,16 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
super.visitModifierList(list);
|
||||
PsiElement parent = list.getParent();
|
||||
if (parent instanceof PsiMethod method) {
|
||||
MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY);
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if (!method.isConstructor()) {
|
||||
try {
|
||||
List<HierarchicalMethodSignature> superMethodSignatures = method.getHierarchicalMethodSignature().getSuperSignatures();
|
||||
if (!superMethodSignatures.isEmpty()) {
|
||||
if (!method.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
if (!hasErrorResults()) {
|
||||
add(HighlightMethodUtil.checkMethodWeakerPrivileges(methodSignature, superMethodSignatures, true, myFile, null));
|
||||
}
|
||||
if (!hasErrorResults()) add(HighlightMethodUtil.checkMethodOverridesFinal(methodSignature, superMethodSignatures));
|
||||
}
|
||||
if (!hasErrorResults()) {
|
||||
add(HighlightMethodUtil.checkMethodIncompatibleReturnType(methodSignature, superMethodSignatures, true, null));
|
||||
}
|
||||
if (aClass != null && !hasErrorResults()) {
|
||||
add(HighlightMethodUtil.checkMethodIncompatibleThrows(methodSignature, superMethodSignatures, true, aClass, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IndexNotReadyException ignored) {
|
||||
}
|
||||
}
|
||||
if (!hasErrorResults() && aClass != null) {
|
||||
GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(aClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
|
||||
myErrorSink.accept(myOverrideEquivalentMethodsErrors.get(method));
|
||||
}
|
||||
}
|
||||
else if (parent instanceof PsiClass aClass) {
|
||||
try {
|
||||
if (!hasErrorResults()) add(HighlightMethodUtil.checkOverrideEquivalentInheritedMethods(aClass, myFile, myLanguageLevel));
|
||||
if (!hasErrorResults()) {
|
||||
GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(aClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
|
||||
myErrorSink.accept(myOverrideEquivalentMethodsErrors.get(aClass));
|
||||
}
|
||||
}
|
||||
catch (IndexNotReadyException ignored) {
|
||||
if (!hasErrorResults()) {
|
||||
GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(aClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
|
||||
myErrorSink.accept(myOverrideEquivalentMethodsErrors.get(aClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-5
@@ -22,6 +22,7 @@ import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.Service;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.light.LightRecordMethod;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
@@ -118,12 +119,36 @@ final class JavaErrorFixProvider {
|
||||
});
|
||||
fix(METHOD_ABSTRACT_BODY, error -> myFactory.createPushDownMethodFix());
|
||||
fix(METHOD_NATIVE_BODY, error -> myFactory.createPushDownMethodFix());
|
||||
fix(METHOD_STATIC_OVERRIDES_INSTANCE, error -> removeModifierFix(error.psi(), PsiModifier.STATIC));
|
||||
fix(METHOD_INSTANCE_OVERRIDES_STATIC, error -> maybeAddModifierFix(error.psi(), PsiModifier.STATIC));
|
||||
fix(METHOD_STATIC_OVERRIDES_INSTANCE, error -> maybeAddModifierFix(error.context(), PsiModifier.STATIC));
|
||||
fix(METHOD_INSTANCE_OVERRIDES_STATIC, error -> removeModifierFix(error.context(), PsiModifier.STATIC));
|
||||
fix(METHOD_STATIC_OVERRIDES_INSTANCE, error -> removeModifierFix(error.context().method(), PsiModifier.STATIC));
|
||||
fix(METHOD_INSTANCE_OVERRIDES_STATIC, error -> maybeAddModifierFix(error.context().method(), PsiModifier.STATIC));
|
||||
fix(METHOD_STATIC_OVERRIDES_INSTANCE, error -> maybeAddModifierFix(error.context().superMethod(), PsiModifier.STATIC));
|
||||
fix(METHOD_INSTANCE_OVERRIDES_STATIC, error -> removeModifierFix(error.context().superMethod(), PsiModifier.STATIC));
|
||||
fix(METHOD_OVERRIDES_FINAL, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
|
||||
fix(METHOD_INHERITANCE_WEAKER_PRIVILEGES, error -> myFactory.createChangeModifierFix());
|
||||
fix(METHOD_INHERITANCE_WEAKER_PRIVILEGES,
|
||||
error -> error.psi() instanceof PsiMethod ? myFactory.createChangeModifierFix() :
|
||||
error.psi() instanceof PsiClass cls ? myFactory.createImplementMethodsFix(cls) : null);
|
||||
multi(METHOD_INHERITANCE_CLASH_DOES_NOT_THROW, error -> List.of(
|
||||
myFactory.createMethodThrowsFix(error.context().method(), error.context().exceptionType(), false, false),
|
||||
myFactory.createMethodThrowsFix(error.context().superMethod(), error.context().exceptionType(), true, true)
|
||||
));
|
||||
multi(METHOD_INHERITANCE_CLASH_INCOMPATIBLE_RETURN_TYPES, error -> {
|
||||
IncompatibleOverrideReturnTypeContext context = error.context();
|
||||
PsiMethod method = context.method();
|
||||
List<CommonIntentionAction> registrar = new ArrayList<>();
|
||||
if (method instanceof LightRecordMethod recordMethod) {
|
||||
registrar.addAll(
|
||||
HighlightFixUtil.getChangeVariableTypeFixes(recordMethod.getRecordComponent(), context.superMethodReturnType()));
|
||||
}
|
||||
else {
|
||||
registrar.add(myFactory.createMethodReturnFix(method, context.superMethodReturnType(), false));
|
||||
}
|
||||
registrar.add(myFactory.createSuperMethodReturnFix(context.superMethod(), context.methodReturnType()));
|
||||
PsiClass returnClass = PsiUtil.resolveClassInClassTypeOnly(context.methodReturnType());
|
||||
if (returnClass != null && context.superMethodReturnType() instanceof PsiClassType classType) {
|
||||
registrar.add(myFactory.createChangeParameterClassFix(returnClass, classType));
|
||||
}
|
||||
return registrar;
|
||||
});
|
||||
}
|
||||
|
||||
private void createConstructorFixes() {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
|
||||
interface ConflictWithObject {
|
||||
<error descr="'notify()' cannot override 'notify()' in 'java.lang.Object'; overridden method is final">public Object notify()</error>;
|
||||
<error descr="'notify()' cannot override 'notify()' in 'java.lang.Object'; overridden method is final">public Object notify();</error>
|
||||
}
|
||||
|
||||
//--override final-------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user