[java-highlighting] Do not report lambda return value error if its return type contains unsubstituted type variable

Fixes IDEA-310129 IDE hint a wrong position of Java method param compile error
Also: AdaptExpressionTypeFixUtil: try to determine wrong parameter even if several parameter types mention type parameter
Also: AdaptExpressionTypeFixUtil: new type mismatch fix to replace call with qualifier

GitOrigin-RevId: fbc63c0eb3415983ccf52ed5ade15b5895b65b21
This commit is contained in:
Tagir Valeev
2023-01-13 13:33:54 +01:00
committed by intellij-monorepo-bot
parent 099b26f082
commit b2dfb4c8a8
10 changed files with 168 additions and 19 deletions

View File

@@ -3,10 +3,7 @@ package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.quickfix.AddTypeCastFix;
import com.intellij.codeInsight.daemon.impl.quickfix.ReplaceExpressionAction;
import com.intellij.codeInsight.daemon.impl.quickfix.WrapExpressionFix;
import com.intellij.codeInsight.daemon.impl.quickfix.WrapWithAdapterMethodCallFix;
import com.intellij.codeInsight.daemon.impl.quickfix.*;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.openapi.util.TextRange;
@@ -26,6 +23,8 @@ import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -63,6 +62,11 @@ final class AdaptExpressionTypeFixUtil {
PsiParameter parameter =
StreamEx.of(parameters).collect(MoreCollectors.onlyOne(p -> PsiTypesUtil.mentionsTypeParameters(p.getType(), set))).orElse(null);
PsiSubstitutor substitutor = ((MethodCandidateInfo)result).getSubstitutor(false);
PsiSubstitutor desiredSubstitutor = substitutor.put(typeParameter, expectedTypeValue);
if (parameter == null) {
parameter = findWrongParameter(call, method, desiredSubstitutor, typeParameter);
}
if (parameter == null) return;
PsiExpression arg = PsiUtil.skipParenthesizedExprDown(MethodCallUtils.getArgumentForParameter(call, parameter));
if (arg == null) return;
@@ -82,8 +86,7 @@ final class AdaptExpressionTypeFixUtil {
info.registerFix(fix, null, null, null, null);
}
}
PsiSubstitutor substitutor = ((MethodCandidateInfo)result).getSubstitutor(false);
PsiType expectedArgType = substitutor.put(typeParameter, expectedTypeValue).substitute(parameterType);
PsiType expectedArgType = desiredSubstitutor.substitute(parameterType);
if (arg instanceof PsiLambdaExpression && parameterType instanceof PsiClassType) {
registerLambdaReturnFixes(info, textRange, (PsiLambdaExpression)arg, (PsiClassType)parameterType, expectedArgType, typeParameter);
return;
@@ -94,6 +97,33 @@ final class AdaptExpressionTypeFixUtil {
registerExpectedTypeFixes(info, textRange, arg, expectedArgType, actualArgType);
}
private static @Nullable PsiParameter findWrongParameter(@NotNull PsiMethodCallExpression call,
@NotNull PsiMethod method,
@NotNull PsiSubstitutor substitutor, @NotNull PsiTypeParameter typeParameter) {
PsiParameter[] parameters = method.getParameterList().getParameters();
PsiExpression[] expressions = call.getArgumentList().getExpressions();
if (expressions.length != parameters.length) return null;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(call.getProject());
List<PsiParameter> candidates = new ArrayList<>();
for (int i = 0; i < parameters.length; i++) {
PsiType type = parameters[i].getType();
if (!PsiTypesUtil.mentionsTypeParameters(type, Set.of(typeParameter))) continue;
PsiType substituted = substitutor.substitute(type);
PsiTypeCastExpression cast = (PsiTypeCastExpression)factory.createExpressionFromText("(x)null", call);
PsiTypeElement typeElement = factory.createTypeElement(substituted);
Objects.requireNonNull(cast.getCastType()).replace(typeElement);
PsiMethodCallExpression copy = (PsiMethodCallExpression)LambdaUtil.copyTopLevelCall(call);
copy.getArgumentList().getExpressions()[i].replace(cast);
JavaResolveResult resolveResult = copy.resolveMethodGenerics();
if (resolveResult instanceof MethodCandidateInfo info &&
info.getElement() == method &&
info.getInferenceErrorMessage() == null) {
candidates.add(parameters[i]);
}
}
return ContainerUtil.getOnlyItem(candidates);
}
private static void registerPatchQualifierFixes(@NotNull HighlightInfo.Builder info,
@NotNull TextRange textRange,
@NotNull PsiMethodCallExpression call,
@@ -209,10 +239,17 @@ final class AdaptExpressionTypeFixUtil {
IntentionAction action = new AddTypeCastFix(castToType, expression, role);
info.registerFix(action, null, null, null, null);
}
if (expression instanceof PsiMethodCallExpression) {
PsiMethod argMethod = ((PsiMethodCallExpression)expression).resolveMethod();
if (expression instanceof PsiMethodCallExpression call) {
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier != null) {
PsiType type = qualifier.getType();
if (type != null && expectedType.isAssignableFrom(type)) {
info.registerFix(new ReplaceWithQualifierFix(call, role), null, null, null, null);
}
}
PsiMethod argMethod = call.resolveMethod();
if (argMethod != null) {
registerPatchParametersFixes(info, textRange, (PsiMethodCallExpression)expression, argMethod, expectedType, actualType);
registerPatchParametersFixes(info, textRange, call, argMethod, expectedType, actualType);
}
}
}

View File

@@ -455,9 +455,15 @@ public final class HighlightMethodUtil {
else if (candidateInfo != null && !candidateInfo.isApplicable()) {
if (candidateInfo.isTypeArgumentsApplicable()) {
builder = createIncompatibleCallHighlightInfo(holder, list, candidateInfo);
if (builder != null) {
HighlightFixUtil.registerQualifyMethodCallFix(resolveHelper.getReferencedMethodCandidates(methodCall, false), methodCall, list, builder);
PsiType expectedTypeByParent = InferenceSession.getTargetTypeByParent(methodCall);
PsiType actualType = ((PsiExpression)methodCall.copy()).getType();
TextRange fixRange = getFixRange(list);
if (expectedTypeByParent != null && actualType != null && !expectedTypeByParent.isAssignableFrom(actualType)) {
AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(builder, fixRange, methodCall, expectedTypeByParent, actualType);
}
HighlightFixUtil.registerQualifyMethodCallFix(resolveHelper.getReferencedMethodCandidates(methodCall, false), methodCall,
list, builder);
registerMethodCallIntentions(builder, methodCall, list, resolveHelper);
registerMethodReturnFixAction(builder, candidateInfo, methodCall);
registerTargetTypeFixesBasedOnApplicabilityInference(methodCall, candidateInfo, resolvedMethod, builder);

View File

@@ -411,18 +411,26 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
if (!myHolder.hasErrorResults() && functionalInterfaceType != null) {
String parentInferenceErrorMessage = null;
PsiCallExpression callExpression = parent instanceof PsiExpressionList && parent.getParent() instanceof PsiCallExpression ?
(PsiCallExpression)parent.getParent() : null;
JavaResolveResult containingCallResolveResult = callExpression != null ? callExpression.resolveMethodGenerics() : null;
if (containingCallResolveResult instanceof MethodCandidateInfo) {
parentInferenceErrorMessage = ((MethodCandidateInfo)containingCallResolveResult).getInferenceErrorMessage();
MethodCandidateInfo parentCallResolveResult =
callExpression != null ? tryCast(callExpression.resolveMethodGenerics(), MethodCandidateInfo.class) : null;
String parentInferenceErrorMessage = parentCallResolveResult != null ? parentCallResolveResult.getInferenceErrorMessage() : null;
PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType);
Map<PsiElement, @Nls String> returnErrors = null;
Set<PsiTypeParameter> 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)
&& !lambdaParametersMentionTypeParameter(functionalInterfaceType, parentTypeParameters);
if (!skipReturnCompatibility) {
returnErrors = LambdaUtil.checkReturnTypeCompatible(expression, returnType);
}
Map<PsiElement, @Nls String> returnErrors = LambdaUtil.checkReturnTypeCompatible(expression, LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType));
if (parentInferenceErrorMessage != null && (returnErrors == null || !returnErrors.containsValue(parentInferenceErrorMessage))) {
if (returnErrors == null) return;
HighlightInfo.Builder info = HighlightMethodUtil.createIncompatibleTypeHighlightInfo(callExpression, getResolveHelper(myHolder.getProject()),
(MethodCandidateInfo)containingCallResolveResult, expression);
parentCallResolveResult, expression);
if (info != null) {
returnErrors.keySet().forEach(k -> {
IntentionAction action = AdjustFunctionContextFix.createFix(k);
@@ -469,6 +477,17 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
}
private static boolean lambdaParametersMentionTypeParameter(PsiType functionalInterfaceType, Set<PsiTypeParameter> 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;
}
@Override
public void visitBreakStatement(@NotNull PsiBreakStatement statement) {
super.visitBreakStatement(statement);

View File

@@ -0,0 +1,46 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMethodCallExpression;
import com.siyeh.ig.psiutils.CommentTracker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ReplaceWithQualifierFix extends LocalQuickFixAndIntentionActionOnPsiElement {
private final String myRole;
public ReplaceWithQualifierFix(@Nullable PsiMethodCallExpression call, @Nullable String role) {
super(call);
myRole = role;
}
@Override
public void invoke(@NotNull Project project,
@NotNull PsiFile file,
@Nullable Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
PsiMethodCallExpression call = (PsiMethodCallExpression)startElement;
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null) return;
new CommentTracker().replace(call, qualifier);
}
@Override
public @NotNull String getText() {
return myRole == null ? QuickFixBundle.message("replace.with.qualifier.text") :
QuickFixBundle.message("replace.with.qualifier.text.role", myRole);
}
@Override
public @NotNull String getFamilyName() {
return QuickFixBundle.message("replace.with.qualifier.text");
}
}

View File

@@ -221,6 +221,8 @@ wrap.expression.using.static.accessor.family=Wrap expression
wrap.expression.using.static.accessor.text=Wrap using ''{0}()''
# {1} = one of fix.expression.role.xyz values
wrap.expression.using.static.accessor.text.role=Wrap {1} using ''{0}()''
replace.with.qualifier.text=Replace with qualifier
replace.with.qualifier.text.role=Replace {0} with qualifier
# {0} - qualified class name suggested to be imported.
side.effect.action.remove=&Remove

View File

@@ -0,0 +1,12 @@
import java.util.function.Predicate;
import java.util.function.Supplier;
class X {
public static String getOrDefault(String prefer, Supplier<String> def) {
return getOrDefault<error descr="'getOrDefault(java.util.function.Supplier<T>, java.util.function.Predicate<T>, java.util.function.Supplier<T>)' in 'X' cannot be applied to '(<lambda expression>, <method reference>, java.lang.String)'">(() -> prefer, String::isEmpty, def.get())</error>;
}
public static <T> T getOrDefault(Supplier<T> prefer, Predicate<T> abandon, Supplier<T> def) {
return null;
}
}

View File

@@ -12,7 +12,7 @@ class TypeArgsConsistency {
I<Integer> i1 = (i, j) -> i + j;
foo((i, j) -> i + j);
I<Integer> i2 = bar((i, j) -> i + j);
I<Integer> i3 = bar(<error descr="Incompatible types. Found: 'TypeArgsConsistency.I<java.lang.String>', required: 'TypeArgsConsistency.I<java.lang.Integer>'">(i, j) -> "" + i + j</error>);
I<Integer> i3 = <error descr="Incompatible types. Found: 'TypeArgsConsistency.I<java.lang.String>', required: 'TypeArgsConsistency.I<java.lang.Integer>'">bar((i, j) -> "" + i + j);</error>
}
}
@@ -43,7 +43,7 @@ class TypeArgsConsistency2 {
I<Integer> i1 = bar(x -> x);
I1<Integer> i2 = bar1(x -> 1);
I2<String> aI2 = bar2(x -> "");
I2<Integer> aI28 = bar2( <error descr="Incompatible types. Found: 'TypeArgsConsistency2.I2<java.lang.String>', required: 'TypeArgsConsistency2.I2<java.lang.Integer>'">x-> ""</error>);
I2<Integer> aI28 = <error descr="Incompatible types. Found: 'TypeArgsConsistency2.I2<java.lang.String>', required: 'TypeArgsConsistency2.I2<java.lang.Integer>'">bar2( x-> "");</error>
I2<Integer> i3 = bar2(x -> x);
I2<Integer> i4 = bar2(x -> foooI());
System.out.println(i4.foo(2));

View File

@@ -0,0 +1,13 @@
// "Replace 3rd argument with qualifier" "true-preview"
import java.util.function.Predicate;
import java.util.function.Supplier;
class X {
public static String getOrDefault(String prefer, Supplier<String> def) {
return getOrDefault(() -> prefer, String::isEmpty, def);
}
public static <T> T getOrDefault(Supplier<T> prefer, Predicate<T> abandon, Supplier<T> def) {
return null;
}
}

View File

@@ -0,0 +1,13 @@
// "Replace 3rd argument with qualifier" "true-preview"
import java.util.function.Predicate;
import java.util.function.Supplier;
class X {
public static String getOrDefault(String prefer, Supplier<String> def) {
return getOrDefault(() -> prefer, String::isEmpty, def<caret>.get());
}
public static <T> T getOrDefault(Supplier<T> prefer, Predicate<T> abandon, Supplier<T> def) {
return null;
}
}

View File

@@ -1205,4 +1205,5 @@ public class GenericsHighlighting8Test extends LightDaemonAnalyzerTestCase {
public void testNoCaptureConversionDuringDetectingSupertypesDeepInHierarchy() { doTest(); }
public void testLowerBoundAssignabilityCheck() { doTest(); }
public void testIgnoreErasureForProperTypeBound() { doTest(); }
public void testInferenceErrorAttribution() {doTest();}
}