java completion: qualify calls to outer class methods when needed (IDEA-144816, IDEA-151237)

This commit is contained in:
peter
2016-02-11 12:39:17 +01:00
parent a72816ad08
commit 047e6d7ff2
9 changed files with 103 additions and 21 deletions
@@ -289,13 +289,8 @@ public class JavaCompletionUtil {
private static Set<LookupElement> processJavaQualifiedReference(PsiElement element, PsiJavaReference javaReference, ElementFilter elementFilter,
JavaCompletionProcessor.Options options,
final PrefixMatcher matcher, CompletionParameters parameters) {
final Set<LookupElement> set = new LinkedHashSet<LookupElement>();
final Condition<String> nameCondition = new Condition<String>() {
@Override
public boolean value(String s) {
return matcher.prefixMatches(s);
}
};
final Set<LookupElement> set = new LinkedHashSet<>();
final Condition<String> nameCondition = matcher::prefixMatches;
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
boolean checkInitialized = parameters.getInvocationCount() <= 1 && call != null && PsiKeyword.SUPER.equals(call.getMethodExpression().getText());
@@ -322,9 +317,9 @@ public class JavaCompletionUtil {
PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifierType);
final boolean honorExcludes = qualifierClass == null || !isInExcludedPackage(qualifierClass, false);
final Set<PsiMember> mentioned = new THashSet<PsiMember>();
final Set<PsiMember> mentioned = new THashSet<>();
for (CompletionElement completionElement : processor.getResults()) {
for (LookupElement item : createLookupElements(completionElement, javaReference)) {
for (LookupElement item : createLookupElements(completionElement, javaReference, processor)) {
item.putUserData(QUALIFIER_TYPE_ATTR, qualifierType);
final Object o = item.getObject();
if (o instanceof PsiClass && !isSourceLevelAccessible(element, (PsiClass)o, pkgContext)) {
@@ -528,7 +523,7 @@ public class JavaCompletionUtil {
return false;
}
static List<? extends LookupElement> createLookupElements(CompletionElement completionElement, PsiJavaReference reference) {
static List<? extends LookupElement> createLookupElements(CompletionElement completionElement, PsiJavaReference reference, JavaCompletionProcessor processor) {
Object completion = completionElement.getElement();
assert !(completion instanceof LookupElement);
@@ -556,7 +551,16 @@ public class JavaCompletionUtil {
return Collections.singletonList(JavaClassNameCompletionContributor.createClassLookupItem((PsiClass)completion, true).setSubstitutor(substitutor));
}
if (completion instanceof PsiMethod) {
return Collections.singletonList(new JavaMethodCallElement((PsiMethod)completion).setQualifierSubstitutor(substitutor));
PsiMethod method = (PsiMethod)completion;
JavaMethodCallElement item = new JavaMethodCallElement(method).setQualifierSubstitutor(substitutor);
if (processor.shouldQualifyMethodCall(method)) {
PsiClass containingClass = method.getContainingClass();
String className = containingClass == null ? null : containingClass.getName();
if (className != null) {
item.setForcedQualifier(className + (method.hasModifierProperty(PsiModifier.STATIC) ? "." : ".this."));
}
}
return Collections.singletonList(item);
}
if (completion instanceof PsiVariable) {
return Collections.singletonList(new VariableLookupItem((PsiVariable)completion).setSubstitutor(substitutor));
@@ -40,6 +40,7 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
private PsiSubstitutor myQualifierSubstitutor = PsiSubstitutor.EMPTY;
private PsiSubstitutor myInferenceSubstitutor = PsiSubstitutor.EMPTY;
private boolean myMayNeedExplicitTypeParameters;
private String myForcedQualifier = "";
public JavaMethodCallElement(@NotNull PsiMethod method) {
this(method, method.getName());
@@ -67,6 +68,11 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
}
}
void setForcedQualifier(@NotNull String forcedQualifier) {
myForcedQualifier = forcedQualifier;
setLookupString(forcedQualifier + getLookupString());
}
@Override
public PsiType getType() {
return getSubstitutor().substitute(getInferenceSubstitutor().substitute(getObject().getReturnType()));
@@ -255,6 +261,9 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
MemberLookupHelper helper = myHelper != null ? myHelper : new MemberLookupHelper(myMethod, myContainingClass, false, false);
helper.renderElement(presentation, myHelper != null, myHelper != null && !myHelper.willBeImported(), getSubstitutor());
if (!myForcedQualifier.isEmpty()) {
presentation.setItemText(myForcedQualifier + presentation.getItemText());
}
if (shouldInsertTypeParameters()) {
String typeParamsText = getTypeParamsText(true);
@@ -48,7 +48,7 @@ class SuperCalls {
fakeSuper.processVariants(superProcessor);
for (CompletionElement completionElement : superProcessor.getResults()) {
for (LookupElement item : JavaCompletionUtil.createLookupElements(completionElement, javaReference)) {
for (LookupElement item : JavaCompletionUtil.createLookupElements(completionElement, javaReference, superProcessor)) {
set.add(withQualifiedSuper(className, item));
}
}
@@ -56,9 +56,12 @@ public class JavaCompletionProcessor extends BaseScopeProcessor implements Eleme
private final boolean myInJavaDoc;
private boolean myStatic = false;
private PsiElement myDeclarationHolder = null;
private final Map<CompletionElement, CompletionElement> myResults = new LinkedHashMap<CompletionElement, CompletionElement>();
private final Map<CompletionElement, CompletionElement> myResults = new LinkedHashMap<>();
private final Set<CompletionElement> mySecondRateResults = ContainerUtil.newIdentityTroveSet();
private final Set<String> myShadowedNames = ContainerUtil.newHashSet();
private final Set<String> myCurrentScopeMethodNames = ContainerUtil.newHashSet();
private final Set<String> myFinishedScopesMethodNames = ContainerUtil.newHashSet();
private final Set<PsiMethod> myMethodsToQualify = ContainerUtil.newHashSet();
private final PsiElement myElement;
private final PsiElement myScope;
private final ElementFilter myFilter;
@@ -68,7 +71,7 @@ public class JavaCompletionProcessor extends BaseScopeProcessor implements Eleme
private PsiClass myQualifierClass = null;
private final Condition<String> myMatcher;
private final Options myOptions;
private final Set<PsiField> myNonInitializedFields = new HashSet<PsiField>();
private final Set<PsiField> myNonInitializedFields = new HashSet<>();
private final boolean myAllowStaticWithInstanceQualifier;
public JavaCompletionProcessor(@NotNull PsiElement element, ElementFilter filter, Options options, @NotNull Condition<String> nameCondition) {
@@ -135,6 +138,7 @@ public class JavaCompletionProcessor extends BaseScopeProcessor implements Eleme
public static Set<PsiField> getNonInitializedFields(PsiElement element) {
final PsiStatement statement = PsiTreeUtil.getParentOfType(element, PsiStatement.class);
//noinspection SSBasedInspection
final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class, true, PsiClass.class);
if (statement == null || method == null || !method.isConstructor()) {
return Collections.emptySet();
@@ -152,7 +156,7 @@ public class JavaCompletionProcessor extends BaseScopeProcessor implements Eleme
parent = next;
}
final Set<PsiField> fields = new HashSet<PsiField>();
final Set<PsiField> fields = new HashSet<>();
final PsiClass containingClass = method.getContainingClass();
assert containingClass != null;
for (PsiField field : containingClass.getFields()) {
@@ -195,6 +199,8 @@ public class JavaCompletionProcessor extends BaseScopeProcessor implements Eleme
}
if(event == JavaScopeProcessorEvent.CHANGE_LEVEL){
myMembersFlag = true;
myFinishedScopesMethodNames.addAll(myCurrentScopeMethodNames);
myCurrentScopeMethodNames.clear();
}
if (event == JavaScopeProcessorEvent.SET_CURRENT_FILE_CONTEXT) {
myDeclarationHolder = (PsiElement)associated;
@@ -251,11 +257,23 @@ public class JavaCompletionProcessor extends BaseScopeProcessor implements Eleme
if (sp == StaticProblem.staticAfterInstance) {
mySecondRateResults.add(completion);
}
if (element instanceof PsiMethod) {
String name = ((PsiMethod)element).getName();
myCurrentScopeMethodNames.add(name);
if (myFinishedScopesMethodNames.contains(name)) {
myMethodsToQualify.add((PsiMethod)element);
}
}
}
return true;
}
public boolean shouldQualifyMethodCall(@NotNull PsiMethod method) {
return myMethodsToQualify.contains(method);
}
private boolean isQualifiedContext() {
final PsiElement elementParent = myElement.getParent();
return elementParent instanceof PsiQualifiedReference && ((PsiQualifiedReference)elementParent).getQualifier() != null;
@@ -331,12 +349,7 @@ public class JavaCompletionProcessor extends BaseScopeProcessor implements Eleme
if (mySecondRateResults.size() == myResults.size()) {
return mySecondRateResults;
}
return ContainerUtil.filter(myResults.values(), new Condition<CompletionElement>() {
@Override
public boolean value(CompletionElement element) {
return !mySecondRateResults.contains(element);
}
});
return ContainerUtil.filter(myResults.values(), element -> !mySecondRateResults.contains(element));
}
public void clear() {
@@ -0,0 +1,10 @@
class A {
void foo(int x){}
class Inner {
void foo(){}
void test() {
fo<caret>
}
}
}
@@ -0,0 +1,10 @@
class A {
void foo(int x){}
class Inner {
void foo(){}
void test() {
A.this.foo(<caret>);
}
}
}
@@ -0,0 +1,10 @@
class A {
static void foo(int x){}
class Inner {
void foo(){}
void test() {
fo<caret>
}
}
}
@@ -0,0 +1,10 @@
class A {
static void foo(int x){}
class Inner {
void foo(){}
void test() {
A.foo(<caret>);
}
}
}
@@ -482,6 +482,22 @@ public class NormalCompletionTest extends LightFixtureCompletionTestCase {
public void testMethodCallAfterFinally() { doTest() }
public void testPrivateInAnonymous() throws Throwable { doTest() }
public void testStaticMethodFromOuterClass() {
configure()
assertStringItems 'foo', 'A.foo', 'for'
assert LookupElementPresentation.renderElement(myItems[1]).itemText == 'A.foo'
selectItem(myItems[1])
checkResult()
}
public void testInstanceMethodFromOuterClass() {
configure()
assertStringItems 'foo', 'A.this.foo', 'for'
assert LookupElementPresentation.renderElement(myItems[1]).itemText == 'A.this.foo'
selectItem(myItems[1])
checkResult()
}
public void testMethodParenthesesSpaces() throws Throwable {
codeStyleSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES = true
codeStyleSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES = true