• 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             // TODO(b/353979671): We previously called:
254             //     type.getTypeElement().getType().getTypeArguments().isEmpty()
255             // which is a bit more symmetric to the call above, but that resulted in b/353979671, so
256             // we've switched to checking `XTypeElement#getTypeParameters()` until the bug is fixed.
257             && !type.getTypeElement().getTypeParameters().isEmpty();
258       case KSP:
259         return isDeclared(type)
260             // TODO(b/245619245): Due to the bug in XProcessing, the logic used for Javac won't work
261             // since XType#getTypeArguments() does not return an empty list for java raw types.
262             // However, the type name seems to get it correct, so we compare the typename to the raw
263             // typename until this bug is fixed.
264             && type.getRawType() != null
265             && type.getTypeName().equals(type.getRawType().getTypeName())
266             && !type.getTypeElement().getType().getTypeArguments().isEmpty();
267     }
268     throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
269   }
270 
271   /** Returns the given {@code type} as an {@link XArrayType}. */
asArray(XType type)272   public static XArrayType asArray(XType type) {
273     return (XArrayType) type;
274   }
275 
276   /** Returns the given {@code type} as an {@link XTypeVariableType}. */
asTypeVariable(XType type)277   public static XTypeVariableType asTypeVariable(XType type) {
278     return (XTypeVariableType) type;
279   }
280 
281   /** Returns {@code true} if the raw type of {@code type} is equal to {@code className}. */
isTypeOf(XType type, ClassName className)282   public static boolean isTypeOf(XType type, ClassName className) {
283     return isDeclared(type) && type.getTypeElement().getClassName().equals(className);
284   }
285 
286   /** Returns {@code true} if the given type represents the {@code null} type. */
isNullType(XType type)287   public static boolean isNullType(XType type) {
288     XProcessingEnv.Backend backend = getProcessingEnv(type).getBackend();
289     switch (backend) {
290       case JAVAC: return toJavac(type).getKind().equals(TypeKind.NULL);
291       // AFAICT, there's no way to actually get a "null" type in KSP's model
292       case KSP:
293         return false;
294     }
295     throw new AssertionError("Unexpected backend: " + backend);
296   }
297 
298   /** Returns {@code true} if the given type has no actual type. */
isNoType(XType type)299   public static boolean isNoType(XType type) {
300     return type.isNone() || isVoid(type);
301   }
302 
303   /** Returns {@code true} if the given type is a declared type. */
isWildcard(XType type)304   public static boolean isWildcard(XType type) {
305     XProcessingEnv.Backend backend = getProcessingEnv(type).getBackend();
306     switch (backend) {
307       case JAVAC:
308         // In Javac, check the TypeKind directly. This also avoids a Javac bug (b/242569252) where
309         // calling XType.getTypeName() too early caches an incorrect type name.
310         return toJavac(type).getKind().equals(TypeKind.WILDCARD);
311       case KSP:
312         // TODO(bcorso): Consider representing this as an actual type in XProcessing.
313         return type.getTypeName() instanceof WildcardTypeName;
314     }
315     throw new AssertionError("Unexpected backend: " + backend);
316   }
317 
318   /** Returns {@code true} if the given type is a declared type. */
isDeclared(XType type)319   public static boolean isDeclared(XType type) {
320     // TODO(b/241477426): Due to a bug in XProcessing, array types accidentally get assigned an
321     // invalid XTypeElement, so we check explicitly until this is fixed.
322     // TODO(b/242918001): Due to a bug in XProcessing, wildcard types accidentally get assigned an
323     // invalid XTypeElement, so we check explicitly until this is fixed.
324     return !isWildcard(type) && !isArray(type) && type.getTypeElement() != null;
325   }
326 
327   /** Returns {@code true} if the given type is a type variable. */
isTypeVariable(XType type)328   public static boolean isTypeVariable(XType type) {
329     // TODO(bcorso): Consider representing this as an actual type in XProcessing.
330     return type.getTypeName() instanceof TypeVariableName;
331   }
332 
333   /** Returns {@code true} if {@code type1} is equivalent to {@code type2}. */
areEquivalentTypes(XType type1, XType type2)334   public static boolean areEquivalentTypes(XType type1, XType type2) {
335     return type1.getTypeName().equals(type2.getTypeName());
336   }
337 
338   /** Returns {@code true} if the given type is a primitive type. */
isPrimitive(XType type)339   public static boolean isPrimitive(XType type) {
340     // TODO(bcorso): Consider representing this as an actual type in XProcessing.
341     return type.getTypeName().isPrimitive();
342   }
343 
344   /** Returns {@code true} if the given type has type parameters. */
hasTypeParameters(XType type)345   public static boolean hasTypeParameters(XType type) {
346     return !type.getTypeArguments().isEmpty();
347   }
348 
isMethod(XExecutableType type)349   public static boolean isMethod(XExecutableType type) {
350     return type instanceof XMethodType;
351   }
352 
isConstructor(XExecutableType type)353   public static boolean isConstructor(XExecutableType type) {
354     return type instanceof XConstructorType;
355   }
356 
isFloat(XType type)357   public static boolean isFloat(XType type) {
358     return type.getTypeName().equals(TypeName.FLOAT)
359         || type.getTypeName().equals(KnownTypeNames.BOXED_FLOAT);
360   }
361 
isShort(XType type)362   public static boolean isShort(XType type) {
363     return type.getTypeName().equals(TypeName.SHORT)
364         || type.getTypeName().equals(KnownTypeNames.BOXED_SHORT);
365   }
366 
isChar(XType type)367   public static boolean isChar(XType type) {
368     return type.getTypeName().equals(TypeName.CHAR)
369         || type.getTypeName().equals(KnownTypeNames.BOXED_CHAR);
370   }
371 
isDouble(XType type)372   public static boolean isDouble(XType type) {
373     return type.getTypeName().equals(TypeName.DOUBLE)
374         || type.getTypeName().equals(KnownTypeNames.BOXED_DOUBLE);
375   }
376 
isBoolean(XType type)377   public static boolean isBoolean(XType type) {
378     return type.getTypeName().equals(TypeName.BOOLEAN)
379         || type.getTypeName().equals(KnownTypeNames.BOXED_BOOLEAN);
380   }
381 
382   private static class KnownTypeNames {
383     static final TypeName BOXED_SHORT = TypeName.SHORT.box();
384     static final TypeName BOXED_DOUBLE = TypeName.DOUBLE.box();
385     static final TypeName BOXED_FLOAT = TypeName.FLOAT.box();
386     static final TypeName BOXED_CHAR = TypeName.CHAR.box();
387     static final TypeName BOXED_BOOLEAN = TypeName.BOOLEAN.box();
388   }
389 
390   /**
391    * Returns the non-{@link Object} superclass of the type with the proper type parameters. An empty
392    * {@link Optional} is returned if there is no non-{@link Object} superclass.
393    */
nonObjectSuperclass(XType type)394   public static Optional<XType> nonObjectSuperclass(XType type) {
395     if (!isDeclared(type)) {
396       return Optional.empty();
397     }
398     // We compare elements (rather than TypeName) here because its more efficient on the heap.
399     XTypeElement objectElement = objectElement(getProcessingEnv(type));
400     XTypeElement typeElement = type.getTypeElement();
401     if (!typeElement.isClass() || typeElement.equals(objectElement)) {
402       return Optional.empty();
403     }
404     XType superClass = typeElement.getSuperClass();
405     if (!isDeclared(superClass)) {
406       return Optional.empty();
407     }
408     XTypeElement superClassElement = superClass.getTypeElement();
409     if (!superClassElement.isClass() || superClassElement.equals(objectElement)) {
410       return Optional.empty();
411     }
412     // TODO(b/310954522): XType#getSuperTypes() is less efficient (especially on the heap) as it
413     // requires creating XType for not just superclass but all super interfaces as well, so we go
414     // through a bit of effort here to avoid that call unless its absolutely necessary since
415     // nonObjectSuperclass is called quite a bit via InjectionSiteFactory. However, we should
416     // eventually optimize this on the XProcessing side instead, e.g. maybe separating
417     // XType#getSuperClass() into a separate method.
418     return superClass.getTypeArguments().isEmpty()
419         ? Optional.of(superClass)
420         : type.getSuperTypes().stream()
421             .filter(XTypes::isDeclared)
422             .filter(supertype -> supertype.getTypeElement().isClass())
423             .filter(supertype -> !supertype.getTypeElement().equals(objectElement))
424             .collect(toOptional());
425   }
426 
objectElement(XProcessingEnv processingEnv)427   private static XTypeElement objectElement(XProcessingEnv processingEnv) {
428     switch (processingEnv.getBackend()) {
429       case JAVAC:
430         return processingEnv.requireTypeElement(TypeName.OBJECT);
431       case KSP:
432         return processingEnv.requireTypeElement("kotlin.Any");
433     }
434     throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
435   }
436 
437   /**
438    * Returns {@code type}'s single type argument.
439    *
440    * <p>For example, if {@code type} is {@code List<Number>} this will return {@code Number}.
441    *
442    * @throws IllegalArgumentException if {@code type} is not a declared type or has zero or more
443    *     than one type arguments.
444    */
unwrapType(XType type)445   public static XType unwrapType(XType type) {
446     XType unwrapped = unwrapTypeOrDefault(type, null);
447     checkArgument(unwrapped != null, "%s is a raw type", type);
448     return unwrapped;
449   }
450 
unwrapTypeOrDefault(XType type, XType defaultType)451   private static XType unwrapTypeOrDefault(XType type, XType defaultType) {
452     // Check the type parameters of the element's XType since the input XType could be raw.
453     checkArgument(isDeclared(type));
454     XTypeElement typeElement = type.getTypeElement();
455     checkArgument(
456         typeElement.getType().getTypeArguments().size() == 1,
457         "%s does not have exactly 1 type parameter. Found: %s",
458         typeElement.getQualifiedName(),
459         typeElement.getType().getTypeArguments());
460     return getOnlyElement(type.getTypeArguments(), defaultType);
461   }
462 
463   /**
464    * Returns {@code type}'s single type argument wrapped in {@code wrappingClass}.
465    *
466    * <p>For example, if {@code type} is {@code List<Number>} and {@code wrappingClass} is {@code
467    * Set.class}, this will return {@code Set<Number>}.
468    *
469    * <p>If {@code type} has no type parameters, returns a {@link XType} for {@code wrappingClass} as
470    * a raw type.
471    *
472    * @throws IllegalArgumentException if {@code} has more than one type argument.
473    */
rewrapType(XType type, ClassName wrappingClassName)474   public static XType rewrapType(XType type, ClassName wrappingClassName) {
475     XProcessingEnv processingEnv = getProcessingEnv(type);
476     XTypeElement wrappingType = processingEnv.requireTypeElement(wrappingClassName.canonicalName());
477     switch (type.getTypeArguments().size()) {
478       case 0:
479         return processingEnv.getDeclaredType(wrappingType);
480       case 1:
481         return processingEnv.getDeclaredType(wrappingType, getOnlyElement(type.getTypeArguments()));
482       default:
483         throw new IllegalArgumentException(type + " has more than 1 type argument");
484     }
485   }
486 
487   /**
488    * Returns a string representation of {@link XType} that is independent of the backend
489    * (javac/ksp).
490    */
491   // TODO(b/241141586): Replace this with TypeName.toString(). Technically, TypeName.toString()
492   // should already be independent of the backend but we supply our own custom implementation to
493   // remain backwards compatible with the previous implementation, which used TypeMirror#toString().
toStableString(XType type)494   public static String toStableString(XType type) {
495     try {
496       return toStableString(type.getTypeName());
497     } catch (TypeNotPresentException e) {
498       return e.typeName();
499     }
500   }
501 
toStableString(TypeName typeName)502   private static String toStableString(TypeName typeName) {
503     if (typeName instanceof ClassName) {
504       return ((ClassName) typeName).canonicalName();
505     } else if (typeName instanceof ArrayTypeName) {
506       return String.format(
507           "%s[]", toStableString(((ArrayTypeName) typeName).componentType));
508     } else if (typeName instanceof ParameterizedTypeName) {
509       ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
510       return String.format(
511           "%s<%s>",
512           parameterizedTypeName.rawType,
513           parameterizedTypeName.typeArguments.stream()
514               .map(XTypes::toStableString)
515               // We purposely don't use a space after the comma to for backwards compatibility with
516               // usages that depended on the previous TypeMirror#toString() implementation.
517               .collect(joining(",")));
518     } else if (typeName instanceof WildcardTypeName) {
519       WildcardTypeName wildcardTypeName = (WildcardTypeName) typeName;
520       // Wildcard types have exactly 1 upper bound.
521       TypeName upperBound = getOnlyElement(wildcardTypeName.upperBounds);
522       if (!upperBound.equals(TypeName.OBJECT)) {
523         // Wildcards with non-Object upper bounds can't have lower bounds.
524         checkState(wildcardTypeName.lowerBounds.isEmpty());
525         return String.format("? extends %s", toStableString(upperBound));
526       }
527       if (!wildcardTypeName.lowerBounds.isEmpty()) {
528         // Wildcard types can have at most 1 lower bound.
529         TypeName lowerBound = getOnlyElement(wildcardTypeName.lowerBounds);
530         return String.format("? super %s", toStableString(lowerBound));
531       }
532       // If the upper bound is Object and there is no lower bound then just use "?".
533       return "?";
534     } else if (typeName instanceof TypeVariableName) {
535       return ((TypeVariableName) typeName).name;
536     } else {
537       // For all other types (e.g. primitive types) just use the TypeName's toString()
538       return typeName.toString();
539     }
540   }
541 
getKindName(XType type)542   public static String getKindName(XType type) {
543     if (isArray(type)) {
544       return "ARRAY";
545     } else if (isWildcard(type)) {
546       return "WILDCARD";
547     } else if (isTypeVariable(type)) {
548       return "TYPEVAR";
549     } else if (isVoid(type)) {
550       return "VOID";
551     } else if (isNullType(type)) {
552       return "NULL";
553     } else if (isNoType(type)) {
554       return "NONE";
555     } else if (isPrimitive(type)) {
556       return LOWER_CAMEL.to(UPPER_UNDERSCORE, type.getTypeName().toString());
557     } else if (type.isError()) {
558       // TODO(b/249801446): For now, we must call XType.isError() after the other checks because
559       // some types in KSP (e.g. Wildcard) are not disjoint from error types and may return true
560       // until this bug is fixed.
561       // Note: Most of these types are disjoint, so ordering doesn't matter. However, error type is
562       // a subtype of declared type so make sure we check isError() before isDeclared() so that
563       // error types are reported as ERROR rather than DECLARED.
564       return "ERROR";
565     } else if (isDeclared(type)) {
566       return "DECLARED";
567     } else {
568       return "UNKNOWN";
569     }
570   }
571 
572   /**
573    * Iterates through the various types referenced within the given {@code type} and resolves it
574    * if needed.
575    */
resolveIfNeeded(XType type)576   public static void resolveIfNeeded(XType type) {
577     if (getProcessingEnv(type).getBackend() == XProcessingEnv.Backend.JAVAC) {
578       // TODO(b/242569252): Due to a bug in javac, a TypeName may incorrectly contain a "$" instead
579       // of "." if the TypeName is requested before the type has been resolved. Thus, we try to
580       // resolve the type by calling Element#getKind() to force the correct TypeName.
581       toJavac(type).accept(TypeResolutionVisitor.INSTANCE, new HashSet<>());
582     }
583   }
584 
585   private static final class TypeResolutionVisitor extends SimpleTypeVisitor8<Void, Set<Element>> {
586     static final TypeResolutionVisitor INSTANCE = new TypeResolutionVisitor();
587 
588     @Override
visitDeclared(DeclaredType t, Set<Element> visited)589     public Void visitDeclared(DeclaredType t, Set<Element> visited) {
590       if (!visited.add(t.asElement())) {
591         return null;
592       }
593       if (MoreElements.asType(t.asElement()).getQualifiedName().toString().contains("$")) {
594         // Force symbol completion/resolution on the type by calling Element#getKind().
595         t.asElement().getKind();
596       }
597       t.getTypeArguments().forEach(arg -> arg.accept(this, visited));
598       return null;
599     }
600 
601     @Override
visitError(ErrorType t, Set<Element> visited)602     public Void visitError(ErrorType t, Set<Element> visited) {
603       visitDeclared(t, visited);
604       return null;
605     }
606 
607     @Override
visitArray(ArrayType t, Set<Element> visited)608     public Void visitArray(ArrayType t, Set<Element> visited) {
609       t.getComponentType().accept(this, visited);
610       return null;
611     }
612 
613     @Override
visitWildcard(WildcardType t, Set<Element> visited)614     public Void visitWildcard(WildcardType t, Set<Element> visited) {
615       if (t.getExtendsBound() != null) {
616         t.getExtendsBound().accept(this, visited);
617       }
618       if (t.getSuperBound() != null) {
619         t.getSuperBound().accept(this, visited);
620       }
621       return null;
622     }
623 
624     @Override
defaultAction(TypeMirror e, Set<Element> visited)625     protected Void defaultAction(TypeMirror e, Set<Element> visited) {
626       return null;
627     }
628   }
629 
XTypes()630   private XTypes() {}
631 }
632