dotty.tools.dotc.transform.Dependencies.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala3-compiler_3 Show documentation
Show all versions of scala3-compiler_3 Show documentation
scala3-compiler-bootstrapped
package dotty.tools
package dotc
package transform
import core.*
import Symbols.*, Contexts.*, Types.*, Flags.*, Decorators.*
import collection.mutable.{LinkedHashMap, LinkedHashSet}
import annotation.constructorOnly
import scala.compiletime.uninitialized
import dotty.tools.backend.sjs.JSDefinitions.jsdefn
/** Exposes the dependencies of the `root` tree in three functions or maps:
* `freeVars`, `tracked`, and `logicalOwner`.
*/
abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Context):
import ast.tpd.*
/** The symbol is a method or a lazy val that will be mapped to a method */
protected def isExpr(sym: Symbol)(using Context): Boolean
/** The closest enclosing symbol in the current context for which `isExpr` is true */
protected def enclosure(using Context): Symbol
/** The set of free variables of a function, including free variables of its callees */
def freeVars(sym: Symbol): collection.Set[Symbol] = free.getOrElse(sym, Set.empty)
/** The set of functions that have free variables, i.e for which `freeVars` is non-empty */
def tracked: Iterable[Symbol] = free.keys
/** The outermost class that captures all free variables of a function
* that are captured by enclosinh classes (this means that the function could
* be placed in that class without having to add more environment parameters)
*/
def logicalOwner: collection.Map[Symbol, Symbol] = logicOwner
private type SymSet = LinkedHashSet[Symbol]
/** A map storing free variables of functions and classes */
private val free: LinkedHashMap[Symbol, SymSet] = new LinkedHashMap
/** A hashtable storing calls between functions */
private val called = new LinkedHashMap[Symbol, SymSet]
/** A map from local methods and classes to the owners to which they will be lifted as members.
* For methods and classes that do not have any dependencies this will be the enclosing package.
* symbols with packages as lifted owners will subsequently represented as static
* members of their toplevel class, unless their enclosing class was already static.
* Note: During tree transform (which runs at phase LambdaLift + 1), liftedOwner
* is also used to decide whether a method had a term owner before.
*/
private val logicOwner = new LinkedHashMap[Symbol, Symbol]
/** A flag to indicate whether new free variables have been found */
private var changedFreeVars: Boolean = uninitialized
/** A flag to indicate whether lifted owners have changed */
private var changedLogicOwner: Boolean = uninitialized
private def newSymSet: LinkedHashSet[Symbol] = new LinkedHashSet[Symbol]
private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet =
f.getOrElseUpdate(sym, newSymSet)
/** A symbol is local if it is owned by a term or a local trait,
* or if it is a constructor of a local symbol.
* Note: we count members of local traits as local since their free variables
* have to be passed on from their callers. By contrast, class members get their
* free variable proxies from their enclosing class.
*/
private def isLocal(sym: Symbol)(using Context): Boolean =
val owner = sym.maybeOwner
owner.isTerm
|| owner.is(Trait) && isLocal(owner)
|| sym.isConstructor && isLocal(owner)
/** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested
* than the previous value of `liftedowner(sym)`.
*/
private def narrowLogicOwner(sym: Symbol, owner: Symbol)(using Context): Unit =
if sym.maybeOwner.isTerm
&& owner.isProperlyContainedIn(logicOwner(sym))
&& owner != sym
then
report.log(i"narrow lifted $sym to $owner")
changedLogicOwner = true
logicOwner(sym) = owner
/** Mark symbol `sym` as being free in `enclosure`, unless `sym` is defined
* in `enclosure` or there is an intermediate class properly containing `enclosure`
* in which `sym` is also free. Also, update `liftedOwner` of `enclosure` so
* that `enclosure` can access `sym`, or its proxy in an intermediate class.
* This means:
*
* 1. If there is an intermediate class in which `sym` is free, `enclosure`
* must be contained in that class (in order to access the `sym proxy stored
* in the class).
*
* 2. If there is no intermediate class, `enclosure` must be contained
* in the class enclosing `sym`.
*
* @return If there is a non-trait class between `enclosure` and
* the owner of `sym`, the largest such class.
* Otherwise, if there is a trait between `enclosure` and
* the owner of `sym`, the largest such trait.
* Otherwise, NoSymbol.
*
* @pre sym.owner.isTerm, (enclosure.isMethod || enclosure.isClass)
*
* The idea of `markFree` is illustrated with an example:
*
* def f(x: int) = {
* class C {
* class D {
* val y = x
* }
* }
* }
*
* In this case `x` is free in the primary constructor of class `C`.
* but it is not free in `D`, because after lambda lift the code would be transformed
* as follows:
*
* def f(x$0: int) {
* class C(x$0: int) {
* val x$1 = x$0
* class D {
* val y = outer.x$1
* }
* }
* }
*/
private def markFree(sym: Symbol, enclosure: Symbol)(using Context): Symbol =
import Dependencies.NoPath
try
if !enclosure.exists then throw NoPath()
if enclosure == sym.enclosure then NoSymbol
else
def nestedInConstructor(sym: Symbol): Boolean =
sym.isConstructor
|| sym.isTerm && nestedInConstructor(sym.enclosure)
report.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure")
val intermediate =
if enclosure.is(PackageClass) then enclosure
else if enclosure.isConstructor then markFree(sym, enclosure.owner.enclosure)
else markFree(sym, enclosure.enclosure)
if intermediate.exists then
narrowLogicOwner(enclosure, intermediate)
if !intermediate.isRealClass || nestedInConstructor(enclosure) then
// Constructors and methods nested inside traits get the free variables
// of the enclosing trait or class.
// Conversely, local traits do not get free variables.
// Methods inside constructors also don't have intermediates,
// need to get all their free variables passed directly.
if !enclosure.is(Trait) then
if symSet(free, enclosure).add(sym) then
changedFreeVars = true
report.log(i"$sym is free in $enclosure")
if intermediate.isRealClass then intermediate
else if enclosure.isRealClass then enclosure
else if intermediate.isClass then intermediate
else if enclosure.isClass then enclosure
else NoSymbol
catch case ex: NoPath =>
println(i"error lambda lifting ${ctx.compilationUnit}: $sym is not visible from $enclosure")
throw ex
private def markCalled(callee: Symbol, caller: Symbol)(using Context): Unit = {
report.debuglog(i"mark called: $callee of ${callee.owner} is called by $caller in ${caller.owner}")
assert(isLocal(callee))
symSet(called, caller) += callee
}
protected def process(tree: Tree)(using Context) =
val sym = tree.symbol
def narrowTo(thisClass: ClassSymbol) =
val enclMethod = enclosure
val enclClass = enclMethod.enclosingClass
narrowLogicOwner(enclMethod,
if enclClass.isContainedIn(thisClass) then thisClass
else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner
/** Set the first owner of a local method or class that's nested inside a term.
* This is either the enclosing package or the enclosing class. If the former,
* the method will be be translated to a static method of its toplevel class.
* In that case, we might later re-adjust the owner to a nested class via
* `narrowTo` when we see that the method refers to the this-type of that class.
* We choose the enclosing package when there's something potentially to gain from this
* and when it is safe to do so
*/
def setLogicOwner(local: Symbol) =
val encClass = local.owner.enclosingClass
// When to prefer the enclosing class over the enclosing package:
val preferEncClass =
encClass.isStatic
// If class is not static, we try to hoist the method out of
// the class to avoid the outer pointer.
&& (encClass.isProperlyContainedIn(local.topLevelClass)
// If class is nested in an outer object, we prefer to leave the method in the class,
// since putting it in the outer object makes access more complicated
|| encClass.is(ModuleClass, butNot = Package)
// If class is an outermost object we also want to avoid making the
// method static since that could cause deadlocks in interacting
// with class initialization. See deadlock.scala
)
&& (!sym.isAnonymousFunction || sym.owner.ownersIterator.exists(_.isConstructor))
// The previous conditions mean methods in static objects and nested static classes
// don't get lifted out to be static. In general it is prudent to do that. However,
// for anonymous functions, we prefer them to be static because that means lambdas
// are memoized and can be serialized even if the enclosing object or class
// is not serializable. See run/lambda-serialization-gc.scala and run/i19224.scala.
// On the other hand, we don't want to lift anonymous functions from inside the
// object or class constructor to be static since that can cause again deadlocks
// by its interaction with class initialization. See run/deadlock.scala, which works
// in Scala 3 but deadlocks in Scala 2.
||
/* Scala.js: Never move any member beyond the boundary of a DynamicImportThunk.
* DynamicImportThunk subclasses are boundaries between the eventual ES modules
* that can be dynamically loaded. Moving members across that boundary changes
* the dynamic and static dependencies between ES modules, which is forbidden.
*/
ctx.settings.scalajs.value && encClass.isSubClass(jsdefn.DynamicImportThunkClass)
logicOwner(sym) = if preferEncClass then encClass else local.enclosingPackageClass
tree match
case tree: Ident =>
if isLocal(sym) then
if isExpr(sym) then markCalled(sym, enclosure)
else if sym.isTerm then markFree(sym, enclosure)
def captureImplicitThis(x: Type): Unit = x match
case tr@TermRef(x, _) if !tr.termSymbol.isStatic => captureImplicitThis(x)
case x: ThisType if !x.tref.typeSymbol.isStaticOwner => narrowTo(x.tref.typeSymbol.asClass)
case _ =>
captureImplicitThis(tree.tpe)
case tree: Select =>
if isExpr(sym) && isLocal(sym) then markCalled(sym, enclosure)
case tree: This =>
narrowTo(tree.symbol.asClass)
case tree: MemberDef if isExpr(sym) && sym.owner.isTerm =>
setLogicOwner(sym)
// this will make methods in supercall constructors of top-level classes owned
// by the enclosing package, which means they will be static.
// On the other hand, all other methods will be indirectly owned by their
// top-level class. This avoids possible deadlocks when a static method
// has to access its enclosing object from the outside.
case tree: DefDef if sym.isPrimaryConstructor && isLocal(sym.owner) && !sym.owner.is(Trait) =>
// add a call edge from the constructor of a local non-trait class to
// the class itself. This is done so that the constructor inherits
// the free variables of the class.
symSet(called, sym) += sym.owner
case tree: TypeDef if sym.owner.isTerm =>
setLogicOwner(sym)
case _ =>
end process
private class CollectDependencies extends TreeTraverser:
def traverse(tree: Tree)(using Context) =
try
process(tree)
traverseChildren(tree)
catch case ex: Exception =>
println(i"$ex while traversing $tree")
throw ex
/** Compute final free variables map `fvs by closing over caller dependencies. */
private def computeFreeVars()(using Context): Unit =
while
changedFreeVars = false
for
caller <- called.keys
callee <- called(caller)
fvs <- free get callee
fv <- fvs
do
markFree(fv, caller)
changedFreeVars
do ()
/** Compute final liftedOwner map by closing over caller dependencies */
private def computeLogicOwners()(using Context): Unit =
while
changedLogicOwner = false
for
caller <- called.keys
callee <- called(caller)
do
val normalizedCallee = callee.skipConstructor
val calleeOwner = normalizedCallee.owner
if calleeOwner.isTerm then narrowLogicOwner(caller, logicOwner(normalizedCallee))
else
assert(calleeOwner.is(Trait))
// methods nested inside local trait methods cannot be lifted out
// beyond the trait. Note that we can also call a trait method through
// a qualifier; in that case no restriction to lifted owner arises.
if caller.isContainedIn(calleeOwner) then
narrowLogicOwner(caller, calleeOwner)
changedLogicOwner
do ()
// initialization
inContext(rootContext) {
CollectDependencies().traverse(root)
computeFreeVars()
computeLogicOwners()
}
object Dependencies:
private class NoPath extends Exception
end Dependencies
© 2015 - 2025 Weber Informatics LLC | Privacy Policy