From c886d5a555ea8819fb0e97139967fd3ca6eac6d3 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Thu, 17 Jun 2021 17:33:46 -0400 Subject: [PATCH 1/9] Allowing non-hot assignment to local val and updating tests --- .../src/dotty/tools/dotc/config/Config.scala | 2 +- .../dotty/tools/dotc/config/Printers.scala | 2 +- .../dotty/tools/dotc/reporting/trace.scala | 4 +- .../tools/dotc/transform/init/Semantic.scala | 47 ++++++++++++++----- tests/init/neg/local-warm.check | 5 -- tests/init/neg/local-warm4.check | 11 +++++ tests/init/neg/local-warm4.scala | 31 ++++++++++++ tests/init/{neg => pos}/local-warm.scala | 0 tests/init/pos/local-warm2.scala | 14 ++++++ tests/init/pos/local-warm3.scala | 10 ++++ tests/init/pos/local-warm5.scala | 12 +++++ 11 files changed, 117 insertions(+), 21 deletions(-) delete mode 100644 tests/init/neg/local-warm.check create mode 100644 tests/init/neg/local-warm4.check create mode 100644 tests/init/neg/local-warm4.scala rename tests/init/{neg => pos}/local-warm.scala (100%) create mode 100644 tests/init/pos/local-warm2.scala create mode 100644 tests/init/pos/local-warm3.scala create mode 100644 tests/init/pos/local-warm5.scala diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index ac1708378e73..51d78a74d866 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -192,7 +192,7 @@ object Config { inline val flattenContextFunctionResults = true /** If set, enables tracing */ - inline val tracingEnabled = false + inline val tracingEnabled = true /** Initial capacity of the uniques HashMap. * Note: This should be a power of two to work with util.HashSet diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 8e13e50e59b7..f690e52631b6 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -28,7 +28,7 @@ object Printers { val implicits = noPrinter val implicitsDetailed = noPrinter val lexical = noPrinter - val init = noPrinter + val init = new Printer val inlining = noPrinter val interactiv = noPrinter val matchTypes = noPrinter diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index 9036ba1c1dc7..65a398b9adf3 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -90,7 +90,7 @@ trait TraceSyntax: finalize(trailing(ex.value)) throw ex case ex: Throwable => - val msg = s"<== $q = (with exception $ex)" - finalize(msg) + // val msg = s"<== $q = (with exception $ex)" + // finalize(msg) throw ex end TraceSyntax diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index f96900824f42..d6e6029ec21d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -194,6 +194,7 @@ class Semantic { class PromotionInfo { var isCurrentObjectPromoted: Boolean = false val values = mutable.Set.empty[Value] + override def toString(): String = values.toString() } /** Values that have been safely promoted */ opaque type Promoted = PromotionInfo @@ -405,6 +406,7 @@ class Semantic { given Trace = trace1 val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] + // try early promotion here; if returns error, returns cold val env2 = Env(ddef, args.map(_.value).widenArgs) if target.isPrimaryConstructor then given Env = env2 @@ -683,8 +685,26 @@ class Semantic { } /** Evaluate a list of expressions */ - def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] = - exprs.map { expr => eval(expr, thisV, klass) } + def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[(List[Result], Env)] = exprs match { + case Nil => (Nil, env) + case h :: t => h match { + case v: ValDef => { + val res = eval(h, thisV, klass) + val newEnv = Env(Map(v.symbol -> res.value)) + withEnv(env.union(newEnv)) { + val (res2, env2) = eval(t, thisV, klass) + (res :: res2, env2) + } + } + case _ => { + val res = eval(h, thisV, klass) + withEnv(env) { + val (res2, env2) = eval(t, thisV, klass) + (res :: res2, env2) + } + } + } + } /** Evaluate arguments of methods */ def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[(List[Error], List[ArgInfo])] = @@ -788,11 +808,13 @@ class Semantic { Result(value, Nil) case Block(stats, expr) => - val ress = eval(stats, thisV, klass) - eval(expr, thisV, klass) ++ ress.flatMap(_.errors) + val (ress, env2) = eval(stats, thisV, klass) + withEnv(env2) { + eval(expr, thisV, klass) ++ ress.flatMap(_.errors) + } case If(cond, thenp, elsep) => - val ress = eval(cond :: thenp :: elsep :: Nil, thisV, klass) + val (ress, env2) = eval(cond :: thenp :: elsep :: Nil, thisV, klass) val value = ress.map(_.value).join val errors = ress.flatMap(_.errors) Result(value, errors) @@ -803,7 +825,7 @@ class Semantic { case Match(selector, cases) => val res1 = eval(selector, thisV, klass).ensureHot("The value to be matched needs to be fully initialized", selector) - val ress = eval(cases.map(_.body), thisV, klass) + val (ress, env) = eval(cases.map(_.body), thisV, klass) val value = ress.map(_.value).join val errors = res1.errors ++ ress.flatMap(_.errors) Result(value, errors) @@ -812,7 +834,7 @@ class Semantic { eval(expr, thisV, klass).ensureHot("return expression may only be initialized value", expr) case WhileDo(cond, body) => - val ress = eval(cond :: body :: Nil, thisV, klass) + val (ress, env2) = eval(cond :: body :: Nil, thisV, klass) Result(Hot, ress.flatMap(_.errors)) case Labeled(_, expr) => @@ -820,7 +842,7 @@ class Semantic { case Try(block, cases, finalizer) => val res1 = eval(block, thisV, klass) - val ress = eval(cases.map(_.body), thisV, klass) + val (ress, env2) = eval(cases.map(_.body), thisV, klass) val errors = ress.flatMap(_.errors) val resValue = ress.map(_.value).join if finalizer.isEmpty then @@ -836,7 +858,7 @@ class Semantic { Result(Hot, ress.flatMap(_.errors)) case Inlined(call, bindings, expansion) => - val ress = eval(bindings, thisV, klass) + val (ress, env2) = eval(bindings, thisV, klass) eval(expansion, thisV, klass) ++ ress.flatMap(_.errors) case Thicket(List()) => @@ -846,7 +868,8 @@ class Semantic { case vdef : ValDef => // local val definition // TODO: support explicit @cold annotation for local definitions - eval(vdef.rhs, thisV, klass).ensureHot("Local definitions may only hold initialized values", vdef) + eval(vdef.rhs, thisV, klass) + // .ensureHot("Local definitions may only hold initialized values", vdef) case ddef : DefDef => // local method @@ -891,7 +914,7 @@ class Semantic { // It's always safe to approximate them with `Cold`. Result(Cold, Nil) else - default() + Result(env.getOrElse(sym, Hot), Nil) case tmref: TermRef => cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) @@ -987,7 +1010,7 @@ class Semantic { // parents def initParent(parent: Tree, tasks: Tasks)(using Env) = parent match { case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen - eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors } + eval(stats, thisV, klass)._1.foreach { res => errorBuffer ++= res.errors } val (errors, args) = evalArgs(argss.flatten, thisV, klass) errorBuffer ++= errors superCall(tref, ctor, args, tree, tasks) diff --git a/tests/init/neg/local-warm.check b/tests/init/neg/local-warm.check deleted file mode 100644 index 73972c1844f2..000000000000 --- a/tests/init/neg/local-warm.check +++ /dev/null @@ -1,5 +0,0 @@ --- Error: tests/init/neg/local-warm.scala:12:12 ------------------------------------------------------------------------ -12 | val t = m1() // error - | ^^^^^^^^^^^^ - |Promote the value under initialization to fully-initialized. Local definitions may only hold initialized values. Calling trace: - | -> val x = g() [ local-warm.scala:15 ] diff --git a/tests/init/neg/local-warm4.check b/tests/init/neg/local-warm4.check new file mode 100644 index 000000000000..2f58c35195c2 --- /dev/null +++ b/tests/init/neg/local-warm4.check @@ -0,0 +1,11 @@ +-- Error: tests/init/neg/local-warm4.scala:18:20 ----------------------------------------------------------------------- +18 | a = newA // error + | ^^^^ + |Promote the value under initialization to fully-initialized. May only assign fully initialized value. Calling trace: + | -> val a = new A(5) [ local-warm4.scala:26 ] + | -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ] + | -> val b = new B(y) [ local-warm4.scala:10 ] + | -> class B(x: Int) extends A(x) { [ local-warm4.scala:13 ] + | -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ] + | -> increment() [ local-warm4.scala:9 ] + | -> updateA() [ local-warm4.scala:21 ] diff --git a/tests/init/neg/local-warm4.scala b/tests/init/neg/local-warm4.scala new file mode 100644 index 000000000000..a6011d598e39 --- /dev/null +++ b/tests/init/neg/local-warm4.scala @@ -0,0 +1,31 @@ +object localWarm { + abstract class Foo(x: Int) { + def increment(): Unit + } + + class A(x: Int) extends Foo(x) { + var y = x + override def increment(): Unit = y = y + 1 + increment() + val b = new B(y) + } + + class B(x: Int) extends A(x) { + var a: A = this + override def increment(): Unit = { + def updateA(): Unit = { + val newA = new A(y) + a = newA // error + } + y = y + 1 + updateA() + } + if y < 10 then increment() + val z = b.y + } + val a = new A(5) +} + + + + diff --git a/tests/init/neg/local-warm.scala b/tests/init/pos/local-warm.scala similarity index 100% rename from tests/init/neg/local-warm.scala rename to tests/init/pos/local-warm.scala diff --git a/tests/init/pos/local-warm2.scala b/tests/init/pos/local-warm2.scala new file mode 100644 index 000000000000..ebe5c8900c92 --- /dev/null +++ b/tests/init/pos/local-warm2.scala @@ -0,0 +1,14 @@ +class A { + var v = 5 + val x = { + class B { + def doubleAndReturnV(): Int = { + v = v * 2 + v + } + } + val b = new B + b.doubleAndReturnV() + } + val y = v +} \ No newline at end of file diff --git a/tests/init/pos/local-warm3.scala b/tests/init/pos/local-warm3.scala new file mode 100644 index 000000000000..047dd0d7a9e8 --- /dev/null +++ b/tests/init/pos/local-warm3.scala @@ -0,0 +1,10 @@ +class A() { + var x = 5 + def decreaseX(): Unit = { + val self = this + self.x -= 1 + } + def decreaseXToZero(): Unit = if x > 0 then decreaseX() + decreaseXToZero() + val y = x +} \ No newline at end of file diff --git a/tests/init/pos/local-warm5.scala b/tests/init/pos/local-warm5.scala new file mode 100644 index 000000000000..705b92362c3f --- /dev/null +++ b/tests/init/pos/local-warm5.scala @@ -0,0 +1,12 @@ +object leakWarm5 { + case class A(x: Int) { + def double(): A = { + class C { + def double(): A = if x < 10 then A(x * 2).double() else A.this + } + val c = new C + c.double() + } + } + val a = A(2).double() +} From 2560f15e87de815c456b769f304fe96a2d37a2af Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Thu, 17 Jun 2021 18:10:44 -0400 Subject: [PATCH 2/9] Try early promotion before updating env --- compiler/src/dotty/tools/dotc/config/Config.scala | 2 +- compiler/src/dotty/tools/dotc/config/Printers.scala | 2 +- compiler/src/dotty/tools/dotc/reporting/trace.scala | 4 ++-- compiler/src/dotty/tools/dotc/transform/init/Semantic.scala | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 51d78a74d866..ac1708378e73 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -192,7 +192,7 @@ object Config { inline val flattenContextFunctionResults = true /** If set, enables tracing */ - inline val tracingEnabled = true + inline val tracingEnabled = false /** Initial capacity of the uniques HashMap. * Note: This should be a power of two to work with util.HashSet diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index f690e52631b6..8e13e50e59b7 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -28,7 +28,7 @@ object Printers { val implicits = noPrinter val implicitsDetailed = noPrinter val lexical = noPrinter - val init = new Printer + val init = noPrinter val inlining = noPrinter val interactiv = noPrinter val matchTypes = noPrinter diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index 65a398b9adf3..9036ba1c1dc7 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -90,7 +90,7 @@ trait TraceSyntax: finalize(trailing(ex.value)) throw ex case ex: Throwable => - // val msg = s"<== $q = (with exception $ex)" - // finalize(msg) + val msg = s"<== $q = (with exception $ex)" + finalize(msg) throw ex end TraceSyntax diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index d6e6029ec21d..b25264c327ff 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -690,7 +690,8 @@ class Semantic { case h :: t => h match { case v: ValDef => { val res = eval(h, thisV, klass) - val newEnv = Env(Map(v.symbol -> res.value)) + val newEnv = + if res.value.promote("Try early promotion", h).isEmpty then Env(Map(v.symbol -> Hot)) else Env(Map(v.symbol -> res.value)) withEnv(env.union(newEnv)) { val (res2, env2) = eval(t, thisV, klass) (res :: res2, env2) From 0f1586d00a32fec121ef34805d97af5771b60607 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Tue, 22 Jun 2021 13:55:28 -0400 Subject: [PATCH 3/9] Evaluate local variable during access --- .../tools/dotc/transform/init/Semantic.scala | 29 ++++--------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index b25264c327ff..65e4fb19de2b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -685,27 +685,8 @@ class Semantic { } /** Evaluate a list of expressions */ - def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[(List[Result], Env)] = exprs match { - case Nil => (Nil, env) - case h :: t => h match { - case v: ValDef => { - val res = eval(h, thisV, klass) - val newEnv = - if res.value.promote("Try early promotion", h).isEmpty then Env(Map(v.symbol -> Hot)) else Env(Map(v.symbol -> res.value)) - withEnv(env.union(newEnv)) { - val (res2, env2) = eval(t, thisV, klass) - (res :: res2, env2) - } - } - case _ => { - val res = eval(h, thisV, klass) - withEnv(env) { - val (res2, env2) = eval(t, thisV, klass) - (res :: res2, env2) - } - } - } - } + def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] = + exprs.map { expr => eval(expr, thisV, klass) } /** Evaluate arguments of methods */ def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[(List[Error], List[ArgInfo])] = @@ -869,7 +850,7 @@ class Semantic { case vdef : ValDef => // local val definition // TODO: support explicit @cold annotation for local definitions - eval(vdef.rhs, thisV, klass) + eval(vdef.rhs, thisV, klass, true) // .ensureHot("Local definitions may only hold initialized values", vdef) case ddef : DefDef => @@ -915,7 +896,9 @@ class Semantic { // It's always safe to approximate them with `Cold`. Result(Cold, Nil) else - Result(env.getOrElse(sym, Hot), Nil) + // resolve this for local variable + val enclosingClass = sym.owner.enclosingClass.asClass + val thisValue2 = resolveThis(enclosingClass, thisV, klass, source) case tmref: TermRef => cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) From fef4ddf5e0d31b1763f89f20c595485670edbef3 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Tue, 22 Jun 2021 17:20:23 -0400 Subject: [PATCH 4/9] Resolving cases for def-tree --- .../tools/dotc/transform/init/Semantic.scala | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 65e4fb19de2b..fa66f3300d05 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -790,13 +790,11 @@ class Semantic { Result(value, Nil) case Block(stats, expr) => - val (ress, env2) = eval(stats, thisV, klass) - withEnv(env2) { - eval(expr, thisV, klass) ++ ress.flatMap(_.errors) - } + val ress = eval(stats, thisV, klass) + eval(expr, thisV, klass) ++ ress.flatMap(_.errors) case If(cond, thenp, elsep) => - val (ress, env2) = eval(cond :: thenp :: elsep :: Nil, thisV, klass) + val ress = eval(cond :: thenp :: elsep :: Nil, thisV, klass) val value = ress.map(_.value).join val errors = ress.flatMap(_.errors) Result(value, errors) @@ -807,7 +805,7 @@ class Semantic { case Match(selector, cases) => val res1 = eval(selector, thisV, klass).ensureHot("The value to be matched needs to be fully initialized", selector) - val (ress, env) = eval(cases.map(_.body), thisV, klass) + val ress = eval(cases.map(_.body), thisV, klass) val value = ress.map(_.value).join val errors = res1.errors ++ ress.flatMap(_.errors) Result(value, errors) @@ -816,7 +814,7 @@ class Semantic { eval(expr, thisV, klass).ensureHot("return expression may only be initialized value", expr) case WhileDo(cond, body) => - val (ress, env2) = eval(cond :: body :: Nil, thisV, klass) + val ress = eval(cond :: body :: Nil, thisV, klass) Result(Hot, ress.flatMap(_.errors)) case Labeled(_, expr) => @@ -824,7 +822,7 @@ class Semantic { case Try(block, cases, finalizer) => val res1 = eval(block, thisV, klass) - val (ress, env2) = eval(cases.map(_.body), thisV, klass) + val ress = eval(cases.map(_.body), thisV, klass) val errors = ress.flatMap(_.errors) val resValue = ress.map(_.value).join if finalizer.isEmpty then @@ -840,7 +838,7 @@ class Semantic { Result(Hot, ress.flatMap(_.errors)) case Inlined(call, bindings, expansion) => - val (ress, env2) = eval(bindings, thisV, klass) + val ress = eval(bindings, thisV, klass) eval(expansion, thisV, klass) ++ ress.flatMap(_.errors) case Thicket(List()) => @@ -896,10 +894,27 @@ class Semantic { // It's always safe to approximate them with `Cold`. Result(Cold, Nil) else - // resolve this for local variable - val enclosingClass = sym.owner.enclosingClass.asClass - val thisValue2 = resolveThis(enclosingClass, thisV, klass, source) - + sym.defTree match { + case vdef: ValDef => { + // resolve this for local variable + val enclosingClass = sym.owner.enclosingClass.asClass + val thisValue2 = resolveThis(enclosingClass, thisV, klass, source) + thisValue2 match { + case Hot => Result(Hot, Errors.empty) + case Cold => { + val error = AccessCold(sym, source, trace.toVector) + Result(Hot, error :: Nil) + } + case addr: Addr => { + val res = eval(vdef.rhs, addr, klass) + if res.value.promote("Try promote", source).isEmpty then Result(Hot, Errors.empty) else res + } + case _ => ??? + } + } + case _ => default() + } + case tmref: TermRef => cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) @@ -994,7 +1009,7 @@ class Semantic { // parents def initParent(parent: Tree, tasks: Tasks)(using Env) = parent match { case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen - eval(stats, thisV, klass)._1.foreach { res => errorBuffer ++= res.errors } + eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors } val (errors, args) = evalArgs(argss.flatten, thisV, klass) errorBuffer ++= errors superCall(tref, ctor, args, tree, tasks) From ccab4eb98381bab47c5c39935c00ad1f56a66d59 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Fri, 25 Jun 2021 00:35:55 +0200 Subject: [PATCH 5/9] Synchronize Symbol.defTree before analysis --- compiler/src/dotty/tools/dotc/Compiler.scala | 4 +-- .../src/dotty/tools/dotc/core/Phases.scala | 2 +- .../tools/dotc/transform/init/Checker.scala | 34 ++++++++++++++++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index e213e1bee01f..39c60ff14682 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -64,8 +64,8 @@ class Compiler { new CheckStatic, // Check restrictions that apply to @static members new BetaReduce, // Reduce closure applications new InlineVals, // Check right hand-sides of an `inline val`s - new ExpandSAMs, // Expand single abstract method closures to anonymous classes - new init.Checker) :: // Check initialization of objects + new ExpandSAMs) :: // Expand single abstract method closures to anonymous classes + List(new init.Checker) :: // Check initialization of objects List(new ElimRepeated, // Rewrite vararg parameters and arguments new ProtectedAccessors, // Add accessors for protected members new ExtensionMethods, // Expand methods of value classes with extension methods diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 4646751192b4..185dd569a993 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -290,7 +290,7 @@ object Phases { /** If set, implicit search is enabled */ def allowsImplicitSearch: Boolean = false - /** List of names of phases that should precede this phase */ + /** List of names of phases that should precede this phase */ def runsAfter: Set[String] = Set.empty /** @pre `isRunnable` returns true */ diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 5d82e4d8b7e5..52fc755f9d07 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -10,15 +10,16 @@ import dotty.tools.dotc.core._ import Contexts._ import Types._ import Symbols._ +import StdNames._ import dotty.tools.dotc.transform._ -import MegaPhase._ +import Phases._ import scala.collection.mutable -class Checker extends MiniPhase { +class Checker extends Phase { import tpd._ val phaseName = "initChecker" @@ -30,9 +31,34 @@ class Checker extends MiniPhase { override def isEnabled(using Context): Boolean = super.isEnabled && ctx.settings.YcheckInit.value - override def transformTypeDef(tree: TypeDef)(using Context): tpd.Tree = { - if (!tree.isClassDef) return tree + override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = + units.foreach { unit => traverser.traverse(unit.tpdTree) } + super.runOn(units) + + val traverser = new TreeTraverser { + override def traverse(tree: Tree)(using Context): Unit = + tree match { + case tdef: MemberDef => + // self-type annotation ValDef has no symbol + if tdef.name != nme.WILDCARD then + tdef.symbol.defTree = tree + case _ => + traverseChildren(tree) + } + } + + override def run(using Context): Unit = { + val unit = ctx.compilationUnit + unit.tpdTree.foreachSubTree { + case tdef: TypeDef if tdef.isClassDef => + transformTypeDef(tdef) + + case _ => + } + } + + private def transformTypeDef(tree: TypeDef)(using Context): tpd.Tree = { val cls = tree.symbol.asClass val instantiable: Boolean = cls.is(Flags.Module) || From ba5f4f06eb5928ed33d22d2ebda79c85b1a98472 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Fri, 30 Jul 2021 12:55:53 -0400 Subject: [PATCH 6/9] Refactoring accessLocal --- .../tools/dotc/transform/init/Checker.scala | 2 +- .../tools/dotc/transform/init/Errors.scala | 2 +- .../tools/dotc/transform/init/Semantic.scala | 86 ++++++++++--------- 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 52fc755f9d07..d13c4df04547 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -37,13 +37,13 @@ class Checker extends Phase { val traverser = new TreeTraverser { override def traverse(tree: Tree)(using Context): Unit = + traverseChildren(tree) tree match { case tdef: MemberDef => // self-type annotation ValDef has no symbol if tdef.name != nme.WILDCARD then tdef.symbol.defTree = tree case _ => - traverseChildren(tree) } } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index c5711fe96438..4b423b9b1ae3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -69,7 +69,7 @@ object Errors { /** Promote `this` under initialization to fully-initialized */ case class PromoteError(msg: String, source: Tree, trace: Seq[Tree]) extends Error { - def show(using Context): String = "Promote the value under initialization to fully-initialized. " + msg + "." + def show(using Context): String = "Cannot prove that the value is fully initialized. " + msg + "." } case class AccessCold(field: Symbol, source: Tree, trace: Seq[Tree]) extends Error { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index fa66f3300d05..073066831662 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -406,7 +406,6 @@ class Semantic { given Trace = trace1 val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] - // try early promotion here; if returns error, returns cold val env2 = Env(ddef, args.map(_.value).widenArgs) if target.isPrimaryConstructor then given Env = env2 @@ -497,6 +496,46 @@ class Semantic { Result(value2, errors) } } + + def accessLocal(tmref: TermRef, klass: ClassSymbol, source: Tree): Contextual[Result] = + val sym = tmref.symbol + + def default() = Result(Hot, Nil) + + if sym.is(Flags.Param) && sym.owner.isConstructor then + // instances of local classes inside secondary constructors cannot + // reach here, as those values are abstracted by Cold instead of Warm. + // This enables us to simplify the domain without sacrificing + // expressiveness nor soundess, as local classes inside secondary + // constructors are uncommon. + if sym.isContainedIn(klass) then + Result(env.lookup(sym), Nil) + else + // We don't know much about secondary constructor parameters in outer scope. + // It's always safe to approximate them with `Cold`. + Result(Cold, Nil) + else if sym.is(Flags.Param) then + default() + else + sym.defTree match { + case vdef: ValDef => + // resolve this for local variable + val enclosingClass = sym.owner.enclosingClass.asClass + val thisValue2 = resolveThis(enclosingClass, value, klass, source) + thisValue2 match { + case Hot => Result(Hot, Errors.empty) + + case Cold => Result(Cold, Nil) + + case addr: Addr => eval(vdef.rhs, addr, klass) + + case _ => + report.error("unexpected defTree when accessing local variable, sym = " + sym.show + ", defTree = " + sym.defTree.show, source) + default() + } + + case _ => default() + } end extension // ----- Promotion ---------------------------------------------------- @@ -685,7 +724,7 @@ class Semantic { } /** Evaluate a list of expressions */ - def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] = + def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] = exprs.map { expr => eval(expr, thisV, klass) } /** Evaluate arguments of methods */ @@ -848,8 +887,7 @@ class Semantic { case vdef : ValDef => // local val definition // TODO: support explicit @cold annotation for local definitions - eval(vdef.rhs, thisV, klass, true) - // .ensureHot("Local definitions may only hold initialized values", vdef) + eval(vdef.rhs, thisV, klass, cacheResult = true) case ddef : DefDef => // local method @@ -877,44 +915,8 @@ class Semantic { Result(Hot, Errors.empty) case tmref: TermRef if tmref.prefix == NoPrefix => - val sym = tmref.symbol - - def default() = Result(Hot, Nil) - - if sym.is(Flags.Param) && sym.owner.isConstructor then - // instances of local classes inside secondary constructors cannot - // reach here, as those values are abstracted by Cold instead of Warm. - // This enables us to simplify the domain without sacrificing - // expressiveness nor soundess, as local classes inside secondary - // constructors are uncommon. - if sym.isContainedIn(klass) then - Result(env.lookup(sym), Nil) - else - // We don't know much about secondary constructor parameters in outer scope. - // It's always safe to approximate them with `Cold`. - Result(Cold, Nil) - else - sym.defTree match { - case vdef: ValDef => { - // resolve this for local variable - val enclosingClass = sym.owner.enclosingClass.asClass - val thisValue2 = resolveThis(enclosingClass, thisV, klass, source) - thisValue2 match { - case Hot => Result(Hot, Errors.empty) - case Cold => { - val error = AccessCold(sym, source, trace.toVector) - Result(Hot, error :: Nil) - } - case addr: Addr => { - val res = eval(vdef.rhs, addr, klass) - if res.value.promote("Try promote", source).isEmpty then Result(Hot, Errors.empty) else res - } - case _ => ??? - } - } - case _ => default() - } - + thisV.accessLocal(tmref, klass, source) + case tmref: TermRef => cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) From 6cbfd9af0b88b04a5464f43b0edfa9f17262ccce Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Fri, 30 Jul 2021 14:02:57 -0400 Subject: [PATCH 7/9] Update tests Fixing inherit-non-hot.check --- tests/init/neg/Desugar.check | 5 ----- tests/init/neg/InteractiveDriver.check | 5 ----- tests/init/neg/closureLeak.check | 6 +++--- tests/init/neg/cycle-structure.check | 4 ++-- tests/init/neg/default-this.check | 4 ++-- tests/init/neg/inherit-non-hot.check | 16 ++++++++++++++ tests/init/neg/inherit-non-hot.scala | 21 +++++++++++++++++++ tests/init/neg/leak-warm.check | 4 ++-- tests/init/neg/local-warm4.check | 16 +++++++------- tests/init/neg/local-warm4.scala | 4 ---- tests/init/neg/promotion-loop.check | 6 +++--- tests/init/neg/secondary-ctor2.scala | 4 ++-- tests/init/{neg => pos}/Desugar.scala | 2 +- .../init/{neg => pos}/InteractiveDriver.scala | 2 +- 14 files changed, 61 insertions(+), 38 deletions(-) delete mode 100644 tests/init/neg/Desugar.check delete mode 100644 tests/init/neg/InteractiveDriver.check create mode 100644 tests/init/neg/inherit-non-hot.check create mode 100644 tests/init/neg/inherit-non-hot.scala rename tests/init/{neg => pos}/Desugar.scala (69%) rename tests/init/{neg => pos}/InteractiveDriver.scala (96%) diff --git a/tests/init/neg/Desugar.check b/tests/init/neg/Desugar.check deleted file mode 100644 index b9c0ddd86913..000000000000 --- a/tests/init/neg/Desugar.check +++ /dev/null @@ -1,5 +0,0 @@ --- Error: tests/init/neg/Desugar.scala:12:61 --------------------------------------------------------------------------- -12 | val f: PartialFunction[A, Int] = {case C(_, rhs) => rhs.x} // error - | ^ - | Calling the external method constructor $anon may cause initialization errors. Calling trace: - | -> val f: PartialFunction[A, Int] = {case C(_, rhs) => rhs.x} // error [ Desugar.scala:12 ] diff --git a/tests/init/neg/InteractiveDriver.check b/tests/init/neg/InteractiveDriver.check deleted file mode 100644 index 32716766aafd..000000000000 --- a/tests/init/neg/InteractiveDriver.check +++ /dev/null @@ -1,5 +0,0 @@ --- Error: tests/init/neg/InteractiveDriver.scala:22:71 ----------------------------------------------------------------- -22 | val l2: Seq[C[?]] = l.collect{ case x: InteractiveDriver[?] => h(x) } // error - | ^ - |Calling the external method constructor $anon may cause initialization errors. Calling trace: - | -> val l2: Seq[C[?]] = l.collect{ case x: InteractiveDriver[?] => h(x) } // error [ InteractiveDriver.scala:22 ] diff --git a/tests/init/neg/closureLeak.check b/tests/init/neg/closureLeak.check index 7a70aeedee2a..8ed6fa79f45b 100644 --- a/tests/init/neg/closureLeak.check +++ b/tests/init/neg/closureLeak.check @@ -1,7 +1,7 @@ -- Error: tests/init/neg/closureLeak.scala:11:14 ----------------------------------------------------------------------- 11 | l.foreach(a => a.addX(this)) // error | ^^^^^^^^^^^^^^^^^ - | Cannot prove that the value is fully-initialized. May only use initialized value as arguments. + | Cannot prove that the value is fully-initialized. May only use initialized value as arguments. | - | The unsafe promotion may cause the following problem: - | Promote the value under initialization to fully-initialized. May only use initialized value as arguments. + | The unsafe promotion may cause the following problem: + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. diff --git a/tests/init/neg/cycle-structure.check b/tests/init/neg/cycle-structure.check index 27b69d25406f..77f48adf766f 100644 --- a/tests/init/neg/cycle-structure.check +++ b/tests/init/neg/cycle-structure.check @@ -1,8 +1,8 @@ -- Error: tests/init/neg/cycle-structure.scala:3:14 -------------------------------------------------------------------- 3 | val x = B(this) // error | ^^^^ - | Promote the value under initialization to fully-initialized. May only use initialized value as arguments. + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. -- Error: tests/init/neg/cycle-structure.scala:9:14 -------------------------------------------------------------------- 9 | val x = A(this) // error | ^^^^ - | Promote the value under initialization to fully-initialized. May only use initialized value as arguments. + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. diff --git a/tests/init/neg/default-this.check b/tests/init/neg/default-this.check index 8b02d96cfeb7..de8ab00ecdc6 100644 --- a/tests/init/neg/default-this.check +++ b/tests/init/neg/default-this.check @@ -1,5 +1,5 @@ -- Error: tests/init/neg/default-this.scala:9:8 ------------------------------------------------------------------------ 9 | compare() // error | ^^^^^^^ - |Promote the value under initialization to fully-initialized. May only use initialized value as arguments. Calling trace: - | -> val result = updateThenCompare(5) [ default-this.scala:11 ] + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. Calling trace: + | -> val result = updateThenCompare(5) [ default-this.scala:11 ] diff --git a/tests/init/neg/inherit-non-hot.check b/tests/init/neg/inherit-non-hot.check new file mode 100644 index 000000000000..f6f45edfc9a6 --- /dev/null +++ b/tests/init/neg/inherit-non-hot.check @@ -0,0 +1,16 @@ +-- Error: tests/init/neg/inherit-non-hot.scala:6:34 -------------------------------------------------------------------- +6 | if b == null then b = new B(this) // error + | ^^^^^^^^^^^ + | Cannot prove that the value is fully-initialized. May only assign fully initialized value. + | Calling trace: + | -> val c = new C [ inherit-non-hot.scala:19 ] + | -> class C extends A { [ inherit-non-hot.scala:15 ] + | -> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ] + | + | The unsafe promotion may cause the following problem: + | Call method Foo.B.this.aCopy.toB on a value with an unknown initialization. Calling trace: + | -> val c = new C [ inherit-non-hot.scala:19 ] + | -> class C extends A { [ inherit-non-hot.scala:15 ] + | -> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ] + | -> if b == null then b = new B(this) // error [ inherit-non-hot.scala:6 ] + | -> def getBAgain: B = aCopy.toB [ inherit-non-hot.scala:12 ] diff --git a/tests/init/neg/inherit-non-hot.scala b/tests/init/neg/inherit-non-hot.scala new file mode 100644 index 000000000000..44be67351630 --- /dev/null +++ b/tests/init/neg/inherit-non-hot.scala @@ -0,0 +1,21 @@ +// This is a minimized test for the warning in Names.scala:174 +object Foo { + abstract class A { + var b: B = null + def toB: B = + if b == null then b = new B(this) // error + b + } + + class B(a: A) { + var aCopy: A = a + def getBAgain: B = aCopy.toB + } + + class C extends A { + val bAgain = toB.getBAgain + } + + val c = new C + assert(c.b == c.bAgain) +} \ No newline at end of file diff --git a/tests/init/neg/leak-warm.check b/tests/init/neg/leak-warm.check index 85f6730cf673..8219283b3c16 100644 --- a/tests/init/neg/leak-warm.check +++ b/tests/init/neg/leak-warm.check @@ -1,8 +1,8 @@ -- Error: tests/init/neg/leak-warm.scala:18:26 ------------------------------------------------------------------------- 18 | val l: List[A] = List(c, d) // error // error | ^ - |Promote the value under initialization to fully-initialized. May only use initialized value as method arguments. + | Cannot prove that the value is fully initialized. May only use initialized value as method arguments. -- Error: tests/init/neg/leak-warm.scala:18:29 ------------------------------------------------------------------------- 18 | val l: List[A] = List(c, d) // error // error | ^ - |Promote the value under initialization to fully-initialized. May only use initialized value as method arguments. + | Cannot prove that the value is fully initialized. May only use initialized value as method arguments. diff --git a/tests/init/neg/local-warm4.check b/tests/init/neg/local-warm4.check index 2f58c35195c2..fda1ee1b928c 100644 --- a/tests/init/neg/local-warm4.check +++ b/tests/init/neg/local-warm4.check @@ -1,11 +1,11 @@ -- Error: tests/init/neg/local-warm4.scala:18:20 ----------------------------------------------------------------------- 18 | a = newA // error | ^^^^ - |Promote the value under initialization to fully-initialized. May only assign fully initialized value. Calling trace: - | -> val a = new A(5) [ local-warm4.scala:26 ] - | -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ] - | -> val b = new B(y) [ local-warm4.scala:10 ] - | -> class B(x: Int) extends A(x) { [ local-warm4.scala:13 ] - | -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ] - | -> increment() [ local-warm4.scala:9 ] - | -> updateA() [ local-warm4.scala:21 ] + | Cannot prove that the value is fully initialized. May only assign fully initialized value. Calling trace: + | -> val a = new A(5) [ local-warm4.scala:26 ] + | -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ] + | -> val b = new B(y) [ local-warm4.scala:10 ] + | -> class B(x: Int) extends A(x) { [ local-warm4.scala:13 ] + | -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ] + | -> increment() [ local-warm4.scala:9 ] + | -> updateA() [ local-warm4.scala:21 ] diff --git a/tests/init/neg/local-warm4.scala b/tests/init/neg/local-warm4.scala index a6011d598e39..a1b3030ba4de 100644 --- a/tests/init/neg/local-warm4.scala +++ b/tests/init/neg/local-warm4.scala @@ -25,7 +25,3 @@ object localWarm { } val a = new A(5) } - - - - diff --git a/tests/init/neg/promotion-loop.check b/tests/init/neg/promotion-loop.check index 11ebef3a23ec..e0c9ba597163 100644 --- a/tests/init/neg/promotion-loop.check +++ b/tests/init/neg/promotion-loop.check @@ -1,7 +1,7 @@ -- Error: tests/init/neg/promotion-loop.scala:16:10 -------------------------------------------------------------------- 16 | println(b) // error | ^ - | Cannot prove that the value is fully-initialized. May only use initialized value as arguments. + | Cannot prove that the value is fully-initialized. May only use initialized value as arguments. | - | The unsafe promotion may cause the following problem: - | Promote the value under initialization to fully-initialized. May only use initialized value as arguments. + | The unsafe promotion may cause the following problem: + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. diff --git a/tests/init/neg/secondary-ctor2.scala b/tests/init/neg/secondary-ctor2.scala index 2e7e14511328..1260cfbd51d1 100644 --- a/tests/init/neg/secondary-ctor2.scala +++ b/tests/init/neg/secondary-ctor2.scala @@ -6,8 +6,8 @@ class A(b: B, x: Int) { } Inner().foo() - val f = () => new A(b, 3) // error: Cannot promote - f() // ok + val f = () => new A(b, 3) + f() } } diff --git a/tests/init/neg/Desugar.scala b/tests/init/pos/Desugar.scala similarity index 69% rename from tests/init/neg/Desugar.scala rename to tests/init/pos/Desugar.scala index 32fb89636a21..4b8f7ca8b460 100644 --- a/tests/init/neg/Desugar.scala +++ b/tests/init/pos/Desugar.scala @@ -9,7 +9,7 @@ case class C[-T >: Int] (lhs: Int, rhs: Tree[T]) extends A { } object DesugarError { - val f: PartialFunction[A, Int] = {case C(_, rhs) => rhs.x} // error + val f: PartialFunction[A, Int] = {case C(_, rhs) => rhs.x} } diff --git a/tests/init/neg/InteractiveDriver.scala b/tests/init/pos/InteractiveDriver.scala similarity index 96% rename from tests/init/neg/InteractiveDriver.scala rename to tests/init/pos/InteractiveDriver.scala index dfec53f0c5cd..a1f3a4d197bc 100644 --- a/tests/init/neg/InteractiveDriver.scala +++ b/tests/init/pos/InteractiveDriver.scala @@ -19,5 +19,5 @@ object InteractiveDriver { case _ => new C[Int] } val l: Seq[Any] = Seq(1, 2, new C[Double], new D[Int]) - val l2: Seq[C[?]] = l.collect{ case x: InteractiveDriver[?] => h(x) } // error + val l2: Seq[C[?]] = l.collect{ case x: InteractiveDriver[?] => h(x) } } From a59e6a3afc88aa5c191a275f9006abd555185b5e Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 26 Jul 2021 22:44:39 +0200 Subject: [PATCH 8/9] Handle OuterSelect in init checker --- .../tools/dotc/transform/init/Semantic.scala | 45 ++++++++++++++++++- tests/init/crash/i8892.scala | 28 ++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/init/crash/i8892.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 073066831662..e95706b4fcef 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -7,6 +7,7 @@ import Contexts._ import Symbols._ import Types._ import StdNames._ +import NameKinds.OuterSelectName import ast.tpd._ import util.EqHashMap @@ -797,7 +798,15 @@ class Semantic { res.call(id.symbol, args, superType = NoType, source = expr) case Select(qualifier, name) => - eval(qualifier, thisV, klass).select(expr.symbol, expr) + val qualRes = eval(qualifier, thisV, klass) + + name match + case OuterSelectName(_, hops) => + val SkolemType(tp) = expr.tpe + val outer = resolveOuterSelect(tp.classSymbol.asClass, qualRes.value, hops, source = expr) + Result(outer, qualRes.errors) + case _ => + qualRes.select(expr.symbol, expr) case _: This => cases(expr.tpe, thisV, klass, expr) @@ -963,6 +972,40 @@ class Semantic { } + /** Resolve outer select introduced during inlining. + * + * See `tpd.outerSelect` and `ElimOuterSelect`. + */ + def resolveOuterSelect(target: ClassSymbol, thisV: Value, hops: Int, source: Tree): Contextual[Value] = log("resolving outer " + target.show + ", this = " + thisV.show + ", hops = " + hops, printer, res => res.asInstanceOf[Value].show) { + // Is `target` reachable from `cls` with the given `hops`? + def reachable(cls: ClassSymbol, hops: Int): Boolean = + if hops == 0 then cls == target + else reachable(cls.lexicallyEnclosingClass.asClass, hops - 1) + + thisV match + case Hot => Hot + + case addr: Addr => + val obj = heap(addr) + val curOpt = obj.klass.baseClasses.find(cls => reachable(cls, hops)) + curOpt match + case Some(cur) => + resolveThis(target, thisV, cur, source) + + case None => + report.warning("unexpected outerSelect, thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, source.srcPos) + Cold + + case RefSet(refs) => + refs.map(ref => resolveOuterSelect(target, ref, hops, source)).join + + case fun: Fun => + report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, source.srcPos) + Cold + + case Cold => Cold + } + /** Compute the outer value that correspond to `tref.prefix` */ def outerValue(tref: TypeRef, thisV: Addr, klass: ClassSymbol, source: Tree): Contextual[Result] = val cls = tref.classSymbol.asClass diff --git a/tests/init/crash/i8892.scala b/tests/init/crash/i8892.scala new file mode 100644 index 000000000000..0a7e7223fe06 --- /dev/null +++ b/tests/init/crash/i8892.scala @@ -0,0 +1,28 @@ +trait Reporter: + def report(m: String): Unit + +class Dummy extends Reporter: + def report(m: String) = () + + object ABug { + sealed trait Nat { + transparent inline def ++ : Succ[this.type] = Succ(this) + + transparent inline def +(inline that: Nat): Nat = + inline this match { + case Zero => that + case Succ(p) => p + that.++ + } + } + + case object Zero extends Nat + case class Succ[N <: Nat](p: N) extends Nat + + transparent inline def toIntg(inline n: Nat): Int = + inline n match { + case Zero => 0 + case Succ(p) => toIntg(p) + 1 + } + + val j31 = toIntg(Zero.++.++.++ + Zero.++) + } \ No newline at end of file From e901a3653f6be1786b387c365ad2dc7c602bf807 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 26 Jul 2021 22:54:07 +0200 Subject: [PATCH 9/9] Fix join of values After #12711, it is no longer the case that `Warm < ThisRef`. The reason is that the class parameters for `ThisRef` are always hot, while it is not the case anymore after #12711. Actually even before #12711, it's not the case because the outers of warm objects may not be hot. The theory does not have inner classes, it thus does not suffer from the problem. --- .../tools/dotc/transform/init/Semantic.scala | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index e95706b4fcef..1aa9656a2084 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -28,24 +28,20 @@ class Semantic { * Value = Hot | Cold | Warm | ThisRef | Fun | RefSet * * Cold - * ┌──────► ▲ ◄──┐ ◄────┐ - * │ │ │ │ - * │ │ │ │ - * ThisRef(C) │ │ │ - * ▲ │ │ │ - * │ Warm(D) Fun RefSet - * │ ▲ ▲ ▲ - * │ │ │ │ - * Warm(C) │ │ │ - * ▲ │ │ │ - * │ │ │ │ - * └─────────┴──────┴───────┘ + * ┌──────► ▲ ◄────┐ ◄────┐ + * │ │ │ │ + * │ │ │ │ + * | │ │ │ + * | │ │ │ + * ThisRef(C) Warm(D) Fun RefSet + * │ ▲ ▲ ▲ + * │ │ │ │ + * | │ │ │ + * ▲ │ │ │ + * │ │ │ │ + * └─────────┴───────┴───────┘ * Hot * - * The most important ordering is the following: - * - * Hot ⊑ Warm(C) ⊑ ThisRef(C) ⊑ Cold - * * The diagram above does not reflect relationship between `RefSet` * and other values. `RefSet` represents a set of values which could * be `ThisRef`, `Warm` or `Fun`. The following ordering applies for @@ -302,9 +298,6 @@ class Semantic { case (Cold, _) => Cold case (_, Cold) => Cold - case (a: Warm, b: ThisRef) if a.klass == b.klass => b - case (a: ThisRef, b: Warm) if a.klass == b.klass => a - case (a: (Fun | Warm | ThisRef), b: (Fun | Warm | ThisRef)) => RefSet(a :: b :: Nil) case (a: (Fun | Warm | ThisRef), RefSet(refs)) => RefSet(a :: refs)