From 8673ad67aaf4d23de60733c350a2c70e5629ae53 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 29 Nov 2017 17:56:25 +0700 Subject: [PATCH] IDEA-182822 Raw type completion options are suggested when actual expression type is inferred --- .../guess/impl/GuessManagerImpl.java | 18 +++++- .../intellij/codeInsight/CodeInsightUtil.java | 45 +++----------- .../src/com/intellij/psi/GenericsUtil.java | 60 +++++++++++++++++++ .../completion/normal/GenericTypeDfa.java | 8 +++ .../normal/GenericTypeDfa_after.java | 8 +++ .../completion/NormalCompletionDfaTest.groovy | 1 + 6 files changed, 101 insertions(+), 39 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/completion/normal/GenericTypeDfa.java create mode 100644 java/java-tests/testData/codeInsight/completion/normal/GenericTypeDfa_after.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java index 5d7c9139d783..62029fe07270 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java @@ -410,13 +410,29 @@ public class GuessManagerImpl extends GuessManager { if (fromDfa != null) { Collection conjuncts = fromDfa.get(expr); if (!conjuncts.isEmpty()) { - return ContainerUtil.newArrayList(PsiIntersectionType.flatten(conjuncts.toArray(PsiType.EMPTY_ARRAY), new LinkedHashSet<>())); + Set flatTypes = PsiIntersectionType.flatten(conjuncts.toArray(PsiType.EMPTY_ARRAY), new LinkedHashSet<>()); + return ContainerUtil.mapNotNull(flatTypes, type -> tryGenerify(expr, type)); } } return Collections.emptyList(); } + private static PsiType tryGenerify(PsiExpression expression, PsiType type) { + if (!(type instanceof PsiClassType)) { + return type; + } + PsiClassType classType = (PsiClassType)type; + if (!classType.isRaw()) { + return classType; + } + PsiClass psiClass = classType.resolve(); + if (psiClass == null) return classType; + PsiType expressionType = expression.getType(); + if (!(expressionType instanceof PsiClassType)) return classType; + return GenericsUtil.getExpectedGenericType(expression, psiClass, (PsiClassType)expressionType); + } + private static class ExpressionTypeInstructionVisitor extends StandardInstructionVisitor { private MultiMap myResult; private final PsiElement myForPlace; diff --git a/java/java-impl/src/com/intellij/codeInsight/CodeInsightUtil.java b/java/java-impl/src/com/intellij/codeInsight/CodeInsightUtil.java index e05809f8274f..3912724f582a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/CodeInsightUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/CodeInsightUtil.java @@ -29,7 +29,10 @@ import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.ClassInheritorsSearch; import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.*; +import com.intellij.psi.util.FileTypeUtils; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.psi.util.TypeConversionUtil; import com.intellij.psi.util.proximity.PsiProximityComparator; import com.intellij.refactoring.util.RefactoringUtil; import com.intellij.util.Consumer; @@ -365,7 +368,8 @@ public class CodeInsightUtil { PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(baseClass, inheritor, PsiSubstitutor.EMPTY); if (superSubstitutor == null) return true; - List typeArgs = getRawSubtypes ? null : getExpectedTypeArgs(context, inheritor, Arrays.asList(inheritor.getTypeParameters()), baseType); + List typeArgs = getRawSubtypes ? null : GenericsUtil + .getExpectedTypeArguments(context, inheritor, Arrays.asList(inheritor.getTypeParameters()), baseType); PsiClassType inheritorType = typeArgs == null || typeArgs.contains(null) ? factory.createType(inheritor, factory.createRawSubstitutor(inheritor)) : factory.createType(inheritor, typeArgs.toArray(PsiType.EMPTY_ARRAY)); @@ -390,45 +394,10 @@ public class CodeInsightUtil { PsiTypeParameterListOwner paramOwner, Iterable typeParams, PsiClassType expectedType) { if (paramOwner instanceof PsiClass) { - PsiClassType.ClassResolveResult resolve = expectedType.resolveGenerics(); - PsiClass expectedClass = resolve.getElement(); - - if (!InheritanceUtil.isInheritorOrSelf((PsiClass)paramOwner, expectedClass, true)) { - return ContainerUtil.map(typeParams, p -> (PsiType)null); - } - - PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(expectedClass, (PsiClass)paramOwner, PsiSubstitutor.EMPTY); - assert substitutor != null; - - return ContainerUtil.map(typeParams, p -> getExpectedTypeArg(context, resolve, substitutor, p)); + return GenericsUtil.getExpectedTypeArguments(context, (PsiClass)paramOwner, typeParams, expectedType); } PsiSubstitutor substitutor = SmartCompletionDecorator.calculateMethodReturnTypeSubstitutor((PsiMethod)paramOwner, expectedType); return ContainerUtil.map(typeParams, substitutor::substitute); } - - @Nullable - private static PsiType getExpectedTypeArg(PsiElement context, - PsiClassType.ClassResolveResult expectedType, - PsiSubstitutor superClassSubstitutor, PsiTypeParameter typeParam) { - PsiClass expectedClass = expectedType.getElement(); - assert expectedClass != null; - for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(expectedClass)) { - PsiType paramSubstitution = superClassSubstitutor.substitute(parameter); - PsiClass inheritorCandidateParameter = PsiUtil.resolveClassInType(paramSubstitution); - if (inheritorCandidateParameter instanceof PsiTypeParameter && - ((PsiTypeParameter)inheritorCandidateParameter).getOwner() == typeParam.getOwner() && - inheritorCandidateParameter != typeParam) { - continue; - } - - PsiType argSubstitution = expectedType.getSubstitutor().substitute(parameter); - PsiType substitution = JavaPsiFacade.getInstance(context.getProject()).getResolveHelper() - .getSubstitutionForTypeParameter(typeParam, paramSubstitution, argSubstitution, true, PsiUtil.getLanguageLevel(context)); - if (substitution != null && substitution != PsiType.NULL) { - return substitution; - } - } - return null; - } } diff --git a/java/java-psi-api/src/com/intellij/psi/GenericsUtil.java b/java/java-psi-api/src/com/intellij/psi/GenericsUtil.java index 85a16a1ec399..b621d0c9a2f3 100644 --- a/java/java-psi-api/src/com/intellij/psi/GenericsUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/GenericsUtil.java @@ -551,4 +551,64 @@ public class GenericsUtil { return !TypeConversionUtil.isAssignable(bound, type, allowUncheckedConversion); } } + + @NotNull + public static PsiClassType getExpectedGenericType(PsiElement context, + PsiClass aClass, + PsiClassType expectedType) { + List arguments = getExpectedTypeArguments(context, aClass, Arrays.asList(aClass.getTypeParameters()), expectedType); + return JavaPsiFacade.getElementFactory(context.getProject()).createType(aClass, arguments.toArray(PsiType.EMPTY_ARRAY)); + } + + /** + * Tries to find the type parameters applied to a class which are compatible with expected supertype + * + * @param context a context element + * @param aClass a class which type parameters should be found + * @param typeParams type parameters to substitute (a subset of all type parameters of a class) + * @param expectedType an expected supertype + * @return a list of type arguments which correspond to passed type parameters + */ + @NotNull + public static List getExpectedTypeArguments(PsiElement context, + PsiClass aClass, + Iterable typeParams, + PsiClassType expectedType) { + PsiClassType.ClassResolveResult resolve = expectedType.resolveGenerics(); + PsiClass expectedClass = resolve.getElement(); + + if (!InheritanceUtil.isInheritorOrSelf(aClass, expectedClass, true)) { + return ContainerUtil.map(typeParams, p -> (PsiType)null); + } + + PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(expectedClass, aClass, PsiSubstitutor.EMPTY); + assert substitutor != null; + + return ContainerUtil.map(typeParams, p -> getExpectedTypeArg(context, resolve, substitutor, p)); + } + + @Nullable + private static PsiType getExpectedTypeArg(PsiElement context, + PsiClassType.ClassResolveResult expectedType, + PsiSubstitutor superClassSubstitutor, PsiTypeParameter typeParam) { + PsiClass expectedClass = expectedType.getElement(); + assert expectedClass != null; + for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(expectedClass)) { + PsiType paramSubstitution = superClassSubstitutor.substitute(parameter); + PsiClass inheritorCandidateParameter = PsiUtil.resolveClassInType(paramSubstitution); + if (inheritorCandidateParameter instanceof PsiTypeParameter && + ((PsiTypeParameter)inheritorCandidateParameter).getOwner() == typeParam.getOwner() && + inheritorCandidateParameter != typeParam) { + continue; + } + + PsiType argSubstitution = expectedType.getSubstitutor().substitute(parameter); + PsiType substitution = JavaPsiFacade.getInstance(context.getProject()).getResolveHelper() + .getSubstitutionForTypeParameter(typeParam, paramSubstitution, argSubstitution, true, PsiUtil.getLanguageLevel(context)); + if (substitution != null && substitution != PsiType.NULL) { + return substitution; + } + } + return null; + } } diff --git a/java/java-tests/testData/codeInsight/completion/normal/GenericTypeDfa.java b/java/java-tests/testData/codeInsight/completion/normal/GenericTypeDfa.java new file mode 100644 index 000000000000..991431175b0a --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/GenericTypeDfa.java @@ -0,0 +1,8 @@ +import java.util.*; + +class Foo { + void test() { + Set set = new TreeSet<>(); + set.ceil + } +} diff --git a/java/java-tests/testData/codeInsight/completion/normal/GenericTypeDfa_after.java b/java/java-tests/testData/codeInsight/completion/normal/GenericTypeDfa_after.java new file mode 100644 index 000000000000..5112e0b02bb0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/GenericTypeDfa_after.java @@ -0,0 +1,8 @@ +import java.util.*; + +class Foo { + void test() { + Set set = new TreeSet<>(); + ((TreeSet) set).ceiling() + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy index f26d973cbc1f..658a45df785f 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy @@ -56,6 +56,7 @@ class NormalCompletionDfaTest extends LightFixtureCompletionTestCase { void testOptionalDfa() { doTest() } void testFieldWithCastingCaret() { doTest() } void testCastWhenMethodComesFromDfaSuperType() { doTest() } + void testGenericTypeDfa() { doTest() } void testCastTwice() { configureByTestName()