aboutsummaryrefslogtreecommitdiffstats
path: root/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/ExpressionCompatibleWithType.java
blob: 30042f8a80e93bac973d2c782f280745e164dada (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
package com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas;

import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.type.UnknownType;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.resolution.types.ResolvedTypeVariable;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.resolution.typeinference.*;
import com.github.javaparser.utils.Pair;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static com.github.javaparser.symbolsolver.resolution.typeinference.ExpressionHelper.isPolyExpression;
import static com.github.javaparser.symbolsolver.resolution.typeinference.ExpressionHelper.isStandaloneExpression;
import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isCompatibleInALooseInvocationContext;
import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isProperType;
import static java.util.stream.Collectors.*;

/**
 * An expression is compatible in a loose invocation context with type T
 *
 * @author Federico Tomassetti
 */
public class ExpressionCompatibleWithType extends ConstraintFormula {
    private TypeSolver typeSolver;
    private Expression expression;
    private ResolvedType T;

    public ExpressionCompatibleWithType(TypeSolver typeSolver, Expression expression, ResolvedType T) {
        this.typeSolver = typeSolver;
        this.expression = expression;
        this.T = T;
    }

    @Override
    public ReductionResult reduce(BoundSet currentBoundSet) {
        // If T is a proper type, the constraint reduces to true if the expression is compatible in a loose
        // invocation context with T (§5.3), and false otherwise.

        if (isProperType(T)) {
            if (isCompatibleInALooseInvocationContext(typeSolver, expression, T)) {
                return ReductionResult.trueResult();
            } else {
                return ReductionResult.falseResult();
            }
        }

        // Otherwise, if the expression is a standalone expression (§15.2) of type S, the constraint reduces
        // to ‹S → T›.

        if (isStandaloneExpression(expression)) {
            ResolvedType s = JavaParserFacade.get(typeSolver).getType(expression, false);
            return ReductionResult.empty().withConstraint(new TypeCompatibleWithType(typeSolver, s, T));
        }

        // Otherwise, the expression is a poly expression (§15.2). The result depends on the form of the expression:

        if (isPolyExpression(expression)) {

            // - If the expression is a parenthesized expression of the form ( Expression' ), the constraint reduces
            //   to ‹Expression' → T›.

            if (expression instanceof EnclosedExpr) {
                EnclosedExpr enclosedExpr = (EnclosedExpr)expression;
                return ReductionResult.oneConstraint(new ExpressionCompatibleWithType(typeSolver, enclosedExpr.getInner(), T));
            }

            // - If the expression is a class instance creation expression or a method invocation expression, the
            //   constraint reduces to the bound set B3 which would be used to determine the expression's invocation
            //   type when targeting T, as defined in §18.5.2. (For a class instance creation expression, the
            //   corresponding "method" used for inference is defined in §15.9.3).
            //
            //   This bound set may contain new inference variables, as well as dependencies between these new
            //   variables and the inference variables in T.

            if (expression instanceof ObjectCreationExpr) {
                BoundSet B3 = new TypeInference(typeSolver).invocationTypeInferenceBoundsSetB3();
                return ReductionResult.bounds(B3);
            }

            if (expression instanceof MethodCallExpr) {
                throw new UnsupportedOperationException();
            }

            // - If the expression is a conditional expression of the form e1 ? e2 : e3, the constraint reduces to two
            //   constraint formulas, ‹e2 → T› and ‹e3 → T›.

            if (expression instanceof ConditionalExpr) {
                ConditionalExpr conditionalExpr = (ConditionalExpr)expression;
                return ReductionResult.withConstraints(
                        new ExpressionCompatibleWithType(typeSolver, conditionalExpr.getThenExpr(), T),
                        new ExpressionCompatibleWithType(typeSolver, conditionalExpr.getElseExpr(), T));
            }

            // - If the expression is a lambda expression or a method reference expression, the result is specified
            //   below.

            // A constraint formula of the form ‹LambdaExpression → T›, where T mentions at least one inference variable, is reduced as follows:

            if (expression instanceof LambdaExpr) {
                LambdaExpr lambdaExpr = (LambdaExpr)expression;

                // - If T is not a functional interface type (§9.8), the constraint reduces to false.

                if (!FunctionalInterfaceLogic.isFunctionalInterfaceType(T)) {
                    return ReductionResult.falseResult();
                }

                // - Otherwise, let T' be the ground target type derived from T, as specified in §15.27.3. If §18.5.3
                //   is used to derive a functional interface type which is parameterized, then the test that
                //   F<A'1, ..., A'm> is a subtype of F<A1, ..., Am> is not performed (instead, it is asserted with a
                //   constraint formula below). Let the target function type for the lambda expression be the
                //   function type of T'. Then:

                Pair<ResolvedType, Boolean> result = TypeHelper.groundTargetTypeOfLambda(lambdaExpr, T, typeSolver);
                ResolvedType TFirst = result.a;
                MethodType targetFunctionType = TypeHelper.getFunctionType(TFirst);
                targetFunctionType = replaceTypeVariablesWithInferenceVariables(targetFunctionType);
                if (result.b) {
                    throw new UnsupportedOperationException();
                }

                //   - If no valid function type can be found, the constraint reduces to false.
                //
                //     Federico: THIS SHOULD NOT HAPPEN, IN CASE WE WILL THROW AN EXCEPTION
                //
                //   - Otherwise, the congruence of LambdaExpression with the target function type is asserted as
                //     follows:
                //
                //     - If the number of lambda parameters differs from the number of parameter types of the function
                //       type, the constraint reduces to false.

                if (targetFunctionType.getFormalArgumentTypes().size() != lambdaExpr.getParameters().size()) {
                    return ReductionResult.falseResult();
                }

                //     - If the lambda expression is implicitly typed and one or more of the function type's parameter
                //       types is not a proper type, the constraint reduces to false.
                //
                //       This condition never arises in practice, due to the handling of implicitly typed lambda
                //       expressions in §18.5.1 and the substitution applied to the target type in §18.5.2.

                //     - If the function type's result is void and the lambda body is neither a statement expression
                //       nor a void-compatible block, the constraint reduces to false.

                if (targetFunctionType.getReturnType().isVoid()) {
                    throw new UnsupportedOperationException();
                }

                //     - If the function type's result is not void and the lambda body is a block that is not
                //       value-compatible, the constraint reduces to false.

                if (!targetFunctionType.getReturnType().isVoid() && lambdaExpr.getBody() instanceof BlockStmt
                        && !isValueCompatibleBlock(lambdaExpr.getBody())) {
                    return ReductionResult.falseResult();
                }

                //     - Otherwise, the constraint reduces to all of the following constraint formulas:
                List<ConstraintFormula> constraints = new LinkedList<>();

                //       - If the lambda parameters have explicitly declared types F1, ..., Fn and the function type
                //         has parameter types G1, ..., Gn, then i) for all i (1 ≤ i ≤ n), ‹Fi = Gi›, and ii) ‹T' <: T›.

                boolean hasExplicitlyDeclaredTypes = lambdaExpr.getParameters().stream().anyMatch(p -> !(p.getType() instanceof UnknownType));
                if (hasExplicitlyDeclaredTypes) {
                    throw new UnsupportedOperationException();
                }

                //       - If the function type's return type is a (non-void) type R, assume the lambda's parameter
                //         types are the same as the function type's parameter types. Then:

                if (!targetFunctionType.getReturnType().isVoid()) {

                    ResolvedType R = targetFunctionType.getReturnType();

                    if (TypeHelper.isProperType(R)) {

                        //         - If R is a proper type, and if the lambda body or some result expression in the lambda body
                        //           is not compatible in an assignment context with R, then false.

                        if (lambdaExpr.getBody() instanceof BlockStmt) {
                            List<Expression> resultExpressions = ExpressionHelper.getResultExpressions((BlockStmt)lambdaExpr.getBody());
                            for (Expression e : resultExpressions) {
                                if (!ExpressionHelper.isCompatibleInAssignmentContext(e, R, typeSolver)) {
                                    return ReductionResult.falseResult();
                                }
                            }
                        } else {
                            Expression e = ((ExpressionStmt)lambdaExpr.getBody()).getExpression();
                            if (!ExpressionHelper.isCompatibleInAssignmentContext(e, R, typeSolver)) {
                                return ReductionResult.falseResult();
                            }
                        }
                    } else {
                        //         - Otherwise, if R is not a proper type, then where the lambda body has the form Expression,
                        //           the constraint ‹Expression → R›; or where the lambda body is a block with result
                        //           expressions e1, ..., em, for all i (1 ≤ i ≤ m), ‹ei → R›.

                        if (lambdaExpr.getBody() instanceof BlockStmt) {
                            getAllReturnExpressions((BlockStmt)lambdaExpr.getBody()).forEach(e -> constraints.add(new ExpressionCompatibleWithType(typeSolver, e, R)));
                        } else {
                            // FEDERICO: Added - Start
                            for (int i=0;i<lambdaExpr.getParameters().size();i++) {
                                ResolvedType paramType = targetFunctionType.getFormalArgumentTypes().get(i);
                                TypeInferenceCache.record(typeSolver, lambdaExpr, lambdaExpr.getParameter(i).getNameAsString(), paramType);
                            }
                            // FEDERICO: Added - End
                            Expression e = ((ExpressionStmt)lambdaExpr.getBody()).getExpression();
                            constraints.add(new ExpressionCompatibleWithType(typeSolver, e, R));
                        }
                    }
                }

                return ReductionResult.withConstraints(constraints);
            }

            // A constraint formula of the form ‹MethodReference → T›, where T mentions at least one inference variable, is reduced as follows:

            if (expression instanceof MethodReferenceExpr) {

                // - 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.
                //
                // - Otherwise, if there does not exist a potentially applicable method for the method reference when targeting T, the constraint reduces to false.
                //
                // - 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:
                //
                //   - 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›.
                //
                //     In all other cases, n = k, and the constraint reduces to, for all i (1 ≤ i ≤ n), ‹Pi → Fi›.
                //
                //   - 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.
                //
                // - Otherwise, the method reference is inexact, and:
                //
                //   - If one or more of the function type's parameter types is not a proper type, the constraint reduces to false.
                //
                //     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.
                //
                //   - 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:
                //
                //     - If the result of the function type is void, the constraint reduces to true.
                //
                //     - 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.
                //
                //     - 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›.

                throw new UnsupportedOperationException();
            }

            throw new RuntimeException("This should not happen");
        }

        throw new RuntimeException("This should not happen");
    }

    private List<Expression> getAllReturnExpressions(BlockStmt blockStmt) {
        return blockStmt.findAll(ReturnStmt.class).stream()
                .filter(r -> r.getExpression().isPresent())
                .map(r -> r.getExpression().get())
                .collect(toList());
    }

    private boolean isValueCompatibleBlock(Statement statement) {
        // A block lambda body is value-compatible if it cannot complete normally (§14.21) and every return statement
        // in the block has the form return Expression;.

        if (statement instanceof BlockStmt) {
            if (!ControlFlowLogic.getInstance().canCompleteNormally(statement)) {
                return true;
            }
            List<ReturnStmt> returnStmts = statement.findAll(ReturnStmt.class);
            return returnStmts.stream().allMatch(r -> r.getExpression().isPresent());
        }
        return false;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ExpressionCompatibleWithType that = (ExpressionCompatibleWithType) o;

        if (!typeSolver.equals(that.typeSolver)) return false;
        if (!expression.equals(that.expression)) return false;
        return T.equals(that.T);
    }

    @Override
    public int hashCode() {
        int result = typeSolver.hashCode();
        result = 31 * result + expression.hashCode();
        result = 31 * result + T.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "ExpressionCompatibleWithType{" +
                "typeSolver=" + typeSolver +
                ", expression=" + expression +
                ", T=" + T +
                '}';
    }

    private MethodType replaceTypeVariablesWithInferenceVariables(MethodType methodType) {
        // Find all type variable
        Map<ResolvedTypeVariable, InferenceVariable> correspondences = new HashMap<>();
        List<ResolvedType> newFormalArgumentTypes = new LinkedList<>();
        for (ResolvedType formalArg : methodType.getFormalArgumentTypes()) {
            newFormalArgumentTypes.add(replaceTypeVariablesWithInferenceVariables(formalArg, correspondences));
        }
        ResolvedType newReturnType = replaceTypeVariablesWithInferenceVariables(methodType.getReturnType(), correspondences);
        return new MethodType(methodType.getTypeParameters(), newFormalArgumentTypes, newReturnType, methodType.getExceptionTypes());
    }

    private ResolvedType replaceTypeVariablesWithInferenceVariables(ResolvedType originalType, Map<ResolvedTypeVariable, InferenceVariable> correspondences) {
        if (originalType.isTypeVariable()) {
            if (!correspondences.containsKey(originalType.asTypeVariable())) {
                correspondences.put(originalType.asTypeVariable(), InferenceVariable.unnamed(originalType.asTypeVariable().asTypeParameter()));
            }
            return correspondences.get(originalType.asTypeVariable());
        }
        if (originalType.isPrimitive()) {
            return originalType;
        }
        throw new UnsupportedOperationException(originalType.toString());
    }
}