• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Dagger Authors.
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 
17 package dagger.internal.codegen.xprocessing;
18 
19 import static androidx.room.compiler.processing.XTypeKt.isArray;
20 import static androidx.room.compiler.processing.XTypeKt.isVoid;
21 import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
22 import static androidx.room.compiler.processing.compat.XConverters.toJavac;
23 import static androidx.room.compiler.processing.compat.XConverters.toXProcessing;
24 import static com.google.auto.common.MoreTypes.asDeclared;
25 import static com.google.common.base.CaseFormat.LOWER_CAMEL;
26 import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
27 import static com.google.common.base.Preconditions.checkArgument;
28 import static com.google.common.base.Preconditions.checkState;
29 import static com.google.common.collect.Iterables.getOnlyElement;
30 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
31 import static dagger.internal.codegen.xprocessing.XTypes.asArray;
32 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
33 import static dagger.internal.codegen.xprocessing.XTypes.isNoType;
34 import static java.util.stream.Collectors.joining;
35 
36 import androidx.room.compiler.processing.XArrayType;
37 import androidx.room.compiler.processing.XConstructorType;
38 import androidx.room.compiler.processing.XExecutableType;
39 import androidx.room.compiler.processing.XMethodType;
40 import androidx.room.compiler.processing.XProcessingEnv;
41 import androidx.room.compiler.processing.XType;
42 import androidx.room.compiler.processing.XTypeElement;
43 import androidx.room.compiler.processing.XTypeVariableType;
44 import com.google.auto.common.MoreElements;
45 import com.google.common.base.Equivalence;
46 import com.squareup.javapoet.ArrayTypeName;
47 import com.squareup.javapoet.ClassName;
48 import com.squareup.javapoet.ParameterizedTypeName;
49 import com.squareup.javapoet.TypeName;
50 import com.squareup.javapoet.TypeVariableName;
51 import com.squareup.javapoet.WildcardTypeName;
52 import java.util.HashSet;
53 import java.util.Optional;
54 import java.util.Set;
55 import javax.lang.model.element.Element;
56 import javax.lang.model.type.ArrayType;
57 import javax.lang.model.type.DeclaredType;
58 import javax.lang.model.type.ErrorType;
59 import javax.lang.model.type.TypeKind;
60 import javax.lang.model.type.TypeMirror;
61 import javax.lang.model.type.WildcardType;
62 import javax.lang.model.util.SimpleTypeVisitor8;
63 
64 // TODO(bcorso): Consider moving these methods into XProcessing library.
65 /** A utility class for {@link XType} helper methods. */
66 public final class XTypes {
67   private static class XTypeEquivalence extends Equivalence<XType> {
68     private final boolean ignoreVariance;
69 
XTypeEquivalence(boolean ignoreVariance)70     XTypeEquivalence(boolean ignoreVariance) {
71       this.ignoreVariance = ignoreVariance;
72     }
73 
74     @Override
doEquivalent(XType left, XType right)75     protected boolean doEquivalent(XType left, XType right) {
76       return getTypeName(left).equals(getTypeName(right));
77     }
78 
79     @Override
doHash(XType type)80     protected int doHash(XType type) {
81       return getTypeName(type).hashCode();
82     }
83 
84     @Override
toString()85     public String toString() {
86       return "XTypes.equivalence()";
87     }
88 
getTypeName(XType type)89     private TypeName getTypeName(XType type) {
90       return ignoreVariance ? stripVariances(type.getTypeName()) : type.getTypeName();
91     }
92   }
93 
stripVariances(TypeName typeName)94   public static TypeName stripVariances(TypeName typeName) {
95     if (typeName instanceof WildcardTypeName) {
96       WildcardTypeName wildcardTypeName = (WildcardTypeName) typeName;
97       if (!wildcardTypeName.lowerBounds.isEmpty()) {
98         return stripVariances(getOnlyElement(wildcardTypeName.lowerBounds));
99       } else if (!wildcardTypeName.upperBounds.isEmpty()) {
100         return stripVariances(getOnlyElement(wildcardTypeName.upperBounds));
101       }
102     } else if (typeName instanceof ArrayTypeName) {
103       ArrayTypeName arrayTypeName = (ArrayTypeName) typeName;
104       return ArrayTypeName.of(stripVariances(arrayTypeName.componentType));
105     } else if (typeName instanceof ParameterizedTypeName) {
106       ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
107       if (parameterizedTypeName.typeArguments.isEmpty()) {
108         return parameterizedTypeName;
109       } else {
110         return ParameterizedTypeName.get(
111             parameterizedTypeName.rawType,
112             parameterizedTypeName.typeArguments.stream()
113                 .map(XTypes::stripVariances)
114                 .toArray(TypeName[]::new));
115       }
116     }
117     return typeName;
118   }
119 
120   private static final Equivalence<XType> XTYPE_EQUIVALENCE_IGNORING_VARIANCE =
121       new XTypeEquivalence(/* ignoreVariance= */ true);
122 
123   /**
124    * Returns an {@link Equivalence} for {@link XType} based on the {@link TypeName} with variances
125    * ignored (e.g. {@code Foo<? extends Bar>} would be equivalent to {@code Foo<Bar>}).
126    *
127    * <p>Currently, this equivalence does not take into account nullability, as it just relies on
128    * JavaPoet's {@link TypeName}. Thus, two types with the same type name but different nullability
129    * are equal with this equivalence.
130    */
equivalenceIgnoringVariance()131   public static Equivalence<XType> equivalenceIgnoringVariance() {
132     return XTYPE_EQUIVALENCE_IGNORING_VARIANCE;
133   }
134 
135   private static final Equivalence<XType> XTYPE_EQUIVALENCE =
136       new XTypeEquivalence(/* ignoreVariance= */ false);
137 
138   /**
139    * Returns an {@link Equivalence} for {@link XType} based on the {@link TypeName}.
140    *
141    * <p>Currently, this equivalence does not take into account nullability, as it just relies on
142    * JavaPoet's {@link TypeName}. Thus, two types with the same type name but different nullability
143    * are equal with this equivalence.
144    */
equivalence()145   public static Equivalence<XType> equivalence() {
146     return XTYPE_EQUIVALENCE;
147   }
148 
149   // TODO(bcorso): Support XType.getEnclosingType() properly in XProcessing.
150   @SuppressWarnings("ReturnMissingNullable")
getEnclosingType(XType type)151   public static XType getEnclosingType(XType type) {
152     checkArgument(isDeclared(type));
153     XProcessingEnv.Backend backend = getProcessingEnv(type).getBackend();
154     switch (backend) {
155       case JAVAC:
156         return toXProcessing(asDeclared(toJavac(type)).getEnclosingType(), getProcessingEnv(type));
157       case KSP:
158         // For now, just return the enclosing type of the XTypeElement, which for most cases is good
159         // enough. This may be incorrect in some rare cases (not tested), e.g. if Outer.Inner<T>
160         // inherits its type parameter from Outer<T> then the enclosing type of Outer.Inner<Foo>
161         // should be Outer<Foo> rather than Outer<T>, as we would get from the code below.
162         XTypeElement enclosingTypeElement = type.getTypeElement().getEnclosingTypeElement();
163         return enclosingTypeElement == null ? null : enclosingTypeElement.getType();
164     }
165     throw new AssertionError("Unexpected backend: " + backend);
166   }
167 
168   /** Returns {@code true} if and only if the {@code type1} is assignable to {@code type2}. */
isAssignableTo(XType type1, XType type2)169   public static boolean isAssignableTo(XType type1, XType type2) {
170     return type2.isAssignableFrom(type1);
171   }
172 
173   /** Returns {@code true} if {@code type1} is a subtype of {@code type2}. */
isSubtype(XType type1, XType type2)174   public static boolean isSubtype(XType type1, XType type2) {
175     XProcessingEnv processingEnv = getProcessingEnv(type1);
176     switch (processingEnv.getBackend()) {
177       case JAVAC:
178         // The implementation used for KSP should technically also work in Javac but we avoid it to
179         // avoid any possible regressions in Javac.
180         return toJavac(processingEnv)
181             .getTypeUtils() // ALLOW_TYPES_ELEMENTS
182             .isSubtype(toJavac(type1), toJavac(type2));
183       case KSP:
184         if (isPrimitive(type1) || isPrimitive(type2)) {
185             // For primitive types we can't just check isAssignableTo since auto-boxing means boxed
186             // types are assignable to primitive (and vice versa) though neither are subtypes.
187             return type1.isSameType(type2);
188         }
189         return isAssignableTo(type1, type2);
190     }
191     throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
192   }
193 
194   /** Returns the erasure of the given {@link TypeName}. */
erasedTypeName(XType type)195   public static TypeName erasedTypeName(XType type) {
196     XProcessingEnv processingEnv = getProcessingEnv(type);
197     switch (processingEnv.getBackend()) {
198       case JAVAC:
199         // The implementation used for KSP should technically also work in Javac but we avoid it to
200         // avoid any possible regressions in Javac.
201         return toXProcessing(
202                 toJavac(processingEnv).getTypeUtils() // ALLOW_TYPES_ELEMENTS
203                     .erasure(toJavac(type)),
204                 processingEnv)
205             .getTypeName();
206       case KSP:
207         // In KSP, we have to derive the erased TypeName ourselves.
208         return erasedTypeName(type.getTypeName());
209     }
210     throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
211   }
212 
erasedTypeName(TypeName typeName)213   private static TypeName erasedTypeName(TypeName typeName) {
214     // See https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.6
215     if (typeName instanceof ArrayTypeName) {
216       // Erasure of 'C[]' is '|C|[]'
217       return ArrayTypeName.of(erasedTypeName(((ArrayTypeName) typeName).componentType));
218     } else if (typeName instanceof ParameterizedTypeName) {
219       // Erasure of 'C<T1, T2, ...>' is '|C|'
220       // Erasure of nested type T.C is |T|.C
221       // Nested types, e.g. Foo<String>.Bar, are also represented as ParameterizedTypeName and
222       // calling ParameterizedTypeName.rawType gives the correct result, e.g. Foo.Bar.
223       return ((ParameterizedTypeName) typeName).rawType;
224     } else if (typeName instanceof TypeVariableName) {
225       // Erasure of type variable is the erasure of its left-most bound
226       return erasedTypeName(((TypeVariableName) typeName).bounds.get(0));
227     }
228     // For every other type, the erasure is the type itself.
229     return typeName;
230   }
231 
232   /**
233    * Throws {@link TypeNotPresentException} if {@code type} is an {@link
234    * javax.lang.model.type.ErrorType}.
235    */
checkTypePresent(XType type)236   public static void checkTypePresent(XType type) {
237     if (isArray(type)) {
238       checkTypePresent(asArray(type).getComponentType());
239     } else if (isDeclared(type)) {
240       type.getTypeArguments().forEach(XTypes::checkTypePresent);
241     } else if (type.isError()) {
242       throw new TypeNotPresentException(type.toString(), null);
243     }
244   }
245 
246   /** Returns {@code true} if the given type is a raw type of a parameterized type. */
isRawParameterizedType(XType type)247   public static boolean isRawParameterizedType(XType type) {
248     XProcessingEnv processingEnv = getProcessingEnv(type);
249     switch (processingEnv.getBackend()) {
250       case JAVAC:
251         return isDeclared(type)
252             && type.getTypeArguments().isEmpty()
253             && !type.getTypeElement().getType().getTypeArguments().isEmpty();
254       case KSP:
255         return isDeclared(type)
256             // TODO(b/245619245): Due to the bug in XProcessing, the logic used for Javac won't work
257             // since XType#getTypeArguments() does not return an empty list for java raw types.
258             // However, the type name seems to get it correct, so we compare the typename to the raw
259             // typename until this bug is fixed.
260             && type.getRawType() != null
261             && type.getTypeName().equals(type.getRawType().getTypeName())
262             && !type.getTypeElement().getType().getTypeArguments().isEmpty();
263     }
264     throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
265   }
266 
267   /** Returns the given {@code type} as an {@link XArrayType}. */
asArray(XType type)268   public static XArrayType asArray(XType type) {
269     return (XArrayType) type;
270   }
271 
272   /** Returns the given {@code type} as an {@link XTypeVariableType}. */
asTypeVariable(XType type)273   public static XTypeVariableType asTypeVariable(XType type) {
274     return (XTypeVariableType) type;
275   }
276 
277   /** Returns {@code true} if the raw type of {@code type} is equal to {@code className}. */
isTypeOf(XType type, ClassName className)278   public static boolean isTypeOf(XType type, ClassName className) {
279     return isDeclared(type) && type.getTypeElement().getClassName().equals(className);
280   }
281 
282   /** Returns {@code true} if the given type represents the {@code null} type. */
isNullType(XType type)283   public static boolean isNullType(XType type) {
284     XProcessingEnv.Backend backend = getProcessingEnv(type).getBackend();
285     switch (backend) {
286       case JAVAC: return toJavac(type).getKind().equals(TypeKind.NULL);
287       // AFAICT, there's no way to actually get a "null" type in KSP's model
288       case KSP:
289         return false;
290     }
291     throw new AssertionError("Unexpected backend: " + backend);
292   }
293 
294   /** Returns {@code true} if the given type has no actual type. */
isNoType(XType type)295   public static boolean isNoType(XType type) {
296     return type.isNone() || isVoid(type);
297   }
298 
299   /** Returns {@code true} if the given type is a declared type. */
isWildcard(XType type)300   public static boolean isWildcard(XType type) {
301     XProcessingEnv.Backend backend = getProcessingEnv(type).getBackend();
302     switch (backend) {
303       case JAVAC:
304         // In Javac, check the TypeKind directly. This also avoids a Javac bug (b/242569252) where
305         // calling XType.getTypeName() too early caches an incorrect type name.
306         return toJavac(type).getKind().equals(TypeKind.WILDCARD);
307       case KSP:
308         // TODO(bcorso): Consider representing this as an actual type in XProcessing.
309         return type.getTypeName() instanceof WildcardTypeName;
310     }
311     throw new AssertionError("Unexpected backend: " + backend);
312   }
313 
314   /** Returns {@code true} if the given type is a declared type. */
isDeclared(XType type)315   public static boolean isDeclared(XType type) {
316     // TODO(b/241477426): Due to a bug in XProcessing, array types accidentally get assigned an
317     // invalid XTypeElement, so we check explicitly until this is fixed.
318     // TODO(b/242918001): Due to a bug in XProcessing, wildcard types accidentally get assigned an
319     // invalid XTypeElement, so we check explicitly until this is fixed.
320     return !isWildcard(type) && !isArray(type) && type.getTypeElement() != null;
321   }
322 
323   /** Returns {@code true} if the given type is a type variable. */
isTypeVariable(XType type)324   public static boolean isTypeVariable(XType type) {
325     // TODO(bcorso): Consider representing this as an actual type in XProcessing.
326     return type.getTypeName() instanceof TypeVariableName;
327   }
328 
329   /** Returns {@code true} if {@code type1} is equivalent to {@code type2}. */
areEquivalentTypes(XType type1, XType type2)330   public static boolean areEquivalentTypes(XType type1, XType type2) {
331     return type1.getTypeName().equals(type2.getTypeName());
332   }
333 
334   /** Returns {@code true} if the given type is a primitive type. */
isPrimitive(XType type)335   public static boolean isPrimitive(XType type) {
336     // TODO(bcorso): Consider representing this as an actual type in XProcessing.
337     return type.getTypeName().isPrimitive();
338   }
339 
340   /** Returns {@code true} if the given type has type parameters. */
hasTypeParameters(XType type)341   public static boolean hasTypeParameters(XType type) {
342     return !type.getTypeArguments().isEmpty();
343   }
344 
isMethod(XExecutableType type)345   public static boolean isMethod(XExecutableType type) {
346     return type instanceof XMethodType;
347   }
348 
isConstructor(XExecutableType type)349   public static boolean isConstructor(XExecutableType type) {
350     return type instanceof XConstructorType;
351   }
352 
isFloat(XType type)353   public static boolean isFloat(XType type) {
354     return type.getTypeName().equals(TypeName.FLOAT)
355         || type.getTypeName().equals(KnownTypeNames.BOXED_FLOAT);
356   }
357 
isShort(XType type)358   public static boolean isShort(XType type) {
359     return type.getTypeName().equals(TypeName.SHORT)
360         || type.getTypeName().equals(KnownTypeNames.BOXED_SHORT);
361   }
362 
isChar(XType type)363   public static boolean isChar(XType type) {
364     return type.getTypeName().equals(TypeName.CHAR)
365         || type.getTypeName().equals(KnownTypeNames.BOXED_CHAR);
366   }
367 
isDouble(XType type)368   public static boolean isDouble(XType type) {
369     return type.getTypeName().equals(TypeName.DOUBLE)
370         || type.getTypeName().equals(KnownTypeNames.BOXED_DOUBLE);
371   }
372 
isBoolean(XType type)373   public static boolean isBoolean(XType type) {
374     return type.getTypeName().equals(TypeName.BOOLEAN)
375         || type.getTypeName().equals(KnownTypeNames.BOXED_BOOLEAN);
376   }
377 
378   private static class KnownTypeNames {
379     static final TypeName BOXED_SHORT = TypeName.SHORT.box();
380     static final TypeName BOXED_DOUBLE = TypeName.DOUBLE.box();
381     static final TypeName BOXED_FLOAT = TypeName.FLOAT.box();
382     static final TypeName BOXED_CHAR = TypeName.CHAR.box();
383     static final TypeName BOXED_BOOLEAN = TypeName.BOOLEAN.box();
384   }
385 
386   /**
387    * Returns the non-{@link Object} superclass of the type with the proper type parameters. An empty
388    * {@link Optional} is returned if there is no non-{@link Object} superclass.
389    */
nonObjectSuperclass(XType type)390   public static Optional<XType> nonObjectSuperclass(XType type) {
391     if (!isDeclared(type)) {
392       return Optional.empty();
393     }
394     // We compare elements (rather than TypeName) here because its more efficient on the heap.
395     XTypeElement objectElement = objectElement(getProcessingEnv(type));
396     XTypeElement typeElement = type.getTypeElement();
397     if (!typeElement.isClass() || typeElement.equals(objectElement)) {
398       return Optional.empty();
399     }
400     XType superClass = typeElement.getSuperClass();
401     if (!isDeclared(superClass)) {
402       return Optional.empty();
403     }
404     XTypeElement superClassElement = superClass.getTypeElement();
405     if (!superClassElement.isClass() || superClassElement.equals(objectElement)) {
406       return Optional.empty();
407     }
408     // TODO(b/310954522): XType#getSuperTypes() is less efficient (especially on the heap) as it
409     // requires creating XType for not just superclass but all super interfaces as well, so we go
410     // through a bit of effort here to avoid that call unless its absolutely necessary since
411     // nonObjectSuperclass is called quite a bit via InjectionSiteFactory. However, we should
412     // eventually optimize this on the XProcessing side instead, e.g. maybe separating
413     // XType#getSuperClass() into a separate method.
414     return superClass.getTypeArguments().isEmpty()
415         ? Optional.of(superClass)
416         : type.getSuperTypes().stream()
417             .filter(XTypes::isDeclared)
418             .filter(supertype -> supertype.getTypeElement().isClass())
419             .filter(supertype -> !supertype.getTypeElement().equals(objectElement))
420             .collect(toOptional());
421   }
422 
objectElement(XProcessingEnv processingEnv)423   private static XTypeElement objectElement(XProcessingEnv processingEnv) {
424     switch (processingEnv.getBackend()) {
425       case JAVAC:
426         return processingEnv.requireTypeElement(TypeName.OBJECT);
427       case KSP:
428         return processingEnv.requireTypeElement("kotlin.Any");
429     }
430     throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
431   }
432 
433   /**
434    * Returns {@code type}'s single type argument.
435    *
436    * <p>For example, if {@code type} is {@code List<Number>} this will return {@code Number}.
437    *
438    * @throws IllegalArgumentException if {@code type} is not a declared type or has zero or more
439    *     than one type arguments.
440    */
unwrapType(XType type)441   public static XType unwrapType(XType type) {
442     XType unwrapped = unwrapTypeOrDefault(type, null);
443     checkArgument(unwrapped != null, "%s is a raw type", type);
444     return unwrapped;
445   }
446 
unwrapTypeOrDefault(XType type, XType defaultType)447   private static XType unwrapTypeOrDefault(XType type, XType defaultType) {
448     // Check the type parameters of the element's XType since the input XType could be raw.
449     checkArgument(isDeclared(type));
450     XTypeElement typeElement = type.getTypeElement();
451     checkArgument(
452         typeElement.getType().getTypeArguments().size() == 1,
453         "%s does not have exactly 1 type parameter. Found: %s",
454         typeElement.getQualifiedName(),
455         typeElement.getType().getTypeArguments());
456     return getOnlyElement(type.getTypeArguments(), defaultType);
457   }
458 
459   /**
460    * Returns {@code type}'s single type argument wrapped in {@code wrappingClass}.
461    *
462    * <p>For example, if {@code type} is {@code List<Number>} and {@code wrappingClass} is {@code
463    * Set.class}, this will return {@code Set<Number>}.
464    *
465    * <p>If {@code type} has no type parameters, returns a {@link XType} for {@code wrappingClass} as
466    * a raw type.
467    *
468    * @throws IllegalArgumentException if {@code} has more than one type argument.
469    */
rewrapType(XType type, ClassName wrappingClassName)470   public static XType rewrapType(XType type, ClassName wrappingClassName) {
471     XProcessingEnv processingEnv = getProcessingEnv(type);
472     XTypeElement wrappingType = processingEnv.requireTypeElement(wrappingClassName.canonicalName());
473     switch (type.getTypeArguments().size()) {
474       case 0:
475         return processingEnv.getDeclaredType(wrappingType);
476       case 1:
477         return processingEnv.getDeclaredType(wrappingType, getOnlyElement(type.getTypeArguments()));
478       default:
479         throw new IllegalArgumentException(type + " has more than 1 type argument");
480     }
481   }
482 
483   /**
484    * Returns a string representation of {@link XType} that is independent of the backend
485    * (javac/ksp).
486    */
487   // TODO(b/241141586): Replace this with TypeName.toString(). Technically, TypeName.toString()
488   // should already be independent of the backend but we supply our own custom implementation to
489   // remain backwards compatible with the previous implementation, which used TypeMirror#toString().
toStableString(XType type)490   public static String toStableString(XType type) {
491     try {
492       return toStableString(type.getTypeName());
493     } catch (TypeNotPresentException e) {
494       return e.typeName();
495     }
496   }
497 
toStableString(TypeName typeName)498   private static String toStableString(TypeName typeName) {
499     if (typeName instanceof ClassName) {
500       return ((ClassName) typeName).canonicalName();
501     } else if (typeName instanceof ArrayTypeName) {
502       return String.format(
503           "%s[]", toStableString(((ArrayTypeName) typeName).componentType));
504     } else if (typeName instanceof ParameterizedTypeName) {
505       ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
506       return String.format(
507           "%s<%s>",
508           parameterizedTypeName.rawType,
509           parameterizedTypeName.typeArguments.stream()
510               .map(XTypes::toStableString)
511               // We purposely don't use a space after the comma to for backwards compatibility with
512               // usages that depended on the previous TypeMirror#toString() implementation.
513               .collect(joining(",")));
514     } else if (typeName instanceof WildcardTypeName) {
515       WildcardTypeName wildcardTypeName = (WildcardTypeName) typeName;
516       // Wildcard types have exactly 1 upper bound.
517       TypeName upperBound = getOnlyElement(wildcardTypeName.upperBounds);
518       if (!upperBound.equals(TypeName.OBJECT)) {
519         // Wildcards with non-Object upper bounds can't have lower bounds.
520         checkState(wildcardTypeName.lowerBounds.isEmpty());
521         return String.format("? extends %s", toStableString(upperBound));
522       }
523       if (!wildcardTypeName.lowerBounds.isEmpty()) {
524         // Wildcard types can have at most 1 lower bound.
525         TypeName lowerBound = getOnlyElement(wildcardTypeName.lowerBounds);
526         return String.format("? super %s", toStableString(lowerBound));
527       }
528       // If the upper bound is Object and there is no lower bound then just use "?".
529       return "?";
530     } else if (typeName instanceof TypeVariableName) {
531       return ((TypeVariableName) typeName).name;
532     } else {
533       // For all other types (e.g. primitive types) just use the TypeName's toString()
534       return typeName.toString();
535     }
536   }
537 
getKindName(XType type)538   public static String getKindName(XType type) {
539     if (isArray(type)) {
540       return "ARRAY";
541     } else if (isWildcard(type)) {
542       return "WILDCARD";
543     } else if (isTypeVariable(type)) {
544       return "TYPEVAR";
545     } else if (isVoid(type)) {
546       return "VOID";
547     } else if (isNullType(type)) {
548       return "NULL";
549     } else if (isNoType(type)) {
550       return "NONE";
551     } else if (isPrimitive(type)) {
552       return LOWER_CAMEL.to(UPPER_UNDERSCORE, type.getTypeName().toString());
553     } else if (type.isError()) {
554       // TODO(b/249801446): For now, we must call XType.isError() after the other checks because
555       // some types in KSP (e.g. Wildcard) are not disjoint from error types and may return true
556       // until this bug is fixed.
557       // Note: Most of these types are disjoint, so ordering doesn't matter. However, error type is
558       // a subtype of declared type so make sure we check isError() before isDeclared() so that
559       // error types are reported as ERROR rather than DECLARED.
560       return "ERROR";
561     } else if (isDeclared(type)) {
562       return "DECLARED";
563     } else {
564       return "UNKNOWN";
565     }
566   }
567 
568   /**
569    * Iterates through the various types referenced within the given {@code type} and resolves it
570    * if needed.
571    */
resolveIfNeeded(XType type)572   public static void resolveIfNeeded(XType type) {
573     if (getProcessingEnv(type).getBackend() == XProcessingEnv.Backend.JAVAC) {
574       // TODO(b/242569252): Due to a bug in javac, a TypeName may incorrectly contain a "$" instead
575       // of "." if the TypeName is requested before the type has been resolved. Thus, we try to
576       // resolve the type by calling Element#getKind() to force the correct TypeName.
577       toJavac(type).accept(TypeResolutionVisitor.INSTANCE, new HashSet<>());
578     }
579   }
580 
581   private static final class TypeResolutionVisitor extends SimpleTypeVisitor8<Void, Set<Element>> {
582     static final TypeResolutionVisitor INSTANCE = new TypeResolutionVisitor();
583 
584     @Override
visitDeclared(DeclaredType t, Set<Element> visited)585     public Void visitDeclared(DeclaredType t, Set<Element> visited) {
586       if (!visited.add(t.asElement())) {
587         return null;
588       }
589       if (MoreElements.asType(t.asElement()).getQualifiedName().toString().contains("$")) {
590         // Force symbol completion/resolution on the type by calling Element#getKind().
591         t.asElement().getKind();
592       }
593       t.getTypeArguments().forEach(arg -> arg.accept(this, visited));
594       return null;
595     }
596 
597     @Override
visitError(ErrorType t, Set<Element> visited)598     public Void visitError(ErrorType t, Set<Element> visited) {
599       visitDeclared(t, visited);
600       return null;
601     }
602 
603     @Override
visitArray(ArrayType t, Set<Element> visited)604     public Void visitArray(ArrayType t, Set<Element> visited) {
605       t.getComponentType().accept(this, visited);
606       return null;
607     }
608 
609     @Override
visitWildcard(WildcardType t, Set<Element> visited)610     public Void visitWildcard(WildcardType t, Set<Element> visited) {
611       if (t.getExtendsBound() != null) {
612         t.getExtendsBound().accept(this, visited);
613       }
614       if (t.getSuperBound() != null) {
615         t.getSuperBound().accept(this, visited);
616       }
617       return null;
618     }
619 
620     @Override
defaultAction(TypeMirror e, Set<Element> visited)621     protected Void defaultAction(TypeMirror e, Set<Element> visited) {
622       return null;
623     }
624   }
625 
XTypes()626   private XTypes() {}
627 }
628