From c4ed6f3022450d0aa29965dc35a89879c19c0af7 Mon Sep 17 00:00:00 2001 From: peter Date: Fri, 9 Mar 2012 15:16:40 +0400 Subject: [PATCH] IDEA-68584 Completion should detect return type and autocast in some cases --- .../completion/JavaCompletionUtil.java | 157 +++++++++--------- ...erenceExpressionCompletionContributor.java | 23 +-- .../scope/JavaCompletionProcessor.java | 2 +- .../codeInsight/lookup/PsiTypeLookupItem.java | 1 + .../completion/normal/CastVisually.java | 14 ++ .../completion/NormalCompletionTest.groovy | 8 + .../completion/GroovyNoVariantsDelegator.java | 2 +- 7 files changed, 109 insertions(+), 98 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/completion/normal/CastVisually.java diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java index 3c5b21d43b46..12adae0a1fcd 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java @@ -30,28 +30,23 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.NullableLazyKey; -import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.patterns.PsiElementPattern; import com.intellij.patterns.PsiJavaPatterns; import com.intellij.psi.*; import com.intellij.psi.codeStyle.*; -import com.intellij.psi.filters.AndFilter; -import com.intellij.psi.filters.ClassFilter; import com.intellij.psi.filters.ElementFilter; -import com.intellij.psi.filters.element.ExcludeDeclaredFilter; -import com.intellij.psi.filters.element.ExcludeSillyAssignment; import com.intellij.psi.html.HtmlTag; +import com.intellij.psi.impl.FakePsiElement; +import com.intellij.psi.impl.light.LightVariableBuilder; import com.intellij.psi.impl.source.PostprocessReformattingAspect; import com.intellij.psi.impl.source.PsiImmediateClassType; import com.intellij.psi.javadoc.PsiDocToken; import com.intellij.psi.scope.BaseScopeProcessor; -import com.intellij.psi.scope.ElementClassFilter; import com.intellij.psi.scope.ElementClassHint; import com.intellij.psi.scope.NameHint; +import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.scope.util.PsiScopesUtil; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiFormatUtil; @@ -78,13 +73,7 @@ public class JavaCompletionUtil { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaCompletionUtil"); public static final Key> DYNAMIC_TYPE_EVALUATOR = Key.create("DYNAMIC_TYPE_EVALUATOR"); - static final Key QUALIFIER_TYPE_ATTR = Key.create("qualifierType"); // SmartPsiElementPointer to PsiType of "qualifier" - @NonNls - public static final String GET_PREFIX = "get"; - @NonNls - public static final String SET_PREFIX = "set"; - @NonNls - public static final String IS_PREFIX = "is"; + private static final Key QUALIFIER_TYPE_ATTR = Key.create("qualifierType"); // SmartPsiElementPointer to PsiType of "qualifier" public static final OffsetKey LPAREN_OFFSET = OffsetKey.create("lparen"); public static final OffsetKey RPAREN_OFFSET = OffsetKey.create("rparen"); public static final OffsetKey ARG_LIST_END_OFFSET = OffsetKey.create("argListEnd"); @@ -435,20 +424,26 @@ public class JavaCompletionUtil { boolean checkInitialized = parameters.getInvocationCount() <= 1 && call != null && PsiKeyword.SUPER.equals(call.getMethodExpression().getText()); final JavaCompletionProcessor processor = new JavaCompletionProcessor(element, elementFilter, checkAccess, checkInitialized, filterStaticAfterInstance, nameCondition); + final PsiType plainQualifier = processor.getQualifierType(); + PsiType qualifierType = plainQualifier; + + PsiType runtimeQualifier = getQualifierCastType(javaReference, parameters); + if (runtimeQualifier != null) { + javaReference = (PsiReferenceExpression) JavaPsiFacade.getElementFactory(element.getProject()).createExpressionFromText("xxx.xxx", createContextWithXxxVariable(element, runtimeQualifier)); + qualifierType = runtimeQualifier; + } + javaReference.processVariants(processor); - final Collection plainResults = processor.getResults(); - final PsiType qualifierType = processor.getQualifierType(); - PsiType castedQualifierType = addQualifierCastingVariants(javaReference, processor, set, parameters); - - boolean mayHighlight = qualifierType != null && (castedQualifierType == null || castedQualifierType.equals(qualifierType) || !qualifierType.isAssignableFrom(castedQualifierType)); + final PsiTypeLookupItem castItem = runtimeQualifier == null ? null : PsiTypeLookupItem.createLookupItem(runtimeQualifier, (PsiReferenceExpression)javaReference); final boolean pkgContext = inSomePackage(element); final Set mentioned = new THashSet(); - for (CompletionElement completionElement : plainResults) { - LookupElement item = createLookupElement(completionElement, qualifierType); + for (CompletionElement completionElement : processor.getResults()) { + LookupElement item = createLookupElement(completionElement); if (item != null) { + item.putUserData(QUALIFIER_TYPE_ATTR, qualifierType); final Object o = item.getObject(); if (o instanceof PsiClass && !isSourceLevelAccessible(element, (PsiClass)o, pkgContext)) { continue; @@ -459,7 +454,7 @@ public class JavaCompletionUtil { } mentioned.add((PsiMember)o); } - set.add(mayHighlight ? highlightIfNeeded(qualifierType, item, o) : item); + set.add(highlightIfNeeded(qualifierType, castQualifier(item, castItem, plainQualifier), o)); } } @@ -479,68 +474,63 @@ public class JavaCompletionUtil { } @Nullable - private static PsiType addQualifierCastingVariants(PsiJavaReference javaReference, JavaCompletionProcessor processor, THashSet set, CompletionParameters parameters) { + private static PsiType getQualifierCastType(PsiJavaReference javaReference, CompletionParameters parameters) { if (javaReference instanceof PsiReferenceExpression) { final PsiReferenceExpression refExpr = (PsiReferenceExpression)javaReference; final PsiExpression qualifier = refExpr.getQualifierExpression(); if (qualifier != null) { final Project project = qualifier.getProject(); - final PairFunction evaluator = refExpr.getContainingFile().getCopyableUserData(DYNAMIC_TYPE_EVALUATOR); - PsiReferenceExpression context = refExpr; PsiType type = null; + final PairFunction evaluator = refExpr.getContainingFile().getCopyableUserData(DYNAMIC_TYPE_EVALUATOR); if (evaluator != null) { type = evaluator.fun(qualifier, parameters); - context = null; } if (type == null) { type = GuessManager.getInstance(project).getControlFlowExpressionType(qualifier); - context = refExpr; - } - if (type != null) { - processor.clear(); - - return addQualifierCastingVariants(processor, refExpr, type, set, context); } + return type; } } return null; } - private static PsiType addQualifierCastingVariants(JavaCompletionProcessor processor, PsiReferenceExpression refExpr, - PsiType castTo, - THashSet set, final PsiReferenceExpression context) { - Project project = refExpr.getProject(); - - PsiExpression qualifier = refExpr.getQualifierExpression(); - assert qualifier != null; - final String newText = "((" + castTo.getCanonicalText() + ") " + qualifier.getText() + ")." + refExpr.getReferenceName(); - final PsiExpression newRef = JavaPsiFacade.getElementFactory(project).createExpressionFromText(newText, context); - ((PsiReferenceExpression)newRef).processVariants(processor); - - final LookupElement castItem = PsiTypeLookupItem.createLookupItem(castTo, refExpr); - - for (CompletionElement completionElement : processor.getResults()) { - final LookupElement item = createLookupElement(completionElement, castTo); - if (item != null) { - LookupElement item1 = castQualifier(project, item, castItem); - set.add(highlightIfNeeded(castTo, item1, item1.getObject())); + @NotNull + private static LookupElement castQualifier(@NotNull LookupElement item, + @Nullable final PsiTypeLookupItem castTypeItem, + @Nullable PsiType plainQualifier) { + if (castTypeItem == null) { + return item; + } + if (plainQualifier != null) { + Object o = item.getObject(); + if (containsMember(plainQualifier, o)) { + if (!(o instanceof PsiMethod)) { + return item; + } + PsiType castType = castTypeItem.getPsiType(); + if (plainQualifier instanceof PsiClassType && castType instanceof PsiClassType) { + PsiMethod method = (PsiMethod)o; + PsiSubstitutor plainSub = ((PsiClassType)plainQualifier).resolveGenerics().getSubstitutor(); + PsiSubstitutor castSub = ((PsiClassType)castType).resolveGenerics().getSubstitutor(); + if (method.getSignature(plainSub).equals(method.getSignature(castSub)) && + plainSub.substitute(method.getReturnType()).equals(castSub.substitute(method.getReturnType()))) { + return item; + } + } } } - return castTo; - } - private static LookupElementDecorator castQualifier(final Project project, LookupElement item, final LookupElement to) { return LookupElementDecorator.withInsertHandler(item, new InsertHandlerDecorator() { public void handleInsert(InsertionContext context, LookupElementDecorator item) { final Document document = context.getEditor().getDocument(); - PsiDocumentManager.getInstance(project).commitDocument(document); + context.commitDocument(); final PsiFile file = context.getFile(); final PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset(), PsiJavaCodeReferenceElement.class, false); if (ref != null) { final PsiElement qualifier = ref.getQualifier(); if (qualifier != null) { - final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project); + final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(qualifier.getProject()); final String parenSpace = settings.SPACE_WITHIN_PARENTHESES ? " " : ""; document.insertString(qualifier.getTextRange().getEndOffset(), parenSpace + ")"); @@ -551,7 +541,7 @@ public class JavaCompletionUtil { final int exprStart = qualifier.getTextRange().getStartOffset(); document.insertString(exprStart, prefix + spaceWithin + ")" + spaceAfter); - CompletionUtil.emulateInsertion(context, exprStart + prefix.length(), to); + CompletionUtil.emulateInsertion(context, exprStart + prefix.length(), castTypeItem); context.getEditor().getCaretModel().moveToOffset(context.getTailOffset()); } } @@ -562,24 +552,26 @@ public class JavaCompletionUtil { } public static LookupElement highlightIfNeeded(PsiType qualifierType, LookupElement item, Object object) { - if (qualifierType instanceof PsiArrayType) { - if (object instanceof PsiField || object instanceof PsiMethod) { //length and clone() - PsiElement parent = ((PsiElement)object).getParent(); - if (parent instanceof PsiClass && parent.getContainingFile().getVirtualFile() == null) { //yes, they're a bit dummy - return highlight(item); - } + return containsMember(qualifierType, object) ? highlight(item) : item; + } + + public static boolean containsMember(PsiType qualifierType, Object object) { + if (qualifierType instanceof PsiArrayType && object instanceof PsiMember) { //length and clone() + if (((PsiMember)object).getContainingFile().getVirtualFile() == null) { //yes, they're a bit dummy + return true; } } else if (qualifierType instanceof PsiClassType) { PsiClass qualifierClass = ((PsiClassType)qualifierType).resolve(); - if (object instanceof PsiField || object instanceof PsiMethod || object instanceof PsiClass) { - PsiClass parent = ((PsiMember)object).getContainingClass(); - if (parent != null && parent.equals(qualifierClass)) { - return highlight(item); - } + if (qualifierClass == null) return false; + if (object instanceof PsiMethod) { + return qualifierClass.findMethodBySignature((PsiMethod)object, false) != null; + } + if (object instanceof PsiMember) { + return qualifierClass.equals(((PsiMember)object).getContainingClass()); } } - return item; + return false; } private static LookupElement highlight(LookupElement decorator) { @@ -593,7 +585,7 @@ public class JavaCompletionUtil { }), 1); } - private static LookupItem createLookupElement(CompletionElement completionElement, PsiType qualifierType) { + private static LookupElement createLookupElement(CompletionElement completionElement) { Object completion = completionElement.getElement(); assert !(completion instanceof LookupElement); @@ -601,14 +593,12 @@ public class JavaCompletionUtil { LookupElement _ret = LookupItemUtil.objectToLookupItem(completion); if (_ret == null || !(_ret instanceof LookupItem)) return null; - LookupItem ret = (LookupItem)_ret; final PsiSubstitutor substitutor = completionElement.getSubstitutor(); if (substitutor != null) { - ret.setAttribute(LookupItem.SUBSTITUTOR, substitutor); + ((LookupItem)_ret).setAttribute(LookupItem.SUBSTITUTOR, substitutor); } - ret.putUserData(QUALIFIER_TYPE_ATTR, qualifierType); - return ret; + return _ret; } public static boolean hasAccessibleConstructor(PsiType type) { @@ -928,4 +918,21 @@ public class JavaCompletionUtil { context.setAddCompletionChar(false); return true; } + + public static FakePsiElement createContextWithXxxVariable(final PsiElement place, final PsiType varType) { + return new FakePsiElement() { + @Override + public boolean processDeclarations(@NotNull PsiScopeProcessor processor, + @NotNull ResolveState state, + PsiElement lastParent, + @NotNull PsiElement place) { + return processor.execute(new LightVariableBuilder("xxx", varType, place), ResolveState.initial()); + } + + @Override + public PsiElement getParent() { + return place; + } + }; + } } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java index 52da4c0f460f..5033f6a629cd 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java @@ -33,11 +33,8 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.filters.*; import com.intellij.psi.filters.element.ModifierFilter; import com.intellij.psi.filters.types.AssignableFromFilter; -import com.intellij.psi.impl.FakePsiElement; -import com.intellij.psi.impl.light.LightVariableBuilder; import com.intellij.psi.impl.source.PostprocessReformattingAspect; import com.intellij.psi.infos.CandidateInfo; -import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -445,24 +442,8 @@ public class ReferenceExpressionCompletionContributor { .createExpressionFromText(((PsiClass)qualifierItem.getObject()).getQualifiedName() + ".xxx", place); } - return (PsiReferenceExpression) factory.createExpressionFromText("xxx.xxx", createContextWithXxxVariable(place, qualifierType)); - } - - public static FakePsiElement createContextWithXxxVariable(final PsiElement place, final PsiType varType) { - return new FakePsiElement() { - @Override - public boolean processDeclarations(@NotNull PsiScopeProcessor processor, - @NotNull ResolveState state, - PsiElement lastParent, - @NotNull PsiElement place) { - return processor.execute(new LightVariableBuilder("xxx", varType, place), ResolveState.initial()); - } - - @Override - public PsiElement getParent() { - return place; - } - }; + return (PsiReferenceExpression) factory.createExpressionFromText("xxx.xxx", JavaCompletionUtil + .createContextWithXxxVariable(place, qualifierType)); } private static boolean shoudChain(PsiElement element, PsiType qualifierType, PsiType expectedType, LookupElement item) { diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/scope/JavaCompletionProcessor.java b/java/java-impl/src/com/intellij/codeInsight/completion/scope/JavaCompletionProcessor.java index 95c74648027a..5723681ecde3 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/scope/JavaCompletionProcessor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/scope/JavaCompletionProcessor.java @@ -85,7 +85,7 @@ public class JavaCompletionProcessor extends BaseScopeProcessor implements Eleme if (qualifier instanceof PsiSuperExpression) { final PsiJavaCodeReferenceElement qSuper = ((PsiSuperExpression)qualifier).getQualifier(); if (qSuper == null) { - myQualifierClass = JavaResolveUtil.getContextClass(myElement); + myQualifierClass = JavaResolveUtil.getContextClass( myElement); } else { final PsiElement target = qSuper.resolve(); myQualifierClass = target instanceof PsiClass ? (PsiClass)target : null; diff --git a/java/java-impl/src/com/intellij/codeInsight/lookup/PsiTypeLookupItem.java b/java/java-impl/src/com/intellij/codeInsight/lookup/PsiTypeLookupItem.java index 7d04ecd1275b..b00372cc9552 100644 --- a/java/java-impl/src/com/intellij/codeInsight/lookup/PsiTypeLookupItem.java +++ b/java/java-impl/src/com/intellij/codeInsight/lookup/PsiTypeLookupItem.java @@ -46,6 +46,7 @@ public class PsiTypeLookupItem extends LookupItem { myBracketsCount = bracketsCount; } + @NotNull public PsiType getPsiType() { Object object = getObject(); PsiType type = object instanceof PsiType ? (PsiType)object : JavaPsiFacade.getElementFactory(((PsiClass) object).getProject()).createType((PsiClass)object); diff --git a/java/java-tests/testData/codeInsight/completion/normal/CastVisually.java b/java/java-tests/testData/codeInsight/completion/normal/CastVisually.java new file mode 100644 index 000000000000..17d30a741d08 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/CastVisually.java @@ -0,0 +1,14 @@ +import java.lang.Object; + +class IFoo { + Object getValue() {} +} + +class Foo extends IFoo { + Foo getValue() {} + void foo(IFoo o) { + if (o instanceof Foo) { + o.getvx + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy index 383ca96325ae..6444c21173bb 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy @@ -1300,4 +1300,12 @@ public class ListUtils { checkResult() } + public void testCastVisually() { + configure() + def p = LookupElementPresentation.renderElement(myFixture.lookupElements[0]) + assert p.itemText == 'getValue' + assert p.itemTextBold + assert p.typeText == 'Foo' + } + } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyNoVariantsDelegator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyNoVariantsDelegator.java index 70ae6f0bd85f..cba75c7052a9 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyNoVariantsDelegator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyNoVariantsDelegator.java @@ -125,7 +125,7 @@ public class GroovyNoVariantsDelegator extends CompletionContributor { } return factory.createReferenceExpressionFromText("xxx.xxx", - ReferenceExpressionCompletionContributor + JavaCompletionUtil .createContextWithXxxVariable(place, qualifierType)); }