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

dotty.tools.dotc.transform.ExtensionMethods.scala Maven / Gradle / Ivy

The newest version!
/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author Martin Odersky
 */
package dotty.tools.dotc
package transform

import dotty.tools.dotc.transform.MegaPhase._
import ValueClasses._
import dotty.tools.dotc.ast.tpd
import scala.collection.mutable
import core._
import Types._, Contexts._, Names._, Flags._, DenotTransformers._
import SymDenotations._, Symbols._, StdNames._, Denotations._
import TypeErasure.{ valueErasure, ErasedValueType }
import NameKinds.{ExtMethName, UniqueExtMethName}
import Decorators._

/**
 * Perform Step 1 in the inline classes SIP: Creates extension methods for all
 * methods in a value class, except parameter or super accessors, or constructors.
 *
 * Additionally, for a value class V, let U be the underlying type after erasure. We add
 * to the companion module of V two cast methods:
 *   def u2evt$(x0: U): ErasedValueType(V, U)
 *   def evt2u$(x0: ErasedValueType(V, U)): U
 * The casts are used in [[Erasure]] to make it typecheck, they are then removed
 * in [[ElimErasedValueType]].
 * This is different from the implementation of value classes in Scala 2
 * (see SIP-15) which uses `asInstanceOf` which does not typecheck.
 *
 * Finally, if the constructor of a value class is private pr protected
 * it is widened to public.
 *
 * Also, drop the Local flag from all private[this] and protected[this] members
 * that will be moved to the companion object.
 */
class ExtensionMethods extends MiniPhase with DenotTransformer with FullParameterization { thisPhase =>

  import tpd._
  import ExtensionMethods._

  /** the following two members override abstract members in Transform */
  override def phaseName: String = ExtensionMethods.name

  override def runsAfter: Set[String] = Set(
    ElimRepeated.name,
    ProtectedAccessors.name,  // protected accessors cannot handle code that is moved from class to companion object
  )

  override def runsAfterGroupsOf: Set[String] = Set(FirstTransform.name) // need companion objects to exist

  override def changesMembers: Boolean = true // the phase adds extension methods

  override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match {
    case moduleClassSym: ClassDenotation if moduleClassSym.is(ModuleClass) =>
      moduleClassSym.linkedClass match {
        case valueClass: ClassSymbol if isDerivedValueClass(valueClass) =>
          val cinfo = moduleClassSym.classInfo
          val decls1 = cinfo.decls.cloneScope
          val moduleSym = moduleClassSym.symbol.asClass

          def enterInModuleClass(sym: Symbol): Unit = {
            decls1.enter(sym)
            // This is tricky: in this denotation transformer, we transform
            // companion modules of value classes by adding methods to them.
            // Running the transformer will create these methods, but they're
            // only valid once it has finished running. This means we cannot use
            // `ctx.withPhase(thisPhase.next)` here without potentially running
            // into cycles. Instead, we manually set their validity after having
            // created them to match the validity of the owner transformed
            // denotation.
            sym.validFor = thisPhase.validFor
          }

          // Create extension methods, except if the class comes from Scala 2
          // because it adds extension methods before pickling.
          if (!(valueClass.is(Scala2x)))
            for (decl <- valueClass.classInfo.decls)
              if (isMethodWithExtension(decl))
                enterInModuleClass(createExtensionMethod(decl, moduleClassSym.symbol))

          // Create synthetic methods to cast values between the underlying type
          // and the ErasedValueType. These methods are removed in ElimErasedValueType.
          val underlying = valueErasure(underlyingOfValueClass(valueClass))
          val evt = ErasedValueType(valueClass.typeRef, underlying)
          val u2evtSym = ctx.newSymbol(moduleSym, nme.U2EVT, Synthetic | Method,
            MethodType(List(nme.x_0), List(underlying), evt))
          val evt2uSym = ctx.newSymbol(moduleSym, nme.EVT2U, Synthetic | Method,
            MethodType(List(nme.x_0), List(evt), underlying))
          enterInModuleClass(u2evtSym)
          enterInModuleClass(evt2uSym)

          moduleClassSym.copySymDenotation(info = cinfo.derivedClassInfo(decls = decls1))
        case _ =>
          moduleClassSym
      }
    case ref: SymDenotation =>
      var ref1 = ref
      if (isMethodWithExtension(ref.symbol) && ref.hasAnnotation(defn.TailrecAnnot)) {
        ref1 = ref.copySymDenotation()
        ref1.removeAnnotation(defn.TailrecAnnot)
      }
      else if (ref.isConstructor && isDerivedValueClass(ref.owner) && ref.isOneOf(AccessFlags)) {
        ref1 = ref.copySymDenotation()
        ref1.resetFlag(AccessFlags)
      }
      // Drop the Local flag from all private[this] and protected[this] members
      // that will be moved to the companion object.
      if (ref.is(Local) && isDerivedValueClass(ref.owner)) {
        if (ref1 ne ref) ref1.resetFlag(Local)
        else ref1 = ref1.copySymDenotation(initFlags = ref1.flags &~ Local)
      }
      ref1
    case _ =>
      ref.info match {
        case ClassInfo(pre, cls, _, _, _) if cls is ModuleClass =>
          cls.linkedClass match {
            case valueClass: ClassSymbol if isDerivedValueClass(valueClass) =>
              val info1 = cls.denot(ctx.withPhase(ctx.phase.next)).asClass.classInfo.derivedClassInfo(prefix = pre)
              ref.derivedSingleDenotation(ref.symbol, info1)
            case _ => ref
          }
        case _ => ref
      }
  }

  protected def rewiredTarget(target: Symbol, derived: Symbol)(implicit ctx: Context): Symbol =
    if (isMethodWithExtension(target) &&
        target.owner.linkedClass == derived.owner) extensionMethod(target)
    else NoSymbol

  private def createExtensionMethod(imeth: Symbol, staticClass: Symbol)(implicit ctx: Context): TermSymbol = {
    val extensionName = extensionNames(imeth).head.toTermName
    val extensionMeth = ctx.newSymbol(staticClass, extensionName,
      (imeth.flags | Final) &~ (Override | Protected | AbsOverride),
      fullyParameterizedType(imeth.info, imeth.owner.asClass),
      privateWithin = imeth.privateWithin, coord = imeth.coord)
    extensionMeth.addAnnotations(imeth.annotations)(ctx.withPhase(thisPhase))
      // need to change phase to add tailrec annotation which gets removed from original method in the same phase.
    extensionMeth
  }

  private val extensionDefs = newMutableSymbolMap[mutable.ListBuffer[Tree]]
  // TODO: this is state and should be per-run
  // todo: check that when transformation finished map is empty

  override def transformTemplate(tree: tpd.Template)(implicit ctx: Context): tpd.Tree = {
    if (isDerivedValueClass(ctx.owner)) {
      /* This is currently redundant since value classes may not
         wrap over other value classes anyway.
        checkNonCyclic(ctx.owner.pos, Set(), ctx.owner) */
      tree
    } else if (ctx.owner.isStaticOwner) {
      extensionDefs remove tree.symbol.owner match {
        case Some(defns) if defns.nonEmpty =>
          cpy.Template(tree)(body = tree.body ++
            defns.map(transformFollowing(_)))
        case _ =>
          tree
      }
    } else tree
  }

  override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context): tpd.Tree = {
    if (isMethodWithExtension(tree.symbol)) {
      val origMeth = tree.symbol
      val origClass = ctx.owner.asClass
      val staticClass = origClass.linkedClass
      assert(staticClass.exists, s"$origClass lacks companion, ${origClass.owner.definedPeriodsString} ${origClass.owner.info.decls} ${origClass.owner.info.decls}")
      val extensionMeth = extensionMethod(origMeth)
      ctx.log(s"Value class $origClass spawns extension method.\n  Old: ${origMeth.showDcl}\n  New: ${extensionMeth.showDcl}")
      val store = extensionDefs.getOrElseUpdate(staticClass, new mutable.ListBuffer[Tree])
      store += fullyParameterizedDef(extensionMeth, tree)
      cpy.DefDef(tree)(rhs = forwarder(extensionMeth, tree))
    } else tree
  }
}

object ExtensionMethods {
  val name: String = "extmethods"

  /** Generate stream of possible names for the extension version of given instance method `imeth`.
   *  If the method is not overloaded, this stream consists of just "imeth$extension".
   *  If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the
   *  index of imeth in the sequence of overloaded alternatives with the same name. This choice will
   *  always be picked as the name of the generated extension method.
   *  After this first choice, all other possible indices in the range of 0 until the number
   *  of overloaded alternatives are returned. The secondary choices are used to find a matching method
   *  in `extensionMethod` if the first name has the wrong type. We thereby gain a level of insensitivity
   *  of how overloaded types are ordered between phases and picklings.
   */
  private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = {
    val decl = imeth.owner.info.decl(imeth.name)

    /** No longer needed for Dotty, as we are more disciplined with scopes now.
    // Bridge generation is done at phase `erasure`, but new scopes are only generated
    // for the phase after that. So bridges are visible in earlier phases.
    //
    // `info.member(imeth.name)` filters these out, but we need to use `decl`
    // to restrict ourselves to members defined in the current class, so we
    // must do the filtering here.
    val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe
    */
    decl match {
      case decl: MultiDenotation =>
        val alts = decl.alternatives
        val index = alts indexOf imeth.denot
        assert(index >= 0, alts + " does not contain " + imeth)
        def altName(index: Int) = UniqueExtMethName(imeth.name.asTermName, index)
        altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName)
      case decl =>
        assert(decl.exists, imeth.name + " not found in " + imeth.owner + "'s decls: " + imeth.owner.info.decls)
        Stream(ExtMethName(imeth.name.asTermName))
    }
  }

  /** Return the extension method that corresponds to given instance method `meth`. */
  def extensionMethod(imeth: Symbol)(implicit ctx: Context): TermSymbol =
    ctx.atPhase(ctx.extensionMethodsPhase.next) { implicit ctx =>
      // FIXME use toStatic instead?
      val companion = imeth.owner.companionModule
      val companionInfo = companion.info
      val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists)
      val matching = candidates filter (c => FullParameterization.memberSignature(c.info) == imeth.signature)
      assert(matching.nonEmpty,
       i"""no extension method found for:
          |
          |  $imeth:${imeth.info.show} with signature ${imeth.signature} in ${companion.moduleClass}
          |
          | Candidates:
          |
          | ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")}
          |
          | Candidates (signatures normalized):
          |
          | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + FullParameterization.memberSignature(c.info)).mkString("\n")}
          |
          | Eligible Names: ${extensionNames(imeth).mkString(",")}""")
      matching.head.asTerm
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy