From caef43a5e50a95c71873f53a25713b886947019d Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 4 Feb 2025 12:34:06 +0100 Subject: [PATCH] [java-highlighting] Incompatible lambda parameter types moved Also: report every incompatible parameter, not only the first one Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: a9d75a78207d0910f0bcbb6060e38acedc3f07d3 --- .../JavaCompilationErrorBundle.properties | 2 ++ .../highlighting/FunctionChecker.java | 26 ++++++++++++++++ .../highlighting/JavaErrorVisitor.java | 2 +- .../highlighting/errors/JavaErrorKinds.java | 11 +++++++ .../impl/analysis/HighlightVisitorImpl.java | 10 ------ .../impl/analysis/LambdaHighlightingUtil.java | 31 ------------------- .../messages/JavaErrorBundle.properties | 25 --------------- .../lambda/highlighting/ConditionalExpr.java | 2 +- .../NoBoxingInLambdaFormalParams.java | 2 +- .../WildcardsAndFormalLambdaParams.java | 2 +- .../lambda/params/FormalParams.java | 2 +- .../lambda/params/IncompatibleTypes.java | 2 +- .../lambda/params/MethodApplicability.java | 4 +-- .../daemonCodeAnalyzer/lambda/params/Raw.java | 2 +- 14 files changed, 48 insertions(+), 75 deletions(-) diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index 6cdcaf69848b..504bc84c7b12 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -54,6 +54,8 @@ 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 +lambda.incompatible.parameter.types=Incompatible parameter type in lambda expression: expected {0} but found {1} +lambda.wrong.number.of.parameters=Wrong number of parameters in lambda expression: expected {0} but found {1} method.reference.not.expected=Method reference expression is not expected here method.reference.sealed=Method reference cannot implement a sealed interface 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 index 461c6fbb6e97..9baf3f90ab6b 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/FunctionChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/FunctionChecker.java @@ -16,6 +16,8 @@ import org.jetbrains.annotations.Nullable; import java.util.*; +import static java.util.Objects.*; + final class FunctionChecker { private final @NotNull JavaErrorVisitor myVisitor; @@ -147,6 +149,30 @@ final class FunctionChecker { } } + void checkParametersCompatible(@NotNull PsiLambdaExpression expression, @Nullable PsiType functionalInterfaceType) { + PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); + if (interfaceMethod == null) return; + PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters(); + PsiParameter[] lambdaParameters = expression.getParameterList().getParameters(); + if (lambdaParameters.length != parameters.length) { + myVisitor.report(JavaErrorKinds.LAMBDA_WRONG_NUMBER_OF_PARAMETERS.create(expression, interfaceMethod)); + return; + } + boolean hasFormalParameterTypes = expression.hasFormalParameterTypes(); + PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, resolveResult); + for (int i = 0; i < lambdaParameters.length; i++) { + PsiParameter lambdaParameter = lambdaParameters[i]; + PsiType lambdaParameterType = lambdaParameter.getType(); + PsiType substitutedParamType = substitutor.substitute(parameters[i].getType()); + if (hasFormalParameterTypes &&!PsiTypesUtil.compareTypes(lambdaParameterType, substitutedParamType, true) || + !TypeConversionUtil.isAssignable(substitutedParamType, lambdaParameterType)) { + myVisitor.report(JavaErrorKinds.LAMBDA_INCOMPATIBLE_PARAMETER_TYPES.create( + lambdaParameter, requireNonNullElse(substitutedParamType, PsiTypes.nullType()))); + } + } + } + private static @Nullable JavaCompilationError getFunctionalInterfaceError( @NotNull PsiFunctionalExpression context, PsiType functionalInterfaceType) { if (functionalInterfaceType instanceof PsiIntersectionType intersection) { 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 24a4409b0c2a..8d5fb407f229 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 @@ -827,8 +827,8 @@ final class JavaErrorVisitor extends JavaElementVisitor { else if (returnErrors != null && !PsiTreeUtil.hasErrorElements(expression)) { returnErrors.forEach((expr, message) -> report(JavaErrorKinds.LAMBDA_RETURN_TYPE_ERROR.create(expr, message))); } + if (!hasErrorResults()) myFunctionChecker.checkParametersCompatible(expression, functionalInterfaceType); } - } /** 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 4fcb03a076bb..33840d81a131 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 @@ -142,6 +142,17 @@ public final class JavaErrorKinds { 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 Parameterized LAMBDA_WRONG_NUMBER_OF_PARAMETERS = + parameterized(PsiLambdaExpression.class, PsiMethod.class, "lambda.wrong.number.of.parameters") + .withAnchor((lambda, method) -> lambda.getParameterList()) + .withRawDescription((lambda, method) -> message("lambda.wrong.number.of.parameters", + method.getParameterList().getParametersCount(), + lambda.getParameterList().getParametersCount())); + public static final Parameterized LAMBDA_INCOMPATIBLE_PARAMETER_TYPES = + parameterized(PsiParameter.class, PsiType.class, "lambda.incompatible.parameter.types") + .withRawDescription((parameter, expectedType) -> message("lambda.incompatible.parameter.types", + expectedType.getPresentableText(), parameter.getType().getPresentableText())); + public static final Simple METHOD_REFERENCE_SEALED = error("method.reference.sealed"); public static final Simple METHOD_REFERENCE_NOT_EXPECTED = error("method.reference.not.expected"); 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 f12e482d6804..a9b2746d886c 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 @@ -269,16 +269,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } } - if (!hasErrorResults() && functionalInterfaceType != null) { - PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); - PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); - if (interfaceMethod != null) { - PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters(); - add(LambdaHighlightingUtil.checkParametersCompatible(expression, parameters, - LambdaUtil.getSubstitutor(interfaceMethod, resolveResult))); - } - } - if (!hasErrorResults()) { PsiElement body = expression.getBody(); if (body instanceof PsiCodeBlock block) { 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 70c197e136dc..541829da7d25 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 @@ -2,7 +2,6 @@ package com.intellij.codeInsight.daemon.impl.analysis; import com.intellij.codeInsight.JavaModuleSystemEx.ErrorWithFixes; -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; @@ -10,43 +9,13 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.psi.*; import com.intellij.psi.impl.PsiClassImplUtil; -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.List; public final class LambdaHighlightingUtil { - - static HighlightInfo.Builder checkParametersCompatible(@NotNull PsiLambdaExpression expression, - PsiParameter @NotNull [] methodParameters, - @NotNull PsiSubstitutor substitutor) { - PsiParameter[] lambdaParameters = expression.getParameterList().getParameters(); - if (lambdaParameters.length != methodParameters.length) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(expression.getParameterList()) - .descriptionAndTooltip(JavaErrorBundle.message("incompatible.parameter.types.in.lambda.wrong.number.of.parameters", methodParameters.length, lambdaParameters.length)); - } - boolean hasFormalParameterTypes = expression.hasFormalParameterTypes(); - for (int i = 0; i < lambdaParameters.length; i++) { - PsiParameter lambdaParameter = lambdaParameters[i]; - PsiType lambdaParameterType = lambdaParameter.getType(); - PsiType substitutedParamType = substitutor.substitute(methodParameters[i].getType()); - if (hasFormalParameterTypes &&!PsiTypesUtil.compareTypes(lambdaParameterType, substitutedParamType, true) || - !TypeConversionUtil.isAssignable(substitutedParamType, lambdaParameterType)) { - String expectedType = substitutedParamType != null ? substitutedParamType.getPresentableText() : null; - String actualType = lambdaParameterType.getPresentableText(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(expression.getParameterList()) - .descriptionAndTooltip( - JavaErrorBundle.message("incompatible.parameter.types.in.lambda", expectedType, actualType)); - } - } - return null; - } - // 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. diff --git a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties index 34450fe799f2..2aa034bbc038 100644 --- a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties @@ -18,7 +18,6 @@ generics.unchecked.cast=Unchecked cast: ''{0}'' to ''{1}'' generics.unchecked.call.to.member.of.raw.type=Unchecked call to ''{0}'' as a member of raw type ''{1}'' generics.unchecked.call=Unchecked method ''{0}'' invocation unsafe.cast.in.instanceof=''{0}'' cannot be safely cast to ''{1}'' -modifiers.for.enum.constants=No modifiers allowed for enum constants unchecked.overriding.incompatible.return.type=Unchecked overriding: return type requires unchecked conversion. Found ''{0}'', required ''{1}'' class.must.be.abstract=Class ''{0}'' must either be declared abstract or implement abstract method ''{1}'' in ''{2}'' @@ -44,10 +43,8 @@ declaration.or.variable.expected=Declaration, final or effectively final variabl initializer.must.be.able.to.complete.normally=Initializer must be able to complete normally incompatible.return.type=attempting to use incompatible return type exception.is.never.thrown=Exception ''{0}'' is never thrown in the method -ambiguous.reference=Reference to ''{0}'' is ambiguous, both ''{1}'' and ''{2}'' match cannot.resolve.method=Cannot resolve method ''{0}'' ambiguous.method.call.no.match=Cannot resolve method ''{0}'' in ''{1}'' -abstract.method.in.non.abstract.class=Abstract method in non-abstract class overrides.deprecated.method=Overrides deprecated method in ''{0}'' overrides.marked.for.removal.method=Overrides method that is deprecated and marked for removal in ''{0}'' deprecated.default.constructor=Default constructor in ''{0}'' is deprecated @@ -271,25 +268,6 @@ record.component.not.initialized=Record component ''{0}'' might not be initializ insufficient.language.level={0} are not supported at language level ''{1}'' cannot.select.from.a.type.parameter=Cannot select from a type parameter -method.reference.expression.is.not.expected=Method reference expression is not expected here -not.a.functional.interface={0} is not a functional interface -cannot.find.class=Cannot find class {0} -cannot.infer.functional.interface.type=Cannot infer functional interface type -lambda.expression.not.expected=Lambda expression not expected here -lambda.parameters.consistency.message=Cannot mix 'var' and explicitly typed parameters in lambda expression -target.method.is.generic=Target method is generic -multiple.non.overriding.abstract.methods.found.in.0=Multiple non-overriding abstract methods found in {0} -multiple.non.overriding.abstract.methods.found.in.interface.0=Multiple non-overriding abstract methods found in interface {0} -no.target.method.found=No target method found -target.type.of.a.lambda.conversion.must.be.an.interface=Target type of a lambda conversion must be an interface -incompatible.parameter.types.in.lambda=Incompatible parameter types in lambda expression: expected {0} but found {1} -incompatible.parameter.types.in.lambda.wrong.number.of.parameters=Incompatible parameter types in lambda expression: wrong number of parameters: expected {0} but found {1} -an.enclosing.instance.of.type.not.in.scope.method.reference.context=An enclosing instance of type {0} is not in scope -parameterized.qualifier.on.static.method.reference.context=Parameterized qualifier on static method reference -static.method.referenced.through.receiver.method.reference.context=Static method referenced through receiver -static.method.referenced.through.non.static.qualifier.method.reference.context=Static method referenced through non-static qualifier -non.static.method.cannot.be.referenced.from.a.static.context.method.reference.context=Non-static method cannot be referenced from a static context -abstract.method.0.cannot.be.accessed.directly.method.reference.context=Abstract method ''{0}'' cannot be accessed directly bad.type.in.switch.expression=Bad type in switch expression: {0} cannot be converted to {1} switch.expression.cannot.be.void=Target type for switch expression cannot be void annotation.on.static.member.qualifying.type.family.name=Move type annotation @@ -297,7 +275,6 @@ not.allowed.in.sealed.hierarchy=''{0}'' is not allowed in the sealed hierarchy invalid.permits.clause.direct.implementation=Invalid permits clause: ''{0}'' must directly {1, choice, 1#extend|2#implement} ''{2}'' permitted.subclass.must.have.modifier=All sealed class subclasses must either be final, sealed or non-sealed permits.list.generics.are.not.allowed=Generics are not allowed in permits list -sealed.cannot.be.functional.interface=Sealed class can not be used as functional interface local.classes.must.not.extend.sealed.classes=Local classes must not extend sealed classes class.not.allowed.to.extend.sealed.class.from.another.package={0} ''{1}'' from another package not allowed to extend sealed {2} ''{3}'' in unnamed module class.not.allowed.to.extend.sealed.class.from.another.module=Class is not allowed to extend sealed class from another module @@ -307,7 +284,5 @@ text.class.inherits.unrelated.defaults={0} inherits unrelated defaults for {1} f text.class.is.not.accessible={0} is not accessible in current context text.class.cannot.access=Cannot access {0} error.cannot.infer.pattern.type=Cannot infer pattern type: {0} -error.extra.semicolons.between.import.statements.not.allowed=Extra semicolons between import statements are not allowed remove.unused.imports.quickfix.text=Remove unused imports incomplete.project.state.pending.reference=Not resolved until the project is fully loaded -implicit.class.with.explicit.constructor=Explicit constructor in implicitly declared class is not allowed \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/ConditionalExpr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/ConditionalExpr.java index 47a96dff54df..3d5b59c2ecd7 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/ConditionalExpr.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/ConditionalExpr.java @@ -12,7 +12,7 @@ class Test { I i = flag ? (() -> 123) : (() -> 222); I i1 = flag ? (() -> {}) : (() -> 222); Object i2 = flag ? (() -> 42) : (() -> 222); - I i3 = flag ? ((x) -> 42) : (() -> 222); + I i3 = flag ? ((x) -> 42) : (() -> 222); I i4 = flag ? (() -> 42) : new I() { @Override public int m() { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/NoBoxingInLambdaFormalParams.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/NoBoxingInLambdaFormalParams.java index ffa608acb7b6..432bf33e1e62 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/NoBoxingInLambdaFormalParams.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/NoBoxingInLambdaFormalParams.java @@ -3,6 +3,6 @@ class LambdaConv10 { interface I { public R call( T t); } { - I in = (int i) -> 2 * i; + I in = (int i) -> 2 * i; } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/WildcardsAndFormalLambdaParams.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/WildcardsAndFormalLambdaParams.java index 57c90b29c492..125ec8ec972d 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/WildcardsAndFormalLambdaParams.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/highlighting/WildcardsAndFormalLambdaParams.java @@ -5,7 +5,7 @@ public class Test { { Predicate p = (Number n) -> n.equals(23); - Predicate p1 = (Number n) -> n.equals(23); + Predicate p1 = (Number n) -> n.equals(23); Predicate p2 = (Number n) -> n.equals(23); } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/FormalParams.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/FormalParams.java index 968fca64e656..d442f0539981 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/FormalParams.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/FormalParams.java @@ -11,7 +11,7 @@ class Foo { }; void bazz() { - bar((String s) -> { + bar((String s) -> { System.out.println(s);}); bar((int i) -> {System.out.println(i);}); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/IncompatibleTypes.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/IncompatibleTypes.java index 04ca4b38b9bf..338271713db9 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/IncompatibleTypes.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/IncompatibleTypes.java @@ -1,6 +1,6 @@ class XXX { Runnable bar() { - return (o)->{ + return (o)->{ System.out.println(); }; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/MethodApplicability.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/MethodApplicability.java index a865a17c037b..0bcbe32468e3 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/MethodApplicability.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/MethodApplicability.java @@ -26,7 +26,7 @@ class Foo { System.out.println(s); }); - foo((String p, String k) -> { + foo((String p, String k) -> { System.out.println(p); }); } @@ -62,7 +62,7 @@ class WithTypeParams { System.out.println(p); }); - foo((int k) -> {System.out.println(k);}); + foo((int k) -> {System.out.println(k);}); } } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/Raw.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/Raw.java index bfd8abe11de7..f02beca6ff02 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/Raw.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/params/Raw.java @@ -1,6 +1,6 @@ class Test { { - Comparable c = (String o)->{ + Comparable c = (String o)->{ return 0; }; }