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

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

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

import core.Phases._
import core.DenotTransformers._
import core.Denotations._
import core.SymDenotations._
import core.Symbols._
import core.Contexts._
import core.Types._
import core.Names._
import core.StdNames._
import core.NameOps._
import core.NameKinds.AdaptedClosureName
import core.Decorators._
import core.Constants._
import core.Definitions._
import typer.NoChecking
import typer.Inliner
import typer.ProtoTypes._
import core.TypeErasure._
import core.Decorators._
import dotty.tools.dotc.ast.{tpd, untpd}
import ast.Trees._
import dotty.tools.dotc.core.{Constants, Flags}
import ValueClasses._
import TypeUtils._
import ExplicitOuter._
import core.Mode
import reporting.trace

class Erasure extends Phase with DenotTransformer {

  override def phaseName: String = Erasure.name

  /** List of names of phases that should precede this phase */
  override def runsAfter: Set[String] = Set(InterceptedMethods.name, ElimRepeated.name)

  override def changesMembers: Boolean = true // the phase adds bridges
  override def changesParents: Boolean = true // the phase drops Any

  def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match {
    case ref: SymDenotation =>
      def isCompacted(sym: Symbol) =
        sym.isAnonymousFunction && {
          sym.info(ctx.withPhase(ctx.phase.next)) match {
            case MethodType(nme.ALLARGS :: Nil) => true
            case _                              => false
          }
        }

      assert(ctx.phase == this, s"transforming $ref at ${ctx.phase}")
      if (ref.symbol eq defn.ObjectClass) {
        // After erasure, all former Any members are now Object members
        val ClassInfo(pre, _, ps, decls, selfInfo) = ref.info
        val extendedScope = decls.cloneScope
        for (decl <- defn.AnyClass.classInfo.decls)
          if (!decl.isConstructor) extendedScope.enter(decl)
        ref.copySymDenotation(
          info = transformInfo(ref.symbol,
              ClassInfo(pre, defn.ObjectClass, ps, extendedScope, selfInfo))
        )
      }
      else {
        val oldSymbol = ref.symbol
        val newSymbol =
          if ((oldSymbol.owner eq defn.AnyClass) && oldSymbol.isConstructor)
            defn.ObjectClass.primaryConstructor
        else oldSymbol
        val oldOwner = ref.owner
        val newOwner = if (oldOwner eq defn.AnyClass) defn.ObjectClass else oldOwner
        val oldName = ref.name
        val newName = ref.erasedName
        val oldInfo = ref.info
        val newInfo = transformInfo(oldSymbol, oldInfo)
        val oldFlags = ref.flags
        val newFlags =
          if (oldSymbol.is(Flags.TermParam) && isCompacted(oldSymbol.owner)) oldFlags &~ Flags.Param
          else oldFlags &~ Flags.HasDefaultParamsFlags // HasDefaultParamsFlags needs to be dropped because overriding might become overloading

        // TODO: define derivedSymDenotation?
        if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldName eq newName) && (oldInfo eq newInfo) && (oldFlags == newFlags))
          ref
        else {
          assert(!ref.is(Flags.PackageClass), s"trans $ref @ ${ctx.phase} oldOwner = $oldOwner, newOwner = $newOwner, oldInfo = $oldInfo, newInfo = $newInfo ${oldOwner eq newOwner} ${oldInfo eq newInfo}")
          ref.copySymDenotation(symbol = newSymbol, owner = newOwner, name = newName, initFlags = newFlags, info = newInfo)
        }
      }
    case ref: JointRefDenotation =>
      new UniqueRefDenotation(
        ref.symbol, transformInfo(ref.symbol, ref.symbol.info), ref.validFor)
    case _ =>
      ref.derivedSingleDenotation(ref.symbol, transformInfo(ref.symbol, ref.symbol.info))
  }

  private[this] val eraser = new Erasure.Typer(this)

  def run(implicit ctx: Context): Unit = {
    val unit = ctx.compilationUnit
    unit.tpdTree = eraser.typedExpr(unit.tpdTree)(ctx.fresh.setPhase(this.next))
  }

  override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = {
    assertErased(tree)
    tree match {
      case res: tpd.This =>
        assert(!ExplicitOuter.referencesOuter(ctx.owner.lexicallyEnclosingClass, res),
          i"Reference to $res from ${ctx.owner.showLocated}")
      case ret: tpd.Return =>
        // checked only after erasure, as checking before erasure is complicated
        // due presence of type params in returned types
        val from = if (ret.from.isEmpty) ctx.owner.enclosingMethod else ret.from.symbol
        val rType = from.info.finalResultType
        assert(ret.expr.tpe <:< rType,
          i"Returned value:${ret.expr}  does not conform to result type(${ret.expr.tpe.widen} of method $from")
      case _ =>
    }
  }

  /** Assert that tree type and its widened underlying type are erased.
   *  Also assert that term refs have fixed symbols (so we are sure
   *  they need not be reloaded using member; this would likely fail as signatures
   *  may change after erasure).
   */
  def assertErased(tree: tpd.Tree)(implicit ctx: Context): Unit = {
    assertErased(tree.typeOpt, tree)
    if (!defn.isPolymorphicAfterErasure(tree.symbol))
      assertErased(tree.typeOpt.widen, tree)
    if (ctx.mode.isExpr)
      tree.tpe match {
        case ref: TermRef =>
          assert(ref.denot.isInstanceOf[SymDenotation] ||
              ref.denot.isInstanceOf[UniqueRefDenotation],
            i"non-sym type $ref of class ${ref.getClass} with denot of class ${ref.denot.getClass} of $tree")
        case _ =>
      }
  }

  def assertErased(tp: Type, tree: tpd.Tree = tpd.EmptyTree)(implicit ctx: Context): Unit = {
    def isAllowed(cls: Symbol, sourceName: String) =
      tp.widen.typeSymbol == cls && ctx.compilationUnit.source.file.name == sourceName
    assert(isErasedType(tp) ||
           isAllowed(defn.ArrayClass, "Array.scala") ||
           isAllowed(defn.TupleClass, "Tuple.scala") ||
           isAllowed(defn.NonEmptyTupleClass, "Tuple.scala") ||
           isAllowed(defn.PairClass, "Tuple.scala"),
        i"The type $tp - ${tp.toString} of class ${tp.getClass} of tree $tree : ${tree.tpe} / ${tree.getClass} is illegal after erasure, phase = ${ctx.phase.prev}")
  }
}

object Erasure {
  import tpd._
  import TypeTestsCasts._

  val name: String = "erasure"

  object Boxing {

    def isUnbox(sym: Symbol)(implicit ctx: Context): Boolean =
      sym.name == nme.unbox && sym.owner.linkedClass.isPrimitiveValueClass

    def isBox(sym: Symbol)(implicit ctx: Context): Boolean =
      sym.name == nme.box && sym.owner.linkedClass.isPrimitiveValueClass

    def boxMethod(cls: ClassSymbol)(implicit ctx: Context): Symbol =
      cls.linkedClass.info.member(nme.box).symbol
    def unboxMethod(cls: ClassSymbol)(implicit ctx: Context): Symbol =
      cls.linkedClass.info.member(nme.unbox).symbol

    /** Isf this tree is an unbox operation which can be safely removed
     *  when enclosed in a box, the unboxed argument, otherwise EmptyTree.
     *  Note that one can't always remove a Box(Unbox(x)) combination because the
     *  process of unboxing x may lead to throwing an exception.
     *  This is important for specialization: calls to the super constructor should not box/unbox specialized
     *  fields (see TupleX). (ID)
     */
    private def safelyRemovableUnboxArg(tree: Tree)(implicit ctx: Context): Tree = tree match {
      case Apply(fn, arg :: Nil)
      if isUnbox(fn.symbol) && defn.ScalaBoxedClasses().contains(arg.tpe.widen.typeSymbol) =>
        arg
      case _ =>
        EmptyTree
    }

    def constant(tree: Tree, const: Tree)(implicit ctx: Context): Tree =
      (if (isPureExpr(tree)) const else Block(tree :: Nil, const)).withSpan(tree.span)

    final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = trace(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") {
      tree.tpe.widen match {
        case ErasedValueType(tycon, _) =>
          New(tycon, cast(tree, underlyingOfValueClass(tycon.symbol.asClass)) :: Nil) // todo: use adaptToType?
        case tp =>
          val cls = tp.classSymbol
          if (cls eq defn.UnitClass) constant(tree, ref(defn.BoxedUnit_UNIT))
          else if (cls eq defn.NothingClass) tree // a non-terminating expression doesn't need boxing
          else {
            assert(cls ne defn.ArrayClass)
            val arg = safelyRemovableUnboxArg(tree)
            if (arg.isEmpty) ref(boxMethod(cls.asClass)).appliedTo(tree)
            else {
              ctx.log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}")
              arg
            }
          }
      }
    }

    def unbox(tree: Tree, pt: Type)(implicit ctx: Context): Tree = trace(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") {
      pt match {
        case ErasedValueType(tycon, underlying) =>
          def unboxedTree(t: Tree) =
            adaptToType(t, tycon)
            .select(valueClassUnbox(tycon.symbol.asClass))
            .appliedToNone

          // Null unboxing needs to be treated separately since we cannot call a method on null.
          // "Unboxing" null to underlying is equivalent to doing null.asInstanceOf[underlying]
          // See tests/pos/valueclasses/nullAsInstanceOfVC.scala for cases where this might happen.
          val tree1 =
            if (tree.tpe isRef defn.NullClass)
              adaptToType(tree, underlying)
            else if (!(tree.tpe <:< tycon)) {
              assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass))
              val nullTree = nullLiteral
              val unboxedNull = adaptToType(nullTree, underlying)

              evalOnce(tree) { t =>
                If(t.select(defn.Object_eq).appliedTo(nullTree),
                  unboxedNull,
                  unboxedTree(t))
              }
            } else unboxedTree(tree)

          cast(tree1, pt)
        case _ =>
          val cls = pt.widen.classSymbol
          if (cls eq defn.UnitClass) constant(tree, Literal(Constant(())))
          else {
            assert(cls ne defn.ArrayClass)
            ref(unboxMethod(cls.asClass)).appliedTo(tree)
          }
      }
    }

    /** Generate a synthetic cast operation from tree.tpe to pt.
     *  Does not do any boxing/unboxing (this is handled upstream).
     *  Casts from and to ErasedValueType are special, see the explanation
     *  in ExtensionMethods#transform.
     */
    def cast(tree: Tree, pt: Type)(implicit ctx: Context): Tree = trace(i"cast ${tree.tpe.widen} --> $pt", show = true) {

      def wrap(tycon: TypeRef) =
        ref(u2evt(tycon.typeSymbol.asClass)).appliedTo(tree)
      def unwrap(tycon: TypeRef) =
        ref(evt2u(tycon.typeSymbol.asClass)).appliedTo(tree)

      assert(!pt.isInstanceOf[SingletonType], pt)
      if (pt isRef defn.UnitClass) unbox(tree, pt)
      else (tree.tpe.widen, pt) match {
        case (JavaArrayType(treeElem), JavaArrayType(ptElem))
        if treeElem.widen.isPrimitiveValueType && !ptElem.isPrimitiveValueType =>
          // See SI-2386 for one example of when this might be necessary.
          cast(ref(defn.runtimeMethodRef(nme.toObjectArray)).appliedTo(tree), pt)

        // When casting between two EVTs, we need to check which one underlies the other to determine
        // whether u2evt or evt2u should be used.
        case (tp1 @ ErasedValueType(tycon1, underlying1), tp2 @ ErasedValueType(tycon2, underlying2)) =>
          if (tp1 <:< underlying2)
            // Cast EVT(tycon1, underlying1) to EVT(tycon2, EVT(tycon1, underlying1))
            wrap(tycon2)
          else {
            assert(underlying1 <:< tp2, i"Non-sensical cast between unrelated types $tp1 and $tp2")
            // Cast EVT(tycon1, EVT(tycon2, underlying2)) to EVT(tycon2, underlying2)
            unwrap(tycon1)
          }

        // When only one type is an EVT then we already know that the other one is the underlying
        case (_, ErasedValueType(tycon2, _)) =>
          wrap(tycon2)
        case (ErasedValueType(tycon1, _), _) =>
          unwrap(tycon1)

        case _ =>
          if (pt.isPrimitiveValueType)
            primitiveConversion(tree, pt.classSymbol)
          else
            tree.asInstance(pt)
      }
    }

    /** Adaptation of an expression `e` to an expected type `PT`, applying the following
     *  rewritings exhaustively as long as the type of `e` is not a subtype of `PT`.
     *
     *    e -> e()           if `e` appears not as the function part of an application
     *    e -> box(e)        if `e` is of erased value type
     *    e -> unbox(e, PT)  otherwise, if `PT` is an erased value type
     *    e -> box(e)        if `e` is of primitive type and `PT` is not a primitive type
     *    e -> unbox(e, PT)  if `PT` is a primitive type and `e` is not of primitive type
     *    e -> cast(e, PT)   otherwise
     */
    def adaptToType(tree: Tree, pt: Type)(implicit ctx: Context): Tree =
      if (pt.isInstanceOf[FunProto]) tree
      else tree.tpe.widen match {
        case MethodType(Nil) if tree.isTerm =>
          adaptToType(tree.appliedToNone, pt)
        case tpw =>
          if (pt.isInstanceOf[ProtoType] || tree.tpe <:< pt)
            tree
          else if (tpw.isErasedValueType)
            adaptToType(box(tree), pt)
          else if (pt.isErasedValueType)
            adaptToType(unbox(tree, pt), pt)
          else if (tpw.isPrimitiveValueType && !pt.isPrimitiveValueType)
            adaptToType(box(tree), pt)
          else if (pt.isPrimitiveValueType && !tpw.isPrimitiveValueType)
            adaptToType(unbox(tree, pt), pt)
          else
            cast(tree, pt)
      }
  }

  class Typer(erasurePhase: DenotTransformer) extends typer.ReTyper with NoChecking {
    import Boxing._

    def isErased(tree: Tree)(implicit ctx: Context): Boolean = tree match {
      case TypeApply(Select(qual, _), _) if tree.symbol == defn.Any_typeCast =>
        isErased(qual)
      case _ => tree.symbol.isEffectivelyErased
    }

    private def checkNotErased(tree: Tree)(implicit ctx: Context): tree.type = {
      if (!ctx.mode.is(Mode.Type)) {
        if (isErased(tree))
          ctx.error(em"${tree.symbol} is declared as erased, but is in fact used", tree.sourcePos)
        tree.symbol.getAnnotation(defn.CompileTimeOnlyAnnot) match {
          case Some(annot) =>
            def defaultMsg =
              s"""Reference to ${tree.symbol.showLocated} should not have survived,
                 |it should have been processed and eliminated during expansion of an enclosing macro or term erasure."""
            val message = annot.argumentConstant(0).fold(defaultMsg)(_.stringValue)
            ctx.error(message, tree.sourcePos)
          case _ => // OK
        }
      }
      tree
    }

    def erasedDef(sym: Symbol)(implicit ctx: Context): Thicket = {
      if (sym.owner.isClass) sym.dropAfter(erasurePhase)
      tpd.EmptyTree
    }

    def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = {
      val tp = tree.typeOpt
      if (tree.isTerm) erasedRef(tp) else valueErasure(tp)
    }

    override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
      assert(tree.hasType)
      val erasedTp = erasedType(tree)
      ctx.log(s"promoting ${tree.show}: ${erasedTp.showWithUnderlying()}")
      tree.withType(erasedTp)
    }

    /** When erasing most TypeTrees we should not semi-erase value types.
     *  This is not the case for [[DefDef#tpt]], [[ValDef#tpt]] and [[Typed#tpt]], they
     *  are handled separately by [[typedDefDef]], [[typedValDef]] and [[typedTyped]].
     */
    override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree =
      tree.withType(erasure(tree.tpe))

    /** This override is only needed to semi-erase type ascriptions */
    override def typedTyped(tree: untpd.Typed, pt: Type)(implicit ctx: Context): Tree = {
      val Typed(expr, tpt) = tree
      val tpt1 = tpt match {
        case Block(_, tpt) => tpt // erase type aliases (statements) from type block
        case tpt => tpt
      }
      val tpt2 = promote(tpt1)
      val expr1 = typed(expr, tpt2.tpe)
      assignType(untpd.cpy.Typed(tree)(expr1, tpt2), tpt2)
    }

    override def typedLiteral(tree: untpd.Literal)(implicit ctx: Context): Tree =
      if (tree.typeOpt.isRef(defn.UnitClass))
        tree.withType(tree.typeOpt)
      else if (tree.const.tag == Constants.ClazzTag)
        Literal(Constant(erasure(tree.const.typeValue)))
      else
        super.typedLiteral(tree)

    override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = {
      checkNotErased(super.typedIdent(tree, pt))
    }

    /** Type check select nodes, applying the following rewritings exhaustively
     *  on selections `e.m`, where `OT` is the type of the owner of `m` and `ET`
     *  is the erased type of the selection's original qualifier expression.
     *
     *      e.m1 -> e.m2          if `m1` is a member of a class that erases to Object and `m2` is
     *                            the same-named member in Object.
     *      e.m -> box(e).m       if `e` is primitive and `m` is a member or a reference class
     *                            or `e` has an erased value class type.
     *      e.m -> unbox(e).m     if `e` is not primitive and `m` is a member of a primtive type.
     *      e.m -> cast(e, OT).m  if the type of `e` does not conform to OT and `m`
     *                            is not an array operation.
     *
     *  If `m` is an array operation, i.e. one of the members apply, update, length, clone, and
     *   of class Array, we additionally try the following rewritings:
     *
     *      e.m -> runtime.array_m(e)   if ET is Object
     *      e.m -> cast(e, ET).m        if the type of `e` does not conform to ET
     *      e.clone -> e.clone'         where clone' is Object's clone method
     *      e.m -> e.[]m                if `m` is an array operation other than `clone`.
     */
    override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = {
      val qual1 = typed(tree.qualifier, AnySelectionProto)

      def mapOwner(sym: Symbol): Symbol = {
        // PolyFunction apply Selects will not have a symbol, so deduce the owner
        // from the typed qual.
        def polyOwner: Symbol =
          if (sym.exists || tree.name != nme.apply) NoSymbol
          else {
            val owner = qual1.tpe.widen.typeSymbol
            if (defn.isFunctionClass(owner)) owner else NoSymbol
          }

        polyOwner orElse {
          val owner = sym.owner
          if (defn.specialErasure.contains(owner)) {
            assert(sym.isConstructor, s"${sym.showLocated}")
            defn.specialErasure(owner)
          } else if (defn.isSyntheticFunctionClass(owner))
            defn.erasedFunctionClass(owner)
          else
            owner
        }
      }

      val origSym = tree.symbol
      val owner = mapOwner(origSym)
      val sym = if (owner eq origSym.maybeOwner) origSym else owner.info.decl(tree.name).symbol
      assert(sym.exists, origSym.showLocated)

      def select(qual: Tree, sym: Symbol): Tree =
        untpd.cpy.Select(tree)(qual, sym.name).withType(NamedType(qual.tpe, sym))

      def selectArrayMember(qual: Tree, erasedPre: Type): Tree =
        if (erasedPre isRef defn.ObjectClass)
          runtimeCallWithProtoArgs(tree.name.genericArrayOp, pt, qual)
        else if (!(qual.tpe <:< erasedPre))
          selectArrayMember(cast(qual, erasedPre), erasedPre)
        else
          assignType(untpd.cpy.Select(tree)(qual, tree.name.primitiveArrayOp), qual)

      def adaptIfSuper(qual: Tree): Tree = qual match {
        case Super(thisQual, untpd.EmptyTypeIdent) =>
          val SuperType(thisType, supType) = qual.tpe
          if (sym.owner.is(Flags.Trait))
            cpy.Super(qual)(thisQual, untpd.Ident(sym.owner.asClass.name))
              .withType(SuperType(thisType, sym.owner.typeRef))
          else
            qual.withType(SuperType(thisType, thisType.firstParent.typeConstructor))
        case _ =>
          qual
      }

      def recur(qual: Tree): Tree = {
        val qualIsPrimitive = qual.tpe.widen.isPrimitiveValueType
        val symIsPrimitive = sym.owner.isPrimitiveValueClass
        if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType)
          recur(box(qual))
        else if (!qualIsPrimitive && symIsPrimitive)
          recur(unbox(qual, sym.owner.typeRef))
        else if (sym.owner eq defn.ArrayClass)
          selectArrayMember(qual, erasure(tree.qualifier.typeOpt.widen.finalResultType))
        else {
          val qual1 = adaptIfSuper(qual)
          if (qual1.tpe.derivesFrom(sym.owner) || qual1.isInstanceOf[Super])
            select(qual1, sym)
          else
            recur(cast(qual1, sym.owner.typeRef))
        }
      }

      checkNotErased(recur(qual1))
    }

    override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
      if (tree.symbol == ctx.owner.lexicallyEnclosingClass || tree.symbol.isStaticOwner) promote(tree)
      else {
        ctx.log(i"computing outer path from ${ctx.owner.ownersIterator.toList}%, % to ${tree.symbol}, encl class = ${ctx.owner.enclosingClass}")
        outer.path(toCls = tree.symbol)
      }

    private def runtimeCallWithProtoArgs(name: Name, pt: Type, args: Tree*)(implicit ctx: Context): Tree = {
      val meth = defn.runtimeMethodRef(name)
      val followingParams = meth.symbol.info.firstParamTypes.drop(args.length)
      val followingArgs = protoArgs(pt, meth.widen).zipWithConserve(followingParams)(typedExpr).asInstanceOf[List[tpd.Tree]]
      ref(meth).appliedToArgs(args.toList ++ followingArgs)
    }

    private def protoArgs(pt: Type, methTp: Type): List[untpd.Tree] = (pt, methTp) match {
      case (pt: FunProto, methTp: MethodType) if methTp.isErasedMethod =>
        protoArgs(pt.resType, methTp.resType)
      case (pt: FunProto, methTp: MethodType) =>
        pt.args ++ protoArgs(pt.resType, methTp.resType)
      case _ => Nil
    }

    override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = {
      val ntree = interceptTypeApply(tree.asInstanceOf[TypeApply])(ctx.withPhase(ctx.erasurePhase))

      ntree match {
        case TypeApply(fun, args) =>
          val fun1 = typedExpr(fun, WildcardType)
          fun1.tpe.widen match {
            case funTpe: PolyType =>
              val args1 = args.mapconserve(typedType(_))
              untpd.cpy.TypeApply(tree)(fun1, args1).withType(funTpe.instantiate(args1.tpes))
            case _ => fun1
          }
        case _ => typedExpr(ntree, pt)
      }
    }

	/** Besides normal typing, this method collects all arguments
	 *  to a compacted function into a single argument of array type.
	 */
    override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {
      val Apply(fun, args) = tree
      if (fun.symbol == defn.cbnArg)
        typedUnadapted(args.head, pt)
      else typedExpr(fun, FunProto(args, pt)(this, isGivenApply = false)) match {
        case fun1: Apply => // arguments passed in prototype were already passed
          fun1
        case fun1 =>
          fun1.tpe.widen match {
            case mt: MethodType =>
              val outers = outer.args(fun.asInstanceOf[tpd.Tree]) // can't use fun1 here because its type is already erased
              val ownArgs = if (mt.paramNames.nonEmpty && !mt.isErasedMethod) args else Nil
              var args0 = outers ::: ownArgs ::: protoArgs(pt, tree.typeOpt)

              if (args0.length > MaxImplementedFunctionArity && mt.paramInfos.length == 1) {
                val bunchedArgs = untpd.JavaSeqLiteral(args0, TypeTree(defn.ObjectType))
                  .withType(defn.ArrayOf(defn.ObjectType))
                args0 = bunchedArgs :: Nil
              }
              assert(args0 hasSameLengthAs mt.paramInfos)
              val args1 = args0.zipWithConserve(mt.paramInfos)(typedExpr)
              untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType
            case _ =>
              throw new MatchError(i"tree $tree has unexpected type of function ${fun1.tpe.widen}, was ${fun.typeOpt.widen}")
          }
      }
    }

    // The following four methods take as the proto-type the erasure of the pre-existing type,
    // if the original proto-type is not a value type.
    // This makes all branches be adapted to the correct type.
    override def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(implicit ctx: Context): SeqLiteral =
      super.typedSeqLiteral(tree, erasure(tree.typeOpt))
        // proto type of typed seq literal is original type;

    override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree =
      super.typedIf(tree, adaptProto(tree, pt))

    override def typedMatch(tree: untpd.Match, pt: Type)(implicit ctx: Context): Tree =
      super.typedMatch(tree, adaptProto(tree, pt))

    override def typedTry(tree: untpd.Try, pt: Type)(implicit ctx: Context): Try =
      super.typedTry(tree, adaptProto(tree, pt))

    private def adaptProto(tree: untpd.Tree, pt: Type)(implicit ctx: Context) = {
      if (pt.isValueType) pt else {
        if (tree.typeOpt.derivesFrom(ctx.definitions.UnitClass))
          tree.typeOpt
        else valueErasure(tree.typeOpt)
      }
    }

    override def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Tree =
      super.typedInlined(tree, pt) match {
        case tree: Inlined => Inliner.dropInlined(tree)
      }

    override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): Tree =
      if (sym.isEffectivelyErased) erasedDef(sym)
      else
        super.typedValDef(untpd.cpy.ValDef(vdef)(
          tpt = untpd.TypedSplice(TypeTree(sym.info).withSpan(vdef.tpt.span))), sym)

    /** Besides normal typing, this function also compacts anonymous functions
     *  with more than `MaxImplementedFunctionArity` parameters to use a single
     *  parameter of type `[]Object`.
     */
    override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context): Tree =
      if (sym.isEffectivelyErased) erasedDef(sym)
      else {
        val restpe =
          if (sym.isConstructor) defn.UnitType
          else sym.info.resultType
        var vparamss1 = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil
        var rhs1 = ddef.rhs match {
          case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe))
          case _ => ddef.rhs
        }
        if (sym.isAnonymousFunction && vparamss1.head.length > MaxImplementedFunctionArity) {
          val bunchedParam = ctx.newSymbol(sym, nme.ALLARGS, Flags.TermParam, JavaArrayType(defn.ObjectType))
          def selector(n: Int) = ref(bunchedParam)
            .select(defn.Array_apply)
            .appliedTo(Literal(Constant(n)))
          val paramDefs = vparamss1.head.zipWithIndex.map {
            case (paramDef, idx) =>
              assignType(untpd.cpy.ValDef(paramDef)(rhs = selector(idx)), paramDef.symbol)
          }
          vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil
          rhs1 = untpd.Block(paramDefs, rhs1)
        }
        vparamss1 = vparamss1.mapConserve(_.filterConserve(!_.symbol.is(Flags.Erased)))
        val ddef1 = untpd.cpy.DefDef(ddef)(
          tparams = Nil,
          vparamss = vparamss1,
          tpt = untpd.TypedSplice(TypeTree(restpe).withSpan(ddef.tpt.span)),
          rhs = rhs1)
        super.typedDefDef(ddef1, sym)
      }

    override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = {
      val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol)
      var implClosure @ Closure(_, meth, _) = super.typedClosure(tree, pt)
      if (xxl) implClosure = cpy.Closure(implClosure)(tpt = TypeTree(defn.FunctionXXLType))
      implClosure.tpe match {
        case SAMType(sam) =>
          val implType = meth.tpe.widen.asInstanceOf[MethodType]

          val implParamTypes = implType.paramInfos
          val List(samParamTypes) = sam.paramInfoss
          val implResultType = implType.resultType
          val samResultType = sam.resultType

          // The following code:
          //
          //     val f: Function1[Int, Any] = x => ...
          //
          // results in the creation of a closure and a method in the typer:
          //
          //     def $anonfun(x: Int): Any = ...
          //     val f: Function1[Int, Any] = closure($anonfun)
          //
          // Notice that `$anonfun` takes a primitive as argument, but the single abstract method
          // of `Function1` after erasure is:
          //
          //     def apply(x: Object): Object
          //
          // which takes a reference as argument. Hence, some form of adaptation is required.
          //
          // If we do nothing, the LambdaMetaFactory bootstrap method will
          // automatically do the adaptation. Unfortunately, the result does not
          // implement the expected Scala semantics: null should be "unboxed" to
          // the default value of the value class, but LMF will throw a
          // NullPointerException instead. LMF is also not capable of doing
          // adaptation for derived value classes.
          //
          // Thus, we need to replace the closure method by a bridge method that
          // forwards to the original closure method with appropriate
          // boxing/unboxing. For our example above, this would be:
          //
          //     def $anonfun1(x: Object): Object = $anonfun(BoxesRunTime.unboxToInt(x))
          //     val f: Function1 = closure($anonfun1)
          //
          // In general a bridge is needed when, after Erasure, one of the
          // parameter type or the result type of the closure method has a
          // different type, and we cannot rely on auto-adaptation.
          //
          // Auto-adaptation works in the following cases:
          // - If the SAM is replaced by JFunction*mc* in
          //   [[FunctionalInterfaces]], no bridge is needed: the SAM contains
          //   default methods to handle adaptation.
          // - If a result type of the closure method is a primitive value type
          //   different from Unit, we can rely on the auto-adaptation done by
          //   LMF (because it only needs to box, not unbox, so no special
          //   handling of null is required).
          // - If the SAM is replaced by JProcedure* in
          //   [[DottyBackendInterface]] (this only happens when no explicit SAM
          //   type is given), no bridge is needed to box a Unit result type:
          //   the SAM contains a default method to handle that.
          //
          // See test cases lambda-*.scala and t8017/ for concrete examples.

          if (!defn.isSpecializableFunction(implClosure.tpe.widen.classSymbol.asClass, implParamTypes, implResultType)) {
            def autoAdaptedParam(tp: Type) = !tp.isErasedValueType && !tp.isPrimitiveValueType
            val explicitSAMType = implClosure.tpt.tpe.exists
            def autoAdaptedResult(tp: Type) = !tp.isErasedValueType &&
              (!explicitSAMType || tp.typeSymbol != defn.UnitClass)
            def sameSymbol(tp1: Type, tp2: Type) = tp1.typeSymbol == tp2.typeSymbol

            val paramAdaptationNeeded =
              (implParamTypes, samParamTypes).zipped.exists((implType, samType) =>
                !sameSymbol(implType, samType) && !autoAdaptedParam(implType))
            val resultAdaptationNeeded =
              !sameSymbol(implResultType, samResultType) && !autoAdaptedResult(implResultType)

            if (paramAdaptationNeeded || resultAdaptationNeeded) {
              val bridgeType =
                if (paramAdaptationNeeded) {
                  if (resultAdaptationNeeded) sam
                  else implType.derivedLambdaType(paramInfos = samParamTypes)
                } else implType.derivedLambdaType(resType = samResultType)
              val bridge = ctx.newSymbol(ctx.owner, AdaptedClosureName(meth.symbol.name.asTermName), Flags.Synthetic | Flags.Method, bridgeType)
              val bridgeCtx = ctx.withOwner(bridge)
              Closure(bridge, bridgeParamss => {
                implicit val ctx = bridgeCtx

                val List(bridgeParams) = bridgeParamss
                val rhs = Apply(meth, (bridgeParams, implParamTypes).zipped.map(adapt(_, _)))
                adapt(rhs, bridgeType.resultType)
              }, targetType = implClosure.tpt.tpe)
            } else implClosure
          } else implClosure
        case _ =>
          implClosure
      }
    }

    override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(implicit ctx: Context): Tree =
      EmptyTree

    override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = {
      val stats1 =
        if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass, erasurePhase).add(stats)
        else stats
      super.typedStats(stats1, exprOwner).filter(!_.isEmpty)
    }

    override def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree =
      trace(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) {
        assert(ctx.phase == ctx.erasurePhase || ctx.phase == ctx.erasurePhase.next, ctx.phase)
        if (tree.isEmpty) tree
        else if (ctx.mode is Mode.Pattern) tree // TODO: replace with assertion once pattern matcher is active
        else adaptToType(tree, pt)
      }

    override def simplify(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): tree.type = tree
  }

  private def takesBridges(sym: Symbol)(implicit ctx: Context): Boolean =
    sym.isClass && !sym.isOneOf(Flags.Trait | Flags.Package)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy