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 / CheckedExceptionCompatibilityConstraint.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.psi.impl.source.resolve.graphInference.constraints;
17
18 import com.intellij.codeInsight.ExceptionUtil;
19 import com.intellij.psi.*;
20 import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
21 import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
22 import com.intellij.psi.impl.source.resolve.graphInference.InferenceVariable;
23 import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
24 import com.intellij.psi.util.PsiUtil;
25 import com.intellij.psi.util.TypeConversionUtil;
26 import com.intellij.util.Function;
27 import com.intellij.util.containers.ContainerUtil;
28
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Set;
32
33 public class CheckedExceptionCompatibilityConstraint extends InputOutputConstraintFormula {
34   private final PsiExpression myExpression;
35
36   public CheckedExceptionCompatibilityConstraint(PsiExpression expression, PsiType t) {
37     super(t);
38     myExpression = expression;
39   }
40
41   @Override
42   public boolean reduce(final InferenceSession session, List<ConstraintFormula> constraints) {
43     if (!PsiPolyExpressionUtil.isPolyExpression(myExpression)) {
44       return true;
45     }
46     PsiType myT = getCurrentType();
47     if (myExpression instanceof PsiParenthesizedExpression) {
48       constraints.add(new CheckedExceptionCompatibilityConstraint(((PsiParenthesizedExpression)myExpression).getExpression(), myT));
49       return true;
50     }
51     if (myExpression instanceof PsiConditionalExpression) {
52       final PsiExpression thenExpression = ((PsiConditionalExpression)myExpression).getThenExpression();
53       if (thenExpression != null) {
54         constraints.add(new CheckedExceptionCompatibilityConstraint(thenExpression, myT));
55       }
56       final PsiExpression elseExpression = ((PsiConditionalExpression)myExpression).getElseExpression();
57       if (elseExpression != null) {
58         constraints.add(new CheckedExceptionCompatibilityConstraint(elseExpression, myT));
59       }
60       return true;
61     }
62     if (myExpression instanceof PsiLambdaExpression || myExpression instanceof PsiMethodReferenceExpression) {
63       if (!LambdaUtil.isFunctionalType(myT)) {
64         session.registerIncompatibleErrorMessage(session.getPresentableText(myT) + " is not a functional interface");
65         return false;
66       }
67
68       final PsiType groundTargetType = myExpression instanceof PsiLambdaExpression ? FunctionalInterfaceParameterizationUtil.getGroundTargetType(myT, (PsiLambdaExpression)myExpression, false) 
69                                                                                    : FunctionalInterfaceParameterizationUtil.getGroundTargetType(myT);
70       final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(groundTargetType);
71       if (interfaceMethod == null) {
72         session.registerIncompatibleErrorMessage("No valid function type can be found for " + session.getPresentableText(myT));
73         return false;
74       }
75
76       final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, PsiUtil.resolveGenericsClassInType(groundTargetType));
77       if (myExpression instanceof PsiLambdaExpression && !((PsiLambdaExpression)myExpression).hasFormalParameterTypes() ||
78           myExpression instanceof PsiMethodReferenceExpression && !((PsiMethodReferenceExpression)myExpression).isExact()) {
79         for (PsiParameter parameter : interfaceMethod.getParameterList().getParameters()) {
80           final PsiType type = substitutor.substitute(parameter.getType());
81           if (!session.isProperType(type)) {
82             session.registerIncompatibleErrorMessage("Parameter type is not yet inferred: " + session.getPresentableText(type));
83             return false;
84           }
85         }
86       }
87
88       final PsiType returnType = interfaceMethod.getReturnType();
89       if (myExpression instanceof PsiLambdaExpression || !((PsiMethodReferenceExpression)myExpression).isExact()) {
90         final PsiType type = substitutor.substitute(returnType);
91         if (!session.isProperType(type)) {
92           session.registerIncompatibleErrorMessage("Return type is not yet inferred: " + session.getPresentableText(type));
93           return false;
94         }
95       }
96
97       final List<PsiType>
98         expectedThrownTypes = ContainerUtil.map(interfaceMethod.getThrowsList().getReferencedTypes(),
99                                                 (Function<PsiType, PsiType>)type -> session.substituteWithInferenceVariables(substitutor.substitute(type)));
100       final List<PsiType> expectedNonProperThrownTypes = new ArrayList<>();
101       for (PsiType type : expectedThrownTypes) {
102         if (!session.isProperType(type)) {
103           expectedNonProperThrownTypes.add(type);
104         }
105       }
106       
107       final List<PsiType> thrownTypes = new ArrayList<>();
108       final PsiElement body = myExpression instanceof PsiLambdaExpression ? ((PsiLambdaExpression)myExpression).getBody() : myExpression;
109       if (body != null) {
110         final List<PsiClassType> exceptions =  ExceptionUtil.getUnhandledExceptions(new PsiElement[] {body});
111         thrownTypes.addAll(ContainerUtil.filter(exceptions, type -> !ExceptionUtil.isUncheckedException(type)));
112       }
113
114       if (expectedNonProperThrownTypes.isEmpty()) {
115         for (PsiType thrownType : thrownTypes) {
116           if (!isAddressed(expectedThrownTypes, thrownType)) {
117             session.registerIncompatibleErrorMessage("Unhandled exception: " + session.getPresentableText(thrownType));
118             return false;
119           }
120         }
121       } else {
122         final ArrayList<PsiType> expectedProperTypes = new ArrayList<>(expectedThrownTypes);
123         expectedProperTypes.removeAll(expectedNonProperThrownTypes);
124         for (PsiType thrownType : thrownTypes) {
125           if (!isAddressed(expectedProperTypes, thrownType)) {
126             for (PsiType expectedNonProperThrownType : expectedNonProperThrownTypes) {
127               constraints.add(new StrictSubtypingConstraint(expectedNonProperThrownType, thrownType));
128             }
129           }
130         }
131
132         for (PsiType expectedNonProperThrownType : expectedNonProperThrownTypes) {
133           final InferenceVariable variable = session.getInferenceVariable(expectedNonProperThrownType);
134           //could be null for invalid code
135           if (variable != null) {
136             variable.setThrownBound();
137           }
138         }
139       }
140     }
141
142     return true;
143   }
144
145   private static boolean isAddressed(List<PsiType> expectedThrownTypes, PsiType thrownType) {
146     for (PsiType expectedThrownType : expectedThrownTypes) {
147       if (TypeConversionUtil.isAssignable(expectedThrownType, thrownType)) {
148         return true;
149       }
150     }
151     return false;
152   }
153
154   @Override
155   public PsiExpression getExpression() {
156     return myExpression;
157   }
158
159   @Override
160   protected InputOutputConstraintFormula createSelfConstraint(PsiType type, PsiExpression expression) {
161     return new CheckedExceptionCompatibilityConstraint(expression, type);
162   }
163
164   @Override
165   protected void collectReturnTypeVariables(InferenceSession session,
166                                             PsiExpression psiExpression,
167                                             PsiType returnType, 
168                                             Set<InferenceVariable> result) {
169     session.collectDependencies(returnType, result);
170   }
171 }