[java-highlighting] Better actual type definition; fixes for some type mismatch in collectors

GitOrigin-RevId: 3652f4511a067b86bc9138f492a2a88cded09de0
This commit is contained in:
Tagir Valeev
2022-08-05 14:48:35 +02:00
committed by intellij-monorepo-bot
parent 099133f6e2
commit a0bc3ee404
15 changed files with 120 additions and 50 deletions

View File

@@ -7,6 +7,7 @@ import com.intellij.codeInsight.daemon.impl.quickfix.*;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
@@ -15,7 +16,6 @@ import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import one.util.streamex.MoreCollectors;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
@@ -38,17 +38,16 @@ class AdaptExpressionTypeFixUtil {
private static void registerPatchParametersFixes(@NotNull HighlightInfo info,
@NotNull PsiMethodCallExpression call,
@NotNull PsiMethod method,
@NotNull PsiType expectedTypeByParent) {
@NotNull PsiType expectedTypeByParent,
@NotNull PsiType actualType) {
JavaResolveResult result = call.resolveMethodGenerics();
if (!(result instanceof MethodCandidateInfo)) return;
PsiType methodType = method.getReturnType();
PsiSubstitutor substitutor = ((MethodCandidateInfo)result).getSubstitutor(false);
PsiType actualType = substitutor.substitute(methodType);
Substitution.TypeParameterSubstitution substitution =
tryCast(findDesiredSubstitution(expectedTypeByParent, actualType, methodType, 0), Substitution.TypeParameterSubstitution.class);
if (substitution == null) return;
PsiTypeParameter typeParameter = substitution.myTypeParameter;
PsiType expectedTypeValue = substitution.myType;
PsiType expectedTypeValue = substitution.myDesiredType;
PsiParameter[] parameters = method.getParameterList().getParameters();
Set<PsiTypeParameter> set = Set.of(typeParameter);
@@ -78,12 +77,16 @@ class AdaptExpressionTypeFixUtil {
QuickFixAction.registerQuickFixAction(info, fix);
}
}
PsiSubstitutor substitutor = ((MethodCandidateInfo)result).getSubstitutor(false);
PsiType expectedArgType = substitutor.put(typeParameter, expectedTypeValue).substitute(parameterType);
if (arg instanceof PsiLambdaExpression && parameterType instanceof PsiClassType) {
registerLambdaReturnFixes(info, (PsiLambdaExpression)arg, (PsiClassType)parameterType, expectedArgType, typeParameter);
return;
}
registerExpectedTypeFixes(info, arg, expectedArgType);
PsiType actualArgType = PsiPolyExpressionUtil.isPolyExpression(arg) ?
substitutor.put(typeParameter, substitution.myActualType).substitute(parameterType) :
arg.getType();
registerExpectedTypeFixes(info, arg, expectedArgType, actualArgType);
}
private static void registerPatchQualifierFixes(@NotNull HighlightInfo info,
@@ -106,8 +109,9 @@ class AdaptExpressionTypeFixUtil {
if (qualifierClass == null) return;
PsiType expectedQualifierType = JavaPsiFacade.getElementFactory(method.getProject())
.createType(qualifierClass, classResolveResult.getSubstitutor().put(typeParameter, expectedTypeValue));
if (!expectedQualifierType.equals(qualifierCall.getType())) {
registerPatchParametersFixes(info, qualifierCall, qualifierMethod, expectedQualifierType);
PsiType actualType = qualifierCall.getType();
if (actualType != null && !expectedQualifierType.equals(actualType)) {
registerPatchParametersFixes(info, qualifierCall, qualifierMethod, expectedQualifierType, actualType);
}
}
@@ -146,25 +150,40 @@ class AdaptExpressionTypeFixUtil {
/**
* Registers fixes (if any) that update code to match the expression type with the desired type.
*
* @param info error highlighting to attach fixes to
* @param expression expression whose type is incorrect
* @param info error highlighting to attach fixes to
* @param expression expression whose type is incorrect
* @param expectedType desired expression type.
*/
static void registerExpectedTypeFixes(@NotNull HighlightInfo info, @NotNull PsiExpression expression, @Nullable PsiType expectedType) {
if (expectedType == null) return;
PsiType actualType;
if (PsiPolyExpressionUtil.isPolyExpression(expression)) {
actualType = ((PsiExpression)expression.copy()).getType();
}
else {
actualType = expression.getType();
}
registerExpectedTypeFixes(info, expression, expectedType, actualType);
}
/**
* Registers fixes (if any) that update code to match the expression type with the desired type.
*
* @param info error highlighting to attach fixes to
* @param expression expression whose type is incorrect
* @param expectedType desired expression type
* @param actualType actual expression type
*/
static void registerExpectedTypeFixes(@NotNull HighlightInfo info,
@NotNull PsiExpression expression,
@Nullable PsiType expectedType,
@Nullable PsiType actualType) {
if (actualType == null || expectedType == null) return;
expectedType = GenericsUtil.getVariableTypeByExpressionType(expectedType);
TextRange range = expression.getTextRange();
String role = info.startOffset == range.getStartOffset() && info.endOffset == range.getEndOffset() ? null : getRole(expression);
QuickFixAction.registerQuickFixAction(info, new WrapWithAdapterMethodCallFix(expectedType, expression, role));
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapWithOptionalFix(expectedType, expression));
QuickFixAction.registerQuickFixAction(info, new WrapExpressionFix(expectedType, expression, role));
PsiType actualType = expression.getType();
if (expression instanceof PsiMethodCallExpression) {
JavaResolveResult result = ((PsiMethodCallExpression)expression).resolveMethodGenerics();
if (result instanceof MethodCandidateInfo) {
actualType = ((MethodCandidateInfo)result).getSubstitutor(false).substitute(actualType);
}
}
if (expectedType instanceof PsiArrayType) {
PsiType erasedValueType = TypeConversionUtil.erasure(actualType);
if (erasedValueType != null &&
@@ -180,7 +199,7 @@ class AdaptExpressionTypeFixUtil {
if (expression instanceof PsiMethodCallExpression) {
PsiMethod argMethod = ((PsiMethodCallExpression)expression).resolveMethod();
if (argMethod != null) {
registerPatchParametersFixes(info, (PsiMethodCallExpression)expression, argMethod, expectedType);
registerPatchParametersFixes(info, (PsiMethodCallExpression)expression, argMethod, expectedType, actualType);
}
}
}
@@ -221,11 +240,13 @@ class AdaptExpressionTypeFixUtil {
class TypeParameterSubstitution implements Substitution {
final @NotNull PsiTypeParameter myTypeParameter;
final @NotNull PsiType myType;
final @NotNull PsiType myActualType;
final @NotNull PsiType myDesiredType;
public TypeParameterSubstitution(@NotNull PsiTypeParameter parameter, @NotNull PsiType type) {
public TypeParameterSubstitution(@NotNull PsiTypeParameter parameter, @NotNull PsiType actualType, @NotNull PsiType desiredType) {
myTypeParameter = parameter;
myType = type;
myActualType = actualType;
myDesiredType = desiredType;
}
@Override
@@ -233,17 +254,17 @@ class AdaptExpressionTypeFixUtil {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TypeParameterSubstitution that = (TypeParameterSubstitution)o;
return myTypeParameter.equals(that.myTypeParameter) && myType.equals(that.myType);
return myTypeParameter.equals(that.myTypeParameter) && myDesiredType.equals(that.myDesiredType);
}
@Override
public int hashCode() {
return Objects.hash(myTypeParameter, myType);
return Objects.hash(myTypeParameter, myDesiredType);
}
@Override
public String toString() {
return myTypeParameter.getName() + "->" + myType.getCanonicalText();
return myTypeParameter.getName() + "->" + myDesiredType.getCanonicalText();
}
}
}
@@ -260,19 +281,6 @@ class AdaptExpressionTypeFixUtil {
((PsiArrayType)methodType).getComponentType(),
level);
}
if (expected instanceof PsiWildcardType && level == 1) {
PsiWildcardType wildcardType = (PsiWildcardType)expected;
if (wildcardType.isExtends()) {
expected = wildcardType.getExtendsBound();
}
else if (wildcardType.isSuper()) {
expected = wildcardType.getSuperBound();
}
}
if (actual instanceof PsiWildcardType && level == 1 && TypeUtils.isJavaLangObject(expected)) {
// workaround for collector accumulator, probably incorrect in general?
return Substitution.TOP;
}
if (expected.equals(actual)) return Substitution.TOP;
if (!(methodType instanceof PsiClassType)) return Substitution.BOTTOM;
PsiClass methodClass = ((PsiClassType)methodType).resolve();
@@ -283,7 +291,7 @@ class AdaptExpressionTypeFixUtil {
PsiSubstitutor substitutor = PsiSubstitutor.EMPTY.put((PsiTypeParameter)methodClass, expected);
if (!substitutor.substitute(superType).isAssignableFrom(expected)) return Substitution.BOTTOM;
}
return new Substitution.TypeParameterSubstitution((PsiTypeParameter)methodClass, expected);
return new Substitution.TypeParameterSubstitution((PsiTypeParameter)methodClass, actual, expected);
}
return Substitution.BOTTOM;
}

View File

@@ -576,13 +576,15 @@ public final class HighlightMethodUtil {
PsiMethod method = resolveResult.getElement();
HighlightInfo highlightInfo;
PsiType expectedTypeByParent = InferenceSession.getTargetTypeByParent(methodCall);
PsiType actualType = resolveResult.getSubstitutor(false).substitute(method.getReturnType());
PsiType actualType =
methodCall instanceof PsiExpression ? ((PsiExpression)methodCall.copy()).getType() :
resolveResult.getSubstitutor(false).substitute(method.getReturnType());
TextRange fixRange = getFixRange(elementToHighlight);
if (expectedTypeByParent != null && actualType != null && !expectedTypeByParent.isAssignableFrom(actualType)) {
highlightInfo = HighlightUtil.createIncompatibleTypeHighlightInfo(
expectedTypeByParent, actualType, fixRange, 0, XmlStringUtil.escapeString(errorMessage));
if (methodCall instanceof PsiMethodCallExpression) {
AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(highlightInfo, (PsiMethodCallExpression)methodCall, expectedTypeByParent);
if (methodCall instanceof PsiExpression) {
AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(highlightInfo, (PsiExpression)methodCall, expectedTypeByParent, actualType);
}
}
else {

View File

@@ -572,7 +572,7 @@ public final class HighlightUtil {
QuickFixAction.registerQuickFixAction(highlightInfo, getFixFactory().createAddTypeCastFix(lType, expression));
}
if (expression != null) {
AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(highlightInfo, expression, lType);
AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(highlightInfo, expression, lType, rType);
if (!(expression.getParent() instanceof PsiConditionalExpression && PsiType.VOID.equals(lType))) {
HighlightFixUtil.registerChangeReturnTypeFix(highlightInfo, expression, lType);
}

View File

@@ -0,0 +1,13 @@
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Demo {
void test() {
Map<Long, List<String>> collect = <error descr="Incompatible types. Found: 'java.util.Map<java.lang.Integer,java.util.List<java.lang.String>>', required: 'java.util.Map<java.lang.Long,java.util.List<java.lang.String>>'">Stream.of("xyz", "asfdasdfdasf", "dasfafasdfdf")
.collect(Collectors.groupingBy(s -> s.length()));</error>
Map<String, Long> map = <error descr="Incompatible types. Found: 'java.util.Map<java.lang.String,java.lang.Integer>', required: 'java.util.Map<java.lang.String,java.lang.Long>'">Stream.of("a", "b", "c").collect(Collectors.toMap(s -> s, s -> s.length()));</error>
}
}

View File

@@ -1,5 +1,5 @@
class Foo<T extends Enum> {
public T bar(Class<? extends T> type, String str) {
return <error descr="Incompatible types: Enum is not convertible to T">Enum.valueOf(type, str);</error>
return <error descr="Incompatible types. Found: 'java.lang.Enum', required: 'T'">Enum.valueOf(type, str);</error>
}
}

View File

@@ -4,7 +4,7 @@ abstract class Group {
}
public <T extends Category> T get(Key<T> key) {
return <error descr="Incompatible types: Category is not convertible to T">getCategory(key);</error>
return <error descr="Incompatible types. Found: 'Category', required: 'T'">getCategory(key);</error>
}
public abstract <R extends Category<R>> R getCategory(Key<R> key);

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.Object>', required: 'TypeArgsConsistency.I<java.lang.Integer>'">(i, j) -> "" + i + j</error>);
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>);
}
}
@@ -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.Object>', required: 'TypeArgsConsistency2.I2<java.lang.Integer>'">x-> ""</error>);
I2<Integer> aI28 = bar2( <error descr="Incompatible types. Found: 'TypeArgsConsistency2.I2<java.lang.String>', required: 'TypeArgsConsistency2.I2<java.lang.Integer>'">x-> ""</error>);
I2<Integer> i3 = bar2(x -> x);
I2<Integer> i4 = bar2(x -> foooI());
System.out.println(i4.foo(2));

View File

@@ -6,7 +6,7 @@ import java.util.function.Function;
class Test {
{
final Map<Comparable, List<Collection<?>>> families = <error descr="Incompatible types. Found: 'java.util.Map<C,java.util.List<java.lang.Object>>', required: 'java.util.Map<java.lang.Comparable,java.util.List<java.util.Collection<?>>>'">sortingMerge((s) -> 0);</error>
final Map<Comparable, List<Collection<?>>> families = <error descr="Incompatible types. Found: 'java.util.Map<java.lang.Integer,java.util.List<java.lang.Object>>', required: 'java.util.Map<java.lang.Comparable,java.util.List<java.util.Collection<?>>>'">sortingMerge((s) -> 0);</error>
}
private <C extends Comparable<C>, T> Map<C, List<T>> sortingMerge(Function<T, C> keyFunction) {

View File

@@ -12,7 +12,7 @@ import java.util.function.Function;
class Test {
{
valueOf(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char[]'">processFirst(x -> x)</error>);
valueOf(<error descr="Incompatible types. Found: 'java.lang.Integer', required: 'char[]'">processFirst(x -> x)</error>);
}
public static <V> V processFirst(Function<Integer,V> f){

View File

@@ -6,7 +6,7 @@ import java.util.stream.Stream;
class Test {
void foo() {
log(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'java.lang.String[]'">get(TreeSet<String>::new)</error>);
log(<error descr="Incompatible types. Found: 'java.util.TreeSet<java.lang.String>', required: 'java.lang.String[]'">get(TreeSet<String>::new)</error>);
}
private void log(String params[]) {

View File

@@ -0,0 +1,12 @@
// "Cast lambda return to 'long'" "true-preview"
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamFilter {
void test() {
Map<Long, List<String>> collect = Stream.of("xyz", "asfdasdfdasf", "dasfafasdfdf")
.collect(Collectors.groupingBy(s -> (long) s.length()));
}
}

View File

@@ -0,0 +1,11 @@
// "Cast lambda return to 'long'" "true-preview"
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamFilter {
void test() {
Map<String, Long> map = Stream.of("a", "b", "c").collect(Collectors.toMap(s -> s, s -> (long) s.length()));
}
}

View File

@@ -0,0 +1,12 @@
// "Cast lambda return to 'long'" "true-preview"
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamFilter {
void test() {
Map<Long, List<String>> collect = Stream.of("xyz", "asfdasdfdasf", "dasfafasdfdf")
.collect(Collectors.groupingBy(s -> <caret>s.length()));
}
}

View File

@@ -0,0 +1,11 @@
// "Cast lambda return to 'long'" "true-preview"
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamFilter {
void test() {
Map<String, Long> map = Stream.of("a", "b", "c").collect(Collectors.<caret>toMap(s -> s, s -> s.length()));
}
}

View File

@@ -32,6 +32,7 @@ public class LightAdvHighlightingJdk8Test extends LightDaemonAnalyzerTestCase {
public void testUncheckedWarningForPolyConditional() { enableInspectionTool(new UncheckedWarningLocalInspection()); doTest(true, true); }
public void testUncheckedWarningOnQualifierWithTypeParameterType() { enableInspectionTool(new UncheckedWarningLocalInspection()); doTest(true, true); }
public void testLambdaExpressions() { doTest(false, true); }
public void testComplexStreamTypeMismatch() { doTest(false, false);}
public void testUnsupportedFeatures() { doTest(false, false); }
public void testModulesNotSupported() { doTest(false, false); }