diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index dbdfb8486ce3..25caf5c9c680 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -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}'' diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java index 68194143642b..695c6b8cc454 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java @@ -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 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); } } diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/MethodChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/MethodChecker.java index 1fc84f6bcef9..08b8eb302f85 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/MethodChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/MethodChecker.java @@ -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 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 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 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 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 exceptionContexts = new ArrayList<>(); + List 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 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 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 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 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)); diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java index 9fbe06c945ff..fa6345b7784b 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java @@ -372,22 +372,22 @@ public final class JavaErrorKinds { error(PsiMethod.class, "method.should.have.body").withRange(JavaErrorFormatUtil::getMethodDeclarationTextRange); public static final Simple METHOD_SHOULD_HAVE_BODY_OR_ABSTRACT = error(PsiMethod.class, "method.should.have.body.or.abstract").withRange(JavaErrorFormatUtil::getMethodDeclarationTextRange); - public static final Parameterized 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 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 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 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 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) : ""); }); - public static final Parameterized 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 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 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 + 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 + 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 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) { + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java index 0177a01cdeff..f142ae79e12c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java @@ -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 superMethodSignatures, - boolean includeRealPositionInfo, - @NotNull PsiFile containingFile, @Nullable Ref 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 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 superMethodSignatures, - boolean includeRealPositionInfo, - @Nullable Ref description) { - return checkMethodIncompatibleReturnType(methodSignature, superMethodSignatures, includeRealPositionInfo, null, description); - } static HighlightInfo.Builder checkMethodIncompatibleReturnType(@NotNull MethodSignatureBackedByPsiMethod methodSignature, @NotNull List 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 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 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) : ""); - 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 superMethodSignatures, - boolean includeRealPositionInfo, - @NotNull PsiClass analyzedClass, @Nullable Ref 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 exceptionContexts; - if (includeRealPositionInfo) { - exceptionContexts = new ArrayList<>(); - referenceElements = method.getThrowsList().getReferenceElements(); - } - else { - exceptionContexts = null; - referenceElements = null; - } - List 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 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 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 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 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 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) : ""); - 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; diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index c567327874a7..5b206f8289b3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -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 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)); } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java index 29e195a1e14d..23f1002108c1 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java @@ -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 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() { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/OverriddenMethodIsFinal.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/OverriddenMethodIsFinal.java index 30c1359c1913..2968e042311f 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/OverriddenMethodIsFinal.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/OverriddenMethodIsFinal.java @@ -1,6 +1,6 @@ interface ConflictWithObject { - public Object notify(); + public Object notify(); } //--override final-------------------------------------------------------------------------