From ba88a960fc131628cba84be78ac14c372dc1d793 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Sun, 19 Nov 2023 11:24:33 +0100 Subject: [PATCH] Handle lacal lazy vals properly 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. --- .../tools/dotc/transform/init/Objects.scala | 40 ++++---- tests/init-global/neg/lazy-local-val.scala | 19 ++++ tests/init-global/pos/i18628-lazy.scala | 91 +++++++++++++++++++ tests/init-global/pos/lazy-local-val.scala | 9 ++ tests/init-global/pos/lazy-local-val2.scala | 7 ++ 5 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 tests/init-global/neg/lazy-local-val.scala create mode 100644 tests/init-global/pos/i18628-lazy.scala create mode 100644 tests/init-global/pos/lazy-local-val.scala create mode 100644 tests/init-global/pos/lazy-local-val2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 60578dbc9d5b..ed8d07e90ce5 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -947,27 +947,32 @@ object Objects: Bottom end if case _ => + // Only vals can be lazy report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position) Bottom else given Env.Data = env - // Assume forward reference check is doing a good job - val value = Env.valValue(sym) - if isByNameParam(sym) then - value match - case fun: Fun => - given Env.Data = fun.env - eval(fun.code, fun.thisV, fun.klass) - case Cold => - report.warning("Calling cold by-name alias. " + Trace.show, Trace.position) - Bottom - case _: ValueSet | _: Ref | _: OfArray => - report.warning("[Internal error] Unexpected by-name value " + value.show + ". " + Trace.show, Trace.position) - Bottom + if sym.is(Flags.Lazy) then + val rhs = sym.defTree.asInstanceOf[ValDef].rhs + eval(rhs, thisV, sym.enclosingClass.asClass, cacheResult = true) else - value + // Assume forward reference check is doing a good job + val value = Env.valValue(sym) + if isByNameParam(sym) then + value match + case fun: Fun => + given Env.Data = fun.env + eval(fun.code, fun.thisV, fun.klass) + case Cold => + report.warning("Calling cold by-name alias. " + Trace.show, Trace.position) + Bottom + case _: ValueSet | _: Ref | _: OfArray => + report.warning("[Internal error] Unexpected by-name value " + value.show + ". " + Trace.show, Trace.position) + Bottom + else + value - case _ => + case None => if isByNameParam(sym) then report.warning("Calling cold by-name alias. " + Trace.show, Trace.position) Bottom @@ -1232,9 +1237,10 @@ object Objects: case vdef : ValDef => // local val definition - val rhs = eval(vdef.rhs, thisV, klass) val sym = vdef.symbol - initLocal(vdef.symbol, rhs) + if !sym.is(Flags.Lazy) then + val rhs = eval(vdef.rhs, thisV, klass) + initLocal(sym, rhs) Bottom case ddef : DefDef => diff --git a/tests/init-global/neg/lazy-local-val.scala b/tests/init-global/neg/lazy-local-val.scala new file mode 100644 index 000000000000..2a645ae78db1 --- /dev/null +++ b/tests/init-global/neg/lazy-local-val.scala @@ -0,0 +1,19 @@ +object A: + class Box(value: => Int) + + def f(a: => Int): Box = + val b = a + Box(b) + + val box = f(n) // error + val n = 10 + +object B: + class Box(value: Int) + + def f(a: => Int): Box = + lazy val b = a + Box(b) + + val box = f(n) // error + val n = 10 diff --git a/tests/init-global/pos/i18628-lazy.scala b/tests/init-global/pos/i18628-lazy.scala new file mode 100644 index 000000000000..f93745a962be --- /dev/null +++ b/tests/init-global/pos/i18628-lazy.scala @@ -0,0 +1,91 @@ +abstract class Reader[+T] { + def first: T + + def rest: Reader[T] + + def atEnd: Boolean +} + +trait Parsers { + type Elem + type Input = Reader[Elem] + + sealed abstract class ParseResult[+T] { + val successful: Boolean + + def map[U](f: T => U): ParseResult[U] + + def flatMapWithNext[U](f: T => Input => ParseResult[U]): ParseResult[U] + } + + sealed abstract class NoSuccess(val msg: String) extends ParseResult[Nothing] { // when we don't care about the difference between Failure and Error + val successful = false + + def map[U](f: Nothing => U) = this + + def flatMapWithNext[U](f: Nothing => Input => ParseResult[U]): ParseResult[U] + = this + } + + case class Failure(override val msg: String) extends NoSuccess(msg) + + case class Error(override val msg: String) extends NoSuccess(msg) + + case class Success[+T](result: T, val next: Input) extends ParseResult[T] { + val successful = true + + def map[U](f: T => U) = Success(f(result), next) + + def flatMapWithNext[U](f: T => Input => ParseResult[U]): ParseResult[U] = f(result)(next) match { + case s @ Success(result, rest) => Success(result, rest) + case f: Failure => f + case e: Error => e + } + } + + case class ~[+a, +b](_1: a, _2: b) { + override def toString = s"(${_1}~${_2})" + } + + abstract class Parser[+T] extends (Input => ParseResult[T]) { + def apply(in: Input): ParseResult[T] + + def ~ [U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q + (for(a <- this; b <- p) yield new ~(a,b)) + } + + def flatMap[U](f: T => Parser[U]): Parser[U] + = Parser{ in => this(in) flatMapWithNext(f)} + + def map[U](f: T => U): Parser[U] //= flatMap{x => success(f(x))} + = Parser{ in => this(in) map(f)} + + def ^^ [U](f: T => U): Parser[U] = map(f) + } + + def Parser[T](f: Input => ParseResult[T]): Parser[T] + = new Parser[T]{ def apply(in: Input) = f(in) } + + def accept(e: Elem): Parser[Elem] = acceptIf(_ == e)("'"+e+"' expected but " + _ + " found") + + def acceptIf(p: Elem => Boolean)(err: Elem => String): Parser[Elem] = Parser { in => + if (in.atEnd) Failure("end of input") + else if (p(in.first)) Success(in.first, in.rest) + else Failure(err(in.first)) + } +} + + +object grammars3 extends Parsers { + type Elem = String + + val a: Parser[String] = accept("a") + val b: Parser[String] = accept("b") + + val AnBnCn: Parser[List[String]] = { + repMany(a,b) + } + + def repMany[T](p: => Parser[T], q: => Parser[T]): Parser[List[T]] = + p~repMany(p,q)~q ^^ {case x~xs~y => x::xs:::(y::Nil)} +} \ No newline at end of file diff --git a/tests/init-global/pos/lazy-local-val.scala b/tests/init-global/pos/lazy-local-val.scala new file mode 100644 index 000000000000..792160c58dfb --- /dev/null +++ b/tests/init-global/pos/lazy-local-val.scala @@ -0,0 +1,9 @@ +object Test: + class Box(value: => Int) + + def f(a: => Int): Box = + lazy val b = a + Box(b) + + val box = f(n) + val n = 10 diff --git a/tests/init-global/pos/lazy-local-val2.scala b/tests/init-global/pos/lazy-local-val2.scala new file mode 100644 index 000000000000..d98c56d62d74 --- /dev/null +++ b/tests/init-global/pos/lazy-local-val2.scala @@ -0,0 +1,7 @@ +object C: + def f(a: => Int): Int = + lazy val a: Int = 10 + b + lazy val b: Int = 20 + a + b + + val n = f(10)