aboutsummaryrefslogtreecommitdiff
path: root/java/dagger/internal/codegen/xprocessing/XElements.java
blob: 552be65f9e9e238f21feefa9f894f1fc79834936 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
/*
 * Copyright (C) 2021 The Dagger Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dagger.internal.codegen.xprocessing;

import static androidx.room.compiler.processing.XElementKt.isConstructor;
import static androidx.room.compiler.processing.XElementKt.isField;
import static androidx.room.compiler.processing.XElementKt.isMethod;
import static androidx.room.compiler.processing.XElementKt.isMethodParameter;
import static androidx.room.compiler.processing.XElementKt.isTypeElement;
import static androidx.room.compiler.processing.XElementKt.isVariableElement;
import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
import static androidx.room.compiler.processing.compat.XConverters.toJavac;
import static androidx.room.compiler.processing.compat.XConverters.toKS;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static java.util.stream.Collectors.joining;

import androidx.room.compiler.processing.XAnnotated;
import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XConstructorElement;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XEnumEntry;
import androidx.room.compiler.processing.XEnumTypeElement;
import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XExecutableParameterElement;
import androidx.room.compiler.processing.XFieldElement;
import androidx.room.compiler.processing.XHasModifiers;
import androidx.room.compiler.processing.XMemberContainer;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XTypeElement;
import androidx.room.compiler.processing.XTypeParameterElement;
import androidx.room.compiler.processing.XVariableElement;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.ksp.symbol.KSAnnotated;
import com.squareup.javapoet.ClassName;
import java.util.Collection;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.lang.model.element.ElementKind;

// TODO(bcorso): Consider moving these methods into XProcessing library.
/** A utility class for {@link XElement} helper methods. */
public final class XElements {

  // TODO(bcorso): Replace usages with getJvmName() once it exists.
  /** Returns the simple name of the member container. */
  public static String getSimpleName(XMemberContainer memberContainer) {
    return memberContainer.getClassName().simpleName();
  }

  /** Returns the simple name of the element. */
  public static String getSimpleName(XElement element) {
    if (isTypeElement(element)) {
      return asTypeElement(element)
          .getName(); // SUPPRESS_GET_NAME_CHECK: This uses java simple name implementation under
      // the hood.
    } else if (isVariableElement(element)) {
      return asVariable(element).getName(); // SUPPRESS_GET_NAME_CHECK
    } else if (isEnumEntry(element)) {
      return asEnumEntry(element).getName(); // SUPPRESS_GET_NAME_CHECK
    } else if (isMethod(element)) {
      // Note: We use "jvm name" here rather than "simple name" because simple name is not reliable
      // in KAPT. In particular, XProcessing relies on matching the method to its descriptor found
      // in the Kotlin @Metadata to get the simple name. However, this doesn't work for method
      // descriptors that contain generated types because the stub and @Metadata will disagree due
      // to the following bug:
      // https://youtrack.jetbrains.com/issue/KT-35124/KAPT-not-correcting-error-types-in-Kotlin-Metadata-information-produced-for-the-stubs.
      // In any case, always using the jvm name should be safe; however, it will unfortunately
      // contain any obfuscation added by kotlinc, e.g. for "internal" methods, which can make the
      // "simple name" not as nice/short when used for things like error messages or class names.
      return asMethod(element).getJvmName();
    } else if (isConstructor(element)) {
      return "<init>";
    } else if (isTypeParameter(element)) {
      return asTypeParameter(element).getName(); // SUPPRESS_GET_NAME_CHECK
    }
    throw new AssertionError("No simple name for: " + element);
  }

  private static boolean isSyntheticElement(XElement element) {
    if (isMethodParameter(element)) {
      XExecutableParameterElement executableParam = asMethodParameter(element);
      return executableParam.isContinuationParam()
          || executableParam.isReceiverParam()
          || executableParam.isKotlinPropertyParam();
    }
    if (isMethod(element)) {
      return asMethod(element).isKotlinPropertyMethod();
    }
    return false;
  }

  @Nullable
  public static KSAnnotated toKSAnnotated(XElement element) {
    if (isSyntheticElement(element)) {
      return toKS(element);
    }
    if (isExecutable(element)) {
      return toKS(asExecutable(element));
    }
    if (isTypeElement(element)) {
      return toKS(asTypeElement(element));
    }
    if (isField(element)) {
      return toKS(asField(element));
    }
    if (isMethodParameter(element)) {
      return toKS(asMethodParameter(element));
    }
    throw new IllegalStateException(
        "Returning KSAnnotated declaration for " + element + " is not supported.");
  }

  /**
   * Returns the closest enclosing element that is a {@link XTypeElement} or throws an {@link
   * IllegalStateException} if one doesn't exist.
   */
  public static XTypeElement closestEnclosingTypeElement(XElement element) {
    return optionalClosestEnclosingTypeElement(element)
        .orElseThrow(() -> new IllegalStateException("No enclosing TypeElement for: " + element));
  }

  /**
   * Returns {@code true} if {@code encloser} is equal to or transitively encloses {@code enclosed}.
   */
  public static boolean transitivelyEncloses(XElement encloser, XElement enclosed) {
    XElement current = enclosed;
    while (current != null) {
      if (current.equals(encloser)) {
        return true;
      }
      current = current.getEnclosingElement();
    }
    return false;
  }

  private static Optional<XTypeElement> optionalClosestEnclosingTypeElement(XElement element) {
    if (isTypeElement(element)) {
      return Optional.of(asTypeElement(element));
    } else if (isConstructor(element)) {
      return Optional.of(asConstructor(element).getEnclosingElement());
    } else if (isMethod(element)) {
      return optionalClosestEnclosingTypeElement(asMethod(element).getEnclosingElement());
    } else if (isField(element)) {
      return optionalClosestEnclosingTypeElement(asField(element).getEnclosingElement());
    } else if (isMethodParameter(element)) {
      return optionalClosestEnclosingTypeElement(asMethodParameter(element).getEnclosingElement());
    }
    return Optional.empty();
  }

  public static boolean isAbstract(XElement element) {
    return asHasModifiers(element).isAbstract();
  }

  public static boolean isPublic(XElement element) {
    return asHasModifiers(element).isPublic();
  }

  public static boolean isPrivate(XElement element) {
    return asHasModifiers(element).isPrivate();
  }

  public static boolean isStatic(XElement element) {
    return asHasModifiers(element).isStatic();
  }

  // TODO(bcorso): Ideally we would modify XElement to extend XHasModifiers to prevent possible
  // runtime exceptions if the element does not extend XHasModifiers. However, for Dagger's purpose
  // all usages should be on elements that do extend XHasModifiers, so generalizing this for
  // XProcessing is probably overkill for now.
  private static XHasModifiers asHasModifiers(XElement element) {
    // In javac, Element implements HasModifiers but in XProcessing XElement does not.
    // Currently, the elements that do not extend XHasModifiers are XMemberContainer, XEnumEntry,
    // XVariableElement. Though most instances of XMemberContainer will extend XHasModifiers through
    // XTypeElement instead.
    checkArgument(element instanceof XHasModifiers, "Element %s does not have modifiers", element);
    return (XHasModifiers) element;
  }

  // Note: This method always returns `false` but I'd rather not remove it from our codebase since
  // if XProcessing adds package elements to their model I'd like to catch it here and fail early.
  public static boolean isPackage(XElement element) {
    // Currently, XProcessing doesn't represent package elements so this method always returns
    // false, but we check the state in Javac just to be sure. There's nothing to check in KSP since
    // there is no concept of package elements in KSP.
    if (getProcessingEnv(element).getBackend() == XProcessingEnv.Backend.JAVAC) {
      checkState(toJavac(element).getKind() != ElementKind.PACKAGE);
    }
    return false;
  }

  public static boolean isTypeParameter(XElement element) {
    return element instanceof XTypeParameterElement;
  }

  public static XTypeParameterElement asTypeParameter(XElement element) {
    return (XTypeParameterElement) element;
  }

  public static boolean isEnumEntry(XElement element) {
    return element instanceof XEnumEntry;
  }

  public static boolean isEnum(XElement element) {
    return element instanceof XEnumTypeElement;
  }

  public static boolean isExecutable(XElement element) {
    return isConstructor(element) || isMethod(element);
  }

  public static XExecutableElement asExecutable(XElement element) {
    checkState(isExecutable(element));
    return (XExecutableElement) element;
  }

  public static XTypeElement asTypeElement(XElement element) {
    checkState(isTypeElement(element));
    return (XTypeElement) element;
  }

  // TODO(bcorso): Rename this and the XElementKt.isMethodParameter to isExecutableParameter.
  public static XExecutableParameterElement asMethodParameter(XElement element) {
    checkState(isMethodParameter(element));
    return (XExecutableParameterElement) element;
  }

  public static XFieldElement asField(XElement element) {
    checkState(isField(element));
    return (XFieldElement) element;
  }

  public static XEnumEntry asEnumEntry(XElement element) {
    return (XEnumEntry) element;
  }

  public static XVariableElement asVariable(XElement element) {
    checkState(isVariableElement(element));
    return (XVariableElement) element;
  }

  public static XConstructorElement asConstructor(XElement element) {
    checkState(isConstructor(element));
    return (XConstructorElement) element;
  }

  public static XMethodElement asMethod(XElement element) {
    checkState(isMethod(element));
    return (XMethodElement) element;
  }

  public static ImmutableSet<XAnnotation> getAnnotatedAnnotations(
      XAnnotated annotated, ClassName annotationName) {
    return annotated.getAllAnnotations().stream()
        .filter(annotation -> annotation.getType().getTypeElement().hasAnnotation(annotationName))
        .collect(toImmutableSet());
  }

  /** Returns {@code true} if {@code annotated} is annotated with any of the given annotations. */
  public static boolean hasAnyAnnotation(XAnnotated annotated, ClassName... annotations) {
    return hasAnyAnnotation(annotated, ImmutableSet.copyOf(annotations));
  }

  /** Returns {@code true} if {@code annotated} is annotated with any of the given annotations. */
  public static boolean hasAnyAnnotation(XAnnotated annotated, Collection<ClassName> annotations) {
    return annotations.stream().anyMatch(annotated::hasAnnotation);
  }

  /**
   * Returns any annotation from {@code annotations} that annotates {@code annotated} or else {@code
   * Optional.empty()}.
   */
  public static Optional<XAnnotation> getAnyAnnotation(
      XAnnotated annotated, ClassName... annotations) {
    return getAnyAnnotation(annotated, ImmutableSet.copyOf(annotations));
  }

  /**
   * Returns any annotation from {@code annotations} that annotates {@code annotated} or else {@code
   * Optional.empty()}.
   */
  public static Optional<XAnnotation> getAnyAnnotation(
      XAnnotated annotated, Collection<ClassName> annotations) {
    return annotations.stream()
        .filter(annotated::hasAnnotation)
        .map(annotated::getAnnotation)
        .findFirst();
  }

  /** Returns all annotations from {@code annotations} that annotate {@code annotated}. */
  public static ImmutableSet<XAnnotation> getAllAnnotations(
      XAnnotated annotated, Collection<ClassName> annotations) {
    return annotations.stream()
        .filter(annotated::hasAnnotation)
        .map(annotated::getAnnotation)
        .collect(toImmutableSet());
  }

  /**
   * Returns a string representation of {@link XElement} that is independent of the backend
   * (javac/ksp).
   */
  public static String toStableString(XElement element) {
    if (element == null) {
      return "<null>";
    }
    try {
      if (isTypeElement(element)) {
        return asTypeElement(element).getQualifiedName();
      } else if (isExecutable(element)) {
        XExecutableElement executable = asExecutable(element);
        return String.format(
            "%s(%s)",
            getSimpleName(
                isConstructor(element)
                    ? asConstructor(element).getEnclosingElement()
                    : executable),
            executable.getParameters().stream()
                .map(XExecutableParameterElement::getType)
                .map(XTypes::toStableString)
                .collect(joining(",")));
      } else if (isEnumEntry(element)
                     || isField(element)
                     || isMethodParameter(element)
                     || isTypeParameter(element)) {
        return getSimpleName(element);
      }
      return element.toString();
    } catch (TypeNotPresentException e) {
      return e.typeName();
    }
  }

  // XElement#kindName() exists, but doesn't give consistent results between JAVAC and KSP (e.g.
  // METHOD vs FUNCTION) so this custom implementation is meant to provide that consistency.
  public static String getKindName(XElement element) {
    if (isTypeElement(element)) {
      XTypeElement typeElement = asTypeElement(element);
      if (typeElement.isClass()) {
        return "CLASS";
      } else if (typeElement.isInterface()) {
        return "INTERFACE";
      } else if (typeElement.isAnnotationClass()) {
        return "ANNOTATION_TYPE";
      }
    } else if (isEnum(element)) {
      return "ENUM";
    } else if (isEnumEntry(element)) {
      return "ENUM_CONSTANT";
    } else if (isConstructor(element)) {
      return "CONSTRUCTOR";
    } else if (isMethod(element)) {
      return "METHOD";
    } else if (isField(element)) {
      return "FIELD";
    } else if (isMethodParameter(element)) {
      return "PARAMETER";
    } else if (isTypeParameter(element)) {
      return "TYPE_PARAMETER";
    }
    return element.kindName();
  }

  public static String packageName(XElement element) {
    return element.getClosestMemberContainer().asClassName().getPackageName();
  }

  private XElements() {}
}