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