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