mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
[java-highlighting] Better actual type definition; fixes for some type mismatch in collectors
GitOrigin-RevId: 3652f4511a067b86bc9138f492a2a88cded09de0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
099133f6e2
commit
a0bc3ee404
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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); }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user