[java-highlighting] Improve error messages around inapplicable method reference

Fixes IDEA-362351 An Incorrect Error warning "Non-static methods cannot be referenced from a static context"
Fixes IDEA-173183 Wrong error message for method reference
Improves IDEA-208532 Incorrect Inspect code error in lambda expression method reference

GitOrigin-RevId: 8f7a17688eaa1aba72bbccd45395967656108668
This commit is contained in:
Tagir Valeev
2024-11-22 14:37:36 +01:00
committed by intellij-monorepo-bot
parent af086b039d
commit d8e08c17bb
11 changed files with 116 additions and 31 deletions

View File

@@ -56,8 +56,8 @@ import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.text.MessageFormat;
import java.util.List;
import java.util.*;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
@@ -683,22 +683,11 @@ public final class HighlightMethodUtil {
}
private static boolean favorParentReport(@NotNull PsiCall methodCall, @NotNull String errorMessage) {
if (errorMessage.equals(JavaPsiBundle.message("error.incompatible.type.failed.to.resolve.argument"))) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(methodCall.getParent());
if (parent instanceof PsiExpressionList) {
PsiElement grandParent = parent.getParent();
if (grandParent instanceof PsiCallExpression callExpression) {
JavaResolveResult parentResolveResult = callExpression.resolveMethodGenerics();
if (parentResolveResult instanceof MethodCandidateInfo info &&
info.getInferenceErrorMessage() != null) {
// Parent resolve failed as well, and it's likely more informative.
// Suppress this error to allow reporting from parent
return true;
}
}
}
}
return false;
// 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);
}
private static void registerUsageFixes(@NotNull PsiMethodCallExpression methodCall,
@@ -2353,6 +2342,22 @@ public final class HighlightMethodUtil {
return MethodSignatureUtil.areSignaturesErasureEqual(valueOfMethod, methodSignature);
}
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;
}
private static final class ReturnModel {
final PsiReturnStatement myStatement;
final PsiType myType;

View File

@@ -1508,9 +1508,10 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
if (!hasErrorResults()) {
if (results.length == 0 || results[0] instanceof MethodCandidateInfo methodInfo &&
!methodInfo.isApplicable() &&
functionalInterfaceType != null || results.length > 1) {
boolean resolvedButNonApplicable = results.length == 1 && results[0] instanceof MethodCandidateInfo methodInfo &&
!methodInfo.isApplicable() &&
functionalInterfaceType != null;
if (results.length != 1 || resolvedButNonApplicable) {
String description = null;
if (results.length == 1) {
description = ((MethodCandidateInfo)results[0]).getInferenceErrorMessage();
@@ -1545,7 +1546,10 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
return;
}
description = JavaErrorBundle.message("cannot.resolve.method", expression.getReferenceName());
if (!(resolvedButNonApplicable && HighlightMethodUtil.hasSurroundingInferenceError(expression))) {
description = JavaErrorBundle.message("cannot.resolve.method", expression.getReferenceName());
}
}
}

View File

@@ -38,7 +38,7 @@ public final class PsiMethodReferenceHighlightingUtil {
public static @NlsContexts.DetailedDescription String checkMethodReferenceContext(@NotNull PsiMethodReferenceExpression methodRef,
@NotNull PsiElement resolve,
PsiType functionalInterfaceType) {
PsiClass containingClass = resolve instanceof PsiMethod ? ((PsiMethod)resolve).getContainingClass() : (PsiClass)resolve;
PsiClass containingClass = resolve instanceof PsiMethod method ? method.getContainingClass() : (PsiClass)resolve;
boolean isStaticSelector = PsiMethodReferenceUtil.isStaticallyReferenced(methodRef);
PsiElement qualifier = methodRef.getQualifier();
@@ -65,6 +65,10 @@ public final class PsiMethodReferenceHighlightingUtil {
if (!receiverReferenced) {
if (isStaticSelector && !isMethodStatic && !isConstructor) {
if (functionalInterfaceType instanceof PsiClassType classType && classType.hasParameters()) {
// Prefer surrounding error, as it could be more descriptive
if (HighlightMethodUtil.hasSurroundingInferenceError(methodRef)) return null;
}
return JavaErrorBundle.message("non.static.method.cannot.be.referenced.from.a.static.context.method.reference.context");
}
if (!isStaticSelector && isMethodStatic) {

View File

@@ -65,13 +65,13 @@ public class PsiMethodReferenceCompatibilityConstraint implements ConstraintForm
final PsiType[] typeParameters = myExpression.getTypeParameters();
final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(myExpression);
final PsiClass qContainingClass = qualifierResolveResult.getContainingClass();
if (myExpression.isExact()) {
final PsiMember applicableMember = myExpression.getPotentiallyApplicableMember();
LOG.assertTrue(applicableMember != null);
final PsiClass applicableMemberContainingClass = applicableMember.getContainingClass();
final PsiClass containingClass = qualifierResolveResult.getContainingClass();
PsiSubstitutor psiSubstitutor = getSubstitutor(signature, qualifierResolveResult, applicableMember, applicableMemberContainingClass, myExpression);
@@ -120,8 +120,8 @@ public class PsiMethodReferenceCompatibilityConstraint implements ConstraintForm
if (applicableMethodReturnType == null &&
(applicableMember instanceof PsiClass || applicableMember instanceof PsiMethod && ((PsiMethod)applicableMember).isConstructor())) {
if (containingClass != null) {
applicableMethodReturnType = JavaPsiFacade.getElementFactory(applicableMember.getProject()).createType(containingClass, PsiSubstitutor.EMPTY);
if (qContainingClass != null) {
applicableMethodReturnType = JavaPsiFacade.getElementFactory(applicableMember.getProject()).createType(qContainingClass, PsiSubstitutor.EMPTY);
}
}
@@ -139,18 +139,20 @@ public class PsiMethodReferenceCompatibilityConstraint implements ConstraintForm
for (PsiType paramType : signature.getParameterTypes()) {
if (!session.isProperType(paramType)) {
//session.registerIncompatibleErrorMessage("Parameter type in not yet inferred: " + session.getPresentableText(paramType));
return false;
}
}
JavaResolveResult resolve = myExpression.advancedResolve(false);
final PsiElement element = resolve.getElement();
if (element == null || resolve instanceof MethodCandidateInfo && !((MethodCandidateInfo)resolve).isApplicable()) {
if (element == null) {
session.registerIncompatibleErrorMessage(
JavaPsiBundle.message("error.incompatible.type.declaration.for.the.method.reference.not.found"));
return false;
}
if (resolve instanceof MethodCandidateInfo && !((MethodCandidateInfo)resolve).isApplicable()) {
return false;
}
if (PsiTypes.voidType().equals(returnType) || returnType == null) {
return true;
@@ -205,7 +207,6 @@ public class PsiMethodReferenceCompatibilityConstraint implements ConstraintForm
}
}
PsiClass qContainingClass = qualifierResolveResult.getContainingClass();
if (qContainingClass != null && PsiUtil.isRawSubstitutor(qContainingClass, qualifierResolveResult.getSubstitutor())) {
//15.13.1 If there exist a parameterization, then it would be used to search, the *raw type* would be used otherwise
if (getParameterization(signature, qualifierResolveResult, method, myExpression, qContainingClass) == null) {

View File

@@ -0,0 +1,14 @@
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
// IDEA-362351
public class FriendlyMessageInBadInference {
void test(Map<Integer, Integer> someMap) {
use(<error descr="Incompatible types. Found: 'java.util.HashMap<java.lang.Integer,java.lang.Integer>', required: 'java.lang.Throwable'">someMap.entrySet().stream()
.sorted(Comparator.comparingInt(Entry::getKey))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (k1, k2) -> k1, HashMap::new))</error>);
}
void use(Throwable t) {}
}

View File

@@ -10,6 +10,6 @@ class Test {
<T> void bar(I i) {}
void test() {
bar(Foo::<error descr="Cannot resolve method 'foo'">foo</error>);
bar<error descr="'bar(Test.I)' in 'Test' cannot be applied to '(<method reference>)'">(Foo::foo)</error>;
}
}

View File

@@ -0,0 +1,13 @@
import java.util.Map;
import java.util.stream.*;
class Either<E>{
public boolean isRight() {
return false;
}
}
class TestClass {
Map<Boolean, Either<String>> test(final Stream<Either<String>> eitherStream) {
return <error descr="Incompatible types. Found: 'java.util.Map<java.lang.Boolean,java.util.List<Either<java.lang.String>>>', required: 'java.util.Map<java.lang.Boolean,Either<java.lang.String>>'">eitherStream.collect(Collectors.groupingBy(Either::isRight));</error>
}
}

View File

@@ -0,0 +1,22 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Test
{
public static void main(String[] args)
{
Map<Long, Long> longLongMap = new HashMap();
List<Map.Entry<Long,Long>> entryListWithError = longLongMap.entrySet().stream()
.sorted<error descr="'sorted(java.util.Comparator<? super java.util.Map.Entry<java.lang.Long,java.lang.Long>>)' in 'java.util.stream.Stream' cannot be applied to '(java.util.Comparator<T>)'">(Comparator.comparing(Map.Entry::getValue).reversed())</error>
.limit(10)
.collect(Collectors.toList());
List<Map.Entry<Long,Long>> entryListWithoutError = longLongMap.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getValue))
.limit(10)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,20 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
class Test {
void m() {
// IDEA-337371 -- error reporting still should be improved
var o = new MapDropdownChoice<String, Integer>(
() -> {
Map<String, Integer> choices = Map.of("id1", 1);
return choices.entrySet().stream()
.collect(Collectors.toMap(<error descr="Non-static method cannot be referenced from a static context">Map.Entry::getKey</error>, <error descr="Non-static method cannot be referenced from a static context">Map.Entry::getValue</error>,
(e1, e2) -> e1, LinkedHashMap::new));
});
}
static class MapDropdownChoice<K, V> {
public MapDropdownChoice(Supplier<? extends Map<K, ? extends V>> choiceMap) {}
}
}

View File

@@ -107,6 +107,7 @@ public class LambdaHighlightingTest extends LightDaemonAnalyzerTestCase {
public void testGenericNotGenericInterfaceMethod() { doTest(); }
public void testInferredFromCast() { doTest(); }
public void testReferencedFromSelf() { doTest(); }
public void testFriendlyMessageInBadInference() { doTest(); }
private void doTest() {
doTest(false);

View File

@@ -6,8 +6,6 @@ import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
import com.intellij.codeInspection.uncheckedWarnings.UncheckedWarningLocalInspection;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase5;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -151,6 +149,9 @@ class NewMethodRefHighlightingTest extends LightJavaCodeInsightFixtureTestCase5
@Test void testGetClassReturnTypeInMethodReference() { doTest(); }
@Test void testCaptureTypeOfNewArrayExpression() { doTest(); }
@Test void testIDEA152659() { doTest(); }
@Test void testIDEA173183() { doTest(); }
@Test void testIDEA208532() { doTest(); }
@Test void testIDEA337371() { doTest(); }
//removing one unsound capture conversion is not enough to leave the system consistent
void _testRegistryOptionToSkipUnsoundCaptureConversionInMethodReferenceReturnType() { doTest(); }
@Test void testFreshVariableLowerBoundsDuringSuperTypeChecks() { doTest(); }