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

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

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

import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Flags._
import dotty.tools.dotc.core.Symbols.Symbol
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
import dotty.tools.dotc.transform.SymUtils._

import scala.collection.mutable

import java.util.IdentityHashMap

object CollectNullableFields {
  val name: String = "collectNullableFields"
}

/** Collect fields that can be nulled out after use in lazy initialization.
 *
 *  This information is used during lazy val transformation to assign null to private
 *  fields that are only used within a lazy val initializer. This is not just an optimization,
 *  but is needed for correctness to prevent memory leaks. E.g.
 *
 *  ```scala
 *  class TestByNameLazy(byNameMsg: => String) {
 *    lazy val byLazyValMsg = byNameMsg
 *  }
 *  ```
 *
 *  Here `byNameMsg` should be null out once `byLazyValMsg` is
 *  initialised.
 *
 *  A field is nullable if all the conditions below hold:
 *    - belongs to a non trait-class
 *    - is private[this]
 *    - is not lazy
 *    - its type is nullable
 *    - is only used in a lazy val initializer
 *    - defined in the same class as the lazy val
 */
class CollectNullableFields extends MiniPhase {
  import tpd._

  override def phaseName: String = CollectNullableFields.name

  /** Running after `ElimByName` to see by names as nullable types. */
  override def runsAfter: Set[String] = Set(ElimByName.name)

  private[this] sealed trait FieldInfo
  private[this] case object NotNullable extends FieldInfo
  private[this] case class Nullable(by: Symbol) extends FieldInfo

  /** Whether or not a field is nullable */
  private[this] var nullability: IdentityHashMap[Symbol, FieldInfo] = _

  override def prepareForUnit(tree: Tree)(implicit ctx: Context): Context = {
    if (nullability == null) nullability = new IdentityHashMap
    ctx
  }

  private def recordUse(tree: Tree)(implicit ctx: Context): Tree = {
    val sym = tree.symbol
    val isNullablePrivateField =
      sym.isField &&
      !sym.is(Lazy) &&
      !sym.owner.is(Trait) &&
      sym.initial.isAllOf(PrivateLocal) &&
      sym.info.widenDealias.typeSymbol.isNullableClass

    if (isNullablePrivateField)
      nullability.get(sym) match {
        case Nullable(from) if from != ctx.owner => // used in multiple lazy val initializers
          nullability.put(sym, NotNullable)
        case null => // not in the map
          val from = ctx.owner
          val isNullable =
            from.is(Lazy, butNot = Module) && // is lazy val
            from.owner.isClass &&             // is field
            from.owner.eq(sym.owner)          // is lazy val and field defined in the same class
          val info = if (isNullable) Nullable(from) else NotNullable
          nullability.put(sym, info)
        case _ =>
          // Do nothing for:
          //  - NotNullable
          //  - Nullable(ctx.owner)
      }

    tree
  }

  override def transformIdent(tree: Ident)(implicit ctx: Context): Tree =
    recordUse(tree)

  override def transformSelect(tree: Select)(implicit ctx: Context): Tree =
    recordUse(tree)

  /** Map lazy values to the fields they should null after initialization. */
  def lazyValNullables(implicit ctx: Context): IdentityHashMap[Symbol, mutable.ListBuffer[Symbol]] = {
    val result = new IdentityHashMap[Symbol, mutable.ListBuffer[Symbol]]

    nullability.forEach {
      case (sym, Nullable(from)) =>
        val bldr = result.computeIfAbsent(from, _ => new mutable.ListBuffer)
        bldr += sym
      case _ =>
    }

    result
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy