From 5b5abb8e13a6c0c7e016a586daff90fea9a1b0cd Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 3 Feb 2025 14:00:20 +0100 Subject: [PATCH] [java-highlighting] Lambda-related type errors migrated Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 1fb0b4c35b4db63d8c15cb392098380d1014ebf2 --- .../JavaCompilationErrorBundle.properties | 12 +- .../highlighting/AnnotationChecker.java | 9 +- .../highlighting/FunctionChecker.java | 194 ++++++++++++++++++ .../highlighting/JavaErrorVisitor.java | 65 ++++++ .../highlighting/errors/JavaErrorKinds.java | 40 +++- .../JavaIncompatibleTypeErrorContext.java | 9 +- .../impl/analysis/HighlightClassUtil.java | 11 +- .../impl/analysis/HighlightMethodUtil.java | 57 ----- .../daemon/impl/analysis/HighlightUtil.java | 25 +-- .../impl/analysis/HighlightVisitorImpl.java | 86 +------- .../impl/analysis/JavaErrorFixProvider.java | 27 ++- .../impl/analysis/LambdaHighlightingUtil.java | 31 --- .../analysis/PatternHighlightingModel.java | 2 +- ...atternsInSwitchBlockHighlightingModel.java | 8 +- .../quickfix/AdjustFunctionContextFix.java | 24 ++- .../SealedFunctionalInterface.java | 8 +- .../FunctionalInterfaceAnnotation.java | 2 +- .../GenericNotGenericInterfaceMethod.java | 2 +- ...alInterfaceShouldHaveExactlyOneMethod.java | 4 +- ...tsWhenParentMethodIsDuringCalculation.java | 2 +- ...mbdaWithInferenceVariableAsTargetType.java | 2 +- 21 files changed, 373 insertions(+), 247 deletions(-) create mode 100644 java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/FunctionChecker.java diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index eeba7be4f054..d495bc63a40d 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -44,8 +44,18 @@ annotation.container.abstract=Container annotation ''{0}'' does not have a defau lambda.not.a.functional.interface={0} is not a functional interface lambda.no.target.method.found=No target method found -lambda.multiple.sam.candidates=Multiple non-overriding abstract methods found in interface {0} +lambda.multiple.sam.candidates=Multiple non-overriding abstract methods found in {0} +lambda.sam.generic=Target method is generic lambda.sealed.functional.interface=Functional interface can't be declared as 'sealed' +lambda.not.expected=Lambda expression not expected here +lambda.parameters.inconsistent.var=Cannot mix 'var' and explicitly typed parameters in lambda expression +lambda.sealed=Lambda cannot implement a sealed interface +lambda.type.inference.failure=Cannot infer functional interface type +lambda.inference.error={0} +lambda.return.type.error={0} +lambda.target.not.interface=Target type of a lambda conversion must be an interface + +method.reference.sealed=Method reference cannot implement a sealed interface safe.varargs.on.record.component=@SafeVarargs is not allowed on a record component safe.varargs.on.fixed.arity=@SafeVarargs is not allowed on methods with fixed arity diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java index 4829aa6dad95..bef77efc1cbc 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java @@ -371,13 +371,14 @@ final class AnnotationChecker { if (owner instanceof PsiModifierList list) { PsiElement parent = list.getParent(); if (parent instanceof PsiClass psiClass) { + PsiClassType type = myVisitor.factory().createType(psiClass); switch (LambdaUtil.checkInterfaceFunctional(psiClass)) { - case NOT_INTERFACE -> myVisitor.report(JavaErrorKinds.LAMBDA_NOT_FUNCTIONAL_INTERFACE.create(annotation, psiClass)); - case NO_ABSTRACT_METHOD -> myVisitor.report(JavaErrorKinds.LAMBDA_NO_TARGET_METHOD.create(annotation, psiClass)); - case MULTIPLE_ABSTRACT_METHODS -> myVisitor.report(JavaErrorKinds.LAMBDA_MULTIPLE_TARGET_METHODS.create(annotation, psiClass)); + case NOT_INTERFACE -> myVisitor.report(JavaErrorKinds.LAMBDA_NOT_FUNCTIONAL_INTERFACE.create(annotation, type)); + case NO_ABSTRACT_METHOD -> myVisitor.report(JavaErrorKinds.LAMBDA_NO_TARGET_METHOD.create(annotation, type)); + case MULTIPLE_ABSTRACT_METHODS -> myVisitor.report(JavaErrorKinds.LAMBDA_MULTIPLE_TARGET_METHODS.create(annotation, type)); } if (psiClass.hasModifierProperty(PsiModifier.SEALED)) { - myVisitor.report(JavaErrorKinds.LAMBDA_FUNCTIONAL_INTERFACE_SEALED.create(annotation, psiClass)); + myVisitor.report(JavaErrorKinds.FUNCTIONAL_INTERFACE_SEALED.create(annotation, psiClass)); } } } diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/FunctionChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/FunctionChecker.java new file mode 100644 index 000000000000..31f6584ebcab --- /dev/null +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/FunctionChecker.java @@ -0,0 +1,194 @@ +// 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.core.JavaPsiBundle; +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.lang.jvm.JvmModifier; +import com.intellij.psi.*; +import com.intellij.psi.impl.IncompleteModelUtil; +import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession; +import com.intellij.psi.infos.MethodCandidateInfo; +import com.intellij.psi.util.MethodSignature; +import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.intellij.psi.LambdaUtil.getFunction; +import static com.intellij.psi.LambdaUtil.getTargetMethod; + +final class FunctionChecker { + private final @NotNull JavaErrorVisitor myVisitor; + + FunctionChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; } + + void checkExtendsSealedClass(@NotNull PsiFunctionalExpression expression, @NotNull PsiType functionalInterfaceType) { + PsiClass functionalInterface = PsiUtil.resolveClassInClassTypeOnly(functionalInterfaceType); + if (functionalInterface == null || !functionalInterface.hasModifierProperty(PsiModifier.SEALED)) return; + if (expression instanceof PsiLambdaExpression lambda) { + myVisitor.report(JavaErrorKinds.LAMBDA_SEALED.create(lambda)); + } + else if (expression instanceof PsiMethodReferenceExpression methodReference) { + myVisitor.report(JavaErrorKinds.METHOD_REFERENCE_SEALED.create(methodReference)); + } + } + + void checkInterfaceFunctional(@NotNull PsiFunctionalExpression context, PsiType functionalInterfaceType) { + JavaCompilationError error = getFunctionalInterfaceError(context, functionalInterfaceType); + if (error != null) { + myVisitor.report(error); + } + } + + private static @Nullable JavaCompilationError getFunctionalInterfaceError( + @NotNull PsiFunctionalExpression context, PsiType functionalInterfaceType) { + if (functionalInterfaceType instanceof PsiIntersectionType intersection) { + Set signatures = new HashSet<>(); + Map typeAndSignature = new HashMap<>(); + for (PsiType type : intersection.getConjuncts()) { + if (getFunctionalInterfaceError(context, type) == null) { + MethodSignature signature = getFunction(PsiUtil.resolveClassInType(type)); + 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 JavaErrorKinds.LAMBDA_NO_TARGET_METHOD.create(context, baseType); + } + PsiMethod baseMethod = baseContainer.targetMethod; + if (signatures.size() > 1) { + for (Map.Entry entry : typeAndSignature.entrySet()) { + if (baseType == entry.getKey()) { + continue; + } + LambdaUtil.TargetMethodContainer container = getTargetMethod(entry.getKey(), baseSignature, baseType); + if (container == null) { + return JavaErrorKinds.LAMBDA_MULTIPLE_TARGET_METHODS.create(context, functionalInterfaceType); + } + if (!LambdaUtil.isLambdaSubsignature(baseMethod, baseType, container.targetMethod, entry.getKey()) || + !container.inheritor.hasModifier(JvmModifier.ABSTRACT)) { + return JavaErrorKinds.LAMBDA_MULTIPLE_TARGET_METHODS.create(context, functionalInterfaceType); + } + } + } + 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 JavaErrorKinds.LAMBDA_NO_TARGET_METHOD.create(context, type); + } + } + 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 JavaErrorKinds.LAMBDA_SAM_GENERIC.create(context); + } + return switch (LambdaUtil.checkInterfaceFunctional(aClass)) { + case VALID -> null; + case NOT_INTERFACE -> JavaErrorKinds.LAMBDA_TARGET_NOT_INTERFACE.create(context, functionalInterfaceType); + case NO_ABSTRACT_METHOD -> JavaErrorKinds.LAMBDA_NO_TARGET_METHOD.create(context, functionalInterfaceType); + case MULTIPLE_ABSTRACT_METHODS -> JavaErrorKinds.LAMBDA_MULTIPLE_TARGET_METHODS.create(context, functionalInterfaceType); + }; + } + if (IncompleteModelUtil.isIncompleteModel(context) && + IncompleteModelUtil.isUnresolvedClassType(functionalInterfaceType)) { + return null; + } + return JavaErrorKinds.LAMBDA_NOT_FUNCTIONAL_INTERFACE.create(context, functionalInterfaceType); + } + + private static boolean hasExplicitType(@NotNull PsiParameter parameter) { + PsiTypeElement typeElement = parameter.getTypeElement(); + return typeElement != null && !typeElement.isInferredType(); + } + + void checkConsistentParameterDeclaration(@NotNull PsiLambdaExpression expression) { + PsiParameterList parameterList = expression.getParameterList(); + PsiParameter[] parameters = parameterList.getParameters(); + if (parameters.length < 2) return; + boolean hasExplicitParameterTypes = hasExplicitType(parameters[0]); + for (int i = 1; i < parameters.length; i++) { + if (hasExplicitParameterTypes != hasExplicitType(parameters[i])) { + myVisitor.report(JavaErrorKinds.LAMBDA_PARAMETERS_INCONSISTENT_VAR.create(parameterList)); + } + } + } + + private static boolean favorParentReport(@NotNull PsiCall methodCall, @NotNull String errorMessage) { + // Parent resolve failed as well, and it's likely more informative. + // Suppress this error to allow reporting from parent + return (errorMessage.equals(JavaPsiBundle.message("error.incompatible.type.failed.to.resolve.argument")) || + errorMessage.equals(JavaPsiBundle.message("error.incompatible.type.declaration.for.the.method.reference.not.found"))) && + hasSurroundingInferenceError(methodCall); + } + + static boolean hasSurroundingInferenceError(@NotNull PsiElement context) { + PsiCall topCall = LambdaUtil.treeWalkUp(context); + if (topCall == null) return false; + while (context != topCall) { + context = context.getParent(); + if (context instanceof PsiMethodCallExpression call && + call.resolveMethodGenerics() instanceof MethodCandidateInfo info && + info.getInferenceErrorMessage() != null) { + // Possibly inapplicable method reference due to the surrounding call inference failure: + // suppress method reference error in order to display more relevant inference error. + return true; + } + } + return false; + } + + void checkLambdaInferenceFailure(@NotNull PsiCall methodCall, + @NotNull MethodCandidateInfo resolveResult, + @NotNull PsiLambdaExpression lambdaExpression) { + String errorMessage = resolveResult.getInferenceErrorMessage(); + if (errorMessage == null) return; + if (favorParentReport(methodCall, errorMessage)) return; + PsiMethod method = resolveResult.getElement(); + PsiType expectedTypeByParent = InferenceSession.getTargetTypeByParent(methodCall); + PsiType actualType = + methodCall instanceof PsiExpression ? ((PsiExpression)methodCall.copy()).getType() : + resolveResult.getSubstitutor(false).substitute(method.getReturnType()); + if (expectedTypeByParent != null && actualType != null && !expectedTypeByParent.isAssignableFrom(actualType)) { + myVisitor.report(JavaErrorKinds.TYPE_INCOMPATIBLE.create( + methodCall, new JavaIncompatibleTypeErrorContext(expectedTypeByParent, actualType, errorMessage))); + } + else { + myVisitor.report(JavaErrorKinds.LAMBDA_INFERENCE_ERROR.create(lambdaExpression, resolveResult)); + } + } + + static boolean lambdaParametersMentionTypeParameter(@NotNull PsiType functionalInterfaceType, + @NotNull Set parameters) { + if (!(functionalInterfaceType instanceof PsiClassType classType)) return false; + PsiSubstitutor substitutor = classType.resolveGenerics().getSubstitutor(); + PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); + if (method == null) return false; + for (PsiParameter parameter : method.getParameterList().getParameters()) { + if (PsiTypesUtil.mentionsTypeParameters(substitutor.substitute(parameter.getType()), parameters)) return true; + } + return false; + } +} 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 3ea2f105f76d..e145c09cb964 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 @@ -23,10 +23,13 @@ import com.intellij.psi.util.*; import com.intellij.refactoring.util.RefactoringChangeUtil; import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import static java.util.Objects.*; @@ -49,6 +52,7 @@ final class JavaErrorVisitor extends JavaElementVisitor { final @NotNull TypeChecker myTypeChecker = new TypeChecker(this); final @NotNull MethodChecker myMethodChecker = new MethodChecker(this); private final @NotNull ReceiverChecker myReceiverChecker = new ReceiverChecker(this); + private final @NotNull FunctionChecker myFunctionChecker = new FunctionChecker(this); final @NotNull PatternChecker myPatternChecker = new PatternChecker(this); final @NotNull ModifierChecker myModifierChecker = new ModifierChecker(this); final @NotNull ExpressionChecker myExpressionChecker = new ExpressionChecker(this); @@ -367,6 +371,9 @@ final class JavaErrorVisitor extends JavaElementVisitor { myExpressionChecker.checkStaticInterfaceCallQualifier(expression, result, containingClass); } } + if (functionalInterfaceType != null) { + if (!hasErrorResults()) myFunctionChecker.checkExtendsSealedClass(expression, functionalInterfaceType); + } } @Override @@ -727,6 +734,64 @@ final class JavaErrorVisitor extends JavaElementVisitor { } } + @Override + public void visitLambdaExpression(@NotNull PsiLambdaExpression expression) { + checkFeature(expression, JavaFeature.LAMBDA_EXPRESSIONS); + PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); + if (toReportFunctionalExpressionProblemOnParent(parent)) return; + if (!hasErrorResults() && !LambdaUtil.isValidLambdaContext(parent)) { + report(JavaErrorKinds.LAMBDA_NOT_EXPECTED.create(expression)); + } + if (!hasErrorResults()) myFunctionChecker.checkConsistentParameterDeclaration(expression); + PsiType functionalInterfaceType = null; + if (!hasErrorResults()) { + functionalInterfaceType = expression.getFunctionalInterfaceType(); + if (functionalInterfaceType != null) { + myFunctionChecker.checkExtendsSealedClass(expression, functionalInterfaceType); + if (!hasErrorResults()) myFunctionChecker.checkInterfaceFunctional(expression, functionalInterfaceType); + } + else if (LambdaUtil.getFunctionalInterfaceType(expression, true) != null) { + report(JavaErrorKinds.LAMBDA_TYPE_INFERENCE_FAILURE.create(expression)); + } + } + if (!hasErrorResults() && functionalInterfaceType != null) { + PsiCallExpression callExpression = parent instanceof PsiExpressionList && parent.getParent() instanceof PsiCallExpression ? + (PsiCallExpression)parent.getParent() : null; + MethodCandidateInfo parentCallResolveResult = + callExpression != null ? ObjectUtils.tryCast(callExpression.resolveMethodGenerics(), MethodCandidateInfo.class) : null; + String parentInferenceErrorMessage = parentCallResolveResult != null ? parentCallResolveResult.getInferenceErrorMessage() : null; + PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType); + Map returnErrors = null; + Set parentTypeParameters = + parentCallResolveResult == null ? Set.of() : Set.of(parentCallResolveResult.getElement().getTypeParameters()); + // If return type of the lambda was not fully inferred and lambda parameters don't mention the same type, + // it means that lambda is not responsible for inference failure and blaming it would be unreasonable. + boolean skipReturnCompatibility = parentCallResolveResult != null && + PsiTypesUtil.mentionsTypeParameters(returnType, parentTypeParameters) + && !FunctionChecker.lambdaParametersMentionTypeParameter(functionalInterfaceType, parentTypeParameters); + if (!skipReturnCompatibility) { + returnErrors = LambdaUtil.checkReturnTypeCompatible(expression, returnType); + } + if (parentInferenceErrorMessage != null && (returnErrors == null || !returnErrors.containsValue(parentInferenceErrorMessage))) { + if (returnErrors == null) return; + myFunctionChecker.checkLambdaInferenceFailure(callExpression, parentCallResolveResult, expression); + } + else if (returnErrors != null && !PsiTreeUtil.hasErrorElements(expression)) { + returnErrors.forEach((expr, message) -> report(JavaErrorKinds.LAMBDA_RETURN_TYPE_ERROR.create(expr, message))); + } + } + + } + + /** + * @return true for {@code functional_expression;} or {@code var l = functional_expression;} + */ + private static boolean toReportFunctionalExpressionProblemOnParent(@Nullable PsiElement parent) { + if (parent instanceof PsiLocalVariable variable) { + return variable.getTypeElement().isInferredType(); + } + return parent instanceof PsiExpressionStatement && !(parent.getParent() instanceof PsiSwitchLabeledRuleStatement); + } @Override public void visitTypeCastExpression(@NotNull PsiTypeCastExpression expression) { 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 abbba19f31dd..246ee9d93018 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 @@ -12,6 +12,7 @@ import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.io.FileUtil; import com.intellij.pom.java.JavaFeature; import com.intellij.psi.*; +import com.intellij.psi.infos.MethodCandidateInfo; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.*; import com.intellij.refactoring.util.RefactoringChangeUtil; @@ -114,19 +115,36 @@ public final class JavaErrorKinds { public static final Simple ANNOTATION_PERMITS = error(PsiReferenceList.class, "annotation.permits") .withAnchor(PsiReferenceList::getFirstChild); - // Can be anchored on @FunctionalInterface annotation or at call site - public static final Parameterized LAMBDA_NOT_FUNCTIONAL_INTERFACE = - parameterized(PsiElement.class, PsiClass.class, "lambda.not.a.functional.interface") - .withRawDescription((element, aClass) -> message("lambda.not.a.functional.interface", aClass.getName())); - // Can be anchored on @FunctionalInterface annotation or at call site - public static final Parameterized LAMBDA_NO_TARGET_METHOD = + // Can be anchored on @FunctionalInterface annotation or at a call site + public static final Parameterized LAMBDA_NOT_FUNCTIONAL_INTERFACE = + parameterized(PsiElement.class, PsiType.class, "lambda.not.a.functional.interface") + .withRawDescription((element, type) -> message("lambda.not.a.functional.interface", formatType(type))); + // Can be anchored on @FunctionalInterface annotation or at a call site + public static final Parameterized LAMBDA_NO_TARGET_METHOD = parameterized("lambda.no.target.method.found"); - // Can be anchored on @FunctionalInterface annotation or at call site - public static final Parameterized LAMBDA_MULTIPLE_TARGET_METHODS = - parameterized(PsiElement.class, PsiClass.class, "lambda.multiple.sam.candidates") - .withRawDescription((psi, aClass) -> message("lambda.multiple.sam.candidates", aClass.getName())); - public static final Parameterized LAMBDA_FUNCTIONAL_INTERFACE_SEALED = + // Can be anchored on @FunctionalInterface annotation or at a call site + public static final Parameterized LAMBDA_MULTIPLE_TARGET_METHODS = + parameterized(PsiElement.class, PsiType.class, "lambda.multiple.sam.candidates") + .withRawDescription((psi, type) -> message("lambda.multiple.sam.candidates", + TypeConversionUtil.erasure(type).getPresentableText())); + public static final Parameterized FUNCTIONAL_INTERFACE_SEALED = parameterized("lambda.sealed.functional.interface"); + public static final Simple LAMBDA_NOT_EXPECTED = error("lambda.not.expected"); + public static final Simple LAMBDA_PARAMETERS_INCONSISTENT_VAR = error("lambda.parameters.inconsistent.var"); + public static final Simple LAMBDA_SEALED = error("lambda.sealed"); + public static final Simple LAMBDA_TYPE_INFERENCE_FAILURE = error("lambda.type.inference.failure"); + public static final Simple LAMBDA_SAM_GENERIC = error("lambda.sam.generic"); + public static final Parameterized LAMBDA_TARGET_NOT_INTERFACE = + parameterized("lambda.target.not.interface"); + public static final Parameterized LAMBDA_INFERENCE_ERROR = + parameterized(PsiLambdaExpression.class, MethodCandidateInfo.class, "lambda.inference.error") + .withRawDescription((psi, candidate) -> message("lambda.inference.error", candidate.getInferenceErrorMessage())); + public static final Parameterized LAMBDA_RETURN_TYPE_ERROR = + parameterized(PsiElement.class, String.class, "lambda.return.type.error") + .withRawDescription((psi, message) -> message("lambda.return.type.error", message)); + + public static final Simple METHOD_REFERENCE_SEALED = error("method.reference.sealed"); + public static final Parameterized> ANNOTATION_NOT_APPLICABLE = error(PsiAnnotation.class, "annotation.not.applicable").<@NotNull List>parameterized() .withValidator((annotation, types) -> { diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaIncompatibleTypeErrorContext.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaIncompatibleTypeErrorContext.java index 68143b21e087..bb6abe3e0287 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaIncompatibleTypeErrorContext.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaIncompatibleTypeErrorContext.java @@ -9,7 +9,6 @@ import com.intellij.openapi.util.text.HtmlChunk; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.xml.util.XmlStringUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -49,15 +48,11 @@ public record JavaIncompatibleTypeErrorContext(@NotNull PsiType lType, @Nullable } @NotNull HtmlChunk createTooltip() { - return createTooltip( - reasonForIncompatibleTypes == null ? getReasonForIncompatibleTypes() : XmlStringUtil.escapeString(reasonForIncompatibleTypes)); - } - - private @NotNull HtmlChunk createTooltip(@NotNull @Nls String reason) { + String reason = reasonForIncompatibleTypes == null ? getReasonForIncompatibleTypes() : reasonForIncompatibleTypes; HtmlChunk styledReason = reason.isEmpty() ? empty() : tag("table").child( tag("tr").child( - tag("td").style("padding-top: 10px; padding-left: 4px;").addRaw(reason))); + tag("td").style("padding-top: 10px; padding-left: 4px;").addText(reason))); IncompatibleTypesTooltipComposer tooltipComposer = (lTypeString, lTypeArguments, rTypeString, rTypeArguments) -> createRequiredProvidedTypeMessage(lTypeString, lTypeArguments, rTypeString, rTypeArguments, styledReason); return createIncompatibleTypesTooltip(tooltipComposer); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java index c508f5bab053..eb0cb5b1738a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java @@ -101,16 +101,7 @@ public final class HighlightClassUtil { return null; } - static HighlightInfo.Builder checkExtendsSealedClass(@NotNull PsiFunctionalExpression expression, @NotNull PsiType functionalInterfaceType) { - PsiClass functionalInterface = PsiUtil.resolveClassInClassTypeOnly(functionalInterfaceType); - if (functionalInterface == null || !functionalInterface.hasModifierProperty(PsiModifier.SEALED)) return null; - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(expression) - .descriptionAndTooltip(JavaErrorBundle.message("sealed.cannot.be.functional.interface")) - ; - } - - public static HighlightInfo.Builder checkExtendsSealedClass(@NotNull PsiClass aClass, + public static HighlightInfo.Builder checkExtendsSealedClass(@NotNull PsiClass aClass, @NotNull PsiClass superClass, @NotNull PsiJavaCodeReferenceElement elementToHighlight) { if (superClass.hasModifierProperty(PsiModifier.SEALED)) { 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 d26902a8fe59..ec47660b931a 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 @@ -8,17 +8,14 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.codeInspection.LocalQuickFixOnPsiElementAsIntentionAdapter; -import com.intellij.core.JavaPsiBundle; import com.intellij.openapi.util.NlsContexts; import com.intellij.openapi.util.TextRange; import com.intellij.pom.java.JavaFeature; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.light.LightRecordMethod; -import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession; import com.intellij.psi.infos.MethodCandidateInfo; import com.intellij.psi.util.*; -import com.intellij.xml.util.XmlStringUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,9 +23,6 @@ import org.jetbrains.annotations.Nullable; import java.text.MessageFormat; import java.util.Collection; import java.util.List; -import java.util.Set; - -import static com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil.asConsumer; public final class HighlightMethodUtil { @@ -165,57 +159,6 @@ public final class HighlightMethodUtil { return errorResult; } - static HighlightInfo.Builder createIncompatibleTypeHighlightInfo(@NotNull PsiCall methodCall, - @NotNull MethodCandidateInfo resolveResult, - @NotNull PsiElement elementToHighlight) { - String errorMessage = resolveResult.getInferenceErrorMessage(); - if (errorMessage == null) return null; - if (favorParentReport(methodCall, errorMessage)) return null; - PsiMethod method = resolveResult.getElement(); - HighlightInfo.Builder builder; - PsiType expectedTypeByParent = InferenceSession.getTargetTypeByParent(methodCall); - PsiType actualType = - methodCall instanceof PsiExpression ? ((PsiExpression)methodCall.copy()).getType() : - resolveResult.getSubstitutor(false).substitute(method.getReturnType()); - TextRange fixRange = getFixRange(elementToHighlight); - if (expectedTypeByParent != null && actualType != null && !expectedTypeByParent.isAssignableFrom(actualType)) { - builder = HighlightUtil.createIncompatibleTypeHighlightInfo( - expectedTypeByParent, actualType, fixRange, 0, XmlStringUtil.escapeString(errorMessage)); - if (methodCall instanceof PsiExpression) { - AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(asConsumer(builder), - (PsiExpression)methodCall, expectedTypeByParent, actualType); - } - PsiElement parent = PsiUtil.skipParenthesizedExprUp(methodCall.getParent()); - if (parent instanceof PsiReturnStatement) { - PsiParameterListOwner context = PsiTreeUtil.getParentOfType(parent, PsiMethod.class, PsiLambdaExpression.class); - if (context instanceof PsiMethod containingMethod) { - HighlightUtil.registerReturnTypeFixes(builder, containingMethod, actualType); - } - } else if (parent instanceof PsiLocalVariable var) { - HighlightFixUtil.registerChangeVariableTypeFixes(var, actualType, builder); - } - } - else { - builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip(errorMessage).range(fixRange); - } - if (methodCall instanceof PsiMethodCallExpression callExpression) { - HighlightFixUtil.registerMethodCallIntentions(asConsumer(builder), callExpression, callExpression.getArgumentList()); - if (!PsiTypesUtil.mentionsTypeParameters(actualType, Set.of(method.getTypeParameters()))) { - HighlightFixUtil.registerMethodReturnFixAction(asConsumer(builder), resolveResult, methodCall); - } - HighlightFixUtil.registerTargetTypeFixesBasedOnApplicabilityInference(callExpression, resolveResult, method, asConsumer(builder)); - } - return builder; - } - - private static boolean favorParentReport(@NotNull PsiCall methodCall, @NotNull String errorMessage) { - // Parent resolve failed as well, and it's likely more informative. - // Suppress this error to allow reporting from parent - return (errorMessage.equals(JavaPsiBundle.message("error.incompatible.type.failed.to.resolve.argument")) || - errorMessage.equals(JavaPsiBundle.message("error.incompatible.type.declaration.for.the.method.reference.not.found"))) && - hasSurroundingInferenceError(methodCall); - } - static HighlightInfo.Builder checkAbstractMethodInConcreteClass(@NotNull PsiMethod method, @NotNull PsiElement elementToHighlight) { HighlightInfo.Builder errorResult = null; PsiClass aClass = method.getContainingClass(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index a327eb36809b..9060e486a3f4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -98,7 +98,7 @@ public final class HighlightUtil { IncompleteModelUtil.isPotentiallyConvertible(lType, expression)) { return null; } - HighlightInfo.Builder highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange, 0); + HighlightInfo.Builder highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange); AddTypeArgumentsConditionalFix.register(asConsumer(highlightInfo), expression, lType); if (expression != null) { AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(asConsumer(highlightInfo), expression, lType, rType); @@ -127,11 +127,6 @@ public final class HighlightUtil { }; } - static void registerReturnTypeFixes(@NotNull HighlightInfo.Builder info, @NotNull PsiMethod method, @NotNull PsiType expectedReturnType) { - IntentionAction action = getFixFactory().createMethodReturnFix(method, expectedReturnType, true, true); - info.registerFix(action, null, null, null, null); - } - public static @NotNull @NlsContexts.DetailedDescription String getUnhandledExceptionsDescriptor(@NotNull Collection unhandled) { return JavaErrorBundle.message("unhandled.exceptions", formatTypes(unhandled), unhandled.size()); } @@ -775,23 +770,15 @@ public final class HighlightUtil { } // cannot derive type of conditional expression // elseType will never be cast-able to thenType, so no quick fix here - return createIncompatibleTypeHighlightInfo(thenType, type, expression.getTextRange(), 0); + return createIncompatibleTypeHighlightInfo(thenType, type, expression.getTextRange()); } return null; } - static @NotNull HighlightInfo.Builder createIncompatibleTypeHighlightInfo(@NotNull PsiType lType, + static HighlightInfo.@NotNull Builder createIncompatibleTypeHighlightInfo(@NotNull PsiType lType, @Nullable PsiType rType, - @NotNull TextRange textRange, - int navigationShift) { - return createIncompatibleTypeHighlightInfo(lType, rType, textRange, navigationShift, getReasonForIncompatibleTypes(rType)); - } - - static @NotNull HighlightInfo.Builder createIncompatibleTypeHighlightInfo(@NotNull PsiType lType, - @Nullable PsiType rType, - @NotNull TextRange textRange, - int navigationShift, - @NotNull String reason) { + @NotNull TextRange textRange) { + @NotNull String reason = getReasonForIncompatibleTypes(rType); PsiType baseLType = PsiUtil.convertAnonymousToBaseType(lType); PsiType baseRType = rType == null ? null : PsiUtil.convertAnonymousToBaseType(rType); boolean leftAnonymous = PsiUtil.resolveClassInClassTypeOnly(lType) instanceof PsiAnonymousClass; @@ -811,7 +798,7 @@ public final class HighlightUtil { .range(textRange) .description(description) .escapedToolTip(toolTip) - .navigationShift(navigationShift); + .navigationShift(0); } static HighlightInfo.Builder checkExtraSemicolonBetweenImportStatements(@NotNull PsiJavaToken token, 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 b272d1ec8794..1eb813d9e357 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 @@ -52,7 +52,6 @@ import com.intellij.util.ui.JBUI; import com.intellij.util.ui.NamedColorUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,7 +60,6 @@ import java.util.function.Consumer; import java.util.function.Function; import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.*; -import static com.intellij.util.ObjectUtils.tryCast; import static java.util.Objects.*; // java highlighting: problems in java code like unresolved/incompatible symbols/methods etc. @@ -105,10 +103,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh return myHasError; } - private @Contract(pure = true) @NotNull Project getProject() { - return myHolder.getProject(); - } - // element -> a constructor inside which this element is contained private PsiMethod findSurroundingConstructor(@NotNull PsiElement entry) { PsiMethod result = null; @@ -276,86 +270,15 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh @Override public void visitLambdaExpression(@NotNull PsiLambdaExpression expression) { - add(checkFeature(expression, JavaFeature.LAMBDA_EXPRESSIONS)); + visitElement(expression); PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); - if (toReportFunctionalExpressionProblemOnParent(parent)) return; - if (!hasErrorResults() && !LambdaUtil.isValidLambdaContext(parent)) { - add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression) - .descriptionAndTooltip(JavaErrorBundle.message("lambda.expression.not.expected"))); - } - - if (!hasErrorResults()) add(LambdaHighlightingUtil.checkConsistentParameterDeclaration(expression)); + if (hasErrorResults() || toReportFunctionalExpressionProblemOnParent(parent)) return; PsiType functionalInterfaceType = null; if (!hasErrorResults()) { functionalInterfaceType = expression.getFunctionalInterfaceType(); if (functionalInterfaceType != null) { - add(HighlightClassUtil.checkExtendsSealedClass(expression, functionalInterfaceType)); - if (!hasErrorResults()) { - String notFunctionalMessage = LambdaHighlightingUtil.checkInterfaceFunctional(expression, functionalInterfaceType); - if (notFunctionalMessage != null) { - add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression) - .descriptionAndTooltip(notFunctionalMessage)); - } - else { - add(LambdaHighlightingUtil.checkFunctionalInterfaceTypeAccessible(myFile.getProject(), expression, functionalInterfaceType)); - } - } - } - else if (LambdaUtil.getFunctionalInterfaceType(expression, true) != null) { - add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip( - JavaErrorBundle.message("cannot.infer.functional.interface.type"))); - } - } - - if (!hasErrorResults() && functionalInterfaceType != null) { - PsiCallExpression callExpression = parent instanceof PsiExpressionList && parent.getParent() instanceof PsiCallExpression ? - (PsiCallExpression)parent.getParent() : null; - MethodCandidateInfo parentCallResolveResult = - callExpression != null ? tryCast(callExpression.resolveMethodGenerics(), MethodCandidateInfo.class) : null; - String parentInferenceErrorMessage = parentCallResolveResult != null ? parentCallResolveResult.getInferenceErrorMessage() : null; - PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType); - Map returnErrors = null; - Set parentTypeParameters = - parentCallResolveResult == null ? Set.of() : Set.of(parentCallResolveResult.getElement().getTypeParameters()); - // If return type of the lambda was not fully inferred and lambda parameters don't mention the same type, - // it means that lambda is not responsible for inference failure and blaming it would be unreasonable. - boolean skipReturnCompatibility = parentCallResolveResult != null && - PsiTypesUtil.mentionsTypeParameters(returnType, parentTypeParameters) - && !LambdaHighlightingUtil.lambdaParametersMentionTypeParameter(functionalInterfaceType, parentTypeParameters); - if (!skipReturnCompatibility) { - returnErrors = LambdaUtil.checkReturnTypeCompatible(expression, returnType); - } - if (parentInferenceErrorMessage != null && (returnErrors == null || !returnErrors.containsValue(parentInferenceErrorMessage))) { - if (returnErrors == null) return; - HighlightInfo.Builder info = - HighlightMethodUtil.createIncompatibleTypeHighlightInfo(callExpression, - parentCallResolveResult, expression); - if (info != null) { - for (PsiElement errorElement : returnErrors.keySet()) { - IntentionAction action = AdjustFunctionContextFix.createFix(errorElement); - if (action != null) { - info.registerFix(action, null, null, null, null); - } - } - add(info); - } - } - else if (returnErrors != null && !PsiTreeUtil.hasErrorElements(expression)) { - for (Map.Entry entry : returnErrors.entrySet()) { - PsiElement element = entry.getKey(); - HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(element) - .descriptionAndTooltip(entry.getValue()); - IntentionAction action = AdjustFunctionContextFix.createFix(element); - if (action != null) { - info.registerFix(action, null, null, null, null); - } - if (element instanceof PsiExpression expr) { - HighlightFixUtil.registerLambdaReturnTypeFixes(HighlightUtil.asConsumer(info), expression, expr); - } - add(info); - } + add(LambdaHighlightingUtil.checkFunctionalInterfaceTypeAccessible(myFile.getProject(), expression, functionalInterfaceType)); } } @@ -757,9 +680,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } if (functionalInterfaceType != null) { - if (!hasErrorResults()) { - add(HighlightClassUtil.checkExtendsSealedClass(expression, functionalInterfaceType)); - } if (!hasErrorResults()) { boolean isFunctional = LambdaUtil.isFunctionalType(functionalInterfaceType); if (!isFunctional && !(IncompleteModelUtil.isIncompleteModel(expression) && 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 dc222615ea01..279b66f81a81 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 @@ -549,6 +549,31 @@ final class JavaErrorFixProvider { HighlightFixUtil.registerCallInferenceFixes(callExpression, sink); } }); + fixes(LAMBDA_INFERENCE_ERROR, (error, sink) -> { + if (error.psi().getParent() instanceof PsiExpressionList list && + list.getParent() instanceof PsiMethodCallExpression callExpression) { + MethodCandidateInfo resolveResult = error.context(); + PsiMethod method = resolveResult.getElement(); + HighlightFixUtil.registerMethodCallIntentions(sink, callExpression, callExpression.getArgumentList()); + if (!PsiTypesUtil.mentionsTypeParameters(((PsiExpression)callExpression.copy()).getType(), Set.of(method.getTypeParameters()))) { + HighlightFixUtil.registerMethodReturnFixAction(sink, resolveResult, callExpression); + } + HighlightFixUtil.registerTargetTypeFixesBasedOnApplicabilityInference(callExpression, resolveResult, method, sink); + LambdaUtil.getReturnExpressions(error.psi()) + .stream().map(PsiExpression::getType).distinct() + .map(type -> AdjustFunctionContextFix.createFix(type, error.psi())) + .forEach(sink); + } + }); + fixes(LAMBDA_RETURN_TYPE_ERROR, (error, sink) -> { + if (error.psi() instanceof PsiExpression expr) { + sink.accept(AdjustFunctionContextFix.createFix(expr)); + PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(expr, PsiLambdaExpression.class); + if (lambda != null) { + HighlightFixUtil.registerLambdaReturnTypeFixes(sink, lambda, expr); + } + } + }); fixes(CALL_WRONG_ARGUMENTS, (error, sink) -> { JavaMismatchedCallContext context = error.context(); PsiExpressionList list = context.list(); @@ -744,7 +769,7 @@ final class JavaErrorFixProvider { ANNOTATION_NOT_ALLOWED_REF, ANNOTATION_NOT_ALLOWED_VAR, ANNOTATION_NOT_ALLOWED_VOID, LAMBDA_MULTIPLE_TARGET_METHODS, LAMBDA_NO_TARGET_METHOD, LAMBDA_NOT_FUNCTIONAL_INTERFACE, ANNOTATION_NOT_APPLICABLE, - LAMBDA_FUNCTIONAL_INTERFACE_SEALED, OVERRIDE_ON_STATIC_METHOD, + FUNCTIONAL_INTERFACE_SEALED, OVERRIDE_ON_STATIC_METHOD, OVERRIDE_ON_NON_OVERRIDING_METHOD, SAFE_VARARGS_ON_FIXED_ARITY, SAFE_VARARGS_ON_NON_FINAL_METHOD, SAFE_VARARGS_ON_RECORD_COMPONENT, ANNOTATION_CONTAINER_WRONG_PLACE, ANNOTATION_CONTAINER_NOT_APPLICABLE)) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/LambdaHighlightingUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/LambdaHighlightingUtil.java index 8aa901a43d60..cccaf5ef44a8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/LambdaHighlightingUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/LambdaHighlightingUtil.java @@ -149,26 +149,6 @@ public final class LambdaHighlightingUtil { return JavaErrorBundle.message("not.a.functional.interface", functionalInterfaceType.getPresentableText()); } - static HighlightInfo.Builder checkConsistentParameterDeclaration(@NotNull PsiLambdaExpression expression) { - PsiParameter[] parameters = expression.getParameterList().getParameters(); - if (parameters.length < 2) return null; - boolean hasExplicitParameterTypes = hasExplicitType(parameters[0]); - for (int i = 1; i < parameters.length; i++) { - if (hasExplicitParameterTypes != hasExplicitType(parameters[i])) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .descriptionAndTooltip(JavaErrorBundle.message("lambda.parameters.consistency.message")) - .range(expression.getParameterList()); - } - } - - return null; - } - - private static boolean hasExplicitType(@NotNull PsiParameter parameter) { - PsiTypeElement typeElement = parameter.getTypeElement(); - return typeElement != null && !typeElement.isInferredType(); - } - // 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. @@ -238,15 +218,4 @@ public final class LambdaHighlightingUtil { return null; } - - static boolean lambdaParametersMentionTypeParameter(@NotNull PsiType functionalInterfaceType, @NotNull Set parameters) { - if (!(functionalInterfaceType instanceof PsiClassType classType)) return false; - PsiSubstitutor substitutor = classType.resolveGenerics().getSubstitutor(); - PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); - if (method == null) return false; - for (PsiParameter parameter : method.getParameterList().getParameters()) { - if (PsiTypesUtil.mentionsTypeParameters(substitutor.substitute(parameter.getType()), parameters)) return true; - } - return false; - } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternHighlightingModel.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternHighlightingModel.java index 23d41dd0274b..fd6009732228 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternHighlightingModel.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternHighlightingModel.java @@ -100,7 +100,7 @@ final class PatternHighlightingModel { continue; } builder = HighlightUtil.createIncompatibleTypeHighlightInfo(substitutedRecordComponentType, deconstructionComponentType, - deconstructionComponent.getTextRange(), 0); + deconstructionComponent.getTextRange()); } errorSink.accept(builder); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternsInSwitchBlockHighlightingModel.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternsInSwitchBlockHighlightingModel.java index 3a553de0cdff..7931b6ae92fa 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternsInSwitchBlockHighlightingModel.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternsInSwitchBlockHighlightingModel.java @@ -209,7 +209,7 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight if (!IncompleteModelUtil.isIncompleteModel(label) || (!IncompleteModelUtil.isPotentiallyConvertible(mySelectorType, patternType, label))) { HighlightInfo.Builder error = - HighlightUtil.createIncompatibleTypeHighlightInfo(mySelectorType, patternType, elementToReport.getTextRange(), 0); + HighlightUtil.createIncompatibleTypeHighlightInfo(mySelectorType, patternType, elementToReport.getTextRange()); if (mySelectorType instanceof PsiPrimitiveType) { HighlightInfo.Builder infoFeature = HighlightUtil.checkFeature(elementToReport, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, @@ -233,7 +233,7 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight else if (label instanceof PsiExpression expr) { if (mySelectorType.equals(PsiTypes.nullType())) { HighlightInfo.Builder info = - HighlightUtil.createIncompatibleTypeHighlightInfo(mySelectorType, expr.getType(), expr.getTextRange(), 0); + HighlightUtil.createIncompatibleTypeHighlightInfo(mySelectorType, expr.getType(), expr.getTextRange()); errorSink.accept(info); return true; } @@ -268,7 +268,7 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight PsiType unboxedType = PsiPrimitiveType.getOptionallyUnboxedType(mySelectorType); if (unboxedType != null) { HighlightInfo.Builder error = - HighlightUtil.createIncompatibleTypeHighlightInfo(unboxedType, expr.getType(), label.getTextRange(), 0); + HighlightUtil.createIncompatibleTypeHighlightInfo(unboxedType, expr.getType(), label.getTextRange()); errorSink.accept(error); return true; } @@ -277,7 +277,7 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight } if (ConstantExpressionUtil.computeCastTo(constValue, mySelectorType) == null) { HighlightInfo.Builder error = - HighlightUtil.createIncompatibleTypeHighlightInfo(mySelectorType, expr.getType(), label.getTextRange(), 0); + HighlightUtil.createIncompatibleTypeHighlightInfo(mySelectorType, expr.getType(), label.getTextRange()); errorSink.accept(error); return true; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AdjustFunctionContextFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AdjustFunctionContextFix.java index 1511b71bc650..c930dda67076 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AdjustFunctionContextFix.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AdjustFunctionContextFix.java @@ -79,21 +79,29 @@ public class AdjustFunctionContextFix extends PsiUpdateModCommandAction remapper = METHOD_NAME_ADJUSTER.mapFirst(call); - if (remapper == null) return null; PsiType actualReturnType; if(expression instanceof PsiMethodReferenceExpression methodRef) { actualReturnType = PsiMethodReferenceUtil.getMethodReferenceReturnType(methodRef); } else { actualReturnType = expression.getType(); } + return createFix(actualReturnType, fn); + } + + /** + * @param actualReturnType actual (unexpected) return type of functional expression + * @param fn functional expression + * @return a fix that aims to adjust the surroundings + */ + public static @Nullable IntentionAction createFix(@Nullable PsiType actualReturnType, @NotNull PsiFunctionalExpression fn) { + PsiExpressionList expressionList = ObjectUtils.tryCast(fn.getParent(), PsiExpressionList.class); + if (expressionList == null || expressionList.getExpressionCount() != 1) return null; + PsiMethodCallExpression call = ObjectUtils.tryCast(expressionList.getParent(), PsiMethodCallExpression.class); + Function remapper = METHOD_NAME_ADJUSTER.mapFirst(call); + if (remapper == null) return null; String targetMethodName = remapper.apply(actualReturnType); if (targetMethodName == null) return null; return new AdjustFunctionContextFix(call, targetMethodName).asIntention(); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/SealedFunctionalInterface.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/SealedFunctionalInterface.java index 7f605dd991a6..474984c91b12 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/SealedFunctionalInterface.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/SealedFunctionalInterface.java @@ -16,10 +16,10 @@ final class MyFn implements Fn { class Test { void test() { - Fn fn = () -> 1; - Fn fn1 = this::doSmth; - foo(() -> 1); - foo(this::doSmth); + Fn fn = () -> 1; + Fn fn1 = this::doSmth; + foo(() -> 1); + foo(this::doSmth); } int doSmth() { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/FunctionalInterfaceAnnotation.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/FunctionalInterfaceAnnotation.java index fb301ed336bc..39b95f53867d 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/FunctionalInterfaceAnnotation.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/FunctionalInterfaceAnnotation.java @@ -1,4 +1,4 @@ -@FunctionalInterface +@FunctionalInterface interface Test { void foo(); void bar(); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/GenericNotGenericInterfaceMethod.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/GenericNotGenericInterfaceMethod.java index 8bf972237460..2c972197593f 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/GenericNotGenericInterfaceMethod.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/GenericNotGenericInterfaceMethod.java @@ -36,7 +36,7 @@ class Test2 { } { - F f = () -> g(); + F f = () -> g(); } void g() {} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/inference/FunctionalInterfaceShouldHaveExactlyOneMethod.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/inference/FunctionalInterfaceShouldHaveExactlyOneMethod.java index 9ac2569f7369..3dde5564b6df 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/inference/FunctionalInterfaceShouldHaveExactlyOneMethod.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/inference/FunctionalInterfaceShouldHaveExactlyOneMethod.java @@ -8,7 +8,7 @@ public class NotAFIT { } void bar() { - foo(() ->{}); + foo(() ->{}); } } @@ -25,7 +25,7 @@ public class NotAFIT { } void bar() { - foo(()->{}); + foo(()->{}); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/newLambda/IncludeConstraintsWhenParentMethodIsDuringCalculation.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/newLambda/IncludeConstraintsWhenParentMethodIsDuringCalculation.java index 07127a3cf9b7..2d94d3969aa8 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/newLambda/IncludeConstraintsWhenParentMethodIsDuringCalculation.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/newLambda/IncludeConstraintsWhenParentMethodIsDuringCalculation.java @@ -9,7 +9,7 @@ class Test { SuperFoo foo(I ax) { return null; } - SuperFoo ls = foo(() -> new Foo<>()); + SuperFoo ls = foo(() -> new Foo<>()); SuperFoo li = foo(() -> new Foo<>()); SuperFoo lw = foo(() -> new Foo<>()); } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/newLambda/NestedLambdaWithInferenceVariableAsTargetType.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/newLambda/NestedLambdaWithInferenceVariableAsTargetType.java index 460ea06b94cc..06b3ee13c4e3 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/newLambda/NestedLambdaWithInferenceVariableAsTargetType.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/newLambda/NestedLambdaWithInferenceVariableAsTargetType.java @@ -12,7 +12,7 @@ class Test { class Test1 { { - Supplier x = foo(() -> () -> null); + Supplier x = foo(() -> () -> null); } static Supplier foo(Supplier> delegate) {