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

org.scalameta.explore.Macros.scala Maven / Gradle / Ivy

Go to download

Bag of private and public helpers used in scala.meta's APIs and implementations

There is a newer version: 4.12.2
Show newest version
package org.scalameta
package explore

import org.scalameta.internal.MacroHelpers

import scala.collection.mutable.Set
import scala.reflect.macros.whitebox.Context

class ExploreMacros(val c: Context) extends MacroHelpers {
  import c.universe._
  lazy val m = c.mirror

  private implicit class XtensionExploreSymbol(sym: Symbol) {
    def isIrrelevant: Boolean = {
      def artefact = sym.name.decodedName.toString.contains("$")
      def trivial = sym.owner == symbolOf[Any] || sym.owner == symbolOf[Object]
      def arbitrary = {
        val banned = List(
          "scala.collection.generic",
          "scala.Enumeration",
          "scala.math",
          "scala.Int",
          "scala.meta.inline.Api"
        )
        banned.exists(prefix => sym.fullName.startsWith(prefix))
      }
      def aliases = sym.fullName.contains(".Aliases.")
      def contrib = sym.fullName.contains(".contrib.") || sym.fullName.endsWith(".contrib")
      def interactive = sym.fullName.contains(".interactive.") ||
        sym.fullName.endsWith(".interactive")
      def testkit = sym.fullName.contains(".testkit.") || sym.fullName.endsWith(".testkit")
      def tests = sym.fullName.contains(".tests.") || sym.fullName.endsWith(".tests")
      def internal = sym.fullName.contains(".internal.") ||
        (sym.fullName.endsWith(".internal") && !sym.fullName.endsWith(".meta.internal"))
      def invisible = !sym.isPublic
      def inexistent = !sym.asInstanceOf[scala.reflect.internal.SymbolTable#Symbol].exists // NOTE: wtf
      artefact || trivial || arbitrary || aliases || contrib || interactive || testkit || tests ||
      internal || invisible || inexistent
    }
    def isRelevant: Boolean = !isIrrelevant
    def isPkgObject: Boolean = sym.owner.isPackage && sym.isModule && sym.name == termNames.PACKAGE
    def isImplicitClass: Boolean = sym.isClass && sym.isImplicit
    def signatureIn(owner: Symbol): String = {
      def paramss(m: Symbol, wrapFirst: Boolean): String = {
        val pss = m.asMethod.paramLists
        val s_ps = pss.zipWithIndex.map { case (ps, i) =>
          val s_p = ps.map(_.info).mkString(", ")
          val designator = if (ps.exists(_.isImplicit)) "implicit " else ""
          val prefix = if (i != 0 || wrapFirst) "(" else ""
          val suffix = if (i != 0 || wrapFirst) ")" else ""
          prefix + designator + s_p + suffix
        }
        s_ps.mkString("")
      }
      val prefix =
        if (sym.owner.isImplicit) {
          val ctor = sym.owner.info.members
            .find(sym => sym.isMethod && sym.asMethod.isPrimaryConstructor).get
          "* " + paramss(ctor, wrapFirst = false)
        } else scala.reflect.NameTransformer.decode(owner.fullName)
      val main = sym.name.decodedName.toString
      val suffix =
        if (sym.isConstructor || sym.isMacro || sym.isMethod) paramss(sym, wrapFirst = true) +
          ": " + sym.info.finalResultType
        else ""
      prefix + "." + main + suffix
    }
  }

  private def staticClassesObjectsAndVals(
      pkg: Symbol,
      onlyImmediatelyAccessible: Boolean
  ): List[Symbol] = {
    val visited = Set[Symbol]()

    def loop(parent: Symbol): Unit = {
      def mark(sym: Symbol): Boolean = {
        val interesting = sym.isRelevant && !visited(sym)
        if (interesting) visited += sym
        interesting
      }
      def markAndRecur(sym: Symbol): Unit = if (mark(sym)) loop(sym)

      if (parent == NoSymbol) return
      parent.info.members.toList.foreach { sym =>
        def failUnsupported(sym: Symbol): Nothing = c
          .abort(c.enclosingPosition, s"unsupported symbol $sym: ${sym.fullName}")
        if (sym.name.decodedName.toString.contains("$")) {
          // ignore: not worth processing
        } else if (sym.isPackage) {
          if (!onlyImmediatelyAccessible) markAndRecur(sym)
          visited += sym
        } else if (sym.isModule)
          // don't check onlyImmediatelyAccessible
          // because we expect many members of modules to be used via full fullNames
          markAndRecur(sym)
        else if (sym.isClass)
          // don't recur into class members
          mark(sym)
        else if (sym.isMethod) {
          if (sym.asMethod.isGetter) {
            val isInPackageClass = sym.owner.isModuleClass && sym.owner.name == typeNames.PACKAGE
            if (isInPackageClass)
              // doesn't make sense to recur into package class methods
              mark(sym)
            else {
              // handle term aliases
              val target = sym.info.finalResultType.typeSymbol
              if (target.isModuleClass) loop(target.asClass.module) else ()
            }
          }
        } else if (sym.isType)
          // handle type aliases
          // doesn't make sense to recur into type aliases
          mark(sym.info.typeSymbol)
        else if (sym.isTerm)
          if (sym.asTerm.getter != NoSymbol || sym.isPrivateThis) {
            // ignore: processed in sym.isMethod
          } else failUnsupported(sym)
        else failUnsupported(sym)
      }
    }

    assert(pkg.isPackage)
    loop(pkg)

    var result = visited.toList
    result = result.filter(_.isRelevant)
    result
  }

  private def staticsImpl(packageName: Tree, onlyImmediatelyAccessible: Boolean): Tree = {
    val Literal(Constant(s_packageName: String)) = packageName

    val statics =
      staticClassesObjectsAndVals(m.staticPackage(s_packageName), onlyImmediatelyAccessible)
    val nonImplicitClassStatics = statics.filter(!_.isImplicitClass)
    val (pkgObjects, nonPkgObjectStatics) = nonImplicitClassStatics.partition(_.isPkgObject)
    val allowedPkgObjects = pkgObjects
      .filter(sym => sym.owner.fullName == s_packageName || !onlyImmediatelyAccessible)
    val pkgObjectMethods = allowedPkgObjects.flatMap(_.info.members.toList.collect {
      case sym: MethodSymbol
          if !sym.isAccessor && !sym.isImplicit && !sym.isConstructor && sym.isRelevant => sym
    })

    // NOTE: We filtered out package objects and implicit classes (hopefully, IDEs and autocompletes will ignore those),
    // and then we added methods from package objects (because `import scala.meta._` will bring those in).
    val effectiveStatics = nonPkgObjectStatics ++ pkgObjectMethods
    val fullNames = effectiveStatics.map(sym => scala.reflect.NameTransformer.decode(sym.fullName))
    q"${fullNames.distinct.sorted}"
  }

  def wildcardImportStaticsImpl(packageName: Tree): Tree =
    staticsImpl(packageName, onlyImmediatelyAccessible = true)

  def allStaticsImpl(packageName: Tree): Tree =
    staticsImpl(packageName, onlyImmediatelyAccessible = false)

  def extensionSurfaceImpl(packageName: Tree): Tree = {
    val Literal(Constant(s_packageName: String)) = packageName
    val statics =
      staticClassesObjectsAndVals(m.staticPackage(s_packageName), onlyImmediatelyAccessible = false)

    val extensionSurface = {
      val implicitClasses = statics.filter(_.isImplicitClass)
      val result = implicitClasses.flatMap { cls =>
        val extensionMethods = cls.info.decls
          .collect { case sym if sym.isMethod && !sym.isConstructor => sym.asMethod }
        extensionMethods.filter(_.isRelevant)
      }
      result.map(sym => (NoSymbol, sym))
    }
    val s_surface = extensionSurface.collect { case (owner, sym) => sym.signatureIn(owner) }
    q"${s_surface.distinct.sorted}"
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy