Skip to content

Commit 98d2ed5

Browse files
author
EnzeXing
committed
Re-design Env to take a tree as parameter
1 parent be17ad1 commit 98d2ed5

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)