Skip to content

Commit f423ed1

Browse files
committed
Use correct outer environment for local methods
1 parent 6b159c2 commit f423ed1

File tree

2 files changed

+41
-15
lines changed

2 files changed

+41
-15
lines changed

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

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,15 @@ object Objects:
182182

183183
def widen(height: Int)(using Context): Data
184184

185+
def level: Int
186+
185187
/** Local environments can be deeply nested, therefore we need `outer`. */
186-
private case class LocalEnv(private[Env] val params: Map[Symbol, Value], owner: Symbol, outer: Data) extends Data:
188+
private case class LocalEnv(private[Env] val params: Map[Symbol, Value], owner: Symbol, outer: Data)(using Context) extends Data:
189+
val level = outer.level + 1
190+
191+
if (level > 3)
192+
report.warning("[Internal error] Deeply nested environemnt, level = " + level + ", " + owner.show + " in " + owner.enclosingClass.show, owner.defTree)
193+
187194
private[Env] val locals: mutable.Map[Symbol, Value] = mutable.Map.empty
188195

189196
private[Env] def get(x: Symbol)(using Context): Option[Value] =
@@ -204,6 +211,8 @@ object Objects:
204211
end LocalEnv
205212

206213
object NoEnv extends Data:
214+
val level = 0
215+
207216
private[Env] def get(x: Symbol)(using Context): Option[Value] =
208217
throw new RuntimeException("Invalid usage of non-existent env")
209218

@@ -215,7 +224,7 @@ object Objects:
215224

216225
/** An empty environment can be used for non-method environments, e.g., field initializers.
217226
*/
218-
def emptyEnv(owner: Symbol): Data = new LocalEnv(Map.empty, owner, NoEnv)
227+
def emptyEnv(owner: Symbol)(using Context): Data = new LocalEnv(Map.empty, owner, NoEnv)
219228

220229
def apply(x: Symbol)(using data: Data, ctx: Context): Value = data.get(x).get
221230

@@ -238,31 +247,31 @@ object Objects:
238247
throw new RuntimeException("Incorrect local environment for initializing " + x.show)
239248

240249
/**
241-
* Resolve the definition environment for the given local variable.
250+
* Resolve the environment owned by the given symbol.
242251
*
243-
* A local variable could be located in outer scope with intermixed classes between its
252+
* The owner (e.g., method) could be located in outer scope with intermixed classes between its
244253
* definition site and usage site.
245254
*
246255
* Due to widening, the corresponding environment might not exist. As a result reading the local
247256
* variable will return `Cold` and it's forbidden to write to the local variable.
248257
*
249-
* @param sym The symbol of the local variable
258+
* @param meth The method which owns the environment
250259
* @param thisV The value for `this` of the enclosing class where the local variable is referenced.
251260
* @param env The local environment where the local variable is referenced.
252261
*/
253-
def resolveDefinitionEnv(sym: Symbol, thisV: Value, env: Data)(using Context): Option[(Value, Data)] =
262+
def resolveEnv(owner: Symbol, thisV: Value, env: Data)(using Context): Option[(Value, Data)] =
254263
env match
255264
case localEnv: LocalEnv =>
256-
if localEnv.owner == sym.owner then Some(thisV -> env)
257-
else resolveDefinitionEnv(sym, thisV, localEnv.outer)
265+
if localEnv.owner == owner then Some(thisV -> env)
266+
else resolveEnv(owner, thisV, localEnv.outer)
258267
case NoEnv =>
259268
// TODO: handle RefSet
260269
thisV match
261270
case ref: OfClass =>
262-
resolveDefinitionEnv(sym, ref.outer, ref.env)
271+
resolveEnv(owner, ref.outer, ref.env)
263272
case _ =>
264273
None
265-
end resolveDefinitionEnv
274+
end resolveEnv
266275
end Env
267276

268277
/** Abstract heap for mutable fields
@@ -420,7 +429,15 @@ object Objects:
420429
case _ =>
421430
val cls = target.owner.enclosingClass.asClass
422431
val ddef = target.defTree.asInstanceOf[DefDef]
423-
val env2 = Env.of(ddef, args.map(_.value), if ddef.symbol.owner.isClass then Env.NoEnv else summon[Env.Data])
432+
val meth = ddef.symbol
433+
434+
val (thisV, outerEnv) =
435+
if meth.owner.isClass then
436+
(ref, Env.NoEnv)
437+
else
438+
Env.resolveEnv(meth.owner, ref, summon[Env.Data]).getOrElse(Cold -> Env.NoEnv)
439+
440+
val env2 = Env.of(ddef, args.map(_.value), outerEnv)
424441
extendTrace(ddef) {
425442
given Env.Data = env2
426443
eval(ddef.rhs, ref, cls, cacheResult = true)
@@ -558,8 +575,11 @@ object Objects:
558575
// The outer can be a bottom value for top-level classes.
559576

560577
// Widen the outer to finitize the domain. Arguments already widened in `evalArgs`.
561-
val outerWidened = outer.widen(1)
562-
val envWidened = if klass.owner.isClass then Env.NoEnv else summon[Env.Data].widen(1)
578+
val (outerWidened, envWidened) =
579+
if klass.owner.isClass then
580+
(outer.widen(1), Env.NoEnv)
581+
else
582+
Env.resolveEnv(klass.owner, outer, summon[Env.Data]).getOrElse(Cold -> Env.NoEnv)
563583

564584
val instance = OfClass(klass, outerWidened, ctor, args.map(_.value), envWidened, State.currentObject)
565585
callConstructor(instance, ctor, args)
@@ -578,7 +598,7 @@ object Objects:
578598
}
579599

580600
def readLocal(thisV: Value, sym: Symbol): Contextual[Value] = log("reading local " + sym.show, printer, (_: Value).show) {
581-
Env.resolveDefinitionEnv(sym, thisV, summon[Env.Data]) match
601+
Env.resolveEnv(sym.owner, thisV, summon[Env.Data]) match
582602
case Some(thisV -> env) =>
583603
if sym.is(Flags.Mutable) then
584604
thisV match
@@ -600,7 +620,7 @@ object Objects:
600620

601621
assert(sym.is(Flags.Mutable), "Writing to immutable variable " + sym.show)
602622

603-
Env.resolveDefinitionEnv(sym, thisV, summon[Env.Data]) match
623+
Env.resolveEnv(sym.owner, thisV, summon[Env.Data]) match
604624
case Some(thisV -> env) =>
605625
thisV match
606626
case ref: Ref =>

tests/init/pos/global-recursion.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object Recursion:
2+
def foo(): Int =
3+
def fact(x: Int): Int = if x == 0 then 1 else x * fact(x - 1)
4+
fact(5)
5+
6+
val n = foo()

0 commit comments

Comments
 (0)