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

scala.tools.nsc.tasty.bridge.ContextOps.scala Maven / Gradle / Ivy

The newest version!
/*
 * Scala (https://www.scala-lang.org)
 *
 * Copyright EPFL and Lightbend, Inc.
 *
 * Licensed under Apache License 2.0
 * (http://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package scala.tools.nsc.tasty.bridge

import scala.annotation.tailrec
import scala.reflect.io.AbstractFile

import scala.tools.tasty.{TastyName, TastyFlags}, TastyFlags._, TastyName.ObjectName
import scala.tools.nsc.tasty.{TastyUniverse, TastyModes, SafeEq}, TastyModes._
import scala.reflect.internal.MissingRequirementError
import scala.collection.mutable

/**This contains the definition for [[Context]], along with standard error throwing capabilities with user friendly
 * formatted errors that can change their output depending on the context mode.
 */
trait ContextOps { self: TastyUniverse =>
  import self.{symbolTable => u}

  private def describeOwner(owner: Symbol): String = {
    val kind =
      if (owner.isOneOf(Param | ParamSetter)) {
        if (owner.isType) "type parameter"
        else "parameter"
      }
      else {
        owner.kindString
      }
    s"$kind ${owner.nameString}"
  }

  @inline final def unsupportedTermTreeError[T](noun: String)(implicit ctx: Context): T =
    unsupportedError(
      if (ctx.mode.is(ReadAnnotation)) s"$noun in an annotation of ${describeOwner(ctx.owner)}; note that complex trees are not yet supported for Annotations"
      else noun
    )

  @inline final def unsupportedError[T](noun: String)(implicit ctx: Context): T = {
    typeError(s"Unsupported Scala 3 $noun; found in ${location(ctx.globallyVisibleOwner)}.")
  }

  final def location(owner: Symbol): String = {
    if (owner.isClass) s"${owner.kindString} ${owner.fullNameString}"
    else s"${describeOwner(owner)} in ${location(owner.owner)}"
  }

  @inline final def typeError[T](msg: String): T = throw new u.TypeError(msg)

  @inline final def assertError[T](msg: String): T =
    throw new AssertionError(s"assertion failed: ${u.supplementErrorMessage(msg)}")

  @inline final def assert(assertion: Boolean, msg: => Any): Unit =
    if (!assertion) assertError(String.valueOf(msg))

  @inline final def assert(assertion: Boolean): Unit =
    if (!assertion) assertError("")

  private final def findObject(owner: Symbol, name: u.Name): Symbol = {
    val scope =
      if (owner != null && owner.isClass) owner.rawInfo.decls
      else u.EmptyScope
    val it = scope.lookupAll(name).filter(_.isModule)
    if (it.hasNext) it.next()
    else u.NoSymbol //throw new AssertionError(s"no module $name in ${location(owner)}")
  }

  /**Perform an operation within a context that has the mode `IndexStats` will force any collected annotations
   * afterwards */
  def inIndexStatsContext(op: Context => Unit)(implicit ctx: Context): Unit = {
    val statsCtx = ctx.addMode(IndexStats)
    op(statsCtx)
    statsCtx.initialContext.forceAnnotations()
  }

  /** Perform an operation within a context that has the mode `InnerScope` will enter any inline methods afterwards */
  def inInnerScopeContext(op: Context => Unit)(implicit ctx: Context): Unit = {
    val innerCtx = ctx.addMode(InnerScope)
    op(innerCtx)
    innerCtx.initialContext.enterLatentDefs(innerCtx.owner)
  }


  /** an aggregate of `inInnerScopeContext` within `inIndexStatsContext` */
  def inIndexScopedStatsContext(op: Context => Unit)(implicit ctx: Context): Unit = {
    inIndexStatsContext(inInnerScopeContext(op)(_))(ctx)
  }

  /**Forces lazy annotations, if one is [[scala.annotation.internal.Child]] then it will add the referenced type as a
   * sealed child.
   */
  private def analyseAnnotations(sym: Symbol)(implicit ctx: Context): Unit = {
    for (annot <- sym.annotations) {
      annot.completeInfo()
      if (annot.tpe.typeSymbolDirect === defn.ChildAnnot) {
        val child = annot.tpe.typeArgs.head.typeSymbolDirect
        sym.addChild(child)
        ctx.log(s"adding sealed child ${showSym(child)} to ${showSym(sym)}")
      }
    }
  }

  /**Maintains state through traversal of a TASTy file, such as the outer scope of the defintion being traversed, the
   * traversal mode, and the root owners and source path for the TASTy file.
   * It also provides all operations for manipulation of the symbol table, such as creating/updating symbols and
   * updating their types.
   */
  sealed abstract class Context { thisCtx =>

    protected implicit final def implyThisCtx: thisCtx.type = thisCtx

    /**Associates the annotations with the symbol, and will force their evaluation if not reading statements.*/
    def adjustAnnotations(sym: Symbol, annots: List[DeferredAnnotation]): Unit = {
      if (annots.nonEmpty) {
        if (mode.is(IndexStats)) {
          log(s"lazily adding annotations to ${showSym(sym)}")
          initialContext.stageSymbolToForceAnnots(sym.setAnnotations(annots.map(_.lzy(sym))))
        }
        else {
          log(s"eagerly adding annotations to ${showSym(sym)}")
          analyseAnnotations(sym.setAnnotations(annots.map(_.eager(sym))))
        }
      }
    }

    final def globallyVisibleOwner: Symbol = owner.logicallyEnclosingMember

    final def ignoreAnnotations: Boolean = u.settings.YtastyNoAnnotations
    final def verboseDebug: Boolean = u.settings.debug

    def requiresLatentEntry(decl: Symbol): Boolean = decl.isScala3Macro || decl.isTraitParamAccessor
    def neverEntered(decl: Symbol): Boolean = decl.isPureMixinCtor

    def canEnterOverload(decl: Symbol): Boolean = {
      !(decl.isModule && isSymbol(findObject(thisCtx.owner, decl.name)))
    }

    final def log(str: => String): Unit = {
      if (u.settings.YdebugTasty)
        u.reporter.echo(
          pos = u.NoPosition,
          msg = str.linesIterator.map(line => s"#[$classRoot]: $line").mkString(System.lineSeparator)
        )
    }

    def owner: Symbol
    def source: AbstractFile
    def mode: TastyMode

    private final def loadingMirror: u.Mirror = u.mirrorThatLoaded(owner)

    final def requiredPackage(fullname: TastyName): Symbol = fullname match {
      case TastyName.Root | TastyName.RootPkg => loadingMirror.RootPackage
      case TastyName.EmptyPkg                 => loadingMirror.EmptyPackage
      case fullname                           =>
        symOrDependencyError(false, true, fullname)(loadingMirror.getPackage(encodeTermName(fullname).toString))
    }

    private def symOrDependencyError(isObject: Boolean, isPackage: Boolean, fullname: TastyName)(sym: => Symbol): Symbol = {
      try sym
      catch {
        case _: MissingRequirementError =>
          val kind = if (isObject) "object" else if (isPackage) "package" else "class"
          val addendum = if (mode.is(ReadAnnotation)) s" whilst reading annotation of $owner" else ""
          val msg =
            s"could not find $kind ${fullname.source}$addendum; perhaps it is missing from the classpath."
          typeError(msg)
      }
    }

    final lazy val classRoot: Symbol = initialContext.topLevelClass

    final def newLocalDummy: Symbol = owner.newLocalDummy(u.NoPosition)

    final def newWildcardSym(info: Type): Symbol =
      owner.newTypeParameter(u.nme.WILDCARD.toTypeName, u.NoPosition, u.NoFlags).setInfo(info)

    final def findRootSymbol(roots: Set[Symbol], name: TastyName): Option[Symbol] = {
      import TastyName.TypeName

      def isSameRoot(root: Symbol, selector: u.Name): Boolean =
        (root.owner `eq` this.owner) && selector === root.name

      val selector = encodeTastyName(name)
      roots.find(isSameRoot(_,selector)).map(found =>
        name match {
          case TypeName(_: ObjectName) => found.linkedClassOfClass
          case _                       => found
        }
      )
    }

    final def findOuterClassTypeParameter(name: TastyName.TypeName): Symbol = {
      val selector: u.Name = encodeTypeName(name)
      owner.owner.typeParams.find(selector === _.name).getOrElse {
        throw new AssertionError(s"${owner.owner} has no type params.")
      }
    }

    final def newRefinementSymbol(parent: Type, owner: Symbol, name: TastyName, tpe: Type): Symbol = {
      val overridden = parent.member(encodeTastyName(name))
      val isOverride = isSymbol(overridden)
      var flags      = if (isOverride && overridden.isType) Override else EmptyTastyFlags
      val info = {
        if (name.isTermName) {
          flags |= Method | Deferred
          tpe match {
            case u.TypeRef(_, u.definitions.ByNameParamClass, arg :: Nil) => // nullary method
              u.NullaryMethodType(arg)
            case u.PolyType(tparams, res) if res.paramss.isEmpty => u.PolyType(tparams, u.NullaryMethodType(res))
            case _:u.MethodType | _:u.PolyType => tpe
            case _ => // val, which is not stable if structural. Dotty does not support vars
              if (isOverride && overridden.is(Stable)) flags |= Stable
              u.NullaryMethodType(tpe)
          }
        }
        else {
          if (tpe.isInstanceOf[u.TypeBounds]) flags |= Deferred
          tpe
        }
      }
      unsafeNewSymbol(owner, name, flags, info)
    }

    /** Guards the creation of an object val by checking for an existing definition in the owner's scope
      */
    final def delayCompletion(owner: Symbol, name: TastyName, completer: TastyCompleter, privateWithin: Symbol = noSymbol): Symbol = {
      def default() = unsafeNewSymbol(owner, name, completer.originalFlagSet, completer, privateWithin)
      if (completer.originalFlagSet.is(Object)) {
        val sourceObject = findObject(owner, encodeTermName(name))
        if (isSymbol(sourceObject))
          adjustSymbol(sourceObject, completer.originalFlagSet, completer, privateWithin)
        else
          default()
      }
      else {
        default()
      }
    }

    /** Guards the creation of an object class by checking for an existing definition in the owner's scope
      */
    final def delayClassCompletion(owner: Symbol, typeName: TastyName.TypeName, completer: TastyCompleter, privateWithin: Symbol): Symbol = {
      def default() = unsafeNewClassSymbol(owner, typeName, completer.originalFlagSet, completer, privateWithin)
      if (completer.originalFlagSet.is(Object)) {
        val sourceObject = findObject(owner, encodeTermName(typeName.toTermName))
        if (isSymbol(sourceObject))
          adjustSymbol(sourceObject.objectImplementation, completer.originalFlagSet, completer, privateWithin)
        else
          default()
      }
      else {
        default()
      }
    }

    final def enterIfUnseen(sym: Symbol): Unit = {
      if (mode.is(IndexScopedStats))
        initialContext.collectLatentEvidence(owner, sym)
      val decl = declaringSymbolOf(sym)
      if (!(requiresLatentEntry(decl) || neverEntered(decl)))
        enterIfUnseen0(owner.rawInfo.decls, decl)
    }

    protected final def enterIfUnseen0(decls: u.Scope, decl: Symbol): Unit = {
      if (allowsOverload(decl)) {
        if (canEnterOverload(decl)) {
          decls.enter(decl)
        }
      }
      else {
        decls.enterIfNew(decl)
      }
    }

    /** Unsafe to call for creation of a object val, prefer [[delayCompletion]] if info is a LazyType
      */
    final def unsafeNewSymbol(owner: Symbol, name: TastyName, flags: TastyFlagSet, info: Type, privateWithin: Symbol = noSymbol): Symbol =
      adjustSymbol(unsafeNewUntypedSymbol(owner, name, flags), info, privateWithin)

    /** Unsafe to call for creation of a object class, prefer [[delayClassCompletion]] if info is a LazyType
      */
    final def unsafeNewClassSymbol(owner: Symbol, typeName: TastyName.TypeName, flags: TastyFlagSet, info: Type, privateWithin: Symbol): Symbol =
      adjustSymbol(unsafeNewUntypedClassSymbol(owner, typeName, flags), info, privateWithin)

    private final def unsafeNewUntypedSymbol(owner: Symbol, name: TastyName, flags: TastyFlagSet): Symbol =
      if (flags.is(Param)) {
        if (name.isTypeName) {
          owner.newTypeParameter(encodeTypeName(name.toTypeName), u.NoPosition, encodeFlagSet(flags))
        }
        else {
          owner.newValueParameter(encodeTermName(name), u.NoPosition, encodeFlagSet(flags))
        }
      }
      else if (name === TastyName.Constructor) {
        owner.newConstructor(u.NoPosition, encodeFlagSet(flags &~ Stable))
      }
      else if (name === TastyName.MixinConstructor) {
        owner.newMethodSymbol(u.nme.MIXIN_CONSTRUCTOR, u.NoPosition, encodeFlagSet(flags &~ Stable))
      }
      else if (flags.is(FlagSets.ObjectCreationFlags)) {
        log(s"!!! visited module value $name first")
        assert(!owner.rawInfo.decls.lookupAll(encodeTermName(name)).exists(_.isModule))
        val module = owner.newModule(encodeTermName(name), u.NoPosition, encodeFlagSet(flags))
        module.moduleClass.info = defn.DefaultInfo
        module
      }
      else if (name.isTypeName) {
        owner.newTypeSymbol(encodeTypeName(name.toTypeName), u.NoPosition, encodeFlagSet(flags))
      }
      else {
        owner.newMethodSymbol(encodeTermName(name), u.NoPosition, encodeFlagSet(flags))
      }

    private final def unsafeNewUntypedClassSymbol(owner: Symbol, typeName: TastyName.TypeName, flags: TastyFlagSet): Symbol = {
      if (flags.is(FlagSets.ObjectClassCreationFlags)) {
        log(s"!!! visited module class $typeName first")
        val module = owner.newModule(encodeTermName(typeName), u.NoPosition, encodeFlagSet(FlagSets.ObjectCreationFlags))
        module.info = defn.DefaultInfo
        module.moduleClass.flags = encodeFlagSet(flags)
        module.moduleClass
      }
      else {
        owner.newClassSymbol(encodeTypeName(typeName), u.NoPosition, encodeFlagSet(flags))
      }
    }

    final def enterClassCompletion(): Symbol = {
      val cls = globallyVisibleOwner.asClass
      val assumedSelfType =
        if (cls.is(Object) && cls.owner.isClass) defn.SingleType(cls.owner.thisType, cls.sourceModule)
        else u.NoType
      cls.info = u.ClassInfoType(cls.repr.parents, cls.repr.decls, assumedSelfType.typeSymbolDirect)
      cls
    }

    /** Normalises the parents and sets up value class machinery */
    final def adjustParents(cls: Symbol, parents: List[Type]): List[Type] = {
      val parentTypes = parents.map { tp =>
        val tpe = tp.dealias
        if (tpe.typeSymbolDirect === u.definitions.ObjectClass) u.definitions.AnyRefTpe
        else tpe
      }
      if (parentTypes.head.typeSymbolDirect === u.definitions.AnyValClass) {
        // TODO [tasty]: please reconsider if there is some shared optimised logic that can be triggered instead.
        withPhaseNoLater("extmethods") { ctx0 =>
          // duplicated from scala.tools.nsc.transform.ExtensionMethods
          cls.primaryConstructor.makeNotPrivate(noSymbol)
          for (decl <- cls.info.decls if decl.isMethod) {
            if (decl.isParamAccessor) decl.makeNotPrivate(cls)
            if (!decl.isClassConstructor) {
              val extensionMeth = decl.newExtensionMethodSymbol(cls.companion, u.NoPosition)
              extensionMeth setInfo u.extensionMethInfo(cls, extensionMeth, decl.info, cls)
            }
          }
        }
      }
      parentTypes
    }

    final def removeFlags(symbol: Symbol, flags: TastyFlagSet): symbol.type =
      symbol.resetFlag(encodeFlagSet(flags))

    final def addFlags(symbol: Symbol, flags: TastyFlagSet): symbol.type =
      symbol.setFlag(encodeFlagSet(flags))

    final def adjustSymbol(symbol: Symbol, flags: TastyFlagSet, info: Type, privateWithin: Symbol): symbol.type =
      adjustSymbol(addFlags(symbol, flags), info, privateWithin)

    final def adjustSymbol(symbol: Symbol, info: Type, privateWithin: Symbol): symbol.type = {
      symbol.privateWithin = privateWithin
      symbol.info = info
      symbol
    }

    /** Determines the owner of a refinement in the current context by the following steps:
     *  1) if the owner if this context is a refinement symbol, we are in a recursive RefinedType. Ensure that the
     *     context owner is initialised with the parent and reuse it.
     *  2) if the parent is also a RefinedType, then we will flatten the nested structure by reusing its owner
     *  3) the parent is not a RefinedType, and we are not in an enclosing RefinedType, so create a new RefinementClassSymbol.
     *  The Parent alongside the RefinedType owner are passed to the given operation
     */
    final def enterRefinement[T](parent: Type)(op: Context => T): T = {
      val clazz = owner match {
        case enclosing: u.RefinementClassSymbol =>
          if (!enclosing.hasRawInfo) mkRefinedTypeWith(parent :: Nil, enclosing, u.newScope)
          enclosing
        case _ => parent match {
          case nested: u.RefinedType => nested.typeSymbol
          case _                     => newRefinementClassSymbol
        }
      }
      op(withOwner(clazz))
    }

    final def newRefinementClassSymbol: Symbol = owner.newRefinementClass(u.NoPosition)

    final def setInfo(sym: Symbol, info: Type): Unit = sym.info = info

    final def markAsEnumSingleton(sym: Symbol): Unit =
      sym.updateAttachment(new u.DottyEnumSingleton(sym.name.toString))

    final def onCompletionError[T](sym: Symbol): PartialFunction[Throwable, T] = {
      case err: u.TypeError =>
        sym.info = u.ErrorType
        throw err
    }

    @tailrec
    final def initialContext: InitialContext = this match {
      case ctx: InitialContext => ctx
      case ctx: FreshContext   => ctx.outer.initialContext
    }

    final def withOwner(owner: Symbol): Context =
      if (owner `ne` this.owner) freshSymbol(owner) else this

    final def withNewScope: Context =
      freshSymbol(newLocalDummy)

    final def freshSymbol(owner: Symbol): FreshContext = new FreshContext(owner, this, this.mode)
    final def freshMode(mode: TastyMode): FreshContext = new FreshContext(this.owner, this, mode)
    final def fresh: FreshContext                      = new FreshContext(this.owner, this, this.mode)

    final def addMode(mode: TastyMode): Context =
      if (!this.mode.is(mode)) freshMode(this.mode | mode)
      else this

    final def retractMode(mode: TastyMode): Context =
      if (this.mode.isOneOf(mode)) freshMode(this.mode &~ mode)
      else this

    final def withMode(mode: TastyMode): Context =
      if (mode != this.mode) freshMode(mode)
      else this

    final def withSource(source: AbstractFile): Context =
      if (source `ne` this.source) fresh.atSource(source)
      else this

    final def withPhaseNoLater[T](phase: String)(op: Context => T): T =
      u.enteringPhaseNotLaterThan[T](u.findPhaseWithName(phase))(op(this))
  }


  final class InitialContext(val topLevelClass: Symbol, val source: AbstractFile) extends Context {
    def mode: TastyMode = EmptyTastyMode
    def owner: Symbol = topLevelClass.owner

    private[this] var mySymbolsToForceAnnots: mutable.LinkedHashSet[Symbol] = _

    private[ContextOps] def stageSymbolToForceAnnots(sym: Symbol): Unit = {
      if (sym.annotations.nonEmpty) {
        if (mySymbolsToForceAnnots == null) {
          mySymbolsToForceAnnots = mutable.LinkedHashSet.empty
        }
        mySymbolsToForceAnnots += sym
      }
    }

    /** Force any lazy annotations collected from declaration statements directly in this scope.
     *
     *  It is important to call this *after* indexing statements in a scope, otherwise calling
     *  [[ownertree.findOwner]] can fail, this is because [[ownertree.findOwner]] cannot traverse a definition tree at
     *  a given address before a symbol has been registered to that address.
     */
    private[ContextOps] def forceAnnotations(): Unit = {
      if (mySymbolsToForceAnnots != null) {
        val toForce = mySymbolsToForceAnnots.toList
        mySymbolsToForceAnnots.clear()
        for (sym <- toForce) {
          log(s"!!! forcing annotations on ${showSym(sym)}")
          analyseAnnotations(sym)
        }
        assert(mySymbolsToForceAnnots.isEmpty, "more symbols added while forcing")
      }
    }

    private[this] var myInlineDefs: mutable.Map[Symbol, mutable.ArrayBuffer[Symbol]] = null
    private[this] var myMacros: mutable.Map[Symbol, mutable.ArrayBuffer[Symbol]] = null
    private[this] var myTraitParamAccessors: mutable.Map[Symbol, mutable.ArrayBuffer[Symbol]] = null

    /** Collect evidence from definitions that is required by `enterLatentDefs`. */
    private[ContextOps] def collectLatentEvidence(owner: Symbol, sym: Symbol): Unit = {

      def macroMap() = {
        if (myMacros == null) myMacros = mutable.HashMap.empty
        myMacros
      }

      def inlineMap() = {
        if (myInlineDefs == null) myInlineDefs = mutable.HashMap.empty
        myInlineDefs
      }

      def traitParamAccessors() = {
        if (myTraitParamAccessors == null) myTraitParamAccessors = mutable.HashMap.empty
        myTraitParamAccessors
      }

      def append(map: mutable.Map[Symbol, mutable.ArrayBuffer[Symbol]])(owner: Symbol, sym: Symbol) =
        map.getOrElseUpdate(owner, mutable.ArrayBuffer.empty) += sym

      if (sym.isScala2Macro) append(macroMap())(owner, sym)
      else if (sym.isScala3Inline) append(inlineMap())(owner, sym)
      else if (sym.isTraitParamAccessor) append(traitParamAccessors())(owner, sym)

    }

    /**Should be called after indexing all symbols in the given owners scope.
     *
     * Enters qualifying definitions into the given owners scope, according to the following rules:
     *   - an `inline macro` method (Scala 3 macro) without a corresponding `erased macro` method (Scala 2 macro).
     *
     * Reports illegal definitions:
     *   - trait constructors with parameters
     *
     *  @param cls should be a symbol associated with a non-empty scope
     */
    private[ContextOps] def enterLatentDefs(cls: Symbol): Unit = {

      def macroDefs(cls: Symbol): Option[Iterable[Symbol]] = {
        if (myMacros != null) myMacros.remove(cls)
        else None
      }

      def inlineDefs(cls: Symbol): Option[Iterable[Symbol]] = {
        if (myInlineDefs != null) myInlineDefs.remove(cls)
        else None
      }

      def traitParamAccessors(cls: Symbol): Option[Iterable[Symbol]] = {
        if (myTraitParamAccessors != null) myTraitParamAccessors.remove(cls)
        else None
      }

      def enterInlineDefs(cls: Symbol, decls: u.Scope): Unit = {
        val macros = macroDefs(cls).getOrElse(Iterable.empty)
        val defs   = inlineDefs(cls).getOrElse(Iterable.empty)

        for (d <- defs if !macros.exists(_.name == d.name))
          enterIfUnseen0(decls, d)
      }

      def reportParameterizedTrait(cls: Symbol, decls: u.Scope): Unit = {
        val traitParams = traitParamAccessors(cls).getOrElse(Iterable.empty)
        if (traitParams.nonEmpty) {
          val parameters = traitParams.map(_.nameString)
          val msg = s"parameterized trait ${parameters.mkString(s"${cls.nameString}(", ", ", ")")}"
          unsupportedError(msg)(this.withOwner(cls.owner))
        }
      }

      val decls = cls.rawInfo.decls
      enterInlineDefs(cls, decls)
      reportParameterizedTrait(cls, decls)

    }
  }

  final class FreshContext(val owner: Symbol, val outer: Context, val mode: TastyMode) extends Context {
    private[this] var mySource: AbstractFile = null
    def atSource(source: AbstractFile): this.type = { mySource = source ; this }
    def source: AbstractFile = if (mySource == null) outer.source else mySource
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy