dagger.internal.codegen.xprocessing.XElements Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dagger-compiler Show documentation
Show all versions of dagger-compiler Show documentation
A fast dependency injector for Android and Java.
The newest version!
/*
* 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.ImmutableList;
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;
import javax.lang.model.element.Modifier;
// 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 "";
} 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 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 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 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 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 getAnyAnnotation(
XAnnotated annotated, Collection annotations) {
return annotations.stream()
.filter(annotated::hasAnnotation)
.map(annotated::getAnnotation)
.findFirst();
}
/** Returns all annotations from {@code annotations} that annotate {@code annotated}. */
public static ImmutableSet getAllAnnotations(
XAnnotated annotated, Collection 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 "";
}
try {
if (isTypeElement(element)) {
return asTypeElement(element).getQualifiedName();
} else if (isExecutable(element)) {
XExecutableElement executable = asExecutable(element);
// TODO(b/318709946) resolving ksp types can be expensive, therefore we should avoid it
// here for extreme cases until ksp improved the performance.
boolean tooManyParameters =
getProcessingEnv(element).getBackend().equals(XProcessingEnv.Backend.KSP)
&& executable.getParameters().size() > 10;
return String.format(
"%s(%s)",
getSimpleName(
isConstructor(element) ? asConstructor(element).getEnclosingElement() : executable),
(tooManyParameters
? executable.getParameters().stream().limit(10)
: executable.getParameters().stream()
.map(XExecutableParameterElement::getType)
.map(XTypes::toStableString)
.collect(joining(",")))
+ (tooManyParameters ? ", ..." : ""));
} 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();
}
public static boolean isFinal(XExecutableElement element) {
if (element.isFinal()) {
return true;
}
if (getProcessingEnv(element).getBackend() == XProcessingEnv.Backend.KSP) {
if (toKS(element).getModifiers().contains(com.google.devtools.ksp.symbol.Modifier.FINAL)) {
return true;
}
}
return false;
}
public static ImmutableList getModifiers(XExecutableElement element) {
ImmutableList.Builder builder = ImmutableList.builder();
if (isFinal(element)) {
builder.add(Modifier.FINAL);
} else if (element.isAbstract()) {
builder.add(Modifier.ABSTRACT);
}
if (element.isStatic()) {
builder.add(Modifier.STATIC);
}
if (element.isPublic()) {
builder.add(Modifier.PUBLIC);
} else if (element.isPrivate()) {
builder.add(Modifier.PRIVATE);
} else if (element.isProtected()) {
builder.add(Modifier.PROTECTED);
}
return builder.build();
}
private XElements() {}
}