Skip to content

Commit

Permalink
Fix crash when initializing val in ByName closure (#22354)
Browse files Browse the repository at this point in the history
This PR creates a new Env when evaluating a by name closure, fixing a
crash due to duplicate initialization of the same value.
  • Loading branch information
odersky authored Jan 14, 2025
2 parents c10def4 + f6d6b17 commit ad90f14
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 6 deletions.
19 changes: 13 additions & 6 deletions compiler/src/dotty/tools/dotc/transform/init/Objects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -405,11 +405,18 @@ class Objects(using Context @constructorOnly):

def getVar(x: Symbol)(using data: Data, ctx: Context): Option[Heap.Addr] = data.getVar(x)

def of(ddef: DefDef, args: List[Value], outer: Data)(using Context): Data =
private[Env] def _of(argMap: Map[Symbol, Value], meth: Symbol, outer: Data): Data =
new LocalEnv(argMap, meth, outer)(valsMap = mutable.Map.empty, varsMap = mutable.Map.empty)

def ofDefDef(ddef: DefDef, args: List[Value], outer: Data)(using Context): Data =
val params = ddef.termParamss.flatten.map(_.symbol)
assert(args.size == params.size, "arguments = " + args.size + ", params = " + params.size)
assert(ddef.symbol.owner.isClass ^ (outer != NoEnv), "ddef.owner = " + ddef.symbol.owner.show + ", outer = " + outer + ", " + ddef.source)
new LocalEnv(params.zip(args).toMap, ddef.symbol, outer)(valsMap = mutable.Map.empty, varsMap = mutable.Map.empty)
_of(params.zip(args).toMap, ddef.symbol, outer)

def ofByName(byNameParam: Symbol, outer: Data): Data =
assert(byNameParam.is(Flags.Param) && byNameParam.info.isInstanceOf[ExprType]);
_of(Map.empty, byNameParam, outer)

def setLocalVal(x: Symbol, value: Value)(using data: Data, ctx: Context): Unit =
assert(!x.isOneOf(Flags.Param | Flags.Mutable), "Only local immutable variable allowed")
Expand Down Expand Up @@ -719,7 +726,7 @@ class Objects(using Context @constructorOnly):
else
Env.resolveEnv(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(Cold -> Env.NoEnv)

val env2 = Env.of(ddef, args.map(_.value), outerEnv)
val env2 = Env.ofDefDef(ddef, args.map(_.value), outerEnv)
extendTrace(ddef) {
given Env.Data = env2
cache.cachedEval(ref, ddef.rhs, cacheResult = true) { expr =>
Expand Down Expand Up @@ -750,7 +757,7 @@ class Objects(using Context @constructorOnly):
code match
case ddef: DefDef =>
if meth.name == nme.apply then
given Env.Data = Env.of(ddef, args.map(_.value), env)
given Env.Data = Env.ofDefDef(ddef, args.map(_.value), env)
extendTrace(code) { eval(ddef.rhs, thisV, klass, cacheResult = true) }
else
// The methods defined in `Any` and `AnyRef` are trivial and don't affect initialization.
Expand Down Expand Up @@ -786,7 +793,7 @@ class Objects(using Context @constructorOnly):
val ddef = ctor.defTree.asInstanceOf[DefDef]
val argValues = args.map(_.value)

given Env.Data = Env.of(ddef, argValues, Env.NoEnv)
given Env.Data = Env.ofDefDef(ddef, argValues, Env.NoEnv)
if ctor.isPrimaryConstructor then
val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template]
extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true) }
Expand Down Expand Up @@ -1013,7 +1020,7 @@ class Objects(using Context @constructorOnly):
if isByNameParam(sym) then
value match
case fun: Fun =>
given Env.Data = fun.env
given Env.Data = Env.ofByName(sym, fun.env)
eval(fun.code, fun.thisV, fun.klass)
case Cold =>
report.warning("Calling cold by-name alias. " + Trace.show, Trace.position)
Expand Down
17 changes: 17 additions & 0 deletions tests/init/pos/byname.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
trait T:
def bar(i: => Int): Int

class A extends T:
override def bar(i: => Int): Int = i + 1

class B extends T:
override def bar(i: => Int): Int = i + 2

object A:
val a: T = if ??? then new A else new B
def foo(b: List[Int]) = a.bar(b match {
case x :: xs => 1
case Nil => 0
})

val f = foo(Nil)

0 comments on commit ad90f14

Please sign in to comment.