Cleanup: NotNull/Nullable
[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   private PsiType myT;
22
23   public ExpressionCompatibilityConstraint(@NotNull PsiExpression expression, @NotNull PsiType type) {
24     myExpression = expression;
25     myT = type;
26   }
27
28   @Override
29   public boolean reduce(InferenceSession session, List<ConstraintFormula> constraints) {
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) {
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) {
76         constraints.add(new ExpressionCompatibilityConstraint(thenExpression, myT));
77       }
78
79       final PsiExpression elseExpression = ((PsiConditionalExpression)myExpression).getElseExpression();
80       if (elseExpression != null) {
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 -> constraints.add(new ExpressionCompatibilityConstraint(expression,myT)));
88       return true;
89     }
90
91     if (myExpression instanceof PsiCall) {
92       final InferenceSession callSession = reduceExpressionCompatibilityConstraint(session, myExpression, myT, true);
93       if (callSession == null) {
94         return false;
95       }
96       if (callSession != session) {
97         session.getInferenceSessionContainer().registerNestedSession(callSession);
98         session.propagateVariables(callSession);
99         for (Pair<InferenceVariable[], PsiClassType> pair : callSession.myIncorporationPhase.getCaptures()) {
100           session.myIncorporationPhase.addCapture(pair.first, pair.second);
101         }
102         callSession.setUncheckedInContext();
103       }
104       return true;
105     }
106
107     if (myExpression instanceof PsiMethodReferenceExpression) {
108       constraints.add(new PsiMethodReferenceCompatibilityConstraint(((PsiMethodReferenceExpression)myExpression), myT));
109       return true;
110     }
111
112     if (myExpression instanceof PsiLambdaExpression) {
113       constraints.add(new LambdaExpressionCompatibilityConstraint((PsiLambdaExpression)myExpression, myT));
114       return true;
115     }
116
117
118     return true;
119   }
120
121   public static InferenceSession reduceExpressionCompatibilityConstraint(InferenceSession session,
122                                                                          PsiExpression expression,
123                                                                          PsiType targetType,
124                                                                          boolean registerErrorOnFailure) {
125     if (!PsiPolyExpressionUtil.isPolyExpression(expression)) {
126       return session;
127     }
128     final PsiExpressionList argumentList = ((PsiCall)expression).getArgumentList();
129     if (argumentList != null) {
130       final MethodCandidateInfo.CurrentCandidateProperties candidateProperties = MethodCandidateInfo.getCurrentMethod(argumentList);
131       PsiType returnType = null;
132       PsiTypeParameter[] typeParams = null;
133       final JavaResolveResult resolveResult = candidateProperties != null ? null : PsiDiamondType
134         .getDiamondsAwareResolveResult((PsiCall)expression);
135       PsiMethod method = candidateProperties != null ? candidateProperties.getMethod() :
136                          resolveResult instanceof MethodCandidateInfo ? ((MethodCandidateInfo)resolveResult).getElement() :
137                          null;
138
139       if (method != null && !method.isConstructor()) {
140         returnType = method.getReturnType();
141         typeParams = method.getTypeParameters();
142       }
143       else if (resolveResult != null) {
144         final PsiClass psiClass = method != null ? method.getContainingClass() : (PsiClass)resolveResult.getElement();
145         if (psiClass != null) {
146           returnType = JavaPsiFacade.getElementFactory(argumentList.getProject()).createType(psiClass, PsiSubstitutor.EMPTY);
147           typeParams = psiClass.getTypeParameters();
148           if (method != null && method.hasTypeParameters()) {
149             typeParams = ArrayUtil.mergeArrays(typeParams, method.getTypeParameters());
150           }
151         }
152       }
153       else {
154         return session;
155       }
156
157       if (typeParams != null) {
158         PsiSubstitutor siteSubstitutor = InferenceSession.chooseSiteSubstitutor(candidateProperties, resolveResult, method);
159         final InferenceSession callSession = new InferenceSession(typeParams, siteSubstitutor, expression.getManager(), expression);
160         callSession.propagateVariables(session);
161         if (method != null) {
162           final PsiExpression[] args = argumentList.getExpressions();
163           final PsiParameter[] parameters = method.getParameterList().getParameters();
164           callSession.initExpressionConstraints(parameters, args, expression, method, InferenceSession
165             .chooseVarargsMode(candidateProperties, resolveResult));
166         }
167         if (callSession.repeatInferencePhases()) {
168
169           if (PsiType.VOID.equals(targetType)) {
170             return callSession;
171           }
172
173           if (returnType != null) {
174             callSession.registerReturnTypeConstraints(siteSubstitutor.substitute(returnType), targetType, expression);
175           }
176           if (callSession.repeatInferencePhases()) {
177             if (callSession.isErased() &&
178                 !JavaGenericsUtil.isReifiableType(targetType) && session.getInferenceVariable(targetType) == null) {
179               session.setErased();
180             }
181             return callSession;
182           }
183         }
184
185         //copy incompatible message if any
186         final List<String> messages = callSession.getIncompatibleErrorMessages();
187         if (messages != null) {
188           for (String message : messages) {
189             session.registerIncompatibleErrorMessage(message);
190           }
191         }
192         return null;
193       }
194       else if (registerErrorOnFailure) {
195         session.registerIncompatibleErrorMessage("Failed to resolve argument");
196         return null;
197       }
198     }
199     return session;
200   }
201
202   @Override
203   public boolean equals(Object o) {
204     if (this == o) return true;
205     if (o == null || getClass() != o.getClass()) return false;
206
207     ExpressionCompatibilityConstraint that = (ExpressionCompatibilityConstraint)o;
208
209     if (!myExpression.equals(that.myExpression)) return false;
210
211     return true;
212   }
213
214   @Override
215   public int hashCode() {
216     return myExpression.hashCode();
217   }
218
219   @Override
220   public PsiExpression getExpression() {
221     return myExpression;
222   }
223
224   @Override
225   public PsiType getT() {
226     return myT;
227   }
228
229   @Override
230   protected void setT(PsiType t) {
231     myT = t;
232   }
233
234   @Override
235   protected InputOutputConstraintFormula createSelfConstraint(PsiType type, PsiExpression expression) {
236     return new ExpressionCompatibilityConstraint(expression, type);
237   }
238
239   @Override
240   protected void collectReturnTypeVariables(InferenceSession session,
241                                             PsiExpression psiExpression,
242                                             PsiType returnType,
243                                             Set<InferenceVariable> result) {
244     if (psiExpression instanceof PsiLambdaExpression) {
245       if (!PsiType.VOID.equals(returnType)) {
246         final List<PsiExpression> returnExpressions = LambdaUtil.getReturnExpressions((PsiLambdaExpression)psiExpression);
247         for (PsiExpression expression : returnExpressions) {
248           final Set<InferenceVariable> resultInputVars = createSelfConstraint(returnType, expression).getInputVariables(session);
249           if (resultInputVars != null) {
250             result.addAll(resultInputVars);
251           }
252         }
253       }
254     }
255   }
256 }