• 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.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