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

org.scalajs.linker.analyzer.Infos.scala Maven / Gradle / Ivy

/*
 * Scala.js (https://www.scala-js.org/)
 *
 * Copyright EPFL.
 *
 * Licensed under Apache License 2.0
 * (https://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package org.scalajs.linker.analyzer

import scala.collection.mutable

import org.scalajs.ir.ClassKind
import org.scalajs.ir.Names._
import org.scalajs.ir.Traversers._
import org.scalajs.ir.Trees._
import org.scalajs.ir.Types._
import org.scalajs.ir.Version

import org.scalajs.linker.backend.emitter.Transients._
import org.scalajs.linker.standard.LinkedTopLevelExport
import org.scalajs.linker.standard.ModuleSet.ModuleID

object Infos {

  private val StringArgConstructorName =
    MethodName.constructor(List(ClassRef(BoxedStringClass)))

  private val cloneMethodName = MethodName("clone", Nil, ClassRef(ObjectClass))

  /* Elements of WrapAsThrowable and UnwrapFromThrowable used by the Emitter
   * In theory, these should be an implementation detail of the Emitter, and
   * requested through `symbolRequirements`. However, doing so would mean that
   * we would never be able to dead-code-eliminate JavaScriptException, which
   * would be annoying.
   */
  private val JavaScriptExceptionClass = ClassName("scala.scalajs.js.JavaScriptException")
  private val AnyArgConstructorName = MethodName.constructor(List(ClassRef(ObjectClass)))

  final case class NamespacedMethodName(
      namespace: MemberNamespace, methodName: MethodName)

  final class ClassInfo(
      val className: ClassName,
      val kind: ClassKind,
      val superClass: Option[ClassName], // always None for interfaces
      val interfaces: List[ClassName], // direct parent interfaces only
      val jsNativeLoadSpec: Option[JSNativeLoadSpec],
      /* Referenced classes of non-static fields.
       *
       * Note that the classes referenced by static fields are reached
       * implicitly by the call-sites that read or write the field: the
       * SelectStatic expression has the same type as the field.
       *
       * Further, note that some existing fields might not appear in this map:
       * This happens when they have non-class type (e.g. Int)
       */
      val referencedFieldClasses: Map[FieldName, ClassName],
      val methods: Array[Map[MethodName, MethodInfo]],
      val jsNativeMembers: Map[MethodName, JSNativeLoadSpec],
      val jsMethodProps: List[ReachabilityInfo],
      val topLevelExports: List[TopLevelExportInfo]
  ) {
    override def toString(): String = className.nameString
  }

  /* MethodInfo should contain, not be a ReachbilityInfo
   *
   * However, since this class is retained over multiple linker runs in the
   * cache, the shallow size of the object shows up in memory performance
   * profiles. Therefore, we (ab)use inheritance to lower the memory overhead.
   */
  final class MethodInfo private (
    val isAbstract: Boolean,
    version: Version,
    byClass: Array[ReachabilityInfoInClass],
    globalFlags: ReachabilityInfo.Flags
  ) extends ReachabilityInfo(version, byClass, globalFlags)

  object MethodInfo {
    def apply(isAbstract: Boolean, reachabilityInfo: ReachabilityInfo): MethodInfo = {
      import reachabilityInfo._
      new MethodInfo(isAbstract, version, byClass, globalFlags)
    }
  }

  final class TopLevelExportInfo private[Infos] (
      val reachability: ReachabilityInfo,
      val moduleID: ModuleID,
      val exportName: String
  )

  sealed class ReachabilityInfo private[Infos] (
    /* The version field does not belong here conceptually.
     * However, it helps the InfoLoader re-use previous infos without
     * additional data held in memory.
     * This reduces the memory we need to cache infos between incremental runs.
     */
    val version: Version,
    val byClass: Array[ReachabilityInfoInClass],
    val globalFlags: ReachabilityInfo.Flags
  )

  object ReachabilityInfo {
    type Flags = Int

    final val FlagAccessedClassClass = 1 << 0
    final val FlagAccessedNewTarget = 1 << 1
    final val FlagAccessedImportMeta = 1 << 2
    final val FlagUsedExponentOperator = 1 << 3
    final val FlagUsedClassSuperClass = 1 << 4
  }

  /** Things from a given class that are reached by one method. */
  final class ReachabilityInfoInClass private[Infos] (
      val className: ClassName,
      /* We use a single field for all members to reduce memory consumption:
       * Typically, there are very few members reached in a single
       * ReachabilityInfoInClass, so the overhead of having a field per type
       * becomes significant in terms of memory usage.
       */
      val memberInfos: Array[MemberReachabilityInfo], // nullable!
      val flags: ReachabilityInfoInClass.Flags
  )

  object ReachabilityInfoInClass {
    type Flags = Int

    /** For a Scala class, it is instantiated with a `New`; for a JS class,
     *  its constructor is accessed with a `JSLoadConstructor`.
     */
    final val FlagInstantiated = 1 << 0

    final val FlagModuleAccessed = 1 << 1
    final val FlagInstanceTestsUsed = 1 << 2
    final val FlagClassDataAccessed = 1 << 3
    final val FlagStaticallyReferenced = 1 << 4
    final val FlagDynamicallyReferenced = 1 << 5
  }

  sealed trait MemberReachabilityInfo

  final case class FieldReachable private[Infos] (
    val fieldName: FieldName,
    val read: Boolean = false,
    val written: Boolean = false
  ) extends MemberReachabilityInfo

  final case class StaticFieldReachable private[Infos] (
    val fieldName: FieldName,
    val read: Boolean = false,
    val written: Boolean = false
  ) extends MemberReachabilityInfo

  final case class MethodReachable private[Infos] (
    val methodName: MethodName
  ) extends MemberReachabilityInfo

  final case class MethodStaticallyReachable private[Infos] (
    val namespace: MemberNamespace,
    val methodName: MethodName
  ) extends MemberReachabilityInfo

  object MethodStaticallyReachable {
    private[Infos] def apply(m: NamespacedMethodName): MethodStaticallyReachable =
      MethodStaticallyReachable(m.namespace, m.methodName)
  }

  final case class JSNativeMemberReachable private[Infos] (
    val methodName: MethodName
  ) extends MemberReachabilityInfo

  def genReferencedFieldClasses(fields: List[AnyFieldDef]): Map[FieldName, ClassName] = {
    val builder = Map.newBuilder[FieldName, ClassName]

    fields.foreach {
      case FieldDef(flags, FieldIdent(name), _, ftpe) =>
        if (!flags.namespace.isStatic) {
          ftpe match {
            case ClassType(cls, _) =>
              builder += name -> cls
            case ArrayType(ArrayTypeRef(ClassRef(cls), _), _) =>
              builder += name -> cls
            case _ =>
          }
        }
      case _: JSFieldDef =>
        // Nothing to do.
    }

    builder.result()
  }

  final class ReachabilityInfoBuilder(version: Version) {
    private val byClass = mutable.Map.empty[ClassName, ReachabilityInfoInClassBuilder]
    private var flags: ReachabilityInfo.Flags = 0

    private def forClass(cls: ClassName): ReachabilityInfoInClassBuilder =
      byClass.getOrElseUpdate(cls, new ReachabilityInfoInClassBuilder(cls))

    def addFieldRead(field: FieldName): this.type = {
      forClass(field.className).addFieldRead(field)
      this
    }

    def addFieldWritten(field: FieldName): this.type = {
      forClass(field.className).addFieldWritten(field)
      this
    }

    def addStaticFieldRead(field: FieldName): this.type = {
      forClass(field.className).addStaticFieldRead(field)
      this
    }

    def addStaticFieldWritten(field: FieldName): this.type = {
      forClass(field.className).addStaticFieldWritten(field)
      this
    }

    def addMethodCalled(receiverTpe: Type, method: MethodName): this.type = {
      receiverTpe match {
        case ClassType(cls, _)        => addMethodCalled(cls, method)
        case AnyType | AnyNotNullType => addMethodCalled(ObjectClass, method)
        case UndefType                => addMethodCalled(BoxedUnitClass, method)
        case BooleanType              => addMethodCalled(BoxedBooleanClass, method)
        case CharType                 => addMethodCalled(BoxedCharacterClass, method)
        case ByteType                 => addMethodCalled(BoxedByteClass, method)
        case ShortType                => addMethodCalled(BoxedShortClass, method)
        case IntType                  => addMethodCalled(BoxedIntegerClass, method)
        case LongType                 => addMethodCalled(BoxedLongClass, method)
        case FloatType                => addMethodCalled(BoxedFloatClass, method)
        case DoubleType               => addMethodCalled(BoxedDoubleClass, method)
        case StringType               => addMethodCalled(BoxedStringClass, method)

        case ArrayType(_, _) =>
          /* The pseudo Array class is not reified in our analyzer/analysis,
           * so we need to cheat here. Since the Array[T] classes do not define
           * any method themselves--they are all inherited from j.l.Object--,
           * we can model their reachability by calling them statically in the
           * Object class.
           */
          addMethodCalledStatically(ObjectClass,
              NamespacedMethodName(MemberNamespace.Public, method))

        case NullType | NothingType =>
          // Nothing to do

        case NoType | RecordType(_) =>
          throw new IllegalArgumentException(
              s"Illegal receiver type: $receiverTpe")
      }

      this
    }

    def addMethodCalled(cls: ClassName, method: MethodName): this.type = {
      forClass(cls).addMethodCalled(method)
      this
    }

    def addMethodCalledStatically(cls: ClassName,
        method: NamespacedMethodName): this.type = {
      forClass(cls).addMethodCalledStatically(method)
      this
    }

    def addMethodCalledDynamicImport(cls: ClassName,
        method: NamespacedMethodName): this.type = {
      forClass(cls).addMethodCalledDynamicImport(method)
      this
    }

    def addJSNativeMemberUsed(cls: ClassName, member: MethodName): this.type = {
      forClass(cls).addJSNativeMemberUsed(member)
      this
    }

    def addInstantiatedClass(cls: ClassName): this.type = {
      forClass(cls).setInstantiated()
      this
    }

    def addInstantiatedClass(cls: ClassName, ctor: MethodName): this.type = {
      forClass(cls).setInstantiated().addMethodCalledStatically(
          NamespacedMethodName(MemberNamespace.Constructor, ctor))
      this
    }

    def addAccessedModule(cls: ClassName): this.type = {
      forClass(cls).setModuleAccessed()
      this
    }

    def maybeAddUsedInstanceTest(tpe: Type): this.type = {
      tpe match {
        case ClassType(className, _) =>
          addUsedInstanceTest(className)
        case ArrayType(ArrayTypeRef(ClassRef(baseClassName), _), _) =>
          addUsedInstanceTest(baseClassName)
        case _ =>
      }
      this
    }

    def addUsedInstanceTest(cls: ClassName): this.type = {
      forClass(cls).setInstanceTestsUsed()
      this
    }

    def maybeAddAccessedClassData(typeRef: TypeRef): this.type = {
      typeRef match {
        case ClassRef(cls) =>
          addAccessedClassData(cls)
        case ArrayTypeRef(ClassRef(cls), _) =>
          addAccessedClassData(cls)
        case _ =>
      }
      this
    }

    def addAccessedClassData(cls: ClassName): this.type = {
      forClass(cls).setClassDataAccessed()
      this
    }

    def maybeAddReferencedClass(typeRef: TypeRef): this.type = {
      typeRef match {
        case ClassRef(cls) =>
          addReferencedClass(cls)
        case ArrayTypeRef(ClassRef(cls), _) =>
          addReferencedClass(cls)
        case _ =>
      }
      this
    }

    def addReferencedClass(cls: ClassName): this.type = {
      /* We only need the class to appear in `byClass` so that the Analyzer
       * knows to perform `lookupClass` for it. But then nothing further needs
       * to happen.
       */
      forClass(cls)
      this
    }

    def addStaticallyReferencedClass(cls: ClassName): this.type = {
      forClass(cls).setStaticallyReferenced()
      this
    }

    def maybeAddReferencedClass(tpe: Type): this.type = {
      tpe match {
        case ClassType(cls, _) =>
          addReferencedClass(cls)
        case ArrayType(ArrayTypeRef(ClassRef(cls), _), _) =>
          addReferencedClass(cls)
        case _ =>
      }
      this
    }

    private def setFlag(flag: ReachabilityInfo.Flags): this.type = {
      flags |= flag
      this
    }

    def addAccessedClassClass(): this.type =
      setFlag(ReachabilityInfo.FlagAccessedClassClass)

    def addAccessNewTarget(): this.type =
      setFlag(ReachabilityInfo.FlagAccessedNewTarget)

    def addAccessImportMeta(): this.type =
      setFlag(ReachabilityInfo.FlagAccessedImportMeta)

    def addUsedExponentOperator(): this.type =
      setFlag(ReachabilityInfo.FlagUsedExponentOperator)

    def addUsedIntLongDivModByMaybeZero(): this.type =
      addInstantiatedClass(ArithmeticExceptionClass, StringArgConstructorName)

    def addUsedClassNewArray(): this.type =
      addInstantiatedClass(IllegalArgumentExceptionClass, NoArgConstructorName)

    def addUsedClassSuperClass(): this.type =
      setFlag(ReachabilityInfo.FlagUsedClassSuperClass)

    def result(): ReachabilityInfo =
      new ReachabilityInfo(version, byClass.valuesIterator.map(_.result()).toArray, flags)
  }

  final class ReachabilityInfoInClassBuilder(val className: ClassName) {
    private val fieldsUsed = mutable.Map.empty[FieldName, FieldReachable]
    private val staticFieldsUsed = mutable.Map.empty[FieldName, StaticFieldReachable]
    private val methodsCalled = mutable.Set.empty[MethodName]
    private val methodsCalledStatically = mutable.Set.empty[NamespacedMethodName]
    private val jsNativeMembersUsed = mutable.Set.empty[MethodName]
    private var flags: ReachabilityInfoInClass.Flags = 0

    def addFieldRead(field: FieldName): this.type = {
      fieldsUsed(field) = fieldsUsed
        .getOrElse(field, FieldReachable(field))
        .copy(read = true)
      this
    }

    def addFieldWritten(field: FieldName): this.type = {
      fieldsUsed(field) = fieldsUsed
        .getOrElse(field, FieldReachable(field))
        .copy(written = true)
      this
    }

    def addStaticFieldRead(field: FieldName): this.type = {
      staticFieldsUsed(field) = staticFieldsUsed
        .getOrElse(field, StaticFieldReachable(field))
        .copy(read = true)
      setStaticallyReferenced()
      this
    }

    def addStaticFieldWritten(field: FieldName): this.type = {
      staticFieldsUsed(field) = staticFieldsUsed
        .getOrElse(field, StaticFieldReachable(field))
        .copy(written = true)
      setStaticallyReferenced()
      this
    }

    def addMethodCalled(method: MethodName): this.type = {
      methodsCalled += method
      // Do not call setStaticallyReferenced: We call these methods on the object.
      this
    }

    def addMethodCalledStatically(method: NamespacedMethodName): this.type = {
      methodsCalledStatically += method
      setStaticallyReferenced()
      this
    }

    def addMethodCalledDynamicImport(method: NamespacedMethodName): this.type = {
      // In terms of reachability, a dynamic import call is just a static call.
      methodsCalledStatically += method
      setFlag(ReachabilityInfoInClass.FlagDynamicallyReferenced)
      this
    }

    def addJSNativeMemberUsed(member: MethodName): this.type = {
      jsNativeMembersUsed += member
      this
    }

    private def setFlag(flag: ReachabilityInfoInClass.Flags): this.type = {
      flags |= flag
      this
    }

    def setInstantiated(): this.type =
      setFlag(ReachabilityInfoInClass.FlagInstantiated)

    def setModuleAccessed(): this.type =
      setFlag(ReachabilityInfoInClass.FlagModuleAccessed)

    def setInstanceTestsUsed(): this.type =
      setFlag(ReachabilityInfoInClass.FlagInstanceTestsUsed)

    def setClassDataAccessed(): this.type =
      setFlag(ReachabilityInfoInClass.FlagClassDataAccessed)

    def setStaticallyReferenced(): this.type =
      setFlag(ReachabilityInfoInClass.FlagStaticallyReferenced)

    def result(): ReachabilityInfoInClass = {
      val memberInfos: Array[MemberReachabilityInfo] = (
          fieldsUsed.valuesIterator ++
          staticFieldsUsed.valuesIterator ++
          methodsCalled.iterator.map(MethodReachable(_)) ++
          methodsCalledStatically.iterator.map(MethodStaticallyReachable(_)) ++
          jsNativeMembersUsed.iterator.map(JSNativeMemberReachable(_))
      ).toArray

      val memberInfosOrNull =
        if (memberInfos.isEmpty) null
        else memberInfos

      new ReachabilityInfoInClass(className, memberInfosOrNull, flags)
    }
  }

  /** Generates the [[MethodInfo]] of a
   *  [[org.scalajs.ir.Trees.MethodDef Trees.MethodDef]].
   */
  def generateMethodInfo(methodDef: MethodDef): MethodInfo =
    new GenInfoTraverser(methodDef.version).generateMethodInfo(methodDef)

  /** Generates the [[ReachabilityInfo]] of a
   *  [[org.scalajs.ir.Trees.JSConstructorDef Trees.JSConstructorDef]].
   */
  def generateJSConstructorInfo(ctorDef: JSConstructorDef): ReachabilityInfo =
    new GenInfoTraverser(ctorDef.version).generateJSConstructorInfo(ctorDef)

  /** Generates the [[ReachabilityInfo]] of a
   *  [[org.scalajs.ir.Trees.JSMethodDef Trees.JSMethodDef]].
   */
  def generateJSMethodInfo(methodDef: JSMethodDef): ReachabilityInfo =
    new GenInfoTraverser(methodDef.version).generateJSMethodInfo(methodDef)

  /** Generates the [[ReachabilityInfo]] of a
   *  [[org.scalajs.ir.Trees.JSPropertyDef Trees.JSPropertyDef]].
   */
  def generateJSPropertyInfo(propertyDef: JSPropertyDef): ReachabilityInfo =
    new GenInfoTraverser(propertyDef.version).generateJSPropertyInfo(propertyDef)

  def generateJSMethodPropDefInfo(member: JSMethodPropDef): ReachabilityInfo = member match {
    case methodDef: JSMethodDef     => generateJSMethodInfo(methodDef)
    case propertyDef: JSPropertyDef => generateJSPropertyInfo(propertyDef)
  }

  /** Generates the [[MethodInfo]] for the top-level exports. */
  def generateTopLevelExportInfo(enclosingClass: ClassName,
      topLevelExportDef: TopLevelExportDef): TopLevelExportInfo = {
    val info = new GenInfoTraverser(Version.Unversioned)
        .generateTopLevelExportInfo(enclosingClass, topLevelExportDef)
    new TopLevelExportInfo(info,
        ModuleID(topLevelExportDef.moduleID),
        topLevelExportDef.topLevelExportName)
  }

  private final class GenInfoTraverser(version: Version) extends Traverser {
    private val builder = new ReachabilityInfoBuilder(version)

    def generateMethodInfo(methodDef: MethodDef): MethodInfo = {
      val methodName = methodDef.methodName
      methodName.paramTypeRefs.foreach(builder.maybeAddReferencedClass)
      builder.maybeAddReferencedClass(methodName.resultTypeRef)

      methodDef.body.foreach(traverse)

      val reachabilityInfo = builder.result()

      MethodInfo(methodDef.body.isEmpty, reachabilityInfo)
    }

    def generateJSConstructorInfo(ctorDef: JSConstructorDef): ReachabilityInfo = {
      ctorDef.body.allStats.foreach(traverse(_))

      builder.result()
    }

    def generateJSMethodInfo(methodDef: JSMethodDef): ReachabilityInfo = {
      traverse(methodDef.name)
      traverse(methodDef.body)

      builder.result()
    }

    def generateJSPropertyInfo(propertyDef: JSPropertyDef): ReachabilityInfo = {
      traverse(propertyDef.name)
      propertyDef.getterBody.foreach(traverse)
      propertyDef.setterArgAndBody foreach { case (_, body) =>
        traverse(body)
      }

      builder.result()
    }

    def generateTopLevelExportInfo(enclosingClass: ClassName,
        topLevelExportDef: TopLevelExportDef): ReachabilityInfo = {
      topLevelExportDef match {
        case _:TopLevelJSClassExportDef =>
          builder.addInstantiatedClass(enclosingClass)

        case _:TopLevelModuleExportDef =>
          builder.addAccessedModule(enclosingClass)

        case topLevelMethodExport: TopLevelMethodExportDef =>
          assert(topLevelMethodExport.methodDef.name.isInstanceOf[StringLiteral])
          traverse(topLevelMethodExport.methodDef.body)

        case topLevelFieldExport: TopLevelFieldExportDef =>
          val field = topLevelFieldExport.field.name
          builder.addStaticFieldRead(field)
          builder.addStaticFieldWritten(field)
      }

      builder.result()
    }

    override def traverse(tree: Tree): Unit = {
      builder.maybeAddReferencedClass(tree.tpe)

      tree match {
        /* Do not call super.traverse() so that fields are not also marked as
         * read.
         */
        case Assign(lhs, rhs) =>
          lhs match {
            case Select(qualifier, field) =>
              builder.addFieldWritten(field.name)
              traverse(qualifier)
            case SelectStatic(field) =>
              builder.addStaticFieldWritten(field.name)
            case JSPrivateSelect(qualifier, field) =>
              builder.addStaticallyReferencedClass(field.name.className) // for the private name of the field
              builder.addFieldWritten(field.name)
              traverse(qualifier)
            case _ =>
              traverse(lhs)
          }
          traverse(rhs)

        // In all other cases, we'll have to call super.traverse()
        case _ =>
          tree match {
            case New(className, ctor, _) =>
              builder.addInstantiatedClass(className, ctor.name)

            case Select(_, field) =>
              builder.addFieldRead(field.name)
            case SelectStatic(field) =>
              builder.addStaticFieldRead(field.name)
            case SelectJSNativeMember(className, member) =>
              builder.addJSNativeMemberUsed(className, member.name)

            case Apply(flags, receiver, method, _) =>
              builder.addMethodCalled(receiver.tpe, method.name)
            case ApplyStatically(flags, _, className, method, _) =>
              val namespace = MemberNamespace.forNonStaticCall(flags)
              builder.addMethodCalledStatically(className,
                  NamespacedMethodName(namespace, method.name))
            case ApplyStatic(flags, className, method, _) =>
              val namespace = MemberNamespace.forStaticCall(flags)
              builder.addMethodCalledStatically(className,
                  NamespacedMethodName(namespace, method.name))
            case ApplyDynamicImport(flags, className, method, _) =>
              val namespace = MemberNamespace.forStaticCall(flags)
              builder.addMethodCalledDynamicImport(className,
                  NamespacedMethodName(namespace, method.name))

            case LoadModule(className) =>
              builder.addAccessedModule(className)

            case IsInstanceOf(_, testType) =>
              builder.maybeAddUsedInstanceTest(testType)

            case AsInstanceOf(_, tpe) =>
              /* In theory, we'd need to reach ClassCastException
               * here (conditional on the semantics) by IR spec.
               * However, since the exact *constructor* is not specified, this
               * makes little sense.
               * Instead, the Emitter simply requests the exception in its
               * symbol requirements.
               */

              builder.maybeAddUsedInstanceTest(tpe)

            case UnaryOp(op, _) =>
              import UnaryOp._

              op match {
                case Class_superClass =>
                  builder.addUsedClassSuperClass()
                case _ =>
                  // do nothing
              }

            case BinaryOp(op, _, rhs) =>
              import BinaryOp._

              op match {
                case Int_/ | Int_% =>
                  rhs match {
                    case IntLiteral(r) if r != 0 =>
                    case _                       => builder.addUsedIntLongDivModByMaybeZero()
                  }
                case Long_/ | Long_% =>
                  rhs match {
                    case LongLiteral(r) if r != 0L =>
                    case _                         => builder.addUsedIntLongDivModByMaybeZero()
                  }
                case Class_newArray =>
                  builder.addUsedClassNewArray()
                case _ =>
                  // do nothing
              }

            case NewArray(typeRef, _) =>
              builder.maybeAddAccessedClassData(typeRef)

            case ArrayValue(typeRef, _) =>
              builder.maybeAddAccessedClassData(typeRef)

            case ArraySelect(_, _) =>
              /* In theory, we'd need to reach ArrayIndexOutOfBoundsException
               * here (conditional on the semantics) by IR spec.
               * However, since the exact *constructor* is not specified, this
               * makes little sense.
               * Instead, the Emitter simply requests the exception in its
               * symbol requirements.
               */

            case ClassOf(cls) =>
              builder.maybeAddAccessedClassData(cls)
              builder.addAccessedClassClass()

            case GetClass(_) =>
              builder.addAccessedClassClass()

            case WrapAsThrowable(_) =>
              builder.addUsedInstanceTest(ThrowableClass)
              builder.addInstantiatedClass(JavaScriptExceptionClass, AnyArgConstructorName)

            case UnwrapFromThrowable(_) =>
              builder.addUsedInstanceTest(JavaScriptExceptionClass)

            case JSPrivateSelect(_, field) =>
              builder.addStaticallyReferencedClass(field.name.className) // for the private name of the field
              builder.addFieldRead(field.name)

            case JSNewTarget() =>
              builder.addAccessNewTarget()

            case JSImportMeta() =>
              builder.addAccessImportMeta()

            case JSBinaryOp(JSBinaryOp.**, _, _) =>
              builder.addUsedExponentOperator()

            case LoadJSConstructor(className) =>
              builder.addInstantiatedClass(className)

            case LoadJSModule(className) =>
              builder.addAccessedModule(className)

            case CreateJSClass(className, _) =>
              builder.addInstantiatedClass(className)

            case VarDef(_, _, vtpe, _, _) =>
              builder.maybeAddReferencedClass(vtpe)

            case _ =>
          }

          super.traverse(tree)
      }
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy