mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
[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:
committed by
intellij-monorepo-bot
parent
099b26f082
commit
b2dfb4c8a8
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user