aboutsummaryrefslogblamecommitdiff
path: root/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala
blob: cf529dfdaa1223d8807cc09d0035eee81d74a371 (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                                     
                                            





                                                                                       
                                                                              






                                                                                             



                                                                                    
                                                                                     

                                                                                      
                                                                                        



                                                  
                                                 

                                    



                                                                               
                                                                                       
                                                             































                                                                                                        

                                                                                                 
                                                     

                                                               


                                                  

                                                                           
                                          

                                                

                                                           
                                                              

                                                                    
                                                                                                                


                                                                      

             



                                                    



                                                           





























                                                                                                                   
                                                                                       





                                                                           



                                                                                  

                                              

                                                

                      
                

                          







                                                            

                                                                                   




                                                                 



                                                                               
                                          



                                                                                 
     


                                                     
 
package dotty.tools
package dotc
package ast

import core._
import Types._, Contexts._, Constants._, Names._, Flags._
import SymDenotations._, Symbols._, Annotations._, Trees._, Symbols._
import Denotations._, Decorators._
import dotty.tools.dotc.transform.SymUtils._

/** A map that applies three functions and a substitution together to a tree and
 *  makes sure they are coordinated so that the result is well-typed. The functions are
 *  @param  typeMap  A function from Type to Type that gets applied to the
 *                   type of every tree node and to all locally defined symbols,
 *                   followed by the substitution [substFrom := substTo].
 *  @param treeMap   A transformer that translates all encountered subtrees in
 *                   prefix traversal orders
 *  @param oldOwners Previous owners. If a top-level local symbol in the mapped tree
 *                   has one of these as an owner, the owner is replaced by the corresponding
 *                   symbol in `newOwners`.
 *  @param newOwners New owners, replacing previous owners.
 *  @param substFrom The symbols that need to be substituted.
 *  @param substTo   The substitution targets.
 *
 *  The reason the substitution is broken out from the rest of the type map is
 *  that all symbols have to be substituted at the same time. If we do not do this,
 *  we risk data races on named types. Example: Say we have `outer#1.inner#2` and we
 *  have two substitutions S1 = [outer#1 := outer#3], S2 = [inner#2 := inner#4] where
 *  hashtags precede symbol ids. If we do S1 first, we get outer#2.inner#3. If we then
 *  do S2 we get outer#2.inner#4. But that means that the named type outer#2.inner
 *  gets two different denotations in the same period. Hence, if -Yno-double-bindings is
 *  set, we would get a data race assertion error.
 */
final class TreeTypeMap(
  val typeMap: Type => Type = IdentityTypeMap,
  val treeMap: tpd.Tree => tpd.Tree = identity _,
  val oldOwners: List[Symbol] = Nil,
  val newOwners: List[Symbol] = Nil,
  val substFrom: List[Symbol] = Nil,
  val substTo: List[Symbol] = Nil)(implicit ctx: Context) extends tpd.TreeMap {
  import tpd._

  /** If `sym` is one of `oldOwners`, replace by corresponding symbol in `newOwners` */
  def mapOwner(sym: Symbol) = sym.subst(oldOwners, newOwners)

  /** Replace occurrences of `This(oldOwner)` in some prefix of a type
   *  by the corresponding `This(newOwner)`.
   */
  private val mapOwnerThis = new TypeMap {
    private def mapPrefix(from: List[Symbol], to: List[Symbol], tp: Type): Type = from match {
      case Nil => tp
      case (cls: ClassSymbol) :: from1 => mapPrefix(from1, to.tail, tp.substThis(cls, to.head.thisType))
      case _ :: from1 => mapPrefix(from1, to.tail, tp)
    }
    def apply(tp: Type): Type = tp match {
      case tp: NamedType => tp.derivedSelect(mapPrefix(oldOwners, newOwners, tp.prefix))
      case _ => mapOver(tp)
    }
  }

  def mapType(tp: Type) =
    mapOwnerThis(typeMap(tp).substSym(substFrom, substTo))

  private def updateDecls(prevStats: List[Tree], newStats: List[Tree]): Unit =
    if (prevStats.isEmpty) assert(newStats.isEmpty)
    else {
      prevStats.head match {
        case pdef: MemberDef =>
          val prevSym = pdef.symbol
          val newSym = newStats.head.symbol
          val newCls = newSym.owner.asClass
          if (prevSym != newSym) newCls.replace(prevSym, newSym)
        case _ =>
      }
      updateDecls(prevStats.tail, newStats.tail)
    }

  override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = treeMap(tree) match {
    case impl @ Template(constr, parents, self, _) =>
      val tmap = withMappedSyms(localSyms(impl :: self :: Nil))
      cpy.Template(impl)(
          constr = tmap.transformSub(constr),
          parents = parents mapconserve transform,
          self = tmap.transformSub(self),
          body = impl.body mapconserve
            (tmap.transform(_)(ctx.withOwner(mapOwner(impl.symbol.owner))))
        ).withType(tmap.mapType(impl.tpe))
    case tree1 =>
      tree1.withType(mapType(tree1.tpe)) match {
        case id: Ident if tpd.needsSelect(id.tpe) =>
          ref(id.tpe.asInstanceOf[TermRef]).withPos(id.pos)
        case ddef @ DefDef(name, tparams, vparamss, tpt, _) =>
          val (tmap1, tparams1) = transformDefs(ddef.tparams)
          val (tmap2, vparamss1) = tmap1.transformVParamss(vparamss)
          val res = cpy.DefDef(ddef)(name, tparams1, vparamss1, tmap2.transform(tpt), tmap2.transform(ddef.rhs))
          res.symbol.transformAnnotations {
            case ann: BodyAnnotation => ann.derivedAnnotation(res.rhs)
            case ann => ann
          }
          res
        case blk @ Block(stats, expr) =>
          val (tmap1, stats1) = transformDefs(stats)
          val expr1 = tmap1.transform(expr)
          cpy.Block(blk)(stats1, expr1)
        case inlined @ Inlined(call, bindings, expanded) =>
          val (tmap1, bindings1) = transformDefs(bindings)
          val expanded1 = tmap1.transform(expanded)
          cpy.Inlined(inlined)(call, bindings1, expanded1)
        case cdef @ CaseDef(pat, guard, rhs) =>
          val tmap = withMappedSyms(patVars(pat))
          val pat1 = tmap.transform(pat)
          val guard1 = tmap.transform(guard)
          val rhs1 = tmap.transform(rhs)
          cpy.CaseDef(cdef)(pat1, guard1, rhs1)
        case tree1 =>
          super.transform(tree1)
      }
  }

  override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context) =
    transformDefs(trees)._2

  private def transformDefs[TT <: tpd.Tree](trees: List[TT])(implicit ctx: Context): (TreeTypeMap, List[TT]) = {
    val tmap = withMappedSyms(tpd.localSyms(trees))
    (tmap, tmap.transformSub(trees))
  }

  private def transformVParamss(vparamss: List[List[ValDef]]): (TreeTypeMap, List[List[ValDef]]) = vparamss match {
    case vparams :: rest =>
      val (tmap1, vparams1) = transformDefs(vparams)
      val (tmap2, vparamss2) = tmap1.transformVParamss(rest)
      (tmap2, vparams1 :: vparamss2)
    case nil =>
      (this, vparamss)
  }

  def apply[ThisTree <: tpd.Tree](tree: ThisTree): ThisTree = transform(tree).asInstanceOf[ThisTree]

  def apply(annot: Annotation): Annotation = annot.derivedAnnotation(apply(annot.tree))

  /** The current tree map composed with a substitution [from -> to] */
  def withSubstitution(from: List[Symbol], to: List[Symbol]): TreeTypeMap =
    if (from eq to) this
    else {
      // assert that substitution stays idempotent, assuming its parts are
      // TODO: It might be better to cater for the asserted-away conditions, by
      // setting up a proper substitution abstraction with a compose operator that
      // guarantees idempotence. But this might be too inefficient in some cases.
      // We'll cross that bridge when we need to.
      assert(!from.exists(substTo contains _))
      assert(!to.exists(substFrom contains _))
      assert(!from.exists(newOwners contains _))
      assert(!to.exists(oldOwners contains _))
      new TreeTypeMap(
        typeMap,
        treeMap,
        from ++ oldOwners,
        to ++ newOwners,
        from ++ substFrom,
        to ++ substTo)
    }

  /** Apply `typeMap` and `ownerMap` to given symbols `syms`
   *  and return a treemap that contains the substitution
   *  between original and mapped symbols.
   */
  def withMappedSyms(syms: List[Symbol], mapAlways: Boolean = false): TreeTypeMap =
    withMappedSyms(syms, ctx.mapSymbols(syms, this, mapAlways))

  /** The tree map with the substitution between originals `syms`
   *  and mapped symbols `mapped`. Also goes into mapped classes
   *  and substitutes their declarations.
   */
  def withMappedSyms(syms: List[Symbol], mapped: List[Symbol]): TreeTypeMap = {
    val symsChanged = syms ne mapped
    val substMap = withSubstitution(syms, mapped)
    val fullMap = (substMap /: mapped.filter(_.isClass)) { (tmap, cls) =>
      val origDcls = cls.info.decls.toList
      val mappedDcls = ctx.mapSymbols(origDcls, tmap)
      val tmap1 = tmap.withMappedSyms(origDcls, mappedDcls)
      if (symsChanged) (origDcls, mappedDcls).zipped.foreach(cls.asClass.replace)
      tmap1
    }
    if (symsChanged || (fullMap eq substMap)) fullMap
    else withMappedSyms(syms, mapAlways = true)
  }
}