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

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

The newest version!
package dotty.tools.dotc
package transform

import MegaPhase._
import core.DenotTransformers.{DenotTransformer, IdentityDenotTransformer}
import core.Symbols._
import core.Contexts._
import core.Types._
import core.Flags._
import core.StdNames.nme
import core.NameKinds.DirectMethodName
import ast.Trees._
import ast.tpd
import util.Store

/** This phase optimizes code using implicit function types, by applying two rewrite rules.
 *  Let IF be the implicit function type
 *
 *      implicit Us => R
 *
 *  (1) A method definition
 *
 *      def m(xs: Ts): IF = implicit (ys: Us) => E
 *
 *  is expanded to two methods:
 *
 *      def m(xs: Ts): IF = implicit (ys: Us) => m$direct(xs)(ys)
 *      def m$direct(xs: Ts)(ys: Us): R = E
 *
 *  (and equivalently for methods with type parameters or a different number of value parameter lists).
 *  An abstract method definition
 *
 *     def m(xs: Ts): IF
 *
 *  is expanded to:
 *
 *     def m(xs: Ts): IF
 *     def m$direct(xs: Ts)(ys: Us): R
 *
 *  (2) A reference `qual.apply` where `qual` has implicit function type and
 *  `qual` refers to a method `m` is rewritten to a reference to `m$direct`,
 *  keeping the same type and value arguments as they are found in `qual`.
 *
 *  Note: The phase adds direct methods for all methods with IFT results that
 *        are defined in the transformed compilation unit, as well as for all
 *        methods that are referenced from inside the unit. It does NOT do an
 *        info transformer that adds these methods everywhere where an IFT returning
 *        method exists (including in separately compiled classes).
 *        Adding such an info transformer is impractical because it would mean
 *        that we have to force the types of all members of classes that are referenced.
 *        But not adding an info transformer can lead to inconsistencies in RefChecks.
 *        We solve that by ignoring direct methods in Refchecks.
 *        Another, related issue is bridge generation, where we also generate
 *        shortcut methods on the fly.
 */
class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisPhase =>
  import ShortcutImplicits._
  import tpd._

  override def phaseName: String = ShortcutImplicits.name

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

  /** A map to cache mapping local methods to their direct counterparts.
   *  A fresh map is created for each unit.
   */
  private var DirectMeth: Store.Location[MutableSymbolMap[Symbol]] = _
  private def directMeth(implicit ctx: Context) = ctx.store(DirectMeth)

  override def initContext(ctx: FreshContext): Unit =
    DirectMeth = ctx.addLocation[MutableSymbolMap[Symbol]]()


  override def prepareForUnit(tree: Tree)(implicit ctx: Context): Context =
    ctx.fresh.updateStore(DirectMeth, newMutableSymbolMap[Symbol])


  /** The direct method `m$direct` that accompanies the given method `m`.
    *  Create one if it does not exist already.
    */
  private def directMethod(sym: Symbol)(implicit ctx: Context): Symbol =
    if (sym.owner.isClass) shortcutMethod(sym, thisPhase)
    else directMeth.getOrElseUpdate(sym, newShortcutMethod(sym))

  /** Transform `qual.apply` occurrences according to rewrite rule (2) above */
  override def transformSelect(tree: Select)(implicit ctx: Context): Tree =
    if (tree.name == nme.apply &&
        defn.isImplicitFunctionType(tree.qualifier.tpe.widen) &&
        needsImplicitShortcut(tree.qualifier.symbol)) {
      def directQual(tree: Tree): Tree = tree match {
        case Apply(fn, args)     => cpy.Apply(tree)(directQual(fn), args)
        case TypeApply(fn, args) => cpy.TypeApply(tree)(directQual(fn), args)
        case Block(stats, expr)  => cpy.Block(tree)(stats, directQual(expr))
        case tree: RefTree =>
          cpy.Ref(tree)(DirectMethodName(tree.name.asTermName))
            .withType(tree.tpe.asInstanceOf[NamedType].prefix.select(directMethod(tree.symbol)))
      }
      directQual(tree.qualifier)
    } else tree

  /** Transform methods with implicit function type result according to rewrite rule (1) above */
  override def transformDefDef(mdef: DefDef)(implicit ctx: Context): Tree = {
    val original = mdef.symbol
    if (needsImplicitShortcut(original)) {
      val direct = directMethod(original)

      // Move @tailrec to the direct method
      original.getAnnotation(defn.TailrecAnnot) match {
        case Some(annot) =>
          direct.addAnnotation(annot)
          original.removeAnnotation(defn.TailrecAnnot)
        case _ =>
      }

      def splitClosure(tree: Tree): (List[Type] => List[List[Tree]] => Tree, Tree) = tree match {
        case Block(Nil, expr) => splitClosure(expr)
        case Block((meth @ DefDef(nme.ANON_FUN, Nil, clparams :: Nil, _, _)) :: Nil, cl: Closure) =>
          val tparamSyms = mdef.tparams.map(_.symbol)
          val vparamSymss = mdef.vparamss.map(_.map(_.symbol))
          val clparamSyms = clparams.map(_.symbol)
          val remappedCore = (ts: List[Type]) => (prefss: List[List[Tree]]) =>
            meth.rhs
              .subst(tparamSyms ::: (vparamSymss.flatten ++ clparamSyms),
                      ts.map(_.typeSymbol) ::: prefss.flatten.map(_.symbol))
              .changeOwnerAfter(original, direct, thisPhase)
              .changeOwnerAfter(meth.symbol, direct, thisPhase)
          val forwarder = ref(direct)
            .appliedToTypeTrees(tparamSyms.map(ref(_)))
            .appliedToArgss(vparamSymss.map(_.map(ref(_))) :+ clparamSyms.map(ref(_)))
          val fwdClosure = cpy.Block(tree)(cpy.DefDef(meth)(rhs = forwarder) :: Nil, cl)
          (remappedCore, fwdClosure)
        case id: RefTree =>
          val SAMType(mt) = id.tpe.widen
          splitClosure(tpd.Lambda(mt, args => id.select(nme.apply).appliedToArgs(args))(ctx.withOwner(original)))
        case EmptyTree =>
          (_ => _ => EmptyTree, EmptyTree)
      }

      val (remappedCore, fwdClosure) = splitClosure(mdef.rhs)
      val originalDef = cpy.DefDef(mdef)(rhs = fwdClosure)
      val directDef = transformDefDef(polyDefDef(direct.asTerm, remappedCore))
      flatTree(List(originalDef, directDef))
    }
    else mdef
  }
}

object ShortcutImplicits {
  val name: String = "shortcutImplicits"

  /** If this option is true, we don't specialize symbols that are known to be only
   *  targets of monomorphic calls.
   *  The reason for this option is that benchmarks show that on the JVM for monomorphic dispatch
   *  scenarios inlining and escape analysis can often remove all calling overhead, so we might as
   *  well not duplicate the code. We need more experience to decide on the best setting of this option.
   */
  final val specializeMonoTargets = true

  /** Should `sym` get a ..$direct companion?
    *  This is the case if `sym` is a method with a non-nullary implicit function type as final result type.
    *  However if `specializeMonoTargets` is false, we exclude symbols that are known
    *  to be only targets of monomorphic calls because they are effectively
    *  final and don't override anything.
    */
  def needsImplicitShortcut(sym: Symbol)(implicit ctx: Context): Boolean =
    sym.is(Method, butNot = Accessor) &&
    defn.isImplicitFunctionType(sym.info.finalResultType) &&
    defn.functionArity(sym.info.finalResultType) > 0 &&
    !sym.isAnonymousFunction &&
    (specializeMonoTargets || !sym.isEffectivelyFinal || sym.allOverriddenSymbols.nonEmpty)

  /** @pre    The type's final result type is an implicit function type `implicit Ts => R`.
    * @return The type of the `apply` member of `implicit Ts => R`.
    */
  private def directInfo(info: Type)(implicit ctx: Context): Type = info match {
    case info: PolyType   => info.derivedLambdaType(resType = directInfo(info.resultType))
    case info: MethodType => info.derivedLambdaType(resType = directInfo(info.resultType))
    case info: ExprType   => directInfo(info.resultType)
    case info             => info.member(nme.apply).info
  }

  /** A new `m$direct` method to accompany the given method `m` */
  private def newShortcutMethod(sym: Symbol)(implicit ctx: Context): Symbol = {
    val direct = sym.copy(
      name = DirectMethodName(sym.name.asTermName).asInstanceOf[sym.ThisName],
      flags = sym.flags | Synthetic,
      info = directInfo(sym.info))
    // make flags conformant to RefChecks. Override is meaningless after RefChecks.
    if (direct.allOverriddenSymbols.isEmpty) direct.resetFlag(Override)
    direct
  }

  def shortcutMethod(sym: Symbol, phase: DenotTransformer)(implicit ctx: Context): Symbol =
    sym.owner.info.decl(DirectMethodName(sym.name.asTermName))
      .suchThat(_.info matches directInfo(sym.info)).symbol
      .orElse(newShortcutMethod(sym).enteredAfter(phase))

  def isImplicitShortcut(sym: Symbol)(implicit ctx: Context): Boolean = sym.name.is(DirectMethodName)
}






© 2015 - 2025 Weber Informatics LLC | Privacy Policy