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

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

package dotty.tools.dotc
package transform.localopt

import core.Constants.Constant
import core.Contexts.Context
import core.Flags._
import core.Symbols._
import core.Types._
import ast.Trees._
import scala.collection.mutable
import config.Printers.simplify
import Simplify._
import transform.SymUtils._

/** Inline vals and remove vals that are aliases to other vals
 *
 *  Notion of alias is a by-value notion, so "good" casts are ignored.
 *
 *  This phase has to be careful not to eliminate vals that are parts of other types
 *
 *  @author DarkDimius, OlivierBlanvillain
 */
class Devalify extends Optimisation {
  import ast.tpd._

  val timesUsed = newMutableSymbolMap[Int]
  val timesUsedAsType = newMutableSymbolMap[Int]

  val defined = mutable.HashSet[Symbol]()
  val usedInInnerClass = newMutableSymbolMap[Int]
  // Either a duplicate or a read through series of immutable fields
  val copies = newMutableSymbolMap[Tree]

  def clear(): Unit = {
    timesUsed.clear()
    timesUsedAsType.clear()
    defined.clear()
    usedInInnerClass.clear()
    copies.clear()
  }

  def visitType(tp: Type)(implicit ctx: Context): Unit = {
    tp.foreachPart(x => x match {
      case TermRef(NoPrefix, _) =>
        val b4 = timesUsedAsType.getOrElseUpdate(x.termSymbol, 0)
        timesUsedAsType.update(x.termSymbol, b4 + 1)
      case _ =>
    })
  }

  def doVisit(tree: Tree, used: MutableSymbolMap[Int])(implicit ctx: Context): Unit = tree match {
    case valdef: ValDef if !valdef.symbol.is(Param | Mutable | Module | Lazy) &&
                           valdef.symbol.exists && !valdef.symbol.owner.isClass =>
      defined += valdef.symbol

      dropCasts(valdef.rhs) match {
        case t: Tree if readingOnlyVals(t) =>
          copies.update(valdef.symbol, valdef.rhs)
        case _ =>
      }
      visitType(valdef.symbol.info)
    case t: New =>
      val normalized = t.tpt.tpe.normalizedPrefix
      val symIfExists = normalized.termSymbol
      val b4 = used.getOrElseUpdate(symIfExists, 0)
      used(symIfExists) = b4 + 1
      visitType(normalized)

    case valdef: ValDef if valdef.symbol.exists && !valdef.symbol.owner.isClass &&
                           !valdef.symbol.is(Param | Module | Lazy) =>
      // TODO: handle params after constructors. Start changing public signatures by eliminating unused arguments.
      defined += valdef.symbol

    case valdef: ValDef => visitType(valdef.symbol.info)
    case t: DefDef      => visitType(t.symbol.info)
    case t: Typed       => visitType(t.tpt.tpe)
    case t: TypeApply   => t.args.foreach(x => visitType(x.tpe))
    case t: RefTree =>
      val b4 = used.getOrElseUpdate(t.symbol, 0)
      used.update(t.symbol, b4 + 1)
    case _ =>
  }

  def visitor(implicit ctx: Context): Tree => Unit = { tree =>
    def crossingClassBoundaries(t: Tree): Boolean = t match {
      case _: New      => true
      case _: Template => true
      case _           => false
    }
    // We shouldn't inline `This` nodes, which we approximate by not inlining
    // anything across class boundaries. To do so, we visit every class a
    // second time and record what's used in the usedInInnerClass Set.
    if (crossingClassBoundaries(tree)) {
      // Doing a foreachSubTree(tree) here would work, but would also
      // be exponential for deeply nested classes. Instead we do a short
      // circuit traversal that doesn't visit further nested classes.
      val reVisitClass = new TreeAccumulator[Unit] {
        def apply(u: Unit, t: Tree)(implicit ctx: Context): Unit = {
          doVisit(t, usedInInnerClass)
          if (!crossingClassBoundaries(t))
            foldOver((), t)
        }
      }
      reVisitClass.foldOver((), tree)
    }
    doVisit(tree, timesUsed)
  }

  def transformer(implicit ctx: Context): Tree => Tree = {
    val valsToDrop = defined -- timesUsed.keysIterator -- timesUsedAsType.keysIterator
    val copiesToReplaceAsDuplicates = copies.filter { x =>
      val rhs = dropCasts(x._2)
      rhs.isInstanceOf[Literal] || (!rhs.symbol.owner.isClass && !rhs.symbol.is(Method | Mutable))
    } -- timesUsedAsType.keysIterator
    // TODO: if a non-synthetic val is duplicate of a synthetic one, rename a synthetic one and drop synthetic flag?

    val copiesToReplaceAsUsedOnce =
      timesUsed.filter(x => x._2 == 1)
        .flatMap(x => copies.get(x._1) match {
          case Some(tr) => List((x._1, tr))
          case None => Nil
        }) -- timesUsedAsType.keysIterator

    val replacements = copiesToReplaceAsDuplicates ++ copiesToReplaceAsUsedOnce -- usedInInnerClass.keysIterator

    val deepReplacer = new TreeMap() {
      override def transform(tree: Tree)(implicit ctx: Context): Tree = {
        def loop(tree: Tree): Tree  =
          tree match {
            case t: RefTree if replacements.contains(t.symbol) =>
              loop(replacements(t.symbol))
            case _ => tree
          }
        super.transform(loop(tree))
      }
    }

    val transformation: Tree => Tree = {
      case t: ValDef if valsToDrop.contains(t.symbol) =>
        // TODO: Could emit a warning for non synthetic code? This valdef is
        // probably something users would want to remove from source...
        simplify.println(s"Dropping definition of ${t.symbol.showFullName} as not used")
        t.rhs.changeOwner(t.symbol, t.symbol.owner)
      case t: ValDef if replacements.contains(t.symbol) =>
        simplify.println(s"Dropping definition of ${t.symbol.showFullName} as an alias")
        EmptyTree
      case t: New =>
        val symIfExists = t.tpt.tpe.normalizedPrefix.termSymbol
        if (replacements.contains(symIfExists)) {
          val newPrefix = deepReplacer.transform(replacements(symIfExists))
          val newTpt = t.tpt.tpe match {
            case t: NamedType =>
              t.derivedSelect(newPrefix.tpe)
          }
          New(newTpt)
        }
        else t
      case t: RefTree if !t.symbol.is(Method | Param | Mutable) =>
        if (replacements.contains(t.symbol))
          deepReplacer.transform(replacements(t.symbol)).ensureConforms(t.tpe.widen)
        else t
      case t: DefDef if !t.symbol.owner.isClass =>
        if (timesUsed.getOrElse(t.symbol, 0) + timesUsedAsType.getOrElse(t.symbol, 0) != 0) t
        else {
          simplify.println(s"Dropping definition of ${t.symbol.showFullName} as not used")
          EmptyTree
        }
      case tree => tree
    }

    transformation
  }

  def dropCasts(t: Tree)(implicit ctx: Context): Tree = t match {
    // case TypeApply(aio@Select(rec, nm), _) if aio.symbol == defn.Any_asInstanceOf => dropCasts(rec)
    case Typed(t, tpe) => t
    case _ => t
  }

  def readingOnlyVals(t: Tree)(implicit ctx: Context): Boolean = dropCasts(t) match {
    case Typed(exp, _) => readingOnlyVals(exp)

    case TypeApply(fun @ Select(rec, _), List(tp)) =>
      val isAsInstanceOf = fun.symbol == defn.Any_asInstanceOf && rec.tpe.derivesFrom(tp.tpe.classSymbol)
      isAsInstanceOf && readingOnlyVals(rec)

    case t @ Apply(Select(rec, _), Nil) =>
      isImmutableAccessor(t) && readingOnlyVals(rec)

    case t @ Select(rec, _) if t.symbol.is(Method) =>
      isImmutableAccessor(t) && readingOnlyVals(rec)

    case t @ Select(qual, _) if !isEffectivelyMutable(t) =>
      readingOnlyVals(qual)

    case t: Ident if !t.symbol.is(Mutable | Method) && !t.symbol.info.dealias.isInstanceOf[ExprType] =>
      desugarIdent(t) match { case s: Select => readingOnlyVals(s); case _ => true }

    case t: This => true
    // null => false, or the following fails devalify:
    // trait I {
    //   def foo: Any = null
    // }
    // object Main {
    //   def main = {
    //     val s: I = null
    //     s.foo
    //   }
    // }
    case Literal(Constant(null)) => false
    case t: Literal => true
    case _ => false
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy