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