Skip to content

Commit a569057

Browse files
authored
Reword error messages in initialization checker to be more user friendly. (#17030)
Rewords some warning messages produced by the -Ysafe-init flag so that they are better understood by the user. Addresses Issue #15836
2 parents 363802f + 8af1b29 commit a569057

18 files changed

+128
-134
lines changed

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ object Errors:
4747

4848
case class AccessCold(field: Symbol)(val trace: Trace) extends Error:
4949
def show(using Context): String =
50-
"Access field " + field.show + " on a cold object." + stacktrace
50+
"Access field " + field.show + " on an uninitialized (Cold) object." + stacktrace
5151

5252
case class CallCold(meth: Symbol)(val trace: Trace) extends Error:
5353
def show(using Context): String =
54-
"Call method " + meth.show + " on a cold object." + stacktrace
54+
"Call method " + meth.show + " on an uninitialized (Cold) object." + stacktrace
5555

5656
case class CallUnknown(meth: Symbol)(val trace: Trace) extends Error:
5757
def show(using Context): String =
@@ -62,7 +62,7 @@ object Errors:
6262
case class UnsafePromotion(msg: String, error: Error)(val trace: Trace) extends Error:
6363
def show(using Context): String =
6464
msg + stacktrace + "\n" +
65-
"Promoting the value to hot (transitively initialized) failed due to the following problem:\n" + {
65+
"Promoting the value to transitively initialized (Hot) failed due to the following problem:\n" + {
6666
val ctx2 = ctx.withProperty(IsFromPromotion, Some(true))
6767
error.show(using ctx2)
6868
}
@@ -96,5 +96,5 @@ object Errors:
9696
acc + text2
9797
}
9898
val verb = if multiple then " are " else " is "
99-
val adjective = "not hot (transitively initialized)."
99+
val adjective = "not transitively initialized (Hot)."
100100
subject + verb + adjective

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

+20-16
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,18 @@ object Semantic:
6464
sealed abstract class Value:
6565
def show(using Context): String = this match
6666
case ThisRef(klass) =>
67-
"ThisRef[" + klass.show + "]"
67+
"the original object of type (" + klass.show + ") where initialization checking started"
6868
case Warm(klass, outer, ctor, args) =>
6969
val argsText = if args.nonEmpty then ", args = " + args.map(_.show).mkString("(", ", ", ")") else ""
70-
"Warm[" + klass.show + "] { outer = " + outer.show + argsText + " }"
70+
"a non-transitively initialized (Warm) object of type (" + klass.show + ") { outer = " + outer.show + argsText + " }"
7171
case Fun(expr, thisV, klass) =>
72-
"Fun { this = " + thisV.show + ", owner = " + klass.show + " }"
72+
"a function where \"this\" is (" + thisV.show + ")"
7373
case RefSet(values) =>
7474
values.map(_.show).mkString("Set { ", ", ", " }")
75-
case _ =>
76-
this.toString()
75+
case Hot =>
76+
"a transitively initialized (Hot) object"
77+
case Cold =>
78+
"an uninitialized (Cold) object"
7779

7880
def isHot = this == Hot
7981
def isCold = this == Cold
@@ -470,7 +472,7 @@ object Semantic:
470472
def widenArg: Contextual[Value] =
471473
a match
472474
case _: Ref | _: Fun =>
473-
val hasError = Reporter.hasErrors { a.promote("Argument cannot be promoted to hot") }
475+
val hasError = Reporter.hasErrors { a.promote("Argument is not provably transitively initialized (Hot)") }
474476
if hasError then Cold else Hot
475477

476478
case RefSet(refs) =>
@@ -707,7 +709,9 @@ object Semantic:
707709
// no source code available
708710
promoteArgs()
709711
// try promoting the receiver as last resort
710-
val hasErrors = Reporter.hasErrors { ref.promote("try promote value to hot") }
712+
val hasErrors = Reporter.hasErrors {
713+
ref.promote(ref.show + " has no source code and is not provably transitively initialized (Hot).")
714+
}
711715
if hasErrors then
712716
val error = CallUnknown(target)(trace)
713717
reporter.report(error)
@@ -992,7 +996,7 @@ object Semantic:
992996
eval(body, thisV, klass, cacheResult = true)
993997
}
994998
given Trace = Trace.empty.add(body)
995-
res.promote("The function return value is not hot. Found = " + res.show + ".")
999+
res.promote("Only transitively initialized (Hot) values can be returned by functions. The function " + fun.show + " returns " + res.show + ".")
9961000
}
9971001
if errors.nonEmpty then
9981002
reporter.report(UnsafePromotion(msg, errors.head)(trace))
@@ -1036,7 +1040,7 @@ object Semantic:
10361040
//
10371041
// This invariant holds because of the Scala/Java/JVM restriction that we cannot use `this` in super constructor calls.
10381042
if subClassSegmentHot && !isHotSegment then
1039-
report.error("[Internal error] Expect current segment to hot in promotion, current klass = " + klass.show +
1043+
report.error("[Internal error] Expect current segment to be transitively initialized (Hot) in promotion, current klass = " + klass.show +
10401044
", subclass = " + subClass.show + Trace.show, Trace.position)
10411045

10421046
// If the outer and parameters of a class are all hot, then accessing fields and methods of the current
@@ -1053,12 +1057,12 @@ object Semantic:
10531057
val args = member.info.paramInfoss.flatten.map(_ => new ArgInfo(Hot: Value, Trace.empty))
10541058
val res = warm.call(member, args, receiver = warm.klass.typeRef, superType = NoType)
10551059
withTrace(trace.add(member.defTree)) {
1056-
res.promote("Cannot prove that the return value of " + member.show + " is hot. Found = " + res.show + ".")
1060+
res.promote("Could not verify that the return value of " + member.show + " is transitively initialized (Hot). It was found to be " + res.show + ".")
10571061
}
10581062
else
10591063
val res = warm.select(member, receiver = warm.klass.typeRef)
10601064
withTrace(trace.add(member.defTree)) {
1061-
res.promote("Cannot prove that the field " + member.show + " is hot. Found = " + res.show + ".")
1065+
res.promote("Could not verify that the field " + member.show + " is transitively initialized (Hot). It was found to be " + res.show + ".")
10621066
}
10631067
end for
10641068

@@ -1144,7 +1148,7 @@ object Semantic:
11441148

11451149
extension (arg: ArgInfo)
11461150
def promote: Contextual[Unit] = withTrace(arg.trace) {
1147-
arg.value.promote("Cannot prove the method argument is hot. Only hot values are safe to leak.\nFound = " + arg.value.show + ".")
1151+
arg.value.promote("Could not verify that the method argument is transitively initialized (Hot). It was found to be " + arg.value.show + ". Only transitively initialized arguments may be passed to methods (except constructors).")
11481152
}
11491153

11501154
/** Evaluate an expression with the given value for `this` in a given class `klass`
@@ -1285,12 +1289,12 @@ object Semantic:
12851289
eval(qual, thisV, klass)
12861290
val res = eval(rhs, thisV, klass)
12871291
extendTrace(expr) {
1288-
res.ensureHot("The RHS of reassignment must be hot. Found = " + res.show + ". ")
1292+
res.ensureHot("The RHS of reassignment must be transitively initialized (Hot). It was found to be " + res.show + ". ")
12891293
}
12901294
case id: Ident =>
12911295
val res = eval(rhs, thisV, klass)
12921296
extendTrace(expr) {
1293-
res.ensureHot("The RHS of reassignment must be hot. Found = " + res.show + ". ")
1297+
res.ensureHot("The RHS of reassignment must be transitively initialized (Hot). It was found to be " + res.show + ". ")
12941298
}
12951299

12961300
case closureDef(ddef) =>
@@ -1313,14 +1317,14 @@ object Semantic:
13131317
case Match(selector, cases) =>
13141318
val res = eval(selector, thisV, klass)
13151319
extendTrace(selector) {
1316-
res.ensureHot("The value to be matched needs to be hot. Found = " + res.show + ". ")
1320+
res.ensureHot("The value to be matched needs to be transitively initialized (Hot). It was found to be " + res.show + ". ")
13171321
}
13181322
eval(cases.map(_.body), thisV, klass).join
13191323

13201324
case Return(expr, from) =>
13211325
val res = eval(expr, thisV, klass)
13221326
extendTrace(expr) {
1323-
res.ensureHot("return expression must be hot. Found = " + res.show + ". ")
1327+
res.ensureHot("return expression must be transitively initialized (Hot). It was found to be " + res.show + ". ")
13241328
}
13251329

13261330
case WhileDo(cond, body) =>

tests/init/neg/closureLeak.check

+10-12
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
-- Error: tests/init/neg/closureLeak.scala:11:14 -----------------------------------------------------------------------
22
11 | l.foreach(a => a.addX(this)) // error
33
| ^^^^^^^^^^^^^^^^^
4-
| Cannot prove the method argument is hot. Only hot values are safe to leak.
5-
| Found = Fun { this = ThisRef[class Outer], owner = class Outer }. Calling trace:
6-
| -> class Outer { [ closureLeak.scala:1 ]
7-
| ^
8-
| -> l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ]
9-
| ^^^^^^^^^^^^^^^^^
4+
|Could not verify that the method argument is transitively initialized (Hot). It was found to be a function where "this" is (the original object of type (class Outer) where initialization checking started). Only transitively initialized arguments may be passed to methods (except constructors). Calling trace:
5+
|-> class Outer { [ closureLeak.scala:1 ]
6+
| ^
7+
|-> l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ]
8+
| ^^^^^^^^^^^^^^^^^
109
|
11-
| Promoting the value to hot (transitively initialized) failed due to the following problem:
12-
| Cannot prove the method argument is hot. Only hot values are safe to leak.
13-
| Found = ThisRef[class Outer].
14-
| Non initialized field(s): value p. Promotion trace:
15-
| -> l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ]
16-
| ^^^^
10+
|Promoting the value to transitively initialized (Hot) failed due to the following problem:
11+
|Could not verify that the method argument is transitively initialized (Hot). It was found to be the original object of type (class Outer) where initialization checking started. Only transitively initialized arguments may be passed to methods (except constructors).
12+
|Non initialized field(s): value p. Promotion trace:
13+
|-> l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ]
14+
| ^^^^

tests/init/neg/cycle-structure.check

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
-- Error: tests/init/neg/cycle-structure.scala:3:13 --------------------------------------------------------------------
22
3 | val x = B(this) // error
33
| ^^^^^^^
4-
| Problematic object instantiation: arg 1 is not hot (transitively initialized). Calling trace:
4+
| Problematic object instantiation: arg 1 is not transitively initialized (Hot). Calling trace:
55
| -> case class A(b: B) { [ cycle-structure.scala:1 ]
66
| ^
77
| -> val x = B(this) // error [ cycle-structure.scala:3 ]
88
| ^^^^^^^
99
|
1010
| It leads to the following error during object initialization:
11-
| Access field value x on a cold object. Calling trace:
11+
| Access field value x on an uninitialized (Cold) object. Calling trace:
1212
| -> case class B(a: A) { [ cycle-structure.scala:7 ]
1313
| ^
1414
| -> val x1 = a.x [ cycle-structure.scala:8 ]
1515
| ^^^
1616
-- Error: tests/init/neg/cycle-structure.scala:9:13 --------------------------------------------------------------------
1717
9 | val x = A(this) // error
1818
| ^^^^^^^
19-
| Problematic object instantiation: arg 1 is not hot (transitively initialized). Calling trace:
19+
| Problematic object instantiation: arg 1 is not transitively initialized (Hot). Calling trace:
2020
| -> case class B(a: A) { [ cycle-structure.scala:7 ]
2121
| ^
2222
| -> val x = A(this) // error [ cycle-structure.scala:9 ]
2323
| ^^^^^^^
2424
|
2525
| It leads to the following error during object initialization:
26-
| Access field value x on a cold object. Calling trace:
26+
| Access field value x on an uninitialized (Cold) object. Calling trace:
2727
| -> case class A(b: B) { [ cycle-structure.scala:1 ]
2828
| ^
2929
| -> val x1 = b.x [ cycle-structure.scala:2 ]

tests/init/neg/default-this.check

+10-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
-- Error: tests/init/neg/default-this.scala:9:8 ------------------------------------------------------------------------
22
9 | compare() // error
33
| ^^^^^^^
4-
| Cannot prove the method argument is hot. Only hot values are safe to leak.
5-
| Found = ThisRef[class B].
6-
| Non initialized field(s): value result. Calling trace:
7-
| -> class B extends A { [ default-this.scala:6 ]
8-
| ^
9-
| -> val result = updateThenCompare(5) [ default-this.scala:11 ]
10-
| ^^^^^^^^^^^^^^^^^^^^
11-
| -> def updateThenCompare(c: Int): Boolean = { [ default-this.scala:7 ]
12-
| ^
13-
| -> compare() // error [ default-this.scala:9 ]
14-
| ^^^^^^^
4+
|Could not verify that the method argument is transitively initialized (Hot). It was found to be the original object of type (class B) where initialization checking started. Only transitively initialized arguments may be passed to methods (except constructors).
5+
|Non initialized field(s): value result. Calling trace:
6+
|-> class B extends A { [ default-this.scala:6 ]
7+
| ^
8+
|-> val result = updateThenCompare(5) [ default-this.scala:11 ]
9+
| ^^^^^^^^^^^^^^^^^^^^
10+
|-> def updateThenCompare(c: Int): Boolean = { [ default-this.scala:7 ]
11+
| ^
12+
|-> compare() // error [ default-this.scala:9 ]
13+
| ^^^^^^^

tests/init/neg/i15363.check

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
-- Error: tests/init/neg/i15363.scala:3:10 -----------------------------------------------------------------------------
22
3 | val b = new B(this) // error
33
| ^^^^^^^^^^^
4-
| Problematic object instantiation: arg 1 is not hot (transitively initialized). Calling trace:
4+
| Problematic object instantiation: arg 1 is not transitively initialized (Hot). Calling trace:
55
| -> class A: [ i15363.scala:1 ]
66
| ^
77
| -> val b = new B(this) // error [ i15363.scala:3 ]
88
| ^^^^^^^^^^^
99
|
1010
| It leads to the following error during object initialization:
11-
| Access field value m on a cold object. Calling trace:
11+
| Access field value m on an uninitialized (Cold) object. Calling trace:
1212
| -> class B(a: A): [ i15363.scala:7 ]
1313
| ^
1414
| -> val x = a.m [ i15363.scala:8 ]

tests/init/neg/i15459.check

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
-- Error: tests/init/neg/i15459.scala:3:10 -----------------------------------------------------------------------------
22
3 | println(this) // error
33
| ^^^^
4-
| Cannot prove the method argument is hot. Only hot values are safe to leak.
5-
| Found = ThisRef[class Sub].
6-
| Non initialized field(s): value b. Calling trace:
7-
| -> class Sub extends Sup: [ i15459.scala:5 ]
8-
| ^
9-
| -> class Sup: [ i15459.scala:1 ]
10-
| ^
11-
| -> println(this) // error [ i15459.scala:3 ]
12-
| ^^^^
4+
|Could not verify that the method argument is transitively initialized (Hot). It was found to be the original object of type (class Sub) where initialization checking started. Only transitively initialized arguments may be passed to methods (except constructors).
5+
|Non initialized field(s): value b. Calling trace:
6+
|-> class Sub extends Sup: [ i15459.scala:5 ]
7+
| ^
8+
|-> class Sup: [ i15459.scala:1 ]
9+
| ^
10+
|-> println(this) // error [ i15459.scala:3 ]
11+
| ^^^^

tests/init/neg/inherit-non-hot.check

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
-- Error: tests/init/neg/inherit-non-hot.scala:6:32 --------------------------------------------------------------------
22
6 | if b == null then b = new B(this) // error
33
| ^^^^^^^^^^^^^^^
4-
| The RHS of reassignment must be hot. Found = Warm[class B] { outer = Hot, args = (Cold) }. Calling trace:
5-
| -> class C extends A { [ inherit-non-hot.scala:15 ]
6-
| ^
7-
| -> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ]
8-
| ^^^
9-
| -> def toB: B = [ inherit-non-hot.scala:5 ]
10-
| ^
11-
| -> if b == null then b = new B(this) // error [ inherit-non-hot.scala:6 ]
12-
| ^^^^^^^^^^^^^^^
4+
|The RHS of reassignment must be transitively initialized (Hot). It was found to be a non-transitively initialized (Warm) object of type (class B) { outer = a transitively initialized (Hot) object, args = (an uninitialized (Cold) object) }. Calling trace:
5+
|-> class C extends A { [ inherit-non-hot.scala:15 ]
6+
| ^
7+
|-> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ]
8+
| ^^^
9+
|-> def toB: B = [ inherit-non-hot.scala:5 ]
10+
| ^
11+
|-> if b == null then b = new B(this) // error [ inherit-non-hot.scala:6 ]
12+
| ^^^^^^^^^^^^^^^
1313
|
14-
| Promoting the value to hot (transitively initialized) failed due to the following problem:
15-
| Cannot prove that the field value a is hot. Found = Cold. Promotion trace:
16-
| -> class B(a: A) { [ inherit-non-hot.scala:10 ]
17-
| ^^^^
14+
|Promoting the value to transitively initialized (Hot) failed due to the following problem:
15+
|Could not verify that the field value a is transitively initialized (Hot). It was found to be an uninitialized (Cold) object. Promotion trace:
16+
|-> class B(a: A) { [ inherit-non-hot.scala:10 ]
17+
| ^^^^

tests/init/neg/inlined-method.check

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
-- Error: tests/init/neg/inlined-method.scala:8:45 ---------------------------------------------------------------------
22
8 | scala.runtime.Scala3RunTime.assertFailed(message) // error
33
| ^^^^^^^
4-
| Cannot prove the method argument is hot. Only hot values are safe to leak.
5-
| Found = ThisRef[class InlineError].
6-
| Non initialized field(s): value v. Calling trace:
7-
| -> class InlineError { [ inlined-method.scala:1 ]
8-
| ^
9-
| -> Assertion.failAssert(this) [ inlined-method.scala:2 ]
10-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
11-
| -> scala.runtime.Scala3RunTime.assertFailed(message) // error [ inlined-method.scala:8 ]
12-
| ^^^^^^^
4+
|Could not verify that the method argument is transitively initialized (Hot). It was found to be the original object of type (class InlineError) where initialization checking started. Only transitively initialized arguments may be passed to methods (except constructors).
5+
|Non initialized field(s): value v. Calling trace:
6+
|-> class InlineError { [ inlined-method.scala:1 ]
7+
| ^
8+
|-> Assertion.failAssert(this) [ inlined-method.scala:2 ]
9+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
10+
|-> scala.runtime.Scala3RunTime.assertFailed(message) // error [ inlined-method.scala:8 ]
11+
| ^^^^^^^

tests/init/neg/inner-first.check

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
-- Error: tests/init/neg/inner-first.scala:3:12 ------------------------------------------------------------------------
22
3 | println(this) // error
33
| ^^^^
4-
| Cannot prove the method argument is hot. Only hot values are safe to leak.
5-
| Found = ThisRef[class B].
6-
| Non initialized field(s): value n. Calling trace:
7-
| -> class B: [ inner-first.scala:2 ]
8-
| ^
9-
| -> println(this) // error [ inner-first.scala:3 ]
10-
| ^^^^
4+
|Could not verify that the method argument is transitively initialized (Hot). It was found to be the original object of type (class B) where initialization checking started. Only transitively initialized arguments may be passed to methods (except constructors).
5+
|Non initialized field(s): value n. Calling trace:
6+
|-> class B: [ inner-first.scala:2 ]
7+
| ^
8+
|-> println(this) // error [ inner-first.scala:3 ]
9+
| ^^^^

0 commit comments

Comments
 (0)