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

dotty.tools.dotc.transform.localopt.InlineCaseIntrinsics.scala Maven / Gradle / Ivy

package dotty.tools.dotc
package transform.localopt

import core.Constants.Constant
import core.Contexts.Context
import core.StdNames._
import core.Symbols._
import core.Types._
import core.Flags._
import core.TypeApplications.noBounds
import ast.Trees._
import transform.SymUtils._
import dotty.tools.dotc.ast.tpd

/** Inline case class specific methods using desugarings assumptions.
 *
 *  Note: to run this optimisation after erasure one would need to specialize
 *  it for constructor with outer pointer and values classes. There is
 *  probably no need to run this more than once.
 *
 *  @author DarkDimius, OlivierBlanvillain
 */
class InlineCaseIntrinsics(val simplifyPhase: Simplify) extends Optimisation {
  import ast.tpd._

  def visitor(implicit ctx: Context): Tree => Unit = NoVisitor
  def clear(): Unit = ()

  def transformer(implicit ctx: Context): Tree => Tree = {
    // For synthetic applies on case classes (both dotty/scalac)
    // - CC.apply(args) → new CC(args)
    case a: Apply
      if !a.tpe.isInstanceOf[MethodicType]           &&
         a.symbol.is(Synthetic)                      &&
         a.symbol.owner.is(Module)                   &&
         a.symbol.name == nme.apply                  &&
         a.symbol.owner.companionClass.is(CaseClass) &&
         !a.tpe.derivesFrom(defn.EnumClass)          &&
         (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) =>

      def unrollArgs(t: Tree, l: List[List[Tree]]): List[List[Tree]] = t match {
        case Apply(t, args) => unrollArgs(t, args :: l)
        case _ => l
      }
      val argss = unrollArgs(a.fun, a.args :: Nil)
      def rollInArgs(l: List[List[Tree]], fun: Tree): Tree = l match {
        case head :: tail => rollInArgs(tail, fun.appliedToArgs(head))
        case _ => fun
      }
      val constructor = a.symbol.owner.companionClass.primaryConstructor.asTerm
      evalreceiver(a, rollInArgs(argss.tail, New(a.tpe.widenDealias.simplified, constructor, argss.head)))

    // For synthetic dotty unapplies on case classes:
    // - CC.unapply(arg): CC → arg
    // - CC.unapply(arg): Boolean → true, dotty only
    // - CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N))
    case a: Apply
      if a.symbol.is(Synthetic)                       &&
         a.symbol.owner.is(Module)                    &&
         a.symbol.name == nme.unapply                 &&
         a.symbol.owner.companionClass.is(CaseClass)  &&
         !a.tpe.derivesFrom(defn.EnumClass)           &&
         (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) =>

      val args = a.args.head
      val isDottyUnapply = !a.symbol.owner.is(Scala2x)
      val isScalaOptionUnapply =
        a.tpe.derivesFrom(defn.OptionClass) &&
        a.args.head.tpe.derivesFrom(a.symbol.owner.companionClass)

      if (isDottyUnapply) { // dotty only
        if (a.tpe.derivesFrom(defn.BooleanClass))
          // CC.unapply(arg): Boolean → true
          evalreceiver(a, Literal(Constant(true)))
        else
          // CC.unapply(arg): CC → arg
          evalreceiver(a, a.args.head)
      }
      else if (isScalaOptionUnapply) {
        // CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N))
        // The output is defined as a Tree => Tree to go thought tpd.evalOnce.
        def some(e: Tree) = {
          val accessors = e.tpe.widenDealias.classSymbol.caseAccessors.filter(_.is(Method))
          val fields    = accessors.map(x => e.select(x).ensureApplied)
          val tplType   = noBounds(a.tpe.baseType(defn.OptionClass).argInfos.head)
          val someTpe   = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass)

          if (fields.tail.nonEmpty)
            New(someTpe, New(tplType, fields) :: Nil)
          else // scalac does not have Tuple1
            New(someTpe, fields.head :: Nil)
        }
        val none = ref(defn.NoneModuleRef)
        def isNull(e: Tree) = e.select(defn.Object_eq).appliedTo(Literal(Constant(null)))
        def fi(e: Tree) = If(isNull(e), none, some(e))
        evalreceiver(a, evalOnce(a.args.head)(fi))
      }
      else a

    // Seq.unapplySeq(arg) → new Some(arg)
    // Where Seq is any companion of type <: SeqFactoryClass
    case a: Apply
      if a.symbol.name == nme.unapplySeq                  &&
         a.symbol.owner.derivesFrom(simplifyPhase.SeqFactoryClass) &&
         a.symbol.extendedOverriddenSymbols.isEmpty       &&
         (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) =>

      def receiver(t: Tree): Type = t match {
        case t: Apply     => receiver(t.fun)
        case t: TypeApply => receiver(t.fun)
        case t: Ident     =>
          val prefix = desugarIdentPrefix(t)
          prefix.tpe.widenDealias
        case t: Select => t.qualifier.tpe.widenDealias
      }

      val recv = receiver(a)
      if (recv.typeSymbol.is(Module)) {
        val someTpe  = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass)
        evalreceiver(a, New(someTpe, a.args.head :: Nil))
      }
      else a
    case t => t
  }

  // Apply fun may be a side-effectful function. E.g. a block, see tests/run/t4859.scala
  // we need to maintain expressions that were in this block
  def evalreceiver(a: Apply, res: Tree)(implicit ctx: Context) = {
    def receiver(t: Tree): Tree = t match {
      case TypeApply(fun, targs) if fun.symbol eq t.symbol => receiver(fun)
      case Apply(fn, args) if fn.symbol == t.symbol => receiver(fn)
      case Select(qual, _) => qual
      case x => x
    }
    val recv = receiver(a)
    if (recv.isEmpty || isPureRef(recv))
      res
    else
      Block(recv :: Nil, res)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy