All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.internal.codegen.xprocessing.XElements Maven / Gradle / Ivy

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() {}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy