mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 05:21:29 +07:00
[java-highlighting] Refactor unrelated default method analysis; extract JavaPsiMethodUtil
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 1e7f4feab6cbaa3d38960a3c0e45aa5dd2def61a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c858eedee0
commit
5014925e91
@@ -0,0 +1,129 @@
|
||||
// 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.core;
|
||||
|
||||
import com.intellij.openapi.util.Couple;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.MethodSignature;
|
||||
import com.intellij.psi.util.MethodSignatureUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Utilities related to Java methods
|
||||
*/
|
||||
public final class JavaPsiMethodUtil {
|
||||
/**
|
||||
* @param aClass class to analyze
|
||||
* @param overrideEquivalentSuperMethods collection of override-equivalent super methods
|
||||
* @return a couple of unrelated methods from the collection (either both default, or default and abstract),
|
||||
* so the absence of a method declaration in a current class leads to ambiguous inheritance.
|
||||
* Returns null if no such a couple is found.
|
||||
*/
|
||||
public static @Nullable Couple<@NotNull PsiMethod> getUnrelatedSuperMethods(
|
||||
@NotNull PsiClass aClass, @NotNull Collection<? extends PsiMethod> overrideEquivalentSuperMethods) {
|
||||
if (overrideEquivalentSuperMethods.size() <= 1) return null;
|
||||
List<PsiMethod> defaults = null;
|
||||
PsiMethod abstractMethod = null;
|
||||
for (PsiMethod method : overrideEquivalentSuperMethods) {
|
||||
boolean isDefault = method.hasModifierProperty(PsiModifier.DEFAULT);
|
||||
boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT);
|
||||
boolean isStatic = method.hasModifierProperty(PsiModifier.STATIC);
|
||||
if (!isDefault && !isAbstract && !isStatic) return null;
|
||||
if (isDefault) {
|
||||
if (defaults == null) defaults = new ArrayList<>(2);
|
||||
defaults.add(method);
|
||||
}
|
||||
if (isAbstract && abstractMethod == null) {
|
||||
abstractMethod = method;
|
||||
}
|
||||
}
|
||||
if (defaults == null) return null;
|
||||
PsiMethod defaultMethod = defaults.get(0);
|
||||
PsiClass defaultMethodContainingClass = defaultMethod.getContainingClass();
|
||||
if (defaultMethodContainingClass == null) return null;
|
||||
if (abstractMethod == null) {
|
||||
return findUnrelatedCouple(defaults);
|
||||
}
|
||||
PsiClass abstractMethodContainingClass = abstractMethod.getContainingClass();
|
||||
if (abstractMethodContainingClass == null) return null;
|
||||
if (aClass.isInterface() || abstractMethodContainingClass.isInterface()) {
|
||||
Couple<@NotNull PsiMethod> unrelatedCouple = findUnrelatedCouple(defaults);
|
||||
if (unrelatedCouple != null) {
|
||||
return unrelatedCouple;
|
||||
}
|
||||
if (hasNotOverriddenAbstract(defaults, abstractMethodContainingClass)) {
|
||||
return Couple.of(defaults.get(0), abstractMethod);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aClass a class to analyze
|
||||
* @param overrideEquivalentSuperMethods collection of override-equivalent super methods
|
||||
* @return an abstract method from the supplied collection that must be implemented, because an override-equivalent
|
||||
* default method is present, and the ambiguity must be resolved.
|
||||
*/
|
||||
public static @Nullable PsiMethod getAbstractMethodToImplementWhenDefaultPresent(
|
||||
@NotNull PsiClass aClass, @NotNull Collection<? extends PsiMethod> overrideEquivalentSuperMethods) {
|
||||
if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) || aClass instanceof PsiTypeParameter) return null;
|
||||
if (overrideEquivalentSuperMethods.size() <= 1) return null;
|
||||
PsiMethod abstractMethod = null;
|
||||
PsiMethod defaultMethod = null;
|
||||
for (PsiMethod method : overrideEquivalentSuperMethods) {
|
||||
if (method.hasModifierProperty(PsiModifier.DEFAULT)) {
|
||||
if (defaultMethod == null) defaultMethod = method;
|
||||
}
|
||||
else if (method.hasModifierProperty(PsiModifier.ABSTRACT)) {
|
||||
if (abstractMethod == null) abstractMethod = method;
|
||||
}
|
||||
else if (!method.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (abstractMethod == null || defaultMethod == null) return null;
|
||||
PsiClass abstractMethodContainingClass = abstractMethod.getContainingClass();
|
||||
if (abstractMethodContainingClass == null || !abstractMethodContainingClass.isInterface()) return null;
|
||||
PsiClass defaultMethodContainingClass = defaultMethod.getContainingClass();
|
||||
if (defaultMethodContainingClass == null) return null;
|
||||
if (defaultMethodContainingClass.isInheritor(abstractMethodContainingClass, true)) {
|
||||
MethodSignature unrelatedMethodSignature = abstractMethod.getSignature(
|
||||
TypeConversionUtil.getSuperClassSubstitutor(abstractMethodContainingClass, defaultMethodContainingClass, PsiSubstitutor.EMPTY));
|
||||
if (MethodSignatureUtil.isSubsignature(unrelatedMethodSignature, defaultMethod.getSignature(PsiSubstitutor.EMPTY))) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return abstractMethod;
|
||||
}
|
||||
|
||||
private static @Nullable Couple<@NotNull PsiMethod> findUnrelatedCouple(List<PsiMethod> methods) {
|
||||
if (methods.size() <= 1) return null;
|
||||
List<PsiClass> classes = ContainerUtil.map(methods, method -> method.getContainingClass());
|
||||
ArrayList<PsiMethod> resultMethods = new ArrayList<>(methods);
|
||||
for (PsiClass aClass1 : classes) {
|
||||
resultMethods.removeIf(method -> aClass1.isInheritor(requireNonNull(method.getContainingClass()), true));
|
||||
}
|
||||
|
||||
if (resultMethods.size() > 1) {
|
||||
return Couple.of(resultMethods.get(0), resultMethods.get(1));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean belongToOneHierarchy(@Nullable PsiClass class1, @Nullable PsiClass class2) {
|
||||
return class1 != null && class2 != null &&
|
||||
(class1.isInheritor(class2, true) || class2.isInheritor(class1, true));
|
||||
}
|
||||
|
||||
private static boolean hasNotOverriddenAbstract(@NotNull List<PsiMethod> defaults, @NotNull PsiClass abstractMethodContainingClass) {
|
||||
return !ContainerUtil.exists(defaults, method -> belongToOneHierarchy(method.getContainingClass(), abstractMethodContainingClass));
|
||||
}
|
||||
}
|
||||
@@ -6,113 +6,33 @@ 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.ide.IdeBundle;
|
||||
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 com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public final class GenericsHighlightUtil {
|
||||
|
||||
private GenericsHighlightUtil() { }
|
||||
|
||||
/**
|
||||
* @param skipMethodInSelf pass false to check if method in {@code aClass} can be deleted
|
||||
*
|
||||
* @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,
|
||||
boolean skipMethodInSelf) {
|
||||
if (overrideEquivalentSuperMethods.size() <= 1) return null;
|
||||
boolean isInterface = aClass.isInterface();
|
||||
List<PsiMethod> defaults = null;
|
||||
List<PsiMethod> abstracts = null;
|
||||
boolean hasConcrete = false;
|
||||
for (PsiMethod method : overrideEquivalentSuperMethods) {
|
||||
boolean isDefault = method.hasModifierProperty(PsiModifier.DEFAULT);
|
||||
boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT);
|
||||
if (isDefault) {
|
||||
if (defaults == null) defaults = new ArrayList<>(2);
|
||||
defaults.add(method);
|
||||
}
|
||||
if (isAbstract) {
|
||||
if (abstracts == null) abstracts = new ArrayList<>(2);
|
||||
abstracts.add(method);
|
||||
}
|
||||
hasConcrete |= !isDefault && !isAbstract && !method.hasModifierProperty(PsiModifier.STATIC);
|
||||
}
|
||||
|
||||
if (!hasConcrete && defaults != null) {
|
||||
PsiMethod defaultMethod = defaults.get(0);
|
||||
if (!skipMethodInSelf && MethodSignatureUtil.findMethodBySuperMethod(aClass, defaultMethod, false) != null) return null;
|
||||
PsiClass defaultMethodContainingClass = defaultMethod.getContainingClass();
|
||||
if (defaultMethodContainingClass == null) return null;
|
||||
PsiMethod unrelatedMethod;
|
||||
if (abstracts != null) {
|
||||
unrelatedMethod = abstracts.get(0);
|
||||
}
|
||||
else if (defaults.size() > 1) {
|
||||
unrelatedMethod = defaults.get(1);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
PsiClass unrelatedMethodContainingClass = unrelatedMethod.getContainingClass();
|
||||
if (unrelatedMethodContainingClass == null) return null;
|
||||
if (!aClass.hasModifierProperty(PsiModifier.ABSTRACT) && !(aClass instanceof PsiTypeParameter)
|
||||
&& abstracts != null && unrelatedMethodContainingClass.isInterface()) {
|
||||
if (defaultMethodContainingClass.isInheritor(unrelatedMethodContainingClass, true) &&
|
||||
MethodSignatureUtil.isSubsignature(unrelatedMethod.getSignature(TypeConversionUtil.getSuperClassSubstitutor(unrelatedMethodContainingClass, defaultMethodContainingClass, PsiSubstitutor.EMPTY)),
|
||||
defaultMethod.getSignature(PsiSubstitutor.EMPTY))) {
|
||||
return 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(abstracts.get(0)),
|
||||
HighlightUtil.formatClass(unrelatedMethodContainingClass, false));
|
||||
}
|
||||
if (isInterface || abstracts == null || unrelatedMethodContainingClass.isInterface()) {
|
||||
List<PsiClass> defaultContainingClasses = ContainerUtil.mapNotNull(defaults, PsiMethod::getContainingClass);
|
||||
String unrelatedDefaults = hasUnrelatedDefaults(defaultContainingClasses);
|
||||
if (unrelatedDefaults == null &&
|
||||
(abstracts == null || !hasNotOverriddenAbstract(defaultContainingClasses, unrelatedMethodContainingClass))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (unrelatedDefaults == null) {
|
||||
return JavaErrorBundle.message("text.class.inherits.abstract.and.default", HighlightUtil.formatClass(aClass),
|
||||
JavaHighlightUtil.formatMethod(defaultMethod),
|
||||
HighlightUtil.formatClass(defaultMethodContainingClass),
|
||||
HighlightUtil.formatClass(unrelatedMethodContainingClass));
|
||||
}
|
||||
else {
|
||||
return JavaErrorBundle.message("text.class.inherits.unrelated.defaults",
|
||||
HighlightUtil.formatClass(aClass),
|
||||
JavaHighlightUtil.formatMethod(defaultMethod),
|
||||
unrelatedDefaults);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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, false);
|
||||
if (errorMessage != null) {
|
||||
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);
|
||||
@@ -124,29 +44,29 @@ public final class GenericsHighlightUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean belongToOneHierarchy(@NotNull PsiClass defaultMethodContainingClass, @NotNull PsiClass unrelatedMethodContainingClass) {
|
||||
return defaultMethodContainingClass.isInheritor(unrelatedMethodContainingClass, true) ||
|
||||
unrelatedMethodContainingClass.isInheritor(defaultMethodContainingClass, true);
|
||||
}
|
||||
|
||||
private static boolean hasNotOverriddenAbstract(@NotNull List<? extends PsiClass> defaultContainingClasses, @NotNull PsiClass abstractMethodContainingClass) {
|
||||
return !ContainerUtil.exists(defaultContainingClasses, containingClass -> belongToOneHierarchy(containingClass, abstractMethodContainingClass));
|
||||
}
|
||||
|
||||
private static @Nls String hasUnrelatedDefaults(@NotNull List<? extends PsiClass> defaults) {
|
||||
if (defaults.size() > 1) {
|
||||
PsiClass[] defaultClasses = defaults.toArray(PsiClass.EMPTY_ARRAY);
|
||||
ArrayList<PsiClass> classes = new ArrayList<>(defaults);
|
||||
for (PsiClass aClass1 : defaultClasses) {
|
||||
classes.removeIf(aClass2 -> aClass1.isInheritor(aClass2, true));
|
||||
}
|
||||
|
||||
if (classes.size() > 1) {
|
||||
return IdeBundle.message("x.and.y", HighlightUtil.formatClass(classes.get(0)),
|
||||
HighlightUtil.formatClass(classes.get(1)));
|
||||
}
|
||||
/**
|
||||
* @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));
|
||||
}
|
||||
|
||||
return null;
|
||||
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())));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// 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.codeInspection.reference;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.GenericsHighlightUtil;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.java.codeserver.core.JavaPsiMethodUtil;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
@@ -18,10 +18,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.uast.*;
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
public final class RefJavaUtilImpl extends RefJavaUtil {
|
||||
private static final Logger LOG = Logger.getInstance(RefJavaUtilImpl.class);
|
||||
@@ -660,8 +657,7 @@ public final class RefJavaUtilImpl extends RefJavaUtil {
|
||||
}
|
||||
}
|
||||
PsiClass aClass = javaMethod.getContainingClass();
|
||||
if (aClass == null ||
|
||||
GenericsHighlightUtil.getUnrelatedDefaultsMessage(aClass, Arrays.asList(superMethods), true) != null) {
|
||||
if (aClass == null || hasUnrelatedDefaults(aClass, Arrays.asList(superMethods))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -793,4 +789,10 @@ public final class RefJavaUtilImpl extends RefJavaUtil {
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
private static boolean hasUnrelatedDefaults(@NotNull PsiClass aClass,
|
||||
@NotNull Collection<? extends PsiMethod> overrideEquivalentSuperMethods) {
|
||||
return JavaPsiMethodUtil.getAbstractMethodToImplementWhenDefaultPresent(aClass, overrideEquivalentSuperMethods) != null ||
|
||||
JavaPsiMethodUtil.getUnrelatedSuperMethods(aClass, overrideEquivalentSuperMethods) != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ 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}
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user