[java-highlighting] Unrelated defaults checks moved to MethodChecker

GenericsHighlightUtil.java is removed completely
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: de2bddb49469c21efe1088e86f6a1ec5b9d35b4a
This commit is contained in:
Tagir Valeev
2025-02-20 15:21:52 +01:00
committed by intellij-monorepo-bot
parent 5014925e91
commit 96bc4480b1
15 changed files with 67 additions and 99 deletions

View File

@@ -129,6 +129,8 @@ class.initializer.must.complete.normally=Initializer must be able to complete no
class.permitted.not.direct.subclass=Invalid ''permits'' clause: ''{0}'' must directly {1, choice, 1#extend|2#implement} ''{2}''
class.permitted.must.have.modifier=All sealed class subclasses must either be final, sealed or non-sealed
class.or.package.expected=Expected class or package
class.inherits.abstract.and.default={0} inherits abstract and default for {1} from types {2} and {3}
class.inherits.unrelated.defaults={0} inherits unrelated defaults for {1} from types {2} and {3}
class.implicit.no.main.method=Implicitly declared class contains no 'main' method
class.implicit.invalid.file.name=The file name of an implicitly declared class is not a valid identifier

View File

@@ -582,6 +582,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults()) myClassChecker.checkClassAlreadyImported(aClass);
if (!hasErrorResults()) myClassChecker.checkClassRestrictedKeyword(identifier);
if (!hasErrorResults()) myGenericsChecker.checkUnrelatedConcrete(aClass);
if (!hasErrorResults() && isApplicable(JavaFeature.EXTENSION_METHODS)) myMethodChecker.checkUnrelatedDefaultMethods(aClass);
}
else if (parent instanceof PsiMethod method) {
myClassChecker.checkImplicitClassMember(method);

View File

@@ -1,13 +1,16 @@
// 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.ClassUtil;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.java.codeserver.core.JavaPsiMethodUtil;
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
@@ -549,4 +552,33 @@ final class MethodChecker {
myVisitor.report(JavaErrorKinds.METHOD_DUPLICATE.create(method));
}
}
void checkUnrelatedDefaultMethods(@NotNull PsiClass aClass) {
Map<? extends MethodSignature, Set<PsiMethod>> overrideEquivalent = PsiSuperMethodUtil.collectOverrideEquivalents(aClass);
for (Set<PsiMethod> overrideEquivalentMethods : overrideEquivalent.values()) {
PsiMethod abstractMethod = JavaPsiMethodUtil.getAbstractMethodToImplementWhenDefaultPresent(aClass, overrideEquivalentMethods);
if (abstractMethod != null) {
PsiMethod anyAbstractMethod = ClassUtil.getAnyAbstractMethod(aClass);
if (anyAbstractMethod != null) {
PsiClass containingClass = anyAbstractMethod.getContainingClass();
if (containingClass != null && containingClass != aClass) {
// Already reported inside ClassChecker.checkClassWithAbstractMethods
continue;
}
}
myVisitor.report(JavaErrorKinds.CLASS_NO_ABSTRACT_METHOD.create(aClass, abstractMethod));
continue;
}
Couple<@NotNull PsiMethod> pair = JavaPsiMethodUtil.getUnrelatedSuperMethods(aClass, overrideEquivalentMethods);
if (pair == null ||
MethodSignatureUtil.findMethodBySuperMethod(aClass, overrideEquivalentMethods.iterator().next(), false) != null) {
continue;
}
var kind = pair.getSecond().hasModifierProperty(PsiModifier.ABSTRACT)
? JavaErrorKinds.CLASS_INHERITS_ABSTRACT_AND_DEFAULT
: JavaErrorKinds.CLASS_INHERITS_UNRELATED_DEFAULTS;
myVisitor.report(kind.create(aClass, new JavaErrorKinds.OverrideClashContext(pair.getFirst(), pair.getSecond())));
}
}
}

View File

@@ -308,6 +308,24 @@ public final class JavaErrorKinds {
return message(messageKey, referenceName, formatMethod(abstractMethod),
formatClass(requireNonNull(abstractMethod.getContainingClass()), false));
});
public static final Parameterized<PsiClass, OverrideClashContext> CLASS_INHERITS_ABSTRACT_AND_DEFAULT =
parameterized(PsiClass.class, OverrideClashContext.class, "class.inherits.abstract.and.default")
.withAnchor(PsiClass::getNameIdentifier)
.withRawDescription((cls, ctx) -> {
return message("class.inherits.abstract.and.default", formatClass(cls),
formatMethod(ctx.method()),
formatClass(requireNonNull(ctx.method().getContainingClass())),
formatClass(requireNonNull(ctx.superMethod().getContainingClass())));
});
public static final Parameterized<PsiClass, OverrideClashContext> CLASS_INHERITS_UNRELATED_DEFAULTS =
parameterized(PsiClass.class, OverrideClashContext.class, "class.inherits.unrelated.defaults")
.withAnchor(PsiClass::getNameIdentifier)
.withRawDescription((cls, ctx) -> {
return message("class.inherits.unrelated.defaults", formatClass(cls),
formatMethod(ctx.method()),
formatClass(requireNonNull(ctx.method().getContainingClass())),
formatClass(requireNonNull(ctx.superMethod().getContainingClass())));
});
public static final Simple<PsiClass> CLASS_ALREADY_IMPORTED =
error(PsiClass.class, "class.already.imported").withAnchor(PsiClass::getNameIdentifier)
.withRawDescription(cls -> message("class.already.imported", formatClass(cls, false)));

View File

@@ -1,72 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.java.codeserver.core.JavaPsiMethodUtil;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.psi.*;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiSuperMethodUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import static java.util.Objects.requireNonNull;
public final class GenericsHighlightUtil {
private GenericsHighlightUtil() { }
static HighlightInfo.Builder checkUnrelatedDefaultMethods(@NotNull PsiClass aClass, @NotNull PsiIdentifier classIdentifier) {
Map<? extends MethodSignature, Set<PsiMethod>> overrideEquivalent = PsiSuperMethodUtil.collectOverrideEquivalents(aClass);
for (Set<PsiMethod> overrideEquivalentMethods : overrideEquivalent.values()) {
String errorMessage = getUnrelatedDefaultsMessage(aClass, overrideEquivalentMethods);
if (errorMessage != null &&
MethodSignatureUtil.findMethodBySuperMethod(aClass, overrideEquivalentMethods.iterator().next(), false) == null) {
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(classIdentifier)
.descriptionAndTooltip(errorMessage);
IntentionAction action = QuickFixFactory.getInstance().createImplementMethodsFix(aClass);
info.registerFix(action, null, null, null, null);
return info;
}
}
return null;
}
/**
* @return error message if class inherits 2 unrelated default methods or abstract and default methods which do not belong to one hierarchy
*/
public static @Nullable @NlsContexts.DetailedDescription String getUnrelatedDefaultsMessage(
@NotNull PsiClass aClass, @NotNull Collection<? extends PsiMethod> overrideEquivalentSuperMethods) {
PsiMethod abstractMethod = JavaPsiMethodUtil.getAbstractMethodToImplementWhenDefaultPresent(aClass, overrideEquivalentSuperMethods);
if (abstractMethod != null) {
String key = aClass instanceof PsiEnumConstantInitializer || aClass.isRecord() || aClass.isEnum() ?
"class.must.implement.method" : "class.must.be.abstract";
return JavaErrorBundle.message(key,
HighlightUtil.formatClass(aClass, false),
JavaHighlightUtil.formatMethod(abstractMethod),
HighlightUtil.formatClass(requireNonNull(abstractMethod.getContainingClass()), false));
}
Couple<@NotNull PsiMethod> pair = JavaPsiMethodUtil.getUnrelatedSuperMethods(aClass, overrideEquivalentSuperMethods);
if (pair == null) return null;
String key = pair.getSecond().hasModifierProperty(PsiModifier.ABSTRACT) ?
"text.class.inherits.abstract.and.default" :
"text.class.inherits.unrelated.defaults";
return JavaErrorBundle.message(key, HighlightUtil.formatClass(aClass),
JavaHighlightUtil.formatMethod(pair.getFirst()),
HighlightUtil.formatClass(requireNonNull(pair.getFirst().getContainingClass())),
HighlightUtil.formatClass(requireNonNull(pair.getSecond().getContainingClass())));
}
}

View File

@@ -20,7 +20,6 @@ import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
@@ -221,18 +220,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
}
@Override
public void visitIdentifier(@NotNull PsiIdentifier identifier) {
PsiElement parent = identifier.getParent();
if (parent instanceof PsiClass aClass) {
if (!hasErrorResults() && JavaFeature.EXTENSION_METHODS.isSufficient(myLanguageLevel)) {
add(GenericsHighlightUtil.checkUnrelatedDefaultMethods(aClass, identifier));
}
}
super.visitIdentifier(identifier);
}
@Override
public void visitImportStaticReferenceElement(@NotNull PsiImportStaticReferenceElement ref) {
super.visitImportStaticReferenceElement(ref);

View File

@@ -801,6 +801,8 @@ final class JavaErrorFixProvider {
}
private void createClassFixes() {
fix(CLASS_INHERITS_UNRELATED_DEFAULTS, error -> myFactory.createImplementMethodsFix(error.psi()));
fix(CLASS_INHERITS_ABSTRACT_AND_DEFAULT, error -> myFactory.createImplementMethodsFix(error.psi()));
fix(CLASS_NO_ABSTRACT_METHOD, error -> {
if (error.psi() instanceof PsiClass aClass && !(aClass instanceof PsiAnonymousClass) && !aClass.isEnum()) {
return maybeAddModifierFix(aClass, PsiModifier.ABSTRACT);

View File

@@ -186,8 +186,6 @@ record.canonical.constructor=Canonical constructor
record.compact.constructor=Compact constructor
annotation.on.static.member.qualifying.type.family.name=Move type annotation
create.class.action.this.not.valid.java.qualified.name=This is not a valid Java qualified name
text.class.inherits.abstract.and.default={0} inherits abstract and default for {1} from types {2} and {3}
text.class.inherits.unrelated.defaults={0} inherits unrelated defaults for {1} from types {2} and {3}
text.class.is.not.accessible={0} is not accessible in current context
text.class.cannot.access=Cannot access {0}
remove.unused.imports.quickfix.text=Remove unused imports

View File

@@ -22,8 +22,8 @@ class AnonymousExtendsJLR {
<error descr="Class 'SuperInterface' must implement abstract method 'run()' in 'Runnable'">record SuperInterface() implements Runnable</error> {}
interface I1 { default void run() {}}
interface I2 { void run();}
record <error descr="Class 'UnrelatedDefaults' must implement abstract method 'run()' in 'I2'">UnrelatedDefaults</error>() implements I1, I2 {}
enum <error descr="Class 'UnrelatedDefaults2' must implement abstract method 'run()' in 'I2'">UnrelatedDefaults2</error> implements I1, I2 {}
<error descr="Class 'UnrelatedDefaults' must implement abstract method 'run()' in 'I2'">record UnrelatedDefaults() implements I1, I2</error> {}
<error descr="Class 'UnrelatedDefaults2' must implement abstract method 'run()' in 'I2'">enum UnrelatedDefaults2 implements I1, I2</error> {}
record ComponentModifiers(
<error descr="Modifier 'public' not allowed here">public</error> int x,

View File

@@ -5,7 +5,7 @@ class Test1 {
default void foo(String x) { }
}
class <error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'A'">C</error> implements A<String> { }
<error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'A'">class C implements A<String></error> { }
abstract class <error descr="Test1.D inherits abstract and default for foo(String) from types Test1.A and Test1.A">D</error> implements A<String> {}
interface <error descr="Test1.E inherits abstract and default for foo(String) from types Test1.A and Test1.A">E</error> extends A<String> {}
}
@@ -19,7 +19,7 @@ class Test2 {
void foo(T x);
}
<error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'B'">class <error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'B'">C</error> implements B<String></error> { }
<error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'B'">class C implements B<String></error> { }
abstract class D implements B<String> {}
interface E extends B<String> {}
}

View File

@@ -49,7 +49,7 @@ class Test4 {
void accept();
}
<error descr="Class 'D' must either be declared abstract or implement abstract method 'accept()' in 'C'">class <error descr="Class 'D' must either be declared abstract or implement abstract method 'accept()' in 'C'">D</error> implements B, C</error> {}
<error descr="Class 'D' must either be declared abstract or implement abstract method 'accept()' in 'C'">class D implements B, C</error> {}
abstract class E implements B, C {}
}

View File

@@ -10,9 +10,9 @@ interface SecondParent {
int doSomething();
}
class <error descr="Class 'FirstSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">FirstSon</error> implements FirstParent, SecondParent {}
<error descr="Class 'FirstSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">class FirstSon implements FirstParent, SecondParent</error> {}
<error descr="Class 'SecondSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">class <error descr="Class 'SecondSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">SecondSon</error> implements SecondParent, FirstParent</error> {}
<error descr="Class 'SecondSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">class SecondSon implements SecondParent, FirstParent</error> {}
interface A {
default int foo() {

View File

@@ -10,4 +10,4 @@ interface C {
void m();
}
class <error descr="Class 'D' must either be declared abstract or implement abstract method 'm()' in 'C'">D</error> implements A, B, C { }
<error descr="Class 'D' must either be declared abstract or implement abstract method 'm()' in 'C'">class D implements A, B, C</error> { }

View File

@@ -9,7 +9,7 @@ class Test1 {
default void foo(T x) { }
}
class <error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(String)' in 'A'">D</error> implements C<String> { }
<error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(String)' in 'A'">class D implements C<String></error> { }
interface E extends C<String> { }
}
@@ -23,7 +23,7 @@ class Test2 {
void foo(T x);
}
<error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(T)' in 'C'">class <error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(T)' in 'C'">D</error> implements C<String></error> {}
<error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(T)' in 'C'">class D implements C<String></error> {}
interface E extends C<String> {}
}
@@ -66,7 +66,7 @@ class Test5 {
default void foo(T x) { }
}
class <error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(String)' in 'A'">D</error> extends B implements C<String> { }
<error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(String)' in 'A'">class D extends B implements C<String></error> { }
}

View File

@@ -13,4 +13,4 @@ class C<K> extends AC<K> implements B<K> {
public void replace(K k) {}
}
<error descr="Class 'D' must either be declared abstract or implement abstract method 'replace(K)' in 'B'">class <error descr="Class 'D' must either be declared abstract or implement abstract method 'replace(K)' in 'B'">D</error><K> extends AC<K> implements B<K></error> {}
<error descr="Class 'D' must either be declared abstract or implement abstract method 'replace(K)' in 'B'">class D<K> extends AC<K> implements B<K></error> {}