mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
[java-highlighting] Refactor FunctionalInterfaceTest to avoid internals dependency; move completion stuff out of LambdaHighlightingUtil
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 4bd5447dd5fc2342f5e9b23e693f7683594470f5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0eeed91d22
commit
fd581aa354
@@ -6,43 +6,19 @@ 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.lang.jvm.JvmModifier;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.IncompleteModelUtil;
|
||||
import com.intellij.psi.impl.PsiClassImplUtil;
|
||||
import com.intellij.psi.util.MethodSignature;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.psi.LambdaUtil.getFunction;
|
||||
import static com.intellij.psi.LambdaUtil.getTargetMethod;
|
||||
import java.util.List;
|
||||
|
||||
public final class LambdaHighlightingUtil {
|
||||
private static final Logger LOG = Logger.getInstance(LambdaHighlightingUtil.class);
|
||||
|
||||
static @NlsContexts.DetailedDescription String checkInterfaceFunctional(@NotNull PsiClass psiClass) {
|
||||
return checkInterfaceFunctional(psiClass, JavaErrorBundle.message("target.type.of.a.lambda.conversion.must.be.an.interface"));
|
||||
}
|
||||
|
||||
static @NlsContexts.DetailedDescription String checkInterfaceFunctional(@NotNull PsiClass psiClass, @NotNull @Nls String interfaceNonFunctionalMessage) {
|
||||
if (psiClass instanceof PsiTypeParameter) return null; //should be logged as cyclic inference
|
||||
return switch (LambdaUtil.checkInterfaceFunctional(psiClass)) {
|
||||
case VALID -> null;
|
||||
case NOT_INTERFACE -> interfaceNonFunctionalMessage;
|
||||
case NO_ABSTRACT_METHOD -> JavaErrorBundle.message("no.target.method.found");
|
||||
case MULTIPLE_ABSTRACT_METHODS ->
|
||||
JavaErrorBundle.message("multiple.non.overriding.abstract.methods.found.in.interface.0", HighlightUtil.formatClass(psiClass));
|
||||
};
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkParametersCompatible(@NotNull PsiLambdaExpression expression,
|
||||
PsiParameter @NotNull [] methodParameters,
|
||||
@@ -71,84 +47,6 @@ public final class LambdaHighlightingUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean insertSemicolonAfter(@NotNull PsiLambdaExpression lambdaExpression) {
|
||||
return lambdaExpression.getBody() instanceof PsiCodeBlock || insertSemicolon(lambdaExpression.getParent());
|
||||
}
|
||||
|
||||
public static boolean insertSemicolon(PsiElement parent) {
|
||||
return !(parent instanceof PsiExpressionList) && !(parent instanceof PsiExpression);
|
||||
}
|
||||
|
||||
public static @NlsContexts.DetailedDescription String checkInterfaceFunctional(@NotNull PsiElement context, @NotNull PsiType functionalInterfaceType) {
|
||||
if (functionalInterfaceType instanceof PsiIntersectionType intersection) {
|
||||
Set<MethodSignature> signatures = new HashSet<>();
|
||||
Map<PsiType, MethodSignature> typeAndSignature = new HashMap<>();
|
||||
for (PsiType type : intersection.getConjuncts()) {
|
||||
if (checkInterfaceFunctional(context, type) == null) {
|
||||
MethodSignature signature = getFunction(PsiUtil.resolveClassInType(type));
|
||||
LOG.assertTrue(signature != null, type.getCanonicalText());
|
||||
signatures.add(signature);
|
||||
typeAndSignature.put(type, signature);
|
||||
}
|
||||
}
|
||||
PsiType baseType = typeAndSignature.entrySet().iterator().next().getKey();
|
||||
MethodSignature baseSignature = typeAndSignature.get(baseType);
|
||||
LambdaUtil.TargetMethodContainer baseContainer = getTargetMethod(baseType, baseSignature, baseType);
|
||||
if (baseContainer == null) {
|
||||
return JavaErrorBundle.message("no.target.method.found");
|
||||
}
|
||||
PsiMethod baseMethod = baseContainer.targetMethod;
|
||||
if (signatures.size() > 1) {
|
||||
for (Map.Entry<PsiType, MethodSignature> entry : typeAndSignature.entrySet()) {
|
||||
if (baseType == entry.getKey()) {
|
||||
continue;
|
||||
}
|
||||
LambdaUtil.TargetMethodContainer container = getTargetMethod(entry.getKey(), baseSignature, baseType);
|
||||
if (container == null) {
|
||||
return JavaErrorBundle.message("multiple.non.overriding.abstract.methods.found.in.0",
|
||||
functionalInterfaceType.getPresentableText());
|
||||
}
|
||||
if (!LambdaUtil.isLambdaSubsignature(baseMethod, baseType, container.targetMethod, entry.getKey()) ||
|
||||
!container.inheritor.hasModifier(JvmModifier.ABSTRACT)) {
|
||||
return JavaErrorBundle.message("multiple.non.overriding.abstract.methods.found.in.0",
|
||||
functionalInterfaceType.getPresentableText());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (PsiType type : intersection.getConjuncts()) {
|
||||
if (typeAndSignature.containsKey(type)) {
|
||||
continue;
|
||||
}
|
||||
LambdaUtil.TargetMethodContainer container = getTargetMethod(type, baseSignature, baseType);
|
||||
if (container == null) {
|
||||
continue;
|
||||
}
|
||||
PsiMethod inheritor = container.inheritor;
|
||||
PsiMethod target = container.targetMethod;
|
||||
if (!inheritor.hasModifier(JvmModifier.ABSTRACT) && LambdaUtil.isLambdaSubsignature(baseMethod, baseType, target, type)) {
|
||||
return JavaErrorBundle.message("no.target.method.found");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
|
||||
PsiClass aClass = resolveResult.getElement();
|
||||
if (aClass != null) {
|
||||
if (aClass instanceof PsiTypeParameter) return null; //should be logged as cyclic inference
|
||||
MethodSignature functionalMethod = getFunction(aClass);
|
||||
if (functionalMethod != null && functionalMethod.getTypeParameters().length > 0) {
|
||||
return JavaErrorBundle
|
||||
.message("target.method.is.generic");
|
||||
}
|
||||
return checkInterfaceFunctional(aClass);
|
||||
}
|
||||
if (IncompleteModelUtil.isIncompleteModel(context) &&
|
||||
IncompleteModelUtil.isUnresolvedClassType(functionalInterfaceType)) {
|
||||
return null;
|
||||
}
|
||||
return JavaErrorBundle.message("not.a.functional.interface", functionalInterfaceType.getPresentableText());
|
||||
}
|
||||
|
||||
// 15.13 | 15.27
|
||||
// It is a compile-time error if any class or interface mentioned by either U or the function type of U
|
||||
// is not accessible from the class or interface in which the method reference expression appears.
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
// 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;
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionMemory;
|
||||
import com.intellij.codeInsight.completion.CompletionUtil;
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.codeInsight.completion.JavaMethodCallElement;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.LambdaHighlightingUtil;
|
||||
import com.intellij.codeInsight.completion.*;
|
||||
import com.intellij.codeInsight.hints.ParameterHintsPass;
|
||||
import com.intellij.codeInsight.lookup.CommaTailType;
|
||||
import com.intellij.openapi.components.Service;
|
||||
@@ -427,8 +423,8 @@ public final class ExpectedTypesProvider {
|
||||
final PsiType functionalInterfaceType = lambdaExpression.getFunctionalInterfaceType();
|
||||
final PsiMethod scopeMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
|
||||
if (scopeMethod != null) {
|
||||
visitMethodReturnType(scopeMethod, LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType), LambdaHighlightingUtil
|
||||
.insertSemicolonAfter(lambdaExpression));
|
||||
visitMethodReturnType(scopeMethod, LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType),
|
||||
JavaCompletionUtil.insertSemicolonAfter(lambdaExpression));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +438,7 @@ public final class ExpectedTypesProvider {
|
||||
final PsiType functionalInterfaceType = ((PsiLambdaExpression)psiElement).getFunctionalInterfaceType();
|
||||
method = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
|
||||
type = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType);
|
||||
tailTypeSemicolon = LambdaHighlightingUtil.insertSemicolonAfter((PsiLambdaExpression)psiElement);
|
||||
tailTypeSemicolon = JavaCompletionUtil.insertSemicolonAfter((PsiLambdaExpression)psiElement);
|
||||
}
|
||||
else if (psiElement instanceof PsiMethod) {
|
||||
method = (PsiMethod)psiElement;
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.intellij.codeInsight.*;
|
||||
import com.intellij.codeInsight.completion.scope.CompletionElement;
|
||||
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.LambdaHighlightingUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.BringVariableIntoScopeFix;
|
||||
import com.intellij.codeInsight.lookup.*;
|
||||
import com.intellij.featureStatistics.FeatureUsageTracker;
|
||||
@@ -1002,7 +1001,7 @@ public final class JavaCompletionContributor extends CompletionContributor imple
|
||||
|
||||
static boolean shouldInsertSemicolon(PsiElement position) {
|
||||
return position.getParent() instanceof PsiMethodReferenceExpression &&
|
||||
LambdaHighlightingUtil.insertSemicolon(position.getParent().getParent());
|
||||
JavaCompletionUtil.insertSemicolon(position.getParent().getParent());
|
||||
}
|
||||
|
||||
private static @Unmodifiable List<LookupElement> processLabelReference(PsiLabelReference reference) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
|
||||
import com.intellij.codeInsight.completion.util.CompletionStyleUtil;
|
||||
import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.LambdaHighlightingUtil;
|
||||
import com.intellij.codeInsight.editorActions.TabOutScopesTracker;
|
||||
import com.intellij.codeInsight.guess.GuessManager;
|
||||
import com.intellij.codeInsight.lookup.*;
|
||||
@@ -467,6 +466,14 @@ public final class JavaCompletionUtil {
|
||||
return type instanceof PsiClassType ? ((PsiClassType)type).rawType() : type;
|
||||
}
|
||||
|
||||
public static boolean insertSemicolonAfter(@NotNull PsiLambdaExpression lambdaExpression) {
|
||||
return lambdaExpression.getBody() instanceof PsiCodeBlock || insertSemicolon(lambdaExpression.getParent());
|
||||
}
|
||||
|
||||
static boolean insertSemicolon(PsiElement parent) {
|
||||
return !(parent instanceof PsiExpressionList) && !(parent instanceof PsiExpression);
|
||||
}
|
||||
|
||||
static class JavaLookupElementHighlighter {
|
||||
private final @NotNull PsiElement myPlace;
|
||||
private final @Nullable VirtualFile myOriginalFile;
|
||||
@@ -882,7 +889,7 @@ public final class JavaCompletionUtil {
|
||||
if (parent instanceof PsiMethodCallExpression) {
|
||||
parent = parent.getParent();
|
||||
}
|
||||
if (parent instanceof PsiLambdaExpression && !LambdaHighlightingUtil.insertSemicolonAfter((PsiLambdaExpression)parent)) {
|
||||
if (parent instanceof PsiLambdaExpression lambda && !insertSemicolonAfter(lambda)) {
|
||||
insertAdditionalSemicolon = false;
|
||||
}
|
||||
if (parent instanceof PsiExpressionStatement && parent.getParent() instanceof PsiForStatement forStatement &&
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<error descr="Foo is not a functional interface">@FunctionalInterface</error>
|
||||
abstract class Foo { public abstract void run(); }
|
||||
@@ -1,3 +1,4 @@
|
||||
<error descr="Multiple non-overriding abstract methods found in Foo">@FunctionalInterface</error>
|
||||
interface Foo {
|
||||
int m();
|
||||
Object clone();
|
||||
|
||||
@@ -8,6 +8,6 @@ interface Y<T> {
|
||||
|
||||
class Test {
|
||||
{
|
||||
((X & <caret>Y<Integer>) (x) -> {}).m(1);
|
||||
((X & Y<Integer>) <error descr="Multiple non-overriding abstract methods found in X & Y<Integer>">(x) -> {}</error>).m<error descr="Ambiguous method call: both 'X.m(Integer)' and 'Y.m(Integer)' match">(1)</error>;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
@FunctionalInterface
|
||||
interface Foo { <T> T execute(Action<T> a); }
|
||||
interface Action<A>{}
|
||||
// Functional
|
||||
|
||||
class Use {
|
||||
// Functional but not lambda-compatible
|
||||
Foo foo = <error descr="Target method is generic">a -> null</error>;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
interface Bar { boolean equals(Object obj); }
|
||||
|
||||
@FunctionalInterface
|
||||
interface Foo extends Bar { int compare(String o1, String o2); }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@FunctionalInterface
|
||||
interface Foo<T> {
|
||||
boolean equals(Object obj);
|
||||
int compare(T o1, T o2);
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<error descr="No target method found">@FunctionalInterface</error>
|
||||
interface Foo { boolean equals(Object obj); }
|
||||
@@ -1 +1,2 @@
|
||||
@FunctionalInterface
|
||||
interface Foo { void run(); }
|
||||
@@ -1,4 +1,5 @@
|
||||
interface X { int m(Iterable<String> arg); }
|
||||
interface Y { int m(Iterable<Integer> arg); }
|
||||
<error descr="Multiple non-overriding abstract methods found in Foo">@FunctionalInterface</error>
|
||||
interface Foo extends X, Y {}
|
||||
// Not functional: No method has a subsignature of all abstract methods
|
||||
@@ -1,4 +1,5 @@
|
||||
interface X { int m(Iterable<String> arg, Class c); }
|
||||
interface Y { int m(Iterable arg, Class<?> c); }
|
||||
<error descr="Multiple non-overriding abstract methods found in Foo">@FunctionalInterface</error>
|
||||
interface Foo extends X, Y {}
|
||||
// Not functional: No method has a subsignature of all abstract methods
|
||||
@@ -1,4 +1,5 @@
|
||||
import java.util.*;
|
||||
interface X { int m(Iterable<String> arg); }
|
||||
interface Y { int m(Iterable<String> arg); }
|
||||
@FunctionalInterface
|
||||
interface Foo extends X, Y {}
|
||||
@@ -1,5 +1,11 @@
|
||||
interface Action<A>{}
|
||||
interface X { <T> T execute(Action<T> a); }
|
||||
interface Y { <S> S execute(Action<S> a); }
|
||||
@FunctionalInterface
|
||||
interface Foo extends X, Y {}
|
||||
// Functional: signatures are "the same"
|
||||
|
||||
class Use {
|
||||
// Functional: signatures are "the same"
|
||||
// but not lambda-compatible
|
||||
Foo foo = <error descr="Target method is generic">a -> null</error>;
|
||||
}
|
||||
@@ -2,4 +2,5 @@ interface Foo1<T, N extends Number> {
|
||||
void m(T arg);
|
||||
void m(N arg);
|
||||
}
|
||||
@FunctionalInterface
|
||||
interface Foo extends Foo1<Integer, Integer> {}
|
||||
@@ -1,4 +1,5 @@
|
||||
import java.util.*;
|
||||
interface X { Iterable m(Iterable<String> arg); }
|
||||
interface Y { Iterable<String> m(Iterable arg); }
|
||||
@FunctionalInterface
|
||||
interface Foo extends X, Y {}
|
||||
@@ -16,104 +16,76 @@
|
||||
package com.intellij.java.codeInsight.daemon.lambda;
|
||||
|
||||
import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.LambdaHighlightingUtil;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypeCastExpression;
|
||||
import com.intellij.psi.PsiTypeElement;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class FunctionalInterfaceTest extends LightDaemonAnalyzerTestCase {
|
||||
@NonNls static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface";
|
||||
|
||||
private void doTestFunctionalInterface(@Nullable String expectedErrorMessage) {
|
||||
String filePath = BASE_PATH + "/" + getTestName(false) + ".java";
|
||||
configureByFile(filePath);
|
||||
final PsiClass psiClass = getJavaFacade().findClass("Foo", GlobalSearchScope.projectScope(getProject()));
|
||||
assertNotNull("Class Foo not found", psiClass);
|
||||
|
||||
final String errorMessage = LambdaHighlightingUtil.checkInterfaceFunctional(psiClass,
|
||||
getJavaFacade().getElementFactory().createType(psiClass));
|
||||
assertEquals(expectedErrorMessage, errorMessage);
|
||||
private void doTest() {
|
||||
doTest(BASE_PATH + "/" + getTestName(false) + ".java", false, false);
|
||||
}
|
||||
|
||||
public void testSimple() {
|
||||
doTestFunctionalInterface(null);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNoMethods() {
|
||||
doTestFunctionalInterface("No target method found");
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testMultipleMethods() {
|
||||
doTestFunctionalInterface(null);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testMultipleMethodsInOne() {
|
||||
doTestFunctionalInterface(null);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testIntersectionOf2FunctionalTypesWithEqualSignatures() {
|
||||
doTestIntersection(null);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testIntersectionOf2FunctionalTypesWithEqualAfterSubstitutionSignatures() {
|
||||
doTestIntersection("Multiple non-overriding abstract methods found in X & Y<Integer>");
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testClone() {
|
||||
doTestFunctionalInterface("Multiple non-overriding abstract methods found in interface Foo");
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTwoMethodsSameSignature() {
|
||||
doTestFunctionalInterface(null);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTwoMethodsSubSignature() {
|
||||
doTestFunctionalInterface(null);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTwoMethodsNoSubSignature() {
|
||||
doTestFunctionalInterface("Multiple non-overriding abstract methods found in interface Foo");
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTwoMethodsNoSubSignature1() {
|
||||
doTestFunctionalInterface("Multiple non-overriding abstract methods found in interface Foo");
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTwoMethodsSameSubstSignature() {
|
||||
doTestFunctionalInterface(null);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testMethodWithTypeParam() {
|
||||
doTestFunctionalInterface("Target method is generic");
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTwoMethodsSameSignatureTypeParams() {
|
||||
doTestFunctionalInterface("Target method is generic");
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testAbstractClass() {
|
||||
doTestFunctionalInterface("Target type of a lambda conversion must be an interface");
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testIntersectionTypeWithSameBaseInterfaceInConjuncts() {
|
||||
doTestIntersection(null);
|
||||
}
|
||||
|
||||
private void doTestIntersection(final String expectedMessage) {
|
||||
String filePath = BASE_PATH + "/" + getTestName(false) + ".java";
|
||||
configureByFile(filePath);
|
||||
final PsiTypeCastExpression castExpression =
|
||||
PsiTreeUtil.getParentOfType(getFile().findElementAt(getEditor().getCaretModel().getOffset()), PsiTypeCastExpression.class);
|
||||
assertNotNull(castExpression);
|
||||
final PsiTypeElement castTypeElement = castExpression.getCastType();
|
||||
assertNotNull(castTypeElement);
|
||||
final PsiType type = castTypeElement.getType();
|
||||
final String errorMessage = LambdaHighlightingUtil.checkInterfaceFunctional(castExpression, type);
|
||||
assertEquals(expectedMessage, errorMessage);
|
||||
doTest();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user