• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas;
2 
3 import com.github.javaparser.ast.expr.*;
4 import com.github.javaparser.ast.stmt.*;
5 import com.github.javaparser.ast.type.UnknownType;
6 import com.github.javaparser.resolution.types.ResolvedType;
7 import com.github.javaparser.resolution.types.ResolvedTypeVariable;
8 import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
9 import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic;
10 import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
11 import com.github.javaparser.symbolsolver.resolution.typeinference.*;
12 import com.github.javaparser.utils.Pair;
13 
14 import java.util.HashMap;
15 import java.util.LinkedList;
16 import java.util.List;
17 import java.util.Map;
18 
19 import static com.github.javaparser.symbolsolver.resolution.typeinference.ExpressionHelper.isPolyExpression;
20 import static com.github.javaparser.symbolsolver.resolution.typeinference.ExpressionHelper.isStandaloneExpression;
21 import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isCompatibleInALooseInvocationContext;
22 import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isProperType;
23 import static java.util.stream.Collectors.*;
24 
25 /**
26  * An expression is compatible in a loose invocation context with type T
27  *
28  * @author Federico Tomassetti
29  */
30 public class ExpressionCompatibleWithType extends ConstraintFormula {
31     private TypeSolver typeSolver;
32     private Expression expression;
33     private ResolvedType T;
34 
ExpressionCompatibleWithType(TypeSolver typeSolver, Expression expression, ResolvedType T)35     public ExpressionCompatibleWithType(TypeSolver typeSolver, Expression expression, ResolvedType T) {
36         this.typeSolver = typeSolver;
37         this.expression = expression;
38         this.T = T;
39     }
40 
41     @Override
reduce(BoundSet currentBoundSet)42     public ReductionResult reduce(BoundSet currentBoundSet) {
43         // If T is a proper type, the constraint reduces to true if the expression is compatible in a loose
44         // invocation context with T (§5.3), and false otherwise.
45 
46         if (isProperType(T)) {
47             if (isCompatibleInALooseInvocationContext(typeSolver, expression, T)) {
48                 return ReductionResult.trueResult();
49             } else {
50                 return ReductionResult.falseResult();
51             }
52         }
53 
54         // Otherwise, if the expression is a standalone expression (§15.2) of type S, the constraint reduces
55         // to ‹S → T›.
56 
57         if (isStandaloneExpression(expression)) {
58             ResolvedType s = JavaParserFacade.get(typeSolver).getType(expression, false);
59             return ReductionResult.empty().withConstraint(new TypeCompatibleWithType(typeSolver, s, T));
60         }
61 
62         // Otherwise, the expression is a poly expression (§15.2). The result depends on the form of the expression:
63 
64         if (isPolyExpression(expression)) {
65 
66             // - If the expression is a parenthesized expression of the form ( Expression' ), the constraint reduces
67             //   to ‹Expression' → T›.
68 
69             if (expression instanceof EnclosedExpr) {
70                 EnclosedExpr enclosedExpr = (EnclosedExpr)expression;
71                 return ReductionResult.oneConstraint(new ExpressionCompatibleWithType(typeSolver, enclosedExpr.getInner(), T));
72             }
73 
74             // - If the expression is a class instance creation expression or a method invocation expression, the
75             //   constraint reduces to the bound set B3 which would be used to determine the expression's invocation
76             //   type when targeting T, as defined in §18.5.2. (For a class instance creation expression, the
77             //   corresponding "method" used for inference is defined in §15.9.3).
78             //
79             //   This bound set may contain new inference variables, as well as dependencies between these new
80             //   variables and the inference variables in T.
81 
82             if (expression instanceof ObjectCreationExpr) {
83                 BoundSet B3 = new TypeInference(typeSolver).invocationTypeInferenceBoundsSetB3();
84                 return ReductionResult.bounds(B3);
85             }
86 
87             if (expression instanceof MethodCallExpr) {
88                 throw new UnsupportedOperationException();
89             }
90 
91             // - If the expression is a conditional expression of the form e1 ? e2 : e3, the constraint reduces to two
92             //   constraint formulas, ‹e2 → T› and ‹e3 → T›.
93 
94             if (expression instanceof ConditionalExpr) {
95                 ConditionalExpr conditionalExpr = (ConditionalExpr)expression;
96                 return ReductionResult.withConstraints(
97                         new ExpressionCompatibleWithType(typeSolver, conditionalExpr.getThenExpr(), T),
98                         new ExpressionCompatibleWithType(typeSolver, conditionalExpr.getElseExpr(), T));
99             }
100 
101             // - If the expression is a lambda expression or a method reference expression, the result is specified
102             //   below.
103 
104             // A constraint formula of the form ‹LambdaExpression → T›, where T mentions at least one inference variable, is reduced as follows:
105 
106             if (expression instanceof LambdaExpr) {
107                 LambdaExpr lambdaExpr = (LambdaExpr)expression;
108 
109                 // - If T is not a functional interface type (§9.8), the constraint reduces to false.
110 
111                 if (!FunctionalInterfaceLogic.isFunctionalInterfaceType(T)) {
112                     return ReductionResult.falseResult();
113                 }
114 
115                 // - Otherwise, let T' be the ground target type derived from T, as specified in §15.27.3. If §18.5.3
116                 //   is used to derive a functional interface type which is parameterized, then the test that
117                 //   F<A'1, ..., A'm> is a subtype of F<A1, ..., Am> is not performed (instead, it is asserted with a
118                 //   constraint formula below). Let the target function type for the lambda expression be the
119                 //   function type of T'. Then:
120 
121                 Pair<ResolvedType, Boolean> result = TypeHelper.groundTargetTypeOfLambda(lambdaExpr, T, typeSolver);
122                 ResolvedType TFirst = result.a;
123                 MethodType targetFunctionType = TypeHelper.getFunctionType(TFirst);
124                 targetFunctionType = replaceTypeVariablesWithInferenceVariables(targetFunctionType);
125                 if (result.b) {
126                     throw new UnsupportedOperationException();
127                 }
128 
129                 //   - If no valid function type can be found, the constraint reduces to false.
130                 //
131                 //     Federico: THIS SHOULD NOT HAPPEN, IN CASE WE WILL THROW AN EXCEPTION
132                 //
133                 //   - Otherwise, the congruence of LambdaExpression with the target function type is asserted as
134                 //     follows:
135                 //
136                 //     - If the number of lambda parameters differs from the number of parameter types of the function
137                 //       type, the constraint reduces to false.
138 
139                 if (targetFunctionType.getFormalArgumentTypes().size() != lambdaExpr.getParameters().size()) {
140                     return ReductionResult.falseResult();
141                 }
142 
143                 //     - If the lambda expression is implicitly typed and one or more of the function type's parameter
144                 //       types is not a proper type, the constraint reduces to false.
145                 //
146                 //       This condition never arises in practice, due to the handling of implicitly typed lambda
147                 //       expressions in §18.5.1 and the substitution applied to the target type in §18.5.2.
148 
149                 //     - If the function type's result is void and the lambda body is neither a statement expression
150                 //       nor a void-compatible block, the constraint reduces to false.
151 
152                 if (targetFunctionType.getReturnType().isVoid()) {
153                     throw new UnsupportedOperationException();
154                 }
155 
156                 //     - If the function type's result is not void and the lambda body is a block that is not
157                 //       value-compatible, the constraint reduces to false.
158 
159                 if (!targetFunctionType.getReturnType().isVoid() && lambdaExpr.getBody() instanceof BlockStmt
160                         && !isValueCompatibleBlock(lambdaExpr.getBody())) {
161                     return ReductionResult.falseResult();
162                 }
163 
164                 //     - Otherwise, the constraint reduces to all of the following constraint formulas:
165                 List<ConstraintFormula> constraints = new LinkedList<>();
166 
167                 //       - If the lambda parameters have explicitly declared types F1, ..., Fn and the function type
168                 //         has parameter types G1, ..., Gn, then i) for all i (1 ≤ i ≤ n), ‹Fi = Gi›, and ii) ‹T' <: T›.
169 
170                 boolean hasExplicitlyDeclaredTypes = lambdaExpr.getParameters().stream().anyMatch(p -> !(p.getType() instanceof UnknownType));
171                 if (hasExplicitlyDeclaredTypes) {
172                     throw new UnsupportedOperationException();
173                 }
174 
175                 //       - If the function type's return type is a (non-void) type R, assume the lambda's parameter
176                 //         types are the same as the function type's parameter types. Then:
177 
178                 if (!targetFunctionType.getReturnType().isVoid()) {
179 
180                     ResolvedType R = targetFunctionType.getReturnType();
181 
182                     if (TypeHelper.isProperType(R)) {
183 
184                         //         - If R is a proper type, and if the lambda body or some result expression in the lambda body
185                         //           is not compatible in an assignment context with R, then false.
186 
187                         if (lambdaExpr.getBody() instanceof BlockStmt) {
188                             List<Expression> resultExpressions = ExpressionHelper.getResultExpressions((BlockStmt)lambdaExpr.getBody());
189                             for (Expression e : resultExpressions) {
190                                 if (!ExpressionHelper.isCompatibleInAssignmentContext(e, R, typeSolver)) {
191                                     return ReductionResult.falseResult();
192                                 }
193                             }
194                         } else {
195                             Expression e = ((ExpressionStmt)lambdaExpr.getBody()).getExpression();
196                             if (!ExpressionHelper.isCompatibleInAssignmentContext(e, R, typeSolver)) {
197                                 return ReductionResult.falseResult();
198                             }
199                         }
200                     } else {
201                         //         - Otherwise, if R is not a proper type, then where the lambda body has the form Expression,
202                         //           the constraint ‹Expression → R›; or where the lambda body is a block with result
203                         //           expressions e1, ..., em, for all i (1 ≤ i ≤ m), ‹ei → R›.
204 
205                         if (lambdaExpr.getBody() instanceof BlockStmt) {
206                             getAllReturnExpressions((BlockStmt)lambdaExpr.getBody()).forEach(e -> constraints.add(new ExpressionCompatibleWithType(typeSolver, e, R)));
207                         } else {
208                             // FEDERICO: Added - Start
209                             for (int i=0;i<lambdaExpr.getParameters().size();i++) {
210                                 ResolvedType paramType = targetFunctionType.getFormalArgumentTypes().get(i);
211                                 TypeInferenceCache.record(typeSolver, lambdaExpr, lambdaExpr.getParameter(i).getNameAsString(), paramType);
212                             }
213                             // FEDERICO: Added - End
214                             Expression e = ((ExpressionStmt)lambdaExpr.getBody()).getExpression();
215                             constraints.add(new ExpressionCompatibleWithType(typeSolver, e, R));
216                         }
217                     }
218                 }
219 
220                 return ReductionResult.withConstraints(constraints);
221             }
222 
223             // A constraint formula of the form ‹MethodReference → T›, where T mentions at least one inference variable, is reduced as follows:
224 
225             if (expression instanceof MethodReferenceExpr) {
226 
227                 // - If T is not a functional interface type, or if T is a functional interface type that does not have a function type (§9.9), the constraint reduces to false.
228                 //
229                 // - Otherwise, if there does not exist a potentially applicable method for the method reference when targeting T, the constraint reduces to false.
230                 //
231                 // - Otherwise, if the method reference is exact (§15.13.1), then let P1, ..., Pn be the parameter types of the function type of T, and let F1, ..., Fk be the parameter types of the potentially applicable method. The constraint reduces to a new set of constraints, as follows:
232                 //
233                 //   - In the special case where n = k+1, the parameter of type P1 is to act as the target reference of the invocation. The method reference expression necessarily has the form ReferenceType :: [TypeArguments] Identifier. The constraint reduces to ‹P1 <: ReferenceType› and, for all i (2 ≤ i ≤ n), ‹Pi → Fi-1›.
234                 //
235                 //     In all other cases, n = k, and the constraint reduces to, for all i (1 ≤ i ≤ n), ‹Pi → Fi›.
236                 //
237                 //   - If the function type's result is not void, let R be its return type. Then, if the result of the potentially applicable compile-time declaration is void, the constraint reduces to false. Otherwise, the constraint reduces to ‹R' → R›, where R' is the result of applying capture conversion (§5.1.10) to the return type of the potentially applicable compile-time declaration.
238                 //
239                 // - Otherwise, the method reference is inexact, and:
240                 //
241                 //   - If one or more of the function type's parameter types is not a proper type, the constraint reduces to false.
242                 //
243                 //     This condition never arises in practice, due to the handling of inexact method references in §18.5.1 and the substitution applied to the target type in §18.5.2.
244                 //
245                 //   - Otherwise, a search for a compile-time declaration is performed, as specified in §15.13.1. If there is no compile-time declaration for the method reference, the constraint reduces to false. Otherwise, there is a compile-time declaration, and:
246                 //
247                 //     - If the result of the function type is void, the constraint reduces to true.
248                 //
249                 //     - Otherwise, if the method reference expression elides TypeArguments, and the compile-time declaration is a generic method, and the return type of the compile-time declaration mentions at least one of the method's type parameters, then the constraint reduces to the bound set B3 which would be used to determine the method reference's invocation type when targeting the return type of the function type, as defined in §18.5.2. B3 may contain new inference variables, as well as dependencies between these new variables and the inference variables in T.
250                 //
251                 //     - Otherwise, let R be the return type of the function type, and let R' be the result of applying capture conversion (§5.1.10) to the return type of the invocation type (§15.12.2.6) of the compile-time declaration. If R' is void, the constraint reduces to false; otherwise, the constraint reduces to ‹R' → R›.
252 
253                 throw new UnsupportedOperationException();
254             }
255 
256             throw new RuntimeException("This should not happen");
257         }
258 
259         throw new RuntimeException("This should not happen");
260     }
261 
getAllReturnExpressions(BlockStmt blockStmt)262     private List<Expression> getAllReturnExpressions(BlockStmt blockStmt) {
263         return blockStmt.findAll(ReturnStmt.class).stream()
264                 .filter(r -> r.getExpression().isPresent())
265                 .map(r -> r.getExpression().get())
266                 .collect(toList());
267     }
268 
isValueCompatibleBlock(Statement statement)269     private boolean isValueCompatibleBlock(Statement statement) {
270         // A block lambda body is value-compatible if it cannot complete normally (§14.21) and every return statement
271         // in the block has the form return Expression;.
272 
273         if (statement instanceof BlockStmt) {
274             if (!ControlFlowLogic.getInstance().canCompleteNormally(statement)) {
275                 return true;
276             }
277             List<ReturnStmt> returnStmts = statement.findAll(ReturnStmt.class);
278             return returnStmts.stream().allMatch(r -> r.getExpression().isPresent());
279         }
280         return false;
281     }
282 
283     @Override
equals(Object o)284     public boolean equals(Object o) {
285         if (this == o) return true;
286         if (o == null || getClass() != o.getClass()) return false;
287 
288         ExpressionCompatibleWithType that = (ExpressionCompatibleWithType) o;
289 
290         if (!typeSolver.equals(that.typeSolver)) return false;
291         if (!expression.equals(that.expression)) return false;
292         return T.equals(that.T);
293     }
294 
295     @Override
hashCode()296     public int hashCode() {
297         int result = typeSolver.hashCode();
298         result = 31 * result + expression.hashCode();
299         result = 31 * result + T.hashCode();
300         return result;
301     }
302 
303     @Override
toString()304     public String toString() {
305         return "ExpressionCompatibleWithType{" +
306                 "typeSolver=" + typeSolver +
307                 ", expression=" + expression +
308                 ", T=" + T +
309                 '}';
310     }
311 
replaceTypeVariablesWithInferenceVariables(MethodType methodType)312     private MethodType replaceTypeVariablesWithInferenceVariables(MethodType methodType) {
313         // Find all type variable
314         Map<ResolvedTypeVariable, InferenceVariable> correspondences = new HashMap<>();
315         List<ResolvedType> newFormalArgumentTypes = new LinkedList<>();
316         for (ResolvedType formalArg : methodType.getFormalArgumentTypes()) {
317             newFormalArgumentTypes.add(replaceTypeVariablesWithInferenceVariables(formalArg, correspondences));
318         }
319         ResolvedType newReturnType = replaceTypeVariablesWithInferenceVariables(methodType.getReturnType(), correspondences);
320         return new MethodType(methodType.getTypeParameters(), newFormalArgumentTypes, newReturnType, methodType.getExceptionTypes());
321     }
322 
replaceTypeVariablesWithInferenceVariables(ResolvedType originalType, Map<ResolvedTypeVariable, InferenceVariable> correspondences)323     private ResolvedType replaceTypeVariablesWithInferenceVariables(ResolvedType originalType, Map<ResolvedTypeVariable, InferenceVariable> correspondences) {
324         if (originalType.isTypeVariable()) {
325             if (!correspondences.containsKey(originalType.asTypeVariable())) {
326                 correspondences.put(originalType.asTypeVariable(), InferenceVariable.unnamed(originalType.asTypeVariable().asTypeParameter()));
327             }
328             return correspondences.get(originalType.asTypeVariable());
329         }
330         if (originalType.isPrimitive()) {
331             return originalType;
332         }
333         throw new UnsupportedOperationException(originalType.toString());
334     }
335 }
336