Skip to content

Commit cdcc5c1

Browse files
authored
Re-design Env of the global object init checker (#24107)
When the global object init checker models lazy fields and by-name parameters, initially it creates an environment for each symbol of the lazy field/by-name parameter. For by-name parameters, the environment includes all the possible environments that pass to the parameter as outer environments. This PR modifies the design of environments to take the tree to evaluate as the argument. Therefore, the same of argument tree passed to a by-name parameter will share the same environment, and there will be multiple environments for the same by-name parameter. This facilitates the process of finding local variables in the argument tree. Now the process of evaluating by-name args in `multiple-by-name.scala` will behave similarly to evaluating closure arguments in `multiple-func-arg.scala`
2 parents be17ad1 + 98d2ed5 commit cdcc5c1

File tree

5 files changed

+91
-39
lines changed

5 files changed

+91
-39
lines changed

compiler/src/dotty/tools/dotc/transform/init/Objects.scala

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,11 @@ class Objects(using Context @constructorOnly):
9393
* | UnknownValue // values whose source are unknown at compile time
9494
* vs ::= ValueSet(Set(ve)) // set of abstract values
9595
* Value ::= ve | vs | Package
96-
* Ref ::= ObjectRef | InstanceRef | ArrayRef // values that represent a reference to some (global or instance) object
96+
* Ref ::= ObjectRef | InstanceRef | ArrayRef // values that represent a reference to some (global or instance) object
9797
* RefSet ::= Set(ref) // set of refs
9898
* Bottom ::= RefSet(Empty) // unreachable code
9999
* ThisValue ::= Ref | RefSet // possible values for 'this'
100-
* EnvRef(meth, ownerObject) // represents environments for methods or functions
100+
* EnvRef(tree, ownerObject) // represents environments for evaluating methods, functions, or lazy/by-name values
101101
* EnvSet ::= Set(EnvRef)
102102
* InstanceBody ::= (valsMap: Map[Symbol, Value],
103103
outersMap: Map[ClassSymbol, Value],
@@ -405,13 +405,18 @@ class Objects(using Context @constructorOnly):
405405

406406
/** Environment for parameters */
407407
object Env:
408-
/** Local environments can be deeply nested, therefore we need `outer`.
409-
*
410-
* For local variables in rhs of class field definitions, the `meth` is the primary constructor.
408+
/** Represents environments for evaluating methods, functions, or lazy/by-name values
409+
* For methods or closures, `tree` is the DefDef of the method.
410+
* For lazy/by-name values, `tree` is the rhs of the definition or the argument passed to by-name param
411411
*/
412-
case class EnvRef(meth: Symbol, owner: ClassSymbol)(using Trace) extends Scope:
412+
case class EnvRef(tree: Tree, owner: ClassSymbol)(using Trace) extends Scope:
413+
override def equals(that: Any): Boolean =
414+
that.isInstanceOf[EnvRef] &&
415+
(that.asInstanceOf[EnvRef].tree eq tree) &&
416+
(that.asInstanceOf[EnvRef].owner == owner)
417+
413418
def show(using Context) =
414-
"meth: " + meth.show + "\n" +
419+
"tree: " + tree.show + "\n" +
415420
"owner: " + owner.show
416421

417422
def valValue(sym: Symbol)(using EnvMap.EnvMapMutableData): Value = EnvMap.readVal(this, sym)
@@ -454,36 +459,49 @@ class Objects(using Context @constructorOnly):
454459

455460
val NoEnv = EnvSet(Set.empty)
456461

457-
/** An empty environment can be used for non-method environments, e.g., field initializers.
458-
*
459-
* The owner for the local environment for field initializers is the primary constructor of the
460-
* enclosing class.
461-
*/
462-
def emptyEnv(meth: Symbol)(using Context, State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef =
463-
_of(Map.empty, meth, Bottom, NoEnv)
464-
465462
def valValue(x: Symbol)(using env: EnvRef, ctx: Context, trace: Trace, envMap: EnvMap.EnvMapMutableData): Value =
466463
if env.hasVal(x) then
467464
env.valValue(x)
468465
else
469466
report.warning("[Internal error] Value not found " + x.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position)
470467
Bottom
471468

472-
private[Env] def _of(argMap: Map[Symbol, Value], meth: Symbol, thisV: ThisValue, outerEnv: EnvSet)
469+
/** The method of creating an Env that evaluates `tree` */
470+
private[Env] def _of(argMap: Map[Symbol, Value], tree: Tree, thisV: ThisValue, outerEnv: EnvSet)
473471
(using State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef =
474-
val env = EnvRef(meth, State.currentObject)
472+
val env = EnvRef(tree, State.currentObject)
475473
argMap.foreach(env.initVal(_, _))
476474
env.initThisV(thisV)
477475
env.initOuterEnvs(outerEnv)
478476
env
479477

478+
/**
479+
* Creates an environment that evaluates the body of a method or the body of a closure
480+
*/
481+
def ofDefDef(ddef: DefDef, args: List[Value], thisV: ThisValue, outerEnv: EnvSet)
482+
(using State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef =
483+
val params = ddef.termParamss.flatten.map(_.symbol)
484+
assert(args.size == params.size, "arguments = " + args.size + ", params = " + params.size)
485+
// assert(ddef.symbol.owner.is(Method) ^ (outerEnv == NoEnv), "ddef.owner = " + ddef.symbol.owner.show + ", outerEnv = " + outerEnv + ", " + ddef.source)
486+
_of(params.zip(args).toMap, ddef, thisV, outerEnv)
487+
488+
489+
/**
490+
* Creates an environment that evaluates a lazy val with `tree` as rhs
491+
* or evaluates a by-name parameter where `tree` is the argument tree
492+
*/
493+
def ofByName(sym: Symbol, tree: Tree, thisV: ThisValue, outerEnv: EnvSet)
494+
(using State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef =
495+
assert((sym.is(Flags.Param) && sym.info.isInstanceOf[ExprType]) || sym.is(Flags.Lazy));
496+
_of(Map.empty, tree, thisV, outerEnv)
497+
480498
/**
481499
* The main procedure for searching through the outer chain
482500
* @param target The symbol to search for if `bySymbol = true`; otherwise the method symbol of the target environment
483501
* @param scopeSet The set of scopes as starting point
484502
* @return The scopes that contains symbol `target` or whose method is `target`,
485503
* and the value for `C.this` where C is the enclosing class of the result scopes
486-
*/
504+
*/
487505
private[Env] def resolveEnvRecur(
488506
target: Symbol, envSet: EnvSet, bySymbol: Boolean = true)
489507
: Contextual[Option[EnvSet]] = log("Resolving environment, target = " + target + ", envSet = " + envSet, printer) {
@@ -493,7 +511,7 @@ class Objects(using Context @constructorOnly):
493511
if bySymbol then
494512
envSet.envs.filter(_.hasVal(target))
495513
else
496-
envSet.envs.filter(_.meth == target)
514+
envSet.envs.filter(env => env.tree.isInstanceOf[DefDef] && env.tree.asInstanceOf[DefDef].symbol == target)
497515

498516
assert(filter.isEmpty || filter.size == envSet.envs.size, "Either all scopes or no scopes contain " + target)
499517
if (!filter.isEmpty) then
@@ -515,19 +533,6 @@ class Objects(using Context @constructorOnly):
515533
resolveEnvRecur(target, outerEnvsOfThis, bySymbol)
516534
}
517535

518-
519-
def ofDefDef(ddef: DefDef, args: List[Value], thisV: ThisValue, outerEnv: EnvSet)
520-
(using State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef =
521-
val params = ddef.termParamss.flatten.map(_.symbol)
522-
assert(args.size == params.size, "arguments = " + args.size + ", params = " + params.size)
523-
// assert(ddef.symbol.owner.is(Method) ^ (outerEnv == NoEnv), "ddef.owner = " + ddef.symbol.owner.show + ", outerEnv = " + outerEnv + ", " + ddef.source)
524-
_of(params.zip(args).toMap, ddef.symbol, thisV, outerEnv)
525-
526-
def ofByName(byNameParam: Symbol, thisV: ThisValue, outerEnv: EnvSet)
527-
(using State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef =
528-
assert(byNameParam.is(Flags.Param) && byNameParam.info.isInstanceOf[ExprType]);
529-
_of(Map.empty, byNameParam, thisV, outerEnv)
530-
531536
def setLocalVal(x: Symbol, value: Value)(using scope: Scope, ctx: Context, heap: Heap.MutableData, envMap: EnvMap.EnvMapMutableData): Unit =
532537
assert(!x.isOneOf(Flags.Param | Flags.Mutable), "Only local immutable variable allowed")
533538
scope match
@@ -692,6 +697,11 @@ class Objects(using Context @constructorOnly):
692697
def setHeap(newHeap: Data)(using mutable: MutableData): Unit = mutable.heap = newHeap
693698
end Heap
694699

700+
/**
701+
* Local environments can be deeply nested, therefore we need `outerEnvs`, which stores the immediate outer environment.
702+
* If the immediate enclosing scope of an environment is a template, then `outerEnvs` is empty in EnvMap.
703+
* We can restore outerEnvs of `this` in the heap.
704+
*/
695705
object EnvMap:
696706
private case class EnvBody(
697707
valsMap: Map[Symbol, Value],
@@ -1191,12 +1201,12 @@ class Objects(using Context @constructorOnly):
11911201

11921202
case ref: Ref =>
11931203
val target = if needResolve then resolve(ref.klass, field) else field
1194-
if target.is(Flags.Lazy) then
1195-
given Scope = Env.emptyEnv(target.owner.asInstanceOf[ClassSymbol].primaryConstructor)
1204+
if target.is(Flags.Lazy) then // select a lazy field
11961205
if ref.hasVal(target) then
11971206
ref.valValue(target)
11981207
else if target.hasSource then
11991208
val rhs = target.defTree.asInstanceOf[ValDef].rhs
1209+
given Scope = Env.ofByName(target, rhs, ref, Env.NoEnv)
12001210
val result = eval(rhs, ref, target.owner.asClass, cacheResult = true)
12011211
ref.initVal(target, result)
12021212
result
@@ -1358,8 +1368,8 @@ class Objects(using Context @constructorOnly):
13581368
def evalByNameParam(value: Value): Contextual[Value] = value match
13591369
case Fun(code, thisV, klass, scope) =>
13601370
val byNameEnv = scope match {
1361-
case ref: Ref => Env.ofByName(sym, thisV, Env.NoEnv)
1362-
case env: Env.EnvRef => Env.ofByName(sym, thisV, Env.EnvSet(Set(env)))
1371+
case ref: Ref => Env.ofByName(sym, code, thisV, Env.NoEnv) // for by-name arguments of constructors
1372+
case env: Env.EnvRef => Env.ofByName(sym, code, thisV, Env.EnvSet(Set(env))) // for by-name arguments of methods/functions
13631373
}
13641374
given Scope = byNameEnv
13651375
eval(code, thisV, klass, cacheResult = true)
@@ -1389,7 +1399,7 @@ class Objects(using Context @constructorOnly):
13891399
else
13901400
if sym.is(Flags.Lazy) then
13911401
val outerThis = envSet.joinThisV
1392-
given Scope = Env.ofByName(sym, outerThis, envSet)
1402+
given Scope = Env.ofByName(sym, sym.defTree, outerThis, envSet)
13931403
val rhs = sym.defTree.asInstanceOf[ValDef].rhs
13941404
eval(rhs, outerThis, sym.enclosingClass.asClass, cacheResult = true)
13951405
else
@@ -2144,7 +2154,7 @@ class Objects(using Context @constructorOnly):
21442154
thisV
21452155
else
21462156
// `target` must enclose `klass`
2147-
assert(klass.enclosingClassNamed(target.name) != NoSymbol, target.show + " does not enclose " + klass.show)
2157+
assert(klass.ownersIterator.contains(target), target.show + " does not enclose " + klass.show)
21482158
val outerThis = thisV match {
21492159
case ref: Ref => ref.outerValue(klass)
21502160
case refSet: RefSet => refSet.joinOuters(klass)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object O {
2+
lazy val f1 = this
3+
val f2 = 5
4+
val f3 = f1.f2 + 5
5+
}

tests/init-global/pos/multiple-by-name.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ object O {
1010
p.bar()
1111
}
1212

13+
def foo2(q: => X) = foo(q)
14+
def foo3(r: => X) = foo(r)
15+
1316
val a = foo(new X)
1417
val b = foo(new Y)
15-
}
18+
val c = foo2(new Y)
19+
val d = foo3(new Y)
20+
}
21+
22+
/**
23+
* Pass arg to by-name parameter: create a Fun where body is the argument expression
24+
* Read value of by-name parameter: call 'apply' on every possible Fun value of the by-name parameter
25+
* Solution: Add special EnvRefs for by-name params;
26+
* differentiate these EnvRefs by the arg tree passed to the by-name param
27+
*/
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class X {
2+
def bar(): Int = 5
3+
}
4+
class Y extends X {
5+
override def bar(): Int = 6
6+
}
7+
8+
object O {
9+
def foo(p: () => X) = {
10+
p().bar() // flow: p <- [Fun(() => new X), Fun(() => new Y), Fun(() => q()), Fun(() => r())]
11+
}
12+
13+
def foo2(q: () => X) = foo(() => q()) // flow: q <- [Fun(() => new Y)]
14+
def foo3(r: () => X) = foo(() => r()) // flow: r <- [Fun(() => new Y)]
15+
16+
val a = foo(() => new X)
17+
val b = foo(() => new Y)
18+
val c = foo2(() => new Y)
19+
val d = foo3(() => new Y)
20+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object O {
2+
lazy val f1 = this
3+
val f2 = 5
4+
val f3: Int = f1.f2 + f1.f3 // warn
5+
}

0 commit comments

Comments
 (0)