• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.github.javaparser.symbolsolver.resolution.typeinference;
2 
3 import com.github.javaparser.ast.expr.Expression;
4 import com.github.javaparser.ast.expr.LambdaExpr;
5 import com.github.javaparser.resolution.MethodUsage;
6 import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration;
7 import com.github.javaparser.resolution.types.*;
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.model.typesystem.*;
12 import com.github.javaparser.utils.Pair;
13 
14 import java.util.*;
15 
16 /**
17  * The term "type" is used loosely in this chapter to include type-like syntax that contains inference variables.
18  *
19  * Assertions that involve inference
20  * variables are assertions about every proper type that can be produced by replacing each inference variable with
21  * a proper type.
22  *
23  * @author Federico Tomassetti
24  */
25 public class TypeHelper {
26 
27     /**
28      * The term proper type excludes such "types" that mention inference variables.
29      */
isProperType(ResolvedType type)30     public static boolean isProperType(ResolvedType type) {
31         if (type instanceof InferenceVariable) {
32             return false;
33         }
34         if (type instanceof ResolvedReferenceType) {
35             ResolvedReferenceType referenceType = (ResolvedReferenceType) type;
36             return referenceType.typeParametersValues().stream().allMatch(it -> isProperType(it));
37         }
38         if (type instanceof ResolvedWildcard) {
39             ResolvedWildcard wildcard = (ResolvedWildcard)type;
40             if (wildcard.isBounded()) {
41                 return isProperType(wildcard.getBoundedType());
42             } else {
43                 return true;
44             }
45         }
46         if (type.isPrimitive()) {
47             return true;
48         }
49         if (type.isTypeVariable()) {
50             // FIXME I am not sure...
51             return false;
52         }
53         if (type.isArray()) {
54             return isProperType(type.asArrayType().getComponentType());
55         }
56         throw new UnsupportedOperationException(type.toString());
57     }
58 
59     /**
60      * see https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.3
61      * @param expression
62      * @param t
63      * @return
64      */
isCompatibleInAStrictInvocationContext(Expression expression, ResolvedType t)65     public static boolean isCompatibleInAStrictInvocationContext(Expression expression, ResolvedType t) {
66         throw new UnsupportedOperationException();
67     }
68 
69     /**
70      * see https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.3
71      * @param expression
72      * @param t
73      * @return
74      */
isCompatibleInALooseInvocationContext(TypeSolver typeSolver, Expression expression, ResolvedType t)75     public static boolean isCompatibleInALooseInvocationContext(TypeSolver typeSolver, Expression expression, ResolvedType t) {
76         //throw new UnsupportedOperationException("Unable to determine if " + expression + " is compatible in a loose invocation context with type " + t);
77         return isCompatibleInALooseInvocationContext(JavaParserFacade.get(typeSolver).getType(expression), t);
78     }
79 
80     /**
81      * see https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.3
82      * @param s
83      * @param t
84      * @return
85      */
isCompatibleInALooseInvocationContext(ResolvedType s, ResolvedType t)86     public static boolean isCompatibleInALooseInvocationContext(ResolvedType s, ResolvedType t) {
87         // Loose invocation contexts allow a more permissive set of conversions, because they are only used for a
88         // particular invocation if no applicable declaration can be found using strict invocation contexts. Loose
89         // invocation contexts allow the use of one of the following:
90         //
91         // - an identity conversion (§5.1.1)
92 
93         if (s.equals(t)) {
94             return true;
95         }
96 
97         // - a widening primitive conversion (§5.1.2)
98 
99         if (s.isPrimitive() && t.isPrimitive() && areCompatibleThroughWideningPrimitiveConversion(s, t)) {
100             return true;
101         }
102 
103         // - a widening reference conversion (§5.1.5)
104 
105         if (s.isReferenceType() && t.isReferenceType() && areCompatibleThroughWideningReferenceConversion(s, t)) {
106             return true;
107         }
108 
109         // - a boxing conversion (§5.1.7) optionally followed by widening reference conversion
110 
111         if (s.isPrimitive() && t.isReferenceType() &&
112                 areCompatibleThroughWideningReferenceConversion(toBoxedType(s.asPrimitive()), t)) {
113             return true;
114         }
115 
116         // - an unboxing conversion (§5.1.8) optionally followed by a widening primitive conversion
117 
118         if (isUnboxable(s) && s.isReferenceType() && t.isPrimitive() &&
119                 areCompatibleThroughWideningPrimitiveConversion(toUnboxedType(s.asReferenceType()), t)) {
120             return true;
121         }
122 
123         // If, after the conversions listed for an invocation context have been applied, the resulting type is a raw
124         // type (§4.8), an unchecked conversion (§5.1.9) may then be applied.
125         //
126         // A value of the null type (the null reference is the only such value) may be assigned to any reference type
127         if (s.isNull() && t.isReferenceType()) {
128             return true;
129         }
130 
131         //throw new UnsupportedOperationException("isCompatibleInALooseInvocationContext unable to decide on s=" + s + ", t=" + t);
132         // TODO FIXME
133         return t.isAssignableBy(s);
134     }
135 
isUnboxable(ResolvedType referenceType)136     private static boolean isUnboxable(ResolvedType referenceType) {
137         if (!referenceType.isReferenceType()) {
138             return false;
139         }
140         return Arrays.stream(ResolvedPrimitiveType.values()).anyMatch(pt -> referenceType.asReferenceType().getQualifiedName().equals(pt.getBoxTypeQName()));
141     }
142 
toUnboxedType(ResolvedReferenceType referenceType)143     private static ResolvedType toUnboxedType(ResolvedReferenceType referenceType) {
144         throw new UnsupportedOperationException(referenceType.toString());
145     }
146 
toBoxedType(ResolvedPrimitiveType primitiveType)147     private static ResolvedType toBoxedType(ResolvedPrimitiveType primitiveType) {
148         throw new UnsupportedOperationException();
149     }
150 
areCompatibleThroughWideningReferenceConversion(ResolvedType s, ResolvedType t)151     private static boolean areCompatibleThroughWideningReferenceConversion(ResolvedType s, ResolvedType t) {
152         Optional<ResolvedPrimitiveType> correspondingPrimitiveTypeForS = Arrays.stream(ResolvedPrimitiveType.values()).filter(pt -> pt.getBoxTypeQName().equals(s.asReferenceType().getQualifiedName())).findFirst();
153         if (!correspondingPrimitiveTypeForS.isPresent()) {
154             return false;
155         }
156         throw new UnsupportedOperationException("areCompatibleThroughWideningReferenceConversion s="+s+", t=" + t);
157     }
158 
areCompatibleThroughWideningPrimitiveConversion(ResolvedType s, ResolvedType t)159     private static boolean areCompatibleThroughWideningPrimitiveConversion(ResolvedType s, ResolvedType t) {
160         if (s.isPrimitive() && t.isPrimitive()) {
161             return s.isAssignableBy(t);
162         } else {
163             return false;
164         }
165     }
166 
isInferenceVariable(ResolvedType type)167     public static boolean isInferenceVariable(ResolvedType type) {
168         return type instanceof InferenceVariable;
169     }
170 
usedInferenceVariables(ResolvedType type)171     public static Set<InferenceVariable> usedInferenceVariables(ResolvedType type) {
172         if (isInferenceVariable(type)) {
173             return new HashSet<>(Arrays.asList((InferenceVariable)type));
174         }
175         if (type.isReferenceType()) {
176             Set<InferenceVariable> res = new HashSet<>();
177             for (ResolvedType tp : type.asReferenceType().typeParametersValues()) {
178                 res.addAll(usedInferenceVariables(tp));
179             }
180             return res;
181         }
182         throw new UnsupportedOperationException(type.toString());
183     }
184 
185     /**
186      * See JLS 4.10.4. Least Upper Bound.
187      */
leastUpperBound(Set<ResolvedType> types)188     public static ResolvedType leastUpperBound(Set<ResolvedType> types) {
189         if (types.size() == 0) {
190             throw new IllegalArgumentException();
191         }
192 
193         // The least upper bound, or "lub", of a set of reference types is a shared supertype that is more specific than
194         // any other shared supertype (that is, no other shared supertype is a subtype of the least upper bound).
195         // This type, lub(U1, ..., Uk), is determined as follows.
196         //
197         // If k = 1, then the lub is the type itself: lub(U) = U.
198 
199         if (types.size() == 1) {
200             return types.stream().findFirst().get();
201         }
202 
203         //
204         //Otherwise:
205         //
206         //For each Ui (1 ≤ i ≤ k):
207         //
208         //Let ST(Ui) be the set of supertypes of Ui.
209         //
210         //Let EST(Ui), the set of erased supertypes of Ui, be:
211         //
212         //EST(Ui) = { |W| | W in ST(Ui) } where |W| is the erasure of W.
213         //
214         //The reason for computing the set of erased supertypes is to deal with situations where the set of types includes several distinct parameterizations of a generic type.
215         //
216         //For example, given List<String> and List<Object>, simply intersecting the sets ST(List<String>) = { List<String>, Collection<String>, Object } and ST(List<Object>) = { List<Object>, Collection<Object>, Object } would yield a set { Object }, and we would have lost track of the fact that the upper bound can safely be assumed to be a List.
217         //
218         //In contrast, intersecting EST(List<String>) = { List, Collection, Object } and EST(List<Object>) = { List, Collection, Object } yields { List, Collection, Object }, which will eventually enable us to produce List<?>.
219         //
220         //Let EC, the erased candidate set for U1 ... Uk, be the intersection of all the sets EST(Ui) (1 ≤ i ≤ k).
221         //
222         //Let MEC, the minimal erased candidate set for U1 ... Uk, be:
223         //
224         //MEC = { V | V in EC, and for all W ≠ V in EC, it is not the case that W <: V }
225         //
226         //Because we are seeking to infer more precise types, we wish to filter out any candidates that are supertypes of other candidates. This is what computing MEC accomplishes. In our running example, we had EC = { List, Collection, Object }, so MEC = { List }. The next step is to recover type arguments for the erased types in MEC.
227         //
228         //For any element G of MEC that is a generic type:
229         //
230         //Let the "relevant" parameterizations of G, Relevant(G), be:
231         //
232         //Relevant(G) = { V | 1 ≤ i ≤ k: V in ST(Ui) and V = G<...> }
233         //
234         //In our running example, the only generic element of MEC is List, and Relevant(List) = { List<String>, List<Object> }. We will now seek to find a type argument for List that contains (§4.5.1) both String and Object.
235         //
236         //This is done by means of the least containing parameterization (lcp) operation defined below. The first line defines lcp() on a set, such as Relevant(List), as an operation on a list of the elements of the set. The next line defines the operation on such lists, as a pairwise reduction on the elements of the list. The third line is the definition of lcp() on pairs of parameterized types, which in turn relies on the notion of least containing type argument (lcta). lcta() is defined for all possible cases.
237         //
238         //Let the "candidate" parameterization of G, Candidate(G), be the most specific parameterization of the generic type G that contains all the relevant parameterizations of G:
239         //
240         //Candidate(G) = lcp(Relevant(G))
241         //
242         //where lcp(), the least containing invocation, is:
243         //
244         //lcp(S) = lcp(e1, ..., en) where ei (1 ≤ i ≤ n) in S
245         //
246         //lcp(e1, ..., en) = lcp(lcp(e1, e2), e3, ..., en)
247         //
248         //lcp(G<X1, ..., Xn>, G<Y1, ..., Yn>) = G<lcta(X1, Y1), ..., lcta(Xn, Yn)>
249         //
250         //lcp(G<X1, ..., Xn>) = G<lcta(X1), ..., lcta(Xn)>
251         //
252         //and where lcta(), the least containing type argument, is: (assuming U and V are types)
253         //
254         //lcta(U, V) = U if U = V, otherwise ? extends lub(U, V)
255         //
256         //lcta(U, ? extends V) = ? extends lub(U, V)
257         //
258         //lcta(U, ? super V) = ? super glb(U, V)
259         //
260         //lcta(? extends U, ? extends V) = ? extends lub(U, V)
261         //
262         //lcta(? extends U, ? super V) = U if U = V, otherwise ?
263         //
264         //lcta(? super U, ? super V) = ? super glb(U, V)
265         //
266         //lcta(U) = ? if U's upper bound is Object, otherwise ? extends lub(U,Object)
267         //
268         //and where glb() is as defined in §5.1.10.
269         //
270         //Let lub(U1 ... Uk) be:
271         //
272         //Best(W1) & ... & Best(Wr)
273         //
274         //where Wi (1 ≤ i ≤ r) are the elements of MEC, the minimal erased candidate set of U1 ... Uk;
275         //
276         //and where, if any of these elements are generic, we use the candidate parameterization (so as to recover type arguments):
277         //
278         //Best(X) = Candidate(X) if X is generic; X otherwise.
279         //
280         //Strictly speaking, this lub() function only approximates a least upper bound. Formally, there may exist some other type T such that all of U1 ... Uk are subtypes of T and T is a subtype of lub(U1, ..., Uk). However, a compiler for the Java programming language must implement lub() as specified above.
281         //
282         //It is possible that the lub() function yields an infinite type. This is permissible, and a compiler for the Java programming language must recognize such situations and represent them appropriately using cyclic data structures.
283         //
284         //The possibility of an infinite type stems from the recursive calls to lub(). Readers familiar with recursive types should note that an infinite type is not the same as a recursive type
285         throw new UnsupportedOperationException();
286     }
287 
288     /**
289      * See JLS 15.27.3. Type of a Lambda Expression
290      * @return
291      */
groundTargetTypeOfLambda(LambdaExpr lambdaExpr, ResolvedType T, TypeSolver typeSolver)292     public static Pair<ResolvedType, Boolean> groundTargetTypeOfLambda(LambdaExpr lambdaExpr, ResolvedType T, TypeSolver typeSolver) {
293         // The ground target type is derived from T as follows:
294         //
295         boolean used18_5_3 = false;
296 
297         boolean wildcardParameterized = T.asReferenceType().typeParametersValues().stream()
298                 .anyMatch(tp -> tp.isWildcard());
299         if (wildcardParameterized) {
300             // - If T is a wildcard-parameterized functional interface type and the lambda expression is explicitly typed,
301             //   then the ground target type is inferred as described in §18.5.3.
302 
303             if (ExpressionHelper.isExplicitlyTyped(lambdaExpr)) {
304                 used18_5_3 = true;
305                 throw new UnsupportedOperationException();
306             }
307 
308             // - If T is a wildcard-parameterized functional interface type and the lambda expression is implicitly typed,
309             //   then the ground target type is the non-wildcard parameterization (§9.9) of T.
310 
311             else {
312                 return new Pair<>(nonWildcardParameterizationOf(T.asReferenceType(), typeSolver), used18_5_3);
313             }
314         }
315 
316         // - Otherwise, the ground target type is T.
317         return new Pair<>(T, used18_5_3);
318     }
319 
320     /**
321      * See JLS 9.9
322      */
nonWildcardParameterizationOf(ResolvedReferenceType originalType, TypeSolver typeSolver)323     private static ResolvedReferenceType nonWildcardParameterizationOf(ResolvedReferenceType originalType, TypeSolver typeSolver) {
324         List<ResolvedType> TIs = new LinkedList<>();
325         List<ResolvedType> AIs = originalType.typeParametersValues();
326         List<ResolvedTypeParameterDeclaration> TPs = originalType.getTypeDeclaration().getTypeParameters();
327 
328         // Let P1...Pn be the type parameters of I with corresponding bounds B1...Bn. For all i (1 ≤ i ≤ n),
329         // Ti is derived according to the form of Ai:
330 
331         ResolvedReferenceType object = new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver);
332 
333         for (int i=0;i<AIs.size();i++) {
334             ResolvedType Ai = AIs.get(i);
335             ResolvedType Ti = null;
336 
337             // - If Ai is a type, then Ti = Ai.
338 
339             if (!Ai.isWildcard()) {
340                 Ti = Ai;
341             }
342 
343             // - If Ai is a wildcard, and the corresponding type parameter's bound, Bi, mentions one of P1...Pn, then
344             //   Ti is undefined and there is no function type.
345 
346             if (Ti == null && Ai.isWildcard() && Ai.asWildcard().mention(originalType.getTypeDeclaration().getTypeParameters())) {
347                 throw new IllegalArgumentException();
348             }
349 
350             // - Otherwise:
351 
352             if (Ti == null) {
353 
354                 ResolvedType Bi = TPs.get(i).hasLowerBound() ? TPs.get(i).getLowerBound() : object;
355 
356                 //   - If Ai is an unbound wildcard ?, then Ti = Bi.
357 
358                 if (Ai.isWildcard() && !Ai.asWildcard().isBounded()) {
359                     Ti = Bi;
360                 }
361 
362                 //   - If Ai is a upper-bounded wildcard ? extends Ui, then Ti = glb(Ui, Bi) (§5.1.10).
363 
364                 else if (Ai.isWildcard() && Ai.asWildcard().isUpperBounded()) {
365                     ResolvedType Ui = Ai.asWildcard().getBoundedType();
366                     Ti = glb(new HashSet<>(Arrays.asList(Ui, Bi)));
367                 }
368 
369                 //   - If Ai is a lower-bounded wildcard ? super Li, then Ti = Li.
370 
371                 else if (Ai.isWildcard() && Ai.asWildcard().isLowerBounded()) {
372                     Ti = Ai.asWildcard().getBoundedType();
373                 }
374 
375                 else throw new RuntimeException("This should not happen");
376             }
377 
378             TIs.add(Ti);
379         }
380 
381         return new ReferenceTypeImpl(originalType.getTypeDeclaration(), TIs, typeSolver);
382     }
383 
getFunctionType(ResolvedType type)384     public static MethodType getFunctionType(ResolvedType type) {
385         Optional<MethodUsage> mu = FunctionalInterfaceLogic.getFunctionalMethod(type);
386         if (mu.isPresent()) {
387             return MethodType.fromMethodUsage(mu.get());
388         } else {
389             throw new IllegalArgumentException();
390         }
391     }
392 
393     /**
394      * See JLS 5.1.10. Capture Conversion.
395      */
glb(Set<ResolvedType> types)396     public static ResolvedType glb(Set<ResolvedType> types) {
397         if (types.size() == 0) {
398             throw new IllegalArgumentException();
399         }
400         if (types.size() == 1) {
401             return types.iterator().next();
402         }
403         return new ResolvedIntersectionType(types);
404     }
405 }
406