constructor reference: don't ignore constructor parameters during method reference...
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / completion / JavaSmartCompletionContributor.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInsight.completion;
3
4 import com.intellij.codeInsight.*;
5 import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
6 import com.intellij.codeInsight.lookup.LookupElement;
7 import com.intellij.codeInsight.lookup.LookupElementDecorator;
8 import com.intellij.patterns.ElementPattern;
9 import com.intellij.psi.*;
10 import com.intellij.psi.filters.ElementExtractorFilter;
11 import com.intellij.psi.filters.ElementFilter;
12 import com.intellij.psi.filters.OrFilter;
13 import com.intellij.psi.filters.getters.ExpectedTypesGetter;
14 import com.intellij.psi.filters.getters.JavaMembersGetter;
15 import com.intellij.psi.filters.types.AssignableFromFilter;
16 import com.intellij.psi.filters.types.AssignableToFilter;
17 import com.intellij.psi.infos.CandidateInfo;
18 import com.intellij.psi.util.PsiTreeUtil;
19 import com.intellij.psi.util.proximity.ReferenceListWeigher;
20 import com.intellij.util.*;
21 import com.intellij.util.containers.ContainerUtil;
22 import gnu.trove.THashSet;
23 import gnu.trove.TObjectHashingStrategy;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
26
27 import java.util.*;
28
29 import static com.intellij.patterns.PlatformPatterns.psiElement;
30 import static com.intellij.patterns.StandardPatterns.or;
31
32 /**
33  * @author peter
34  */
35 public class JavaSmartCompletionContributor extends CompletionContributor {
36   private static final TObjectHashingStrategy<ExpectedTypeInfo> EXPECTED_TYPE_INFO_STRATEGY = new TObjectHashingStrategy<ExpectedTypeInfo>() {
37     @Override
38     public int computeHashCode(ExpectedTypeInfo object) {
39       return object.getType().hashCode();
40     }
41
42     @Override
43     public boolean equals(ExpectedTypeInfo o1, ExpectedTypeInfo o2) {
44       return o1.getType().equals(o2.getType());
45     }
46   };
47
48   private static final ElementExtractorFilter THROWABLES_FILTER = new ElementExtractorFilter(new AssignableFromFilter(CommonClassNames.JAVA_LANG_THROWABLE));
49   public static final ElementPattern<PsiElement> AFTER_NEW =
50       psiElement().afterLeaf(
51           psiElement().withText(PsiKeyword.NEW).andNot(
52               psiElement().afterLeaf(
53                   psiElement().withText(PsiKeyword.THROW))));
54   static final ElementPattern<PsiElement> AFTER_THROW_NEW = psiElement().afterLeaf(psiElement().withText(PsiKeyword.NEW).afterLeaf(PsiKeyword.THROW));
55   public static final ElementPattern<PsiElement> INSIDE_EXPRESSION = or(
56         psiElement().withParent(PsiExpression.class)
57           .andNot(psiElement().withParent(PsiLiteralExpression.class))
58           .andNot(psiElement().withParent(PsiMethodReferenceExpression.class)),
59         psiElement().inside(PsiClassObjectAccessExpression.class),
60         psiElement().inside(PsiThisExpression.class),
61         psiElement().inside(PsiSuperExpression.class));
62   static final ElementPattern<PsiElement> INSIDE_TYPECAST_EXPRESSION = psiElement().withParent(
63     psiElement(PsiReferenceExpression.class).afterLeaf(psiElement().withText(")").withParent(PsiTypeCastExpression.class)));
64
65   @Nullable
66   private static ElementFilter getClassReferenceFilter(final PsiElement element, final boolean inRefList) {
67     //throw new foo
68     if (AFTER_THROW_NEW.accepts(element)) {
69       return THROWABLES_FILTER;
70     }
71
72     //new xxx.yyy
73     if (psiElement().afterLeaf(psiElement().withText(".")).withSuperParent(2, psiElement(PsiNewExpression.class)).accepts(element)) {
74       if (((PsiNewExpression)element.getParent().getParent()).getClassReference() == element.getParent()) {
75         PsiType[] types = ExpectedTypesGetter.getExpectedTypes(element, false);
76         return new OrFilter(ContainerUtil.map2Array(types, ElementFilter.class, (Function<PsiType, ElementFilter>)type -> new AssignableFromFilter(type)));
77       }
78     }
79
80     // extends/implements/throws
81     if (inRefList) {
82       return new ElementExtractorFilter(new ElementFilter() {
83         @Override
84         public boolean isAcceptable(Object aClass, @Nullable PsiElement context) {
85           return aClass instanceof PsiClass && ReferenceListWeigher.INSTANCE.getApplicability((PsiClass)aClass, element) !=
86                                                ReferenceListWeigher.ReferenceListApplicability.inapplicable;
87         }
88
89         @Override
90         public boolean isClassAcceptable(Class hintClass) {
91           return true;
92         }
93       });
94     }
95
96     return null;
97   }
98
99   public JavaSmartCompletionContributor() {
100     extend(CompletionType.SMART, SmartCastProvider.TYPECAST_TYPE_CANDIDATE, new SmartCastProvider());
101
102     extend(CompletionType.SMART, SameSignatureCallParametersProvider.IN_CALL_ARGUMENT, new SameSignatureCallParametersProvider());
103
104     extend(CompletionType.SMART, MethodReturnTypeProvider.IN_METHOD_RETURN_TYPE, new MethodReturnTypeProvider());
105
106     extend(CompletionType.SMART, InstanceofTypeProvider.AFTER_INSTANCEOF, new InstanceofTypeProvider());
107
108     extend(CompletionType.SMART, psiElement(), new CompletionProvider<CompletionParameters>() {
109       @Override
110       protected void addCompletions(@NotNull final CompletionParameters parameters, @NotNull final ProcessingContext context, @NotNull final CompletionResultSet result) {
111         if (SmartCastProvider.shouldSuggestCast(parameters)) return;
112
113         final PsiElement element = parameters.getPosition();
114         final PsiJavaCodeReferenceElement reference =
115           PsiTreeUtil.findElementOfClassAtOffset(element.getContainingFile(), parameters.getOffset(), PsiJavaCodeReferenceElement.class, false);
116         if (reference != null) {
117           boolean inRefList = ReferenceListWeigher.INSIDE_REFERENCE_LIST.accepts(element);
118           ElementFilter filter = getClassReferenceFilter(element, inRefList);
119           if (filter != null) {
120             final List<ExpectedTypeInfo> infos = Arrays.asList(getExpectedTypes(parameters));
121             for (LookupElement item : completeReference(element, reference, filter, true, false, parameters, result.getPrefixMatcher())) {
122               Object o = item.getObject();
123               if (o instanceof PsiClass ||
124                   CodeInsightSettings.getInstance().SHOW_PARAMETER_NAME_HINTS_ON_COMPLETION &&
125                   JavaConstructorCallElement.isConstructorCallPlace(element) && o instanceof PsiMethod && ((PsiMethod)o).isConstructor()) {
126                 if (!inRefList && o instanceof PsiClass) {
127                   item = LookupElementDecorator.withInsertHandler(item, ConstructorInsertHandler.SMART_INSTANCE);
128                 }
129                 result.addElement(decorate(item, infos));
130               }
131             }
132           }
133           else if (INSIDE_TYPECAST_EXPRESSION.accepts(element)) {
134             final PsiTypeCastExpression cast = PsiTreeUtil.getContextOfType(element, PsiTypeCastExpression.class, true);
135             if (cast != null && cast.getCastType() != null) {
136               filter = new AssignableToFilter(cast.getCastType().getType());
137               for (final LookupElement item : completeReference(element, reference, filter, false, true, parameters, result.getPrefixMatcher())) {
138                 result.addElement(item);
139               }
140             }
141           }
142         }
143       }
144     });
145
146     extend(CompletionType.SMART, INSIDE_EXPRESSION, new ExpectedTypeBasedCompletionProvider() {
147       @Override
148       protected void addCompletions(final CompletionParameters params, final CompletionResultSet result, final Collection<? extends ExpectedTypeInfo> _infos) {
149         if (SmartCastProvider.shouldSuggestCast(params)) return;
150
151         Consumer<LookupElement> noTypeCheck = decorateWithoutTypeCheck(result, _infos);
152
153         THashSet<ExpectedTypeInfo> mergedInfos = new THashSet<>(_infos, EXPECTED_TYPE_INFO_STRATEGY);
154         List<Runnable> chainedEtc = new ArrayList<>();
155         for (final ExpectedTypeInfo info : mergedInfos) {
156           Runnable slowContinuation =
157             ReferenceExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(params, info), noTypeCheck);
158           ContainerUtil.addIfNotNull(chainedEtc, slowContinuation);
159         }
160         addExpectedTypeMembers(params, mergedInfos, true, noTypeCheck);
161
162         PsiElement parent = params.getPosition().getParent();
163         if (parent instanceof PsiReferenceExpression) {
164           CollectConversion.addCollectConversion((PsiReferenceExpression)parent, mergedInfos, noTypeCheck);
165         }
166
167         for (final ExpectedTypeInfo info : mergedInfos) {
168           BasicExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(params, info), lookupElement -> {
169             final PsiType psiType = JavaCompletionUtil.getLookupElementType(lookupElement);
170             if (psiType != null && info.getType().isAssignableFrom(psiType)) {
171               result.addElement(decorate(lookupElement, _infos));
172             }
173           }, result.getPrefixMatcher());
174
175         }
176
177         for (Runnable runnable : chainedEtc) {
178           runnable.run();
179         }
180
181
182         final boolean searchInheritors = params.getInvocationCount() > 1;
183         if (searchInheritors) {
184           addExpectedTypeMembers(params, mergedInfos, false, noTypeCheck);
185         }
186       }
187     });
188
189     extend(CompletionType.SMART, ExpectedAnnotationsProvider.ANNOTATION_ATTRIBUTE_VALUE, new ExpectedAnnotationsProvider());
190
191     extend(CompletionType.SMART, CatchTypeProvider.CATCH_CLAUSE_TYPE, new CatchTypeProvider());
192
193     extend(CompletionType.SMART, TypeArgumentCompletionProvider.IN_TYPE_ARGS, new TypeArgumentCompletionProvider(true, null));
194
195     extend(CompletionType.SMART, AFTER_NEW, new JavaInheritorsGetter(ConstructorInsertHandler.SMART_INSTANCE));
196
197     extend(CompletionType.SMART, LabelReferenceCompletion.LABEL_REFERENCE, new LabelReferenceCompletion());
198
199     extend(CompletionType.SMART, psiElement(), new FunctionalExpressionCompletionProvider());
200     extend(CompletionType.SMART, psiElement().afterLeaf("::"), new MethodReferenceCompletionProvider());
201   }
202
203   @NotNull
204   private static Consumer<LookupElement> decorateWithoutTypeCheck(final CompletionResultSet result, final Collection<? extends ExpectedTypeInfo> infos) {
205     return lookupElement -> result.addElement(decorate(lookupElement, infos));
206   }
207
208   private static void addExpectedTypeMembers(CompletionParameters params,
209                                              THashSet<? extends ExpectedTypeInfo> mergedInfos,
210                                              boolean quick,
211                                              Consumer<? super LookupElement> consumer) {
212     PsiElement position = params.getPosition();
213     if (!JavaKeywordCompletion.AFTER_DOT.accepts(position)) {
214       for (ExpectedTypeInfo info : mergedInfos) {
215         new JavaMembersGetter(info.getType(), params).addMembers(!quick, consumer);
216         if (!info.getDefaultType().equals(info.getType())) {
217           new JavaMembersGetter(info.getDefaultType(), params).addMembers(!quick, consumer);
218         }
219       }
220     }
221   }
222
223   @Override
224   public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) {
225     if (parameters.getPosition() instanceof PsiComment) {
226       return;
227     }
228
229     super.fillCompletionVariants(parameters, JavaCompletionSorting.addJavaSorting(parameters, result));
230   }
231
232   public static SmartCompletionDecorator decorate(LookupElement lookupElement, Collection<? extends ExpectedTypeInfo> infos) {
233     return new SmartCompletionDecorator(lookupElement, infos);
234   }
235
236   @NotNull
237   public static ExpectedTypeInfo[] getExpectedTypes(final CompletionParameters parameters) {
238     return getExpectedTypes(parameters.getPosition(), parameters.getCompletionType() == CompletionType.SMART);
239   }
240
241   @NotNull
242   public static ExpectedTypeInfo[] getExpectedTypes(PsiElement position, boolean voidable) {
243     if (psiElement().withParent(psiElement(PsiReferenceExpression.class).withParent(PsiThrowStatement.class)).accepts(position)) {
244       final PsiElementFactory factory = JavaPsiFacade.getElementFactory(position.getProject());
245       final PsiClassType classType = factory
246           .createTypeByFQClassName(CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION, position.getResolveScope());
247       final List<ExpectedTypeInfo> result = new SmartList<>();
248       result.add(new ExpectedTypeInfoImpl(classType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, classType, TailType.SEMICOLON, null, ExpectedTypeInfoImpl.NULL));
249       final PsiMethod method = PsiTreeUtil.getContextOfType(position, PsiMethod.class, true);
250       if (method != null) {
251         for (final PsiClassType type : method.getThrowsList().getReferencedTypes()) {
252           result.add(new ExpectedTypeInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.SEMICOLON, null, ExpectedTypeInfoImpl.NULL));
253         }
254       }
255       return result.toArray(ExpectedTypeInfo.EMPTY_ARRAY);
256     }
257
258     PsiExpression expression = PsiTreeUtil.getContextOfType(position, PsiExpression.class, true);
259     if (expression == null) return ExpectedTypeInfo.EMPTY_ARRAY;
260
261     return ExpectedTypesProvider.getExpectedTypes(expression, true, voidable, false);
262   }
263
264   static Set<LookupElement> completeReference(final PsiElement element,
265                                               PsiJavaCodeReferenceElement reference,
266                                               final ElementFilter filter,
267                                               final boolean acceptClasses,
268                                               final boolean acceptMembers,
269                                               CompletionParameters parameters, final PrefixMatcher matcher) {
270     ElementFilter checkClass = new ElementFilter() {
271       @Override
272       public boolean isAcceptable(Object element, PsiElement context) {
273         return filter.isAcceptable(element, context);
274       }
275
276       @Override
277       public boolean isClassAcceptable(Class hintClass) {
278         if (ReflectionUtil.isAssignable(PsiClass.class, hintClass)) {
279           return acceptClasses;
280         }
281
282         if (ReflectionUtil.isAssignable(PsiVariable.class, hintClass) ||
283             ReflectionUtil.isAssignable(PsiMethod.class, hintClass) ||
284             ReflectionUtil.isAssignable(CandidateInfo.class, hintClass)) {
285           return acceptMembers;
286         }
287         return false;
288       }
289     };
290     JavaCompletionProcessor.Options options =
291       JavaCompletionProcessor.Options.DEFAULT_OPTIONS.withFilterStaticAfterInstance(parameters.getInvocationCount() <= 1);
292     return JavaCompletionUtil.processJavaReference(element, reference, checkClass, options, matcher, parameters);
293   }
294
295   @Override
296   public void beforeCompletion(@NotNull CompletionInitializationContext context) {
297     if (context.getCompletionType() != CompletionType.SMART) {
298       return;
299     }
300
301     if (!context.getEditor().getSelectionModel().hasSelection()) {
302       final PsiFile file = context.getFile();
303       PsiElement element = file.findElementAt(context.getStartOffset());
304       if (element instanceof PsiIdentifier) {
305         element = element.getParent();
306         while (element instanceof PsiJavaCodeReferenceElement || element instanceof PsiCall ||
307                element instanceof PsiThisExpression || element instanceof PsiSuperExpression ||
308                element instanceof PsiTypeElement ||
309                element instanceof PsiClassObjectAccessExpression) {
310           int newEnd = element.getTextRange().getEndOffset();
311           if (element instanceof PsiMethodCallExpression) {
312             newEnd = ((PsiMethodCallExpression)element).getMethodExpression().getTextRange().getEndOffset();
313           }
314           else if (element instanceof PsiNewExpression) {
315             final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)element).getClassReference();
316             if (classReference != null) {
317               newEnd = classReference.getTextRange().getEndOffset();
318             }
319           }
320           context.setReplacementOffset(newEnd);
321           element = element.getParent();
322         }
323       }
324     }
325
326     PsiElement lastElement = context.getFile().findElementAt(context.getStartOffset() - 1);
327     if (lastElement != null && lastElement.getText().equals("(") && lastElement.getParent() instanceof PsiParenthesizedExpression) {
328       // don't trim dummy identifier or we won't be able to determine the type of the expression after '('
329       // which is needed to insert correct cast
330       return;
331     }
332     context.setDummyIdentifier(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED);
333   }
334 }