Skip to content

Commit 39ec69f

Browse files
authored
Handle local lazy vals properly (#18998)
Lazy vals should be evaluated lazily. In the abstract semantics, we over-approximate lazy vals by treating them as parameter-less methods. The abstract cache will guard against non-terminating recursion and avoids unnecessary re-evaluation. The checker for class instances already handles local lazy vals properly.
2 parents 6b69c39 + ba88a96 commit 39ec69f

File tree

5 files changed

+149
-17
lines changed

5 files changed

+149
-17
lines changed

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

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -947,27 +947,32 @@ object Objects:
947947
Bottom
948948
end if
949949
case _ =>
950+
// Only vals can be lazy
950951
report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position)
951952
Bottom
952953
else
953954
given Env.Data = env
954-
// Assume forward reference check is doing a good job
955-
val value = Env.valValue(sym)
956-
if isByNameParam(sym) then
957-
value match
958-
case fun: Fun =>
959-
given Env.Data = fun.env
960-
eval(fun.code, fun.thisV, fun.klass)
961-
case Cold =>
962-
report.warning("Calling cold by-name alias. " + Trace.show, Trace.position)
963-
Bottom
964-
case _: ValueSet | _: Ref | _: OfArray =>
965-
report.warning("[Internal error] Unexpected by-name value " + value.show + ". " + Trace.show, Trace.position)
966-
Bottom
955+
if sym.is(Flags.Lazy) then
956+
val rhs = sym.defTree.asInstanceOf[ValDef].rhs
957+
eval(rhs, thisV, sym.enclosingClass.asClass, cacheResult = true)
967958
else
968-
value
959+
// Assume forward reference check is doing a good job
960+
val value = Env.valValue(sym)
961+
if isByNameParam(sym) then
962+
value match
963+
case fun: Fun =>
964+
given Env.Data = fun.env
965+
eval(fun.code, fun.thisV, fun.klass)
966+
case Cold =>
967+
report.warning("Calling cold by-name alias. " + Trace.show, Trace.position)
968+
Bottom
969+
case _: ValueSet | _: Ref | _: OfArray =>
970+
report.warning("[Internal error] Unexpected by-name value " + value.show + ". " + Trace.show, Trace.position)
971+
Bottom
972+
else
973+
value
969974

970-
case _ =>
975+
case None =>
971976
if isByNameParam(sym) then
972977
report.warning("Calling cold by-name alias. " + Trace.show, Trace.position)
973978
Bottom
@@ -1232,9 +1237,10 @@ object Objects:
12321237

12331238
case vdef : ValDef =>
12341239
// local val definition
1235-
val rhs = eval(vdef.rhs, thisV, klass)
12361240
val sym = vdef.symbol
1237-
initLocal(vdef.symbol, rhs)
1241+
if !sym.is(Flags.Lazy) then
1242+
val rhs = eval(vdef.rhs, thisV, klass)
1243+
initLocal(sym, rhs)
12381244
Bottom
12391245

12401246
case ddef : DefDef =>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
object A:
2+
class Box(value: => Int)
3+
4+
def f(a: => Int): Box =
5+
val b = a
6+
Box(b)
7+
8+
val box = f(n) // error
9+
val n = 10
10+
11+
object B:
12+
class Box(value: Int)
13+
14+
def f(a: => Int): Box =
15+
lazy val b = a
16+
Box(b)
17+
18+
val box = f(n) // error
19+
val n = 10
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
abstract class Reader[+T] {
2+
def first: T
3+
4+
def rest: Reader[T]
5+
6+
def atEnd: Boolean
7+
}
8+
9+
trait Parsers {
10+
type Elem
11+
type Input = Reader[Elem]
12+
13+
sealed abstract class ParseResult[+T] {
14+
val successful: Boolean
15+
16+
def map[U](f: T => U): ParseResult[U]
17+
18+
def flatMapWithNext[U](f: T => Input => ParseResult[U]): ParseResult[U]
19+
}
20+
21+
sealed abstract class NoSuccess(val msg: String) extends ParseResult[Nothing] { // when we don't care about the difference between Failure and Error
22+
val successful = false
23+
24+
def map[U](f: Nothing => U) = this
25+
26+
def flatMapWithNext[U](f: Nothing => Input => ParseResult[U]): ParseResult[U]
27+
= this
28+
}
29+
30+
case class Failure(override val msg: String) extends NoSuccess(msg)
31+
32+
case class Error(override val msg: String) extends NoSuccess(msg)
33+
34+
case class Success[+T](result: T, val next: Input) extends ParseResult[T] {
35+
val successful = true
36+
37+
def map[U](f: T => U) = Success(f(result), next)
38+
39+
def flatMapWithNext[U](f: T => Input => ParseResult[U]): ParseResult[U] = f(result)(next) match {
40+
case s @ Success(result, rest) => Success(result, rest)
41+
case f: Failure => f
42+
case e: Error => e
43+
}
44+
}
45+
46+
case class ~[+a, +b](_1: a, _2: b) {
47+
override def toString = s"(${_1}~${_2})"
48+
}
49+
50+
abstract class Parser[+T] extends (Input => ParseResult[T]) {
51+
def apply(in: Input): ParseResult[T]
52+
53+
def ~ [U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q
54+
(for(a <- this; b <- p) yield new ~(a,b))
55+
}
56+
57+
def flatMap[U](f: T => Parser[U]): Parser[U]
58+
= Parser{ in => this(in) flatMapWithNext(f)}
59+
60+
def map[U](f: T => U): Parser[U] //= flatMap{x => success(f(x))}
61+
= Parser{ in => this(in) map(f)}
62+
63+
def ^^ [U](f: T => U): Parser[U] = map(f)
64+
}
65+
66+
def Parser[T](f: Input => ParseResult[T]): Parser[T]
67+
= new Parser[T]{ def apply(in: Input) = f(in) }
68+
69+
def accept(e: Elem): Parser[Elem] = acceptIf(_ == e)("'"+e+"' expected but " + _ + " found")
70+
71+
def acceptIf(p: Elem => Boolean)(err: Elem => String): Parser[Elem] = Parser { in =>
72+
if (in.atEnd) Failure("end of input")
73+
else if (p(in.first)) Success(in.first, in.rest)
74+
else Failure(err(in.first))
75+
}
76+
}
77+
78+
79+
object grammars3 extends Parsers {
80+
type Elem = String
81+
82+
val a: Parser[String] = accept("a")
83+
val b: Parser[String] = accept("b")
84+
85+
val AnBnCn: Parser[List[String]] = {
86+
repMany(a,b)
87+
}
88+
89+
def repMany[T](p: => Parser[T], q: => Parser[T]): Parser[List[T]] =
90+
p~repMany(p,q)~q ^^ {case x~xs~y => x::xs:::(y::Nil)}
91+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object Test:
2+
class Box(value: => Int)
3+
4+
def f(a: => Int): Box =
5+
lazy val b = a
6+
Box(b)
7+
8+
val box = f(n)
9+
val n = 10
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object C:
2+
def f(a: => Int): Int =
3+
lazy val a: Int = 10 + b
4+
lazy val b: Int = 20 + a
5+
b
6+
7+
val n = f(10)

0 commit comments

Comments
 (0)