From 227616c5e161f3152ff3aaace1bcde1b1c1281ba Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 20 Jun 2022 22:07:08 +0200 Subject: [PATCH] Fix #15459: Display uninitialized fields in promotion error --- .../tools/dotc/transform/init/Semantic.scala | 21 +++++++++++++++++-- tests/init/neg/closureLeak.check | 3 ++- tests/init/neg/default-this.check | 19 +++++++++-------- tests/init/neg/i15459.check | 11 ++++++++++ tests/init/neg/i15459.scala | 8 +++++++ tests/init/neg/inlined-method.check | 15 ++++++------- tests/init/neg/promotion-loop.check | 1 + 7 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 tests/init/neg/i15459.check create mode 100644 tests/init/neg/i15459.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 0b4bafb10124..33edf79b2f1a 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -1006,6 +1006,19 @@ object Semantic: } } + def nonInitFields(): Contextual[List[Symbol]] = + val obj = ref.objekt + ref.klass.baseClasses.flatMap { klass => + if klass.hasSource then + klass.info.decls.filter { member => + !member.isOneOf(Flags.Method | Flags.Lazy | Flags.Deferred) + && !member.isType + && !obj.hasField(member) + } + else + Nil + } + end extension extension (thisRef: ThisRef) @@ -1032,8 +1045,12 @@ object Semantic: reporter.report(PromoteError(msg, trace.toVector)) case thisRef: ThisRef => - if !thisRef.tryPromoteCurrentObject() then - reporter.report(PromoteError(msg, trace.toVector)) + val emptyFields = thisRef.nonInitFields() + if emptyFields.isEmpty then + promoted.promoteCurrent(thisRef) + else + val fields = "Non initialized field(s): " + emptyFields.map(_.show).mkString(", ") + "." + reporter.report(PromoteError(msg + "\n" + fields, trace.toVector)) case warm: Warm => if !promoted.contains(warm) then diff --git a/tests/init/neg/closureLeak.check b/tests/init/neg/closureLeak.check index a90355fce1d4..b25130a9ee64 100644 --- a/tests/init/neg/closureLeak.check +++ b/tests/init/neg/closureLeak.check @@ -8,6 +8,7 @@ | ^^^^^^^^^^^^^^^^^ | | Promoting the value to fully initialized failed due to the following problem: - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. Calling trace: + | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. + | Non initialized field(s): value p. Calling trace: | -> l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ] | ^^^^ diff --git a/tests/init/neg/default-this.check b/tests/init/neg/default-this.check index cccfa47a1fe7..25054c8360d3 100644 --- a/tests/init/neg/default-this.check +++ b/tests/init/neg/default-this.check @@ -1,12 +1,13 @@ -- Error: tests/init/neg/default-this.scala:9:8 ------------------------------------------------------------------------ 9 | compare() // error | ^^^^^^^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. Calling trace: - | -> class B extends A { [ default-this.scala:6 ] - | ^ - | -> val result = updateThenCompare(5) [ default-this.scala:11 ] - | ^^^^^^^^^^^^^^^^^^^^ - | -> def updateThenCompare(c: Int): Boolean = { [ default-this.scala:7 ] - | ^ - | -> compare() // error [ default-this.scala:9 ] - | ^^^^^^^ + | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. + | Non initialized field(s): value result. Calling trace: + | -> class B extends A { [ default-this.scala:6 ] + | ^ + | -> val result = updateThenCompare(5) [ default-this.scala:11 ] + | ^^^^^^^^^^^^^^^^^^^^ + | -> def updateThenCompare(c: Int): Boolean = { [ default-this.scala:7 ] + | ^ + | -> compare() // error [ default-this.scala:9 ] + | ^^^^^^^ diff --git a/tests/init/neg/i15459.check b/tests/init/neg/i15459.check new file mode 100644 index 000000000000..f9f08c488605 --- /dev/null +++ b/tests/init/neg/i15459.check @@ -0,0 +1,11 @@ +-- Error: tests/init/neg/i15459.scala:3:10 ----------------------------------------------------------------------------- +3 | println(this) // error + | ^^^^ + | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. + | Non initialized field(s): value b. Calling trace: + | -> class Sub extends Sup: [ i15459.scala:5 ] + | ^ + | -> class Sup: [ i15459.scala:1 ] + | ^ + | -> println(this) // error [ i15459.scala:3 ] + | ^^^^ diff --git a/tests/init/neg/i15459.scala b/tests/init/neg/i15459.scala new file mode 100644 index 000000000000..4d54f31f7af8 --- /dev/null +++ b/tests/init/neg/i15459.scala @@ -0,0 +1,8 @@ +class Sup: + val a = 10 + println(this) // error + +class Sub extends Sup: + val b = 20 + + override def toString() = "a = " + a + ", b = " + b diff --git a/tests/init/neg/inlined-method.check b/tests/init/neg/inlined-method.check index 72637948ceb9..627623f315a3 100644 --- a/tests/init/neg/inlined-method.check +++ b/tests/init/neg/inlined-method.check @@ -1,10 +1,11 @@ -- Error: tests/init/neg/inlined-method.scala:8:45 --------------------------------------------------------------------- 8 | scala.runtime.Scala3RunTime.assertFailed(message) // error | ^^^^^^^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. Calling trace: - | -> class InlineError { [ inlined-method.scala:1 ] - | ^ - | -> Assertion.failAssert(this) [ inlined-method.scala:2 ] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -> scala.runtime.Scala3RunTime.assertFailed(message) // error [ inlined-method.scala:8 ] - | ^^^^^^^ + | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. + | Non initialized field(s): value v. Calling trace: + | -> class InlineError { [ inlined-method.scala:1 ] + | ^ + | -> Assertion.failAssert(this) [ inlined-method.scala:2 ] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | -> scala.runtime.Scala3RunTime.assertFailed(message) // error [ inlined-method.scala:8 ] + | ^^^^^^^ diff --git a/tests/init/neg/promotion-loop.check b/tests/init/neg/promotion-loop.check index 5fae90f0ebfa..48eb763a7296 100644 --- a/tests/init/neg/promotion-loop.check +++ b/tests/init/neg/promotion-loop.check @@ -9,3 +9,4 @@ | | Promoting the value to fully initialized failed due to the following problem: | Cannot prove that the field val outer is fully initialized. + | Non initialized field(s): value n.