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.XElementKt.isConstructor; 20 import static androidx.room.compiler.processing.XElementKt.isField; 21 import static androidx.room.compiler.processing.XElementKt.isMethod; 22 import static androidx.room.compiler.processing.XElementKt.isMethodParameter; 23 import static androidx.room.compiler.processing.XElementKt.isTypeElement; 24 import static androidx.room.compiler.processing.XElementKt.isVariableElement; 25 import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv; 26 import static androidx.room.compiler.processing.compat.XConverters.toJavac; 27 import static androidx.room.compiler.processing.compat.XConverters.toKS; 28 import static com.google.common.base.Preconditions.checkArgument; 29 import static com.google.common.base.Preconditions.checkState; 30 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 31 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 32 import static java.util.stream.Collectors.joining; 33 34 import androidx.room.compiler.processing.XAnnotated; 35 import androidx.room.compiler.processing.XAnnotation; 36 import androidx.room.compiler.processing.XConstructorElement; 37 import androidx.room.compiler.processing.XElement; 38 import androidx.room.compiler.processing.XEnumEntry; 39 import androidx.room.compiler.processing.XEnumTypeElement; 40 import androidx.room.compiler.processing.XExecutableElement; 41 import androidx.room.compiler.processing.XExecutableParameterElement; 42 import androidx.room.compiler.processing.XFieldElement; 43 import androidx.room.compiler.processing.XHasModifiers; 44 import androidx.room.compiler.processing.XMemberContainer; 45 import androidx.room.compiler.processing.XMethodElement; 46 import androidx.room.compiler.processing.XProcessingEnv; 47 import androidx.room.compiler.processing.XTypeElement; 48 import androidx.room.compiler.processing.XTypeParameterElement; 49 import androidx.room.compiler.processing.XVariableElement; 50 import com.google.common.collect.ImmutableList; 51 import com.google.common.collect.ImmutableSet; 52 import com.google.devtools.ksp.symbol.KSAnnotated; 53 import com.squareup.javapoet.ClassName; 54 import java.util.Collection; 55 import java.util.Optional; 56 import javax.annotation.Nullable; 57 import javax.lang.model.element.ElementKind; 58 import javax.lang.model.element.Modifier; 59 60 // TODO(bcorso): Consider moving these methods into XProcessing library. 61 /** A utility class for {@link XElement} helper methods. */ 62 public final class XElements { 63 64 // TODO(bcorso): Replace usages with getJvmName() once it exists. 65 /** Returns the simple name of the member container. */ getSimpleName(XMemberContainer memberContainer)66 public static String getSimpleName(XMemberContainer memberContainer) { 67 return memberContainer.getClassName().simpleName(); 68 } 69 70 /** Returns the simple name of the element. */ getSimpleName(XElement element)71 public static String getSimpleName(XElement element) { 72 if (isTypeElement(element)) { 73 return asTypeElement(element) 74 .getName(); // SUPPRESS_GET_NAME_CHECK: This uses java simple name implementation under 75 // the hood. 76 } else if (isVariableElement(element)) { 77 return asVariable(element).getName(); // SUPPRESS_GET_NAME_CHECK 78 } else if (isEnumEntry(element)) { 79 return asEnumEntry(element).getName(); // SUPPRESS_GET_NAME_CHECK 80 } else if (isMethod(element)) { 81 // Note: We use "jvm name" here rather than "simple name" because simple name is not reliable 82 // in KAPT. In particular, XProcessing relies on matching the method to its descriptor found 83 // in the Kotlin @Metadata to get the simple name. However, this doesn't work for method 84 // descriptors that contain generated types because the stub and @Metadata will disagree due 85 // to the following bug: 86 // https://youtrack.jetbrains.com/issue/KT-35124/KAPT-not-correcting-error-types-in-Kotlin-Metadata-information-produced-for-the-stubs. 87 // In any case, always using the jvm name should be safe; however, it will unfortunately 88 // contain any obfuscation added by kotlinc, e.g. for "internal" methods, which can make the 89 // "simple name" not as nice/short when used for things like error messages or class names. 90 return asMethod(element).getJvmName(); 91 } else if (isConstructor(element)) { 92 return "<init>"; 93 } else if (isTypeParameter(element)) { 94 return asTypeParameter(element).getName(); // SUPPRESS_GET_NAME_CHECK 95 } 96 throw new AssertionError("No simple name for: " + element); 97 } 98 isSyntheticElement(XElement element)99 private static boolean isSyntheticElement(XElement element) { 100 if (isMethodParameter(element)) { 101 XExecutableParameterElement executableParam = asMethodParameter(element); 102 return executableParam.isContinuationParam() 103 || executableParam.isReceiverParam() 104 || executableParam.isKotlinPropertyParam(); 105 } 106 if (isMethod(element)) { 107 return asMethod(element).isKotlinPropertyMethod(); 108 } 109 return false; 110 } 111 112 @Nullable toKSAnnotated(XElement element)113 public static KSAnnotated toKSAnnotated(XElement element) { 114 if (isSyntheticElement(element)) { 115 return toKS(element); 116 } 117 if (isExecutable(element)) { 118 return toKS(asExecutable(element)); 119 } 120 if (isTypeElement(element)) { 121 return toKS(asTypeElement(element)); 122 } 123 if (isField(element)) { 124 return toKS(asField(element)); 125 } 126 if (isMethodParameter(element)) { 127 return toKS(asMethodParameter(element)); 128 } 129 throw new IllegalStateException( 130 "Returning KSAnnotated declaration for " + element + " is not supported."); 131 } 132 133 /** 134 * Returns the closest enclosing element that is a {@link XTypeElement} or throws an {@link 135 * IllegalStateException} if one doesn't exist. 136 */ closestEnclosingTypeElement(XElement element)137 public static XTypeElement closestEnclosingTypeElement(XElement element) { 138 return optionalClosestEnclosingTypeElement(element) 139 .orElseThrow(() -> new IllegalStateException("No enclosing TypeElement for: " + element)); 140 } 141 142 /** 143 * Returns {@code true} if {@code encloser} is equal to or transitively encloses {@code enclosed}. 144 */ transitivelyEncloses(XElement encloser, XElement enclosed)145 public static boolean transitivelyEncloses(XElement encloser, XElement enclosed) { 146 XElement current = enclosed; 147 while (current != null) { 148 if (current.equals(encloser)) { 149 return true; 150 } 151 current = current.getEnclosingElement(); 152 } 153 return false; 154 } 155 optionalClosestEnclosingTypeElement(XElement element)156 private static Optional<XTypeElement> optionalClosestEnclosingTypeElement(XElement element) { 157 if (isTypeElement(element)) { 158 return Optional.of(asTypeElement(element)); 159 } else if (isConstructor(element)) { 160 return Optional.of(asConstructor(element).getEnclosingElement()); 161 } else if (isMethod(element)) { 162 return optionalClosestEnclosingTypeElement(asMethod(element).getEnclosingElement()); 163 } else if (isField(element)) { 164 return optionalClosestEnclosingTypeElement(asField(element).getEnclosingElement()); 165 } else if (isMethodParameter(element)) { 166 return optionalClosestEnclosingTypeElement(asMethodParameter(element).getEnclosingElement()); 167 } 168 return Optional.empty(); 169 } 170 isAbstract(XElement element)171 public static boolean isAbstract(XElement element) { 172 return asHasModifiers(element).isAbstract(); 173 } 174 isPublic(XElement element)175 public static boolean isPublic(XElement element) { 176 return asHasModifiers(element).isPublic(); 177 } 178 isPrivate(XElement element)179 public static boolean isPrivate(XElement element) { 180 return asHasModifiers(element).isPrivate(); 181 } 182 isStatic(XElement element)183 public static boolean isStatic(XElement element) { 184 return asHasModifiers(element).isStatic(); 185 } 186 187 // TODO(bcorso): Ideally we would modify XElement to extend XHasModifiers to prevent possible 188 // runtime exceptions if the element does not extend XHasModifiers. However, for Dagger's purpose 189 // all usages should be on elements that do extend XHasModifiers, so generalizing this for 190 // XProcessing is probably overkill for now. asHasModifiers(XElement element)191 private static XHasModifiers asHasModifiers(XElement element) { 192 // In javac, Element implements HasModifiers but in XProcessing XElement does not. 193 // Currently, the elements that do not extend XHasModifiers are XMemberContainer, XEnumEntry, 194 // XVariableElement. Though most instances of XMemberContainer will extend XHasModifiers through 195 // XTypeElement instead. 196 checkArgument(element instanceof XHasModifiers, "Element %s does not have modifiers", element); 197 return (XHasModifiers) element; 198 } 199 200 // Note: This method always returns `false` but I'd rather not remove it from our codebase since 201 // if XProcessing adds package elements to their model I'd like to catch it here and fail early. isPackage(XElement element)202 public static boolean isPackage(XElement element) { 203 // Currently, XProcessing doesn't represent package elements so this method always returns 204 // false, but we check the state in Javac just to be sure. There's nothing to check in KSP since 205 // there is no concept of package elements in KSP. 206 if (getProcessingEnv(element).getBackend() == XProcessingEnv.Backend.JAVAC) { 207 checkState(toJavac(element).getKind() != ElementKind.PACKAGE); 208 } 209 return false; 210 } 211 isTypeParameter(XElement element)212 public static boolean isTypeParameter(XElement element) { 213 return element instanceof XTypeParameterElement; 214 } 215 asTypeParameter(XElement element)216 public static XTypeParameterElement asTypeParameter(XElement element) { 217 return (XTypeParameterElement) element; 218 } 219 isEnumEntry(XElement element)220 public static boolean isEnumEntry(XElement element) { 221 return element instanceof XEnumEntry; 222 } 223 isEnum(XElement element)224 public static boolean isEnum(XElement element) { 225 return element instanceof XEnumTypeElement; 226 } 227 isExecutable(XElement element)228 public static boolean isExecutable(XElement element) { 229 return isConstructor(element) || isMethod(element); 230 } 231 asExecutable(XElement element)232 public static XExecutableElement asExecutable(XElement element) { 233 checkState(isExecutable(element)); 234 return (XExecutableElement) element; 235 } 236 asTypeElement(XElement element)237 public static XTypeElement asTypeElement(XElement element) { 238 checkState(isTypeElement(element)); 239 return (XTypeElement) element; 240 } 241 242 // TODO(bcorso): Rename this and the XElementKt.isMethodParameter to isExecutableParameter. asMethodParameter(XElement element)243 public static XExecutableParameterElement asMethodParameter(XElement element) { 244 checkState(isMethodParameter(element)); 245 return (XExecutableParameterElement) element; 246 } 247 asField(XElement element)248 public static XFieldElement asField(XElement element) { 249 checkState(isField(element)); 250 return (XFieldElement) element; 251 } 252 asEnumEntry(XElement element)253 public static XEnumEntry asEnumEntry(XElement element) { 254 return (XEnumEntry) element; 255 } 256 asVariable(XElement element)257 public static XVariableElement asVariable(XElement element) { 258 checkState(isVariableElement(element)); 259 return (XVariableElement) element; 260 } 261 asConstructor(XElement element)262 public static XConstructorElement asConstructor(XElement element) { 263 checkState(isConstructor(element)); 264 return (XConstructorElement) element; 265 } 266 asMethod(XElement element)267 public static XMethodElement asMethod(XElement element) { 268 checkState(isMethod(element)); 269 return (XMethodElement) element; 270 } 271 getAnnotatedAnnotations( XAnnotated annotated, ClassName annotationName)272 public static ImmutableSet<XAnnotation> getAnnotatedAnnotations( 273 XAnnotated annotated, ClassName annotationName) { 274 return annotated.getAllAnnotations().stream() 275 .filter(annotation -> annotation.getType().getTypeElement().hasAnnotation(annotationName)) 276 .collect(toImmutableSet()); 277 } 278 279 /** Returns {@code true} if {@code annotated} is annotated with any of the given annotations. */ hasAnyAnnotation(XAnnotated annotated, ClassName... annotations)280 public static boolean hasAnyAnnotation(XAnnotated annotated, ClassName... annotations) { 281 return hasAnyAnnotation(annotated, ImmutableSet.copyOf(annotations)); 282 } 283 284 /** Returns {@code true} if {@code annotated} is annotated with any of the given annotations. */ hasAnyAnnotation(XAnnotated annotated, Collection<ClassName> annotations)285 public static boolean hasAnyAnnotation(XAnnotated annotated, Collection<ClassName> annotations) { 286 return annotations.stream().anyMatch(annotated::hasAnnotation); 287 } 288 289 /** 290 * Returns any annotation from {@code annotations} that annotates {@code annotated} or else {@code 291 * Optional.empty()}. 292 */ getAnyAnnotation( XAnnotated annotated, ClassName... annotations)293 public static Optional<XAnnotation> getAnyAnnotation( 294 XAnnotated annotated, ClassName... annotations) { 295 return getAnyAnnotation(annotated, ImmutableSet.copyOf(annotations)); 296 } 297 298 /** 299 * Returns any annotation from {@code annotations} that annotates {@code annotated} or else {@code 300 * Optional.empty()}. 301 */ getAnyAnnotation( XAnnotated annotated, Collection<ClassName> annotations)302 public static Optional<XAnnotation> getAnyAnnotation( 303 XAnnotated annotated, Collection<ClassName> annotations) { 304 return annotations.stream() 305 .filter(annotated::hasAnnotation) 306 .map(annotated::getAnnotation) 307 .findFirst(); 308 } 309 310 /** Returns all annotations from {@code annotations} that annotate {@code annotated}. */ getAllAnnotations( XAnnotated annotated, Collection<ClassName> annotations)311 public static ImmutableSet<XAnnotation> getAllAnnotations( 312 XAnnotated annotated, Collection<ClassName> annotations) { 313 return annotations.stream() 314 .filter(annotated::hasAnnotation) 315 .map(annotated::getAnnotation) 316 .collect(toImmutableSet()); 317 } 318 319 /** 320 * Returns a string representation of {@link XElement} that is independent of the backend 321 * (javac/ksp). 322 */ toStableString(XElement element)323 public static String toStableString(XElement element) { 324 if (element == null) { 325 return "<null>"; 326 } 327 try { 328 if (isTypeElement(element)) { 329 return asTypeElement(element).getQualifiedName(); 330 } else if (isExecutable(element)) { 331 XExecutableElement executable = asExecutable(element); 332 // TODO(b/318709946) resolving ksp types can be expensive, therefore we should avoid it 333 // here for extreme cases until ksp improved the performance. 334 boolean tooManyParameters = 335 getProcessingEnv(element).getBackend().equals(XProcessingEnv.Backend.KSP) 336 && executable.getParameters().size() > 10; 337 return String.format( 338 "%s(%s)", 339 getSimpleName( 340 isConstructor(element) ? asConstructor(element).getEnclosingElement() : executable), 341 (tooManyParameters 342 ? executable.getParameters().stream().limit(10) 343 : executable.getParameters().stream() 344 .map(XExecutableParameterElement::getType) 345 .map(XTypes::toStableString) 346 .collect(joining(","))) 347 + (tooManyParameters ? ", ..." : "")); 348 } else if (isEnumEntry(element) 349 || isField(element) 350 || isMethodParameter(element) 351 || isTypeParameter(element)) { 352 return getSimpleName(element); 353 } 354 return element.toString(); 355 } catch (TypeNotPresentException e) { 356 return e.typeName(); 357 } 358 } 359 360 // XElement#kindName() exists, but doesn't give consistent results between JAVAC and KSP (e.g. 361 // METHOD vs FUNCTION) so this custom implementation is meant to provide that consistency. getKindName(XElement element)362 public static String getKindName(XElement element) { 363 if (isTypeElement(element)) { 364 XTypeElement typeElement = asTypeElement(element); 365 if (typeElement.isClass()) { 366 return "CLASS"; 367 } else if (typeElement.isInterface()) { 368 return "INTERFACE"; 369 } else if (typeElement.isAnnotationClass()) { 370 return "ANNOTATION_TYPE"; 371 } 372 } else if (isEnum(element)) { 373 return "ENUM"; 374 } else if (isEnumEntry(element)) { 375 return "ENUM_CONSTANT"; 376 } else if (isConstructor(element)) { 377 return "CONSTRUCTOR"; 378 } else if (isMethod(element)) { 379 return "METHOD"; 380 } else if (isField(element)) { 381 return "FIELD"; 382 } else if (isMethodParameter(element)) { 383 return "PARAMETER"; 384 } else if (isTypeParameter(element)) { 385 return "TYPE_PARAMETER"; 386 } 387 return element.kindName(); 388 } 389 packageName(XElement element)390 public static String packageName(XElement element) { 391 return element.getClosestMemberContainer().asClassName().getPackageName(); 392 } 393 isFinal(XExecutableElement element)394 public static boolean isFinal(XExecutableElement element) { 395 if (element.isFinal()) { 396 return true; 397 } 398 if (getProcessingEnv(element).getBackend() == XProcessingEnv.Backend.KSP) { 399 if (toKS(element).getModifiers().contains(com.google.devtools.ksp.symbol.Modifier.FINAL)) { 400 return true; 401 } 402 } 403 return false; 404 } 405 getModifiers(XExecutableElement element)406 public static ImmutableList<Modifier> getModifiers(XExecutableElement element) { 407 ImmutableList.Builder<Modifier> builder = ImmutableList.builder(); 408 if (isFinal(element)) { 409 builder.add(Modifier.FINAL); 410 } else if (element.isAbstract()) { 411 builder.add(Modifier.ABSTRACT); 412 } 413 if (element.isStatic()) { 414 builder.add(Modifier.STATIC); 415 } 416 if (element.isPublic()) { 417 builder.add(Modifier.PUBLIC); 418 } else if (element.isPrivate()) { 419 builder.add(Modifier.PRIVATE); 420 } else if (element.isProtected()) { 421 builder.add(Modifier.PROTECTED); 422 } 423 return builder.build(); 424 } 425 XElements()426 private XElements() {} 427 } 428