java: prohibit caching when using thread-local types imposed on expressions and decla...
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / resolve / graphInference / constraints / ExpressionCompatibilityConstraint.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.resolve.graphInference.constraints;
3
4 import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
5 import com.intellij.openapi.util.Pair;
6 import com.intellij.psi.*;
7 import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
8 import com.intellij.psi.impl.source.resolve.graphInference.InferenceVariable;
9 import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
10 import com.intellij.psi.infos.MethodCandidateInfo;
11 import com.intellij.psi.util.PsiUtil;
12 import com.intellij.psi.util.TypeConversionUtil;
13 import com.intellij.util.ArrayUtil;
14 import org.jetbrains.annotations.NotNull;
15
16 import java.util.List;
17 import java.util.Set;
18
19 public class ExpressionCompatibilityConstraint extends InputOutputConstraintFormula {
20   private final PsiExpression myExpression;
21
22   public ExpressionCompatibilityConstraint(@NotNull PsiExpression expression, @NotNull PsiType type) {
23     super(type);
24     myExpression = expression;
25   }
26
27   @Override
28   public boolean reduce(InferenceSession session, List<ConstraintFormula> constraints) {
29     PsiType myT = getCurrentType();
30     if (!PsiPolyExpressionUtil.isPolyExpression(myExpression)) {
31
32       PsiType exprType = myExpression.getType();
33
34       if (session.isProperType(myT)) {
35         final boolean assignmentCompatible = exprType == null || TypeConversionUtil.isAssignable(myT, exprType);
36         if (!assignmentCompatible) {
37           final PsiType type = myExpression.getType();
38           session.registerIncompatibleErrorMessage((type != null ? type.getPresentableText() : myExpression.getText()) + " is not compatible with " + session.getPresentableText(myT));
39         }
40         else if (TypeCompatibilityConstraint.isUncheckedConversion(myT, exprType, session) && !JavaGenericsUtil.isReifiableType(myT)) {
41           session.setErased();
42         }
43         return assignmentCompatible;
44       }
45
46       if (exprType instanceof PsiLambdaParameterType) {
47         return false;
48       }
49
50       if (exprType instanceof PsiClassType) {
51         if (((PsiClassType)exprType).resolve() == null) {
52           return true;
53         }
54       }
55
56       if (exprType != null && exprType != PsiType.NULL) {
57         if (exprType instanceof PsiDisjunctionType) {
58           exprType = ((PsiDisjunctionType)exprType).getLeastUpperBound();
59         }
60
61         constraints.add(new TypeCompatibilityConstraint(myT, exprType));
62       }
63       return true;
64     }
65     if (myExpression instanceof PsiParenthesizedExpression) {
66       final PsiExpression expression = ((PsiParenthesizedExpression)myExpression).getExpression();
67       if (expression != null && !InferenceSession.ignoreLambdaConstraintTree(expression)) {
68         constraints.add(new ExpressionCompatibilityConstraint(expression, myT));
69         return true;
70       }
71     }
72
73     if (myExpression instanceof PsiConditionalExpression) {
74       final PsiExpression thenExpression = ((PsiConditionalExpression)myExpression).getThenExpression();
75       if (thenExpression != null && !InferenceSession.ignoreLambdaConstraintTree(thenExpression)) {
76         constraints.add(new ExpressionCompatibilityConstraint(thenExpression, myT));
77       }
78
79       final PsiExpression elseExpression = ((PsiConditionalExpression)myExpression).getElseExpression();
80       if (elseExpression != null && !InferenceSession.ignoreLambdaConstraintTree(elseExpression)) {
81         constraints.add(new ExpressionCompatibilityConstraint(elseExpression, myT));
82       }
83       return true;
84     }
85
86     if (myExpression instanceof PsiSwitchExpression) {
87       PsiUtil.getSwitchResultExpressions((PsiSwitchExpression)myExpression).forEach(expression -> {
88         if (!InferenceSession.ignoreLambdaConstraintTree(expression)) {
89           constraints.add(new ExpressionCompatibilityConstraint(expression, myT));
90         }
91       });
92       return true;
93     }
94
95     if (myExpression instanceof PsiCall) {
96       final InferenceSession callSession = reduceExpressionCompatibilityConstraint(session, myExpression, myT, true);
97       if (callSession == null) {
98         return false;
99       }
100       if (callSession != session) {
101         session.getInferenceSessionContainer().registerNestedSession(callSession);
102         session.propagateVariables(callSession);
103         for (Pair<InferenceVariable[], PsiClassType> pair : callSession.myIncorporationPhase.getCaptures()) {
104           session.myIncorporationPhase.addCapture(pair.first, pair.second);
105         }
106         final MethodCandidateInfo currentMethod = session.getCurrentMethod(((PsiCall)myExpression).getArgumentList());
107         final JavaResolveResult resolveResult = currentMethod != null ? currentMethod : PsiDiamondType.getDiamondsAwareResolveResult((PsiCall)myExpression);
108         if (resolveResult instanceof MethodCandidateInfo) {
109           ((MethodCandidateInfo)resolveResult).setErased(callSession.isErased());
110         }
111       }
112       return true;
113     }
114
115     if (myExpression instanceof PsiMethodReferenceExpression) {
116       constraints.add(new PsiMethodReferenceCompatibilityConstraint(((PsiMethodReferenceExpression)myExpression), myT));
117       return true;
118     }
119
120     if (myExpression instanceof PsiLambdaExpression) {
121       constraints.add(new LambdaExpressionCompatibilityConstraint((PsiLambdaExpression)myExpression, myT));
122       return true;
123     }
124
125
126     return true;
127   }
128
129   public static InferenceSession reduceExpressionCompatibilityConstraint(InferenceSession session,
130                                                                          PsiExpression expression,
131                                                                          PsiType targetType,
132                                                                          boolean registerErrorOnFailure) {
133     if (!PsiPolyExpressionUtil.isPolyExpression(expression)) {
134       return session;
135     }
136     final PsiExpressionList argumentList = ((PsiCall)expression).getArgumentList();
137     if (argumentList != null) {
138       final MethodCandidateInfo currentMethod = session.getCurrentMethod(argumentList);
139       PsiType returnType = null;
140       PsiTypeParameter[] typeParams = null;
141       final JavaResolveResult resolveResult = currentMethod != null ? null : PsiDiamondType.getDiamondsAwareResolveResult((PsiCall)expression);
142       PsiMethod method = currentMethod != null ? currentMethod.getElement() :
143                          resolveResult instanceof MethodCandidateInfo ? ((MethodCandidateInfo)resolveResult).getElement() :
144                          null;
145
146       if (method != null && !method.isConstructor()) {
147         returnType = method.getReturnType();
148         typeParams = method.getTypeParameters();
149       }
150       else if (resolveResult != null) {
151         final PsiClass psiClass = method != null ? method.getContainingClass() : (PsiClass)resolveResult.getElement();
152         if (psiClass != null) {
153           returnType = JavaPsiFacade.getElementFactory(argumentList.getProject()).createType(psiClass, PsiSubstitutor.EMPTY);
154           typeParams = psiClass.getTypeParameters();
155           if (method != null && method.hasTypeParameters()) {
156             typeParams = ArrayUtil.mergeArrays(typeParams, method.getTypeParameters());
157           }
158         }
159       }
160       else {
161         return session;
162       }
163
164       if (typeParams != null) {
165         PsiSubstitutor siteSubstitutor = InferenceSession.chooseSiteSubstitutor(currentMethod, resolveResult, method);
166         InferenceSession callSession = new InferenceSession(typeParams, siteSubstitutor, expression.getManager(), expression, session.getInferencePolicy());
167         callSession.propagateVariables(session);
168         if (method != null) {
169           final PsiExpression[] args = argumentList.getExpressions();
170           final PsiParameter[] parameters = method.getParameterList().getParameters();
171           callSession.initExpressionConstraints(parameters, args, method, InferenceSession.chooseVarargsMode(currentMethod, resolveResult));
172         }
173         if (callSession.repeatInferencePhases()) {
174
175           if (PsiType.VOID.equals(targetType)) {
176             return callSession;
177           }
178
179           if (returnType != null) {
180             callSession.registerReturnTypeConstraints(siteSubstitutor.substitute(returnType), targetType, expression);
181           }
182           if (callSession.repeatInferencePhases()) {
183             if (callSession.isErased() &&
184                 !JavaGenericsUtil.isReifiableType(targetType) && session.getInferenceVariable(targetType) == null) {
185               session.setErased();
186             }
187             return callSession;
188           }
189         }
190
191         //copy incompatible message if any
192         final List<String> messages = callSession.getIncompatibleErrorMessages();
193         if (messages != null) {
194           for (String message : messages) {
195             session.registerIncompatibleErrorMessage(message);
196           }
197         }
198         return null;
199       }
200       else if (registerErrorOnFailure) {
201         session.registerIncompatibleErrorMessage("Failed to resolve argument");
202         return null;
203       }
204     }
205     return session;
206   }
207
208   @Override
209   public boolean equals(Object o) {
210     if (this == o) return true;
211     if (o == null || getClass() != o.getClass()) return false;
212
213     ExpressionCompatibilityConstraint that = (ExpressionCompatibilityConstraint)o;
214
215     if (!myExpression.equals(that.myExpression)) return false;
216
217     return true;
218   }
219
220   @Override
221   public int hashCode() {
222     return myExpression.hashCode();
223   }
224
225   @Override
226   public PsiExpression getExpression() {
227     return myExpression;
228   }
229
230   @Override
231   protected InputOutputConstraintFormula createSelfConstraint(PsiType type, PsiExpression expression) {
232     return new ExpressionCompatibilityConstraint(expression, type);
233   }
234
235   @Override
236   protected void collectReturnTypeVariables(InferenceSession session,
237                                             PsiExpression psiExpression,
238                                             PsiType returnType,
239                                             Set<InferenceVariable> result) {
240     if (psiExpression instanceof PsiLambdaExpression) {
241       if (!PsiType.VOID.equals(returnType)) {
242         final List<PsiExpression> returnExpressions = LambdaUtil.getReturnExpressions((PsiLambdaExpression)psiExpression);
243         for (PsiExpression expression : returnExpressions) {
244           final Set<InferenceVariable> resultInputVars = createSelfConstraint(returnType, expression).getInputVariables(session);
245           if (resultInputVars != null) {
246             result.addAll(resultInputVars);
247           }
248         }
249       }
250     }
251   }
252 }