[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / tree / java / PsiMethodCallExpressionImpl.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.psi.impl.source.tree.java;
3
4 import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
5 import com.intellij.lang.ASTNode;
6 import com.intellij.openapi.diagnostic.Logger;
7 import com.intellij.openapi.projectRoots.JavaSdkVersion;
8 import com.intellij.openapi.projectRoots.JavaVersionService;
9 import com.intellij.openapi.util.Comparing;
10 import com.intellij.pom.java.LanguageLevel;
11 import com.intellij.psi.*;
12 import com.intellij.psi.impl.DebugUtil;
13 import com.intellij.psi.impl.PsiClassImplUtil;
14 import com.intellij.psi.impl.source.resolve.JavaResolveCache;
15 import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
16 import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
17 import com.intellij.psi.impl.source.tree.ChildRole;
18 import com.intellij.psi.impl.source.tree.ElementType;
19 import com.intellij.psi.impl.source.tree.JavaElementType;
20 import com.intellij.psi.infos.MethodCandidateInfo;
21 import com.intellij.psi.tree.ChildRoleBase;
22 import com.intellij.psi.tree.IElementType;
23 import com.intellij.psi.util.PsiTreeUtil;
24 import com.intellij.psi.util.PsiTypesUtil;
25 import com.intellij.psi.util.PsiUtil;
26 import com.intellij.psi.util.TypeConversionUtil;
27 import com.intellij.util.Function;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.util.Arrays;
32
33 public class PsiMethodCallExpressionImpl extends ExpressionPsiElement implements PsiMethodCallExpression {
34   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiMethodCallExpressionImpl");
35
36   public PsiMethodCallExpressionImpl() {
37     super(JavaElementType.METHOD_CALL_EXPRESSION);
38   }
39
40   @Override
41   public PsiType getType() {
42     return JavaResolveCache.getInstance(getProject()).getType(this, ourTypeEvaluator);
43   }
44
45   @Override
46   public PsiMethod resolveMethod() {
47     return (PsiMethod)getMethodExpression().resolve();
48   }
49
50   @Override
51   @NotNull
52   public JavaResolveResult resolveMethodGenerics() {
53     return getMethodExpression().advancedResolve(false);
54   }
55
56   @Override
57   public void removeChild(@NotNull ASTNode child) {
58     if (child == getArgumentList()) {
59       LOG.error("Cannot delete argument list since it will break contract on argument list notnullity");
60     }
61     super.removeChild(child);
62   }
63
64   @Override
65   @NotNull
66   public PsiReferenceParameterList getTypeArgumentList() {
67     PsiReferenceExpression expression = getMethodExpression();
68     PsiReferenceParameterList result = expression.getParameterList();
69     if (result != null) return result;
70     LOG.error("Invalid method call expression. Children:\n" + DebugUtil.psiTreeToString(expression, false));
71     return result;
72   }
73
74   @Override
75   @NotNull
76   public PsiType[] getTypeArguments() {
77     return getMethodExpression().getTypeParameters();
78   }
79
80   @Override
81   @NotNull
82   public PsiReferenceExpression getMethodExpression() {
83     return (PsiReferenceExpression)findChildByRoleAsPsiElement(ChildRole.METHOD_EXPRESSION);
84   }
85
86   @Override
87   @NotNull
88   public PsiExpressionList getArgumentList() {
89     PsiExpressionList list = (PsiExpressionList)findChildByRoleAsPsiElement(ChildRole.ARGUMENT_LIST);
90     if (list == null) {
91       LOG.error("Invalid PSI for'" + getText() + ". Parent:" + DebugUtil.psiToString(getParent(), false));
92     }
93     return list;
94   }
95
96   @Override
97   public ASTNode findChildByRole(int role) {
98     LOG.assertTrue(ChildRole.isUnique(role));
99     switch (role) {
100       default:
101         return null;
102
103       case ChildRole.METHOD_EXPRESSION:
104         return getFirstChildNode();
105
106       case ChildRole.ARGUMENT_LIST:
107         return findChildByType(JavaElementType.EXPRESSION_LIST);
108     }
109   }
110
111   @Override
112   public int getChildRole(@NotNull ASTNode child) {
113     LOG.assertTrue(child.getTreeParent() == this);
114     IElementType i = child.getElementType();
115     if (i == JavaElementType.EXPRESSION_LIST) {
116       return ChildRole.ARGUMENT_LIST;
117     }
118     else {
119       if (ElementType.EXPRESSION_BIT_SET.contains(child.getElementType())) {
120         return ChildRole.METHOD_EXPRESSION;
121       }
122       return ChildRoleBase.NONE;
123     }
124   }
125
126   @Override
127   public void accept(@NotNull PsiElementVisitor visitor) {
128     if (visitor instanceof JavaElementVisitor) {
129       ((JavaElementVisitor)visitor).visitMethodCallExpression(this);
130     }
131     else {
132       visitor.visitElement(this);
133     }
134   }
135
136   @Override
137   public String toString() {
138     return "PsiMethodCallExpression:" + getText();
139   }
140
141   private static final TypeEvaluator ourTypeEvaluator = new TypeEvaluator();
142
143   private static class TypeEvaluator implements Function<PsiMethodCallExpression, PsiType> {
144     @Override
145     @Nullable
146     public PsiType fun(final PsiMethodCallExpression call) {
147       PsiReferenceExpression methodExpression = call.getMethodExpression();
148       final JavaResolveResult[] results = methodExpression.multiResolve(false);
149       PsiFile file = call.getContainingFile();
150       LanguageLevel languageLevel = PsiUtil.getLanguageLevel(file);
151
152       final PsiElement callParent = PsiUtil.skipParenthesizedExprUp(call.getParent());
153       final PsiExpressionList parentArgList;
154       if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
155         parentArgList = callParent instanceof PsiConditionalExpression && !PsiPolyExpressionUtil.isPolyExpression((PsiExpression)callParent)
156                         ? null : PsiTreeUtil.getParentOfType(call, PsiExpressionList.class, true, PsiReferenceExpression.class);
157       }
158       else {
159         parentArgList = null;
160       }
161       final boolean genericParentOverloadResolution = parentArgList != null && 
162                                                       MethodCandidateInfo.isOverloadCheck(parentArgList) &&
163                                                       Arrays.stream(parentArgList.getExpressions())
164                                                         .map(expression -> PsiUtil.skipParenthesizedExprDown(expression))
165                                                         .noneMatch(expression -> LambdaUtil.getFunctionalTypeMap().containsKey(expression));
166
167       PsiType theOnly = null;
168       for (int i = 0; i < results.length; i++) {
169         final JavaResolveResult candidateInfo = results[i];
170
171         if (genericParentOverloadResolution && PsiPolyExpressionUtil.isMethodCallPolyExpression(call, (PsiMethod)candidateInfo.getElement())) {
172           LOG.error("poly expression evaluation during overload resolution");
173         }
174
175         final PsiType type = getResultType(call, methodExpression, candidateInfo, languageLevel);
176         if (type == null) {
177           return null;
178         }
179
180         if (i == 0) {
181           theOnly = type;
182         }
183         else if (!theOnly.equals(type)) {
184           return null;
185         }
186       }
187
188       return PsiClassImplUtil.correctType(theOnly, file.getResolveScope());
189     }
190
191     @Nullable
192     private static PsiType getResultType(@NotNull PsiMethodCallExpression call,
193                                          @NotNull PsiReferenceExpression methodExpression,
194                                          @NotNull JavaResolveResult result,
195                                          @NotNull final LanguageLevel languageLevel) {
196       final PsiMethod method = (PsiMethod)result.getElement();
197       if (method == null) return null;
198
199       boolean is15OrHigher = languageLevel.compareTo(LanguageLevel.JDK_1_5) >= 0;
200       final PsiType getClassReturnType = PsiTypesUtil.patchMethodGetClassReturnType(call, methodExpression, method,
201                                                                                     type -> type != JavaElementType.CLASS && 
202                                                                                              //enum can be created inside enum only, no need to mention it here
203                                                                                             type != JavaElementType.ANONYMOUS_CLASS
204                                                                                     , languageLevel);
205
206       if (getClassReturnType != null) {
207         return getClassReturnType;
208       }
209
210       PsiType ret = method.getReturnType();
211       if (ret == null) return null;
212       if (ret instanceof PsiClassType) {
213         ret = ((PsiClassType)ret).setLanguageLevel(languageLevel);
214       }
215       if (is15OrHigher) {
216         return captureReturnType(call, method, ret, result, languageLevel);
217       }
218       return TypeConversionUtil.erasure(ret);
219     }
220   }
221
222   private static PsiType captureReturnType(PsiMethodCallExpression call,
223                                            PsiMethod method,
224                                            PsiType ret,
225                                            JavaResolveResult result,
226                                            LanguageLevel languageLevel) {
227     PsiSubstitutor substitutor = result.getSubstitutor();
228     PsiType substitutedReturnType = substitutor.substitute(ret);
229     if (substitutedReturnType == null) {
230       return TypeConversionUtil.erasure(ret);
231     }
232
233     if (InferenceSession.wasUncheckedConversionPerformed(call)) {
234       // 18.5.2
235       // if unchecked conversion was necessary, then this substitution provides the parameter types of the invocation type, 
236       // while the return type and thrown types are given by the erasure of m's type (without applying theta').
237       //due to https://bugs.openjdk.java.net/browse/JDK-8135087 erasure is called on substitutedReturnType and not on ret type itself as by spec
238       return TypeConversionUtil.erasure(substitutedReturnType);
239     }
240
241     //15.12.2.6. Method Invocation Type
242     // If unchecked conversion was necessary for the method to be applicable, 
243     // the parameter types of the invocation type are the parameter types of the method's type,
244     // and the return type and thrown types are given by the erasures of the return type and thrown types of the method's type.
245     if (((!languageLevel.isAtLeast(LanguageLevel.JDK_1_8) || call.getTypeArguments().length > 0) && method.hasTypeParameters() ||
246          !method.hasTypeParameters() && JavaVersionService.getInstance().isAtLeast(call, JavaSdkVersion.JDK_1_8)) &&
247         result instanceof MethodCandidateInfo && ((MethodCandidateInfo)result).isApplicable()) {
248       final PsiType[] args = call.getArgumentList().getExpressionTypes();
249       final PsiParameter[] parameters = method.getParameterList().getParameters();
250       final boolean varargs = ((MethodCandidateInfo)result).getApplicabilityLevel() == MethodCandidateInfo.ApplicabilityLevel.VARARGS;
251       for (int i = 0; i < args.length; i++) {
252         final PsiType parameterType = substitutor.substitute(PsiTypesUtil.getParameterType(parameters, i, varargs));
253         final PsiType expressionType = args[i];
254         if (expressionType != null && parameterType != null && JavaGenericsUtil.isRawToGeneric(parameterType, expressionType)) {
255           return TypeConversionUtil.erasure(substitutedReturnType);
256         }
257       }
258     }
259
260     if (PsiUtil.isRawSubstitutor(method, substitutor)) {
261       final PsiType returnTypeErasure = TypeConversionUtil.erasure(ret);
262       if (Comparing.equal(TypeConversionUtil.erasure(substitutedReturnType), returnTypeErasure)) {
263         return returnTypeErasure;
264       }
265     }
266     return PsiUtil.captureToplevelWildcards(substitutedReturnType, call);
267   }
268 }
269