mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 11:50:54 +07:00
[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:
committed by
intellij-monorepo-bot
parent
af086b039d
commit
d8e08c17bb
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
Reference in New Issue
Block a user