Skip to content

Commit 84f8fc3

Browse files
committed
Add underlining to safe-init stack traces
This underlines the source positions in the safe-init stack traces similarly to how the MessageRendering will underline its stack positions. Before, a safe-init error might look like this: ``` [error] -- Error: /******/dotty/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala:1032:22 [error] 1032 | "inlineVars" -> inlineVars [error] | ^^^^^^^^^^ [error] |Cannot prove that the value is fully initialized. Only initialized values may be used as arguments [error] | [error] |The unsafe promotion may cause the following problem: [error] |Calling the external method method apply may cause initialization errors. Calling trace: [error] | -> "inlineVars" -> inlineVars [ PatternMatcher.scala:1032 ] [error] | -> Inliner(plan) [ PatternMatcher.scala:700 ] [error] | -> case plan: TestPlan => apply(plan) [ PatternMatcher.scala:492 ] [error] | -> plan.scrutinee = apply(plan.scrutinee) [ PatternMatcher.scala:472 ] [error] | -> def apply(tree: Tree): Tree = treeMap.transform(tree) [ PatternMatcher.scala:470 ] [error] | -> if (toDrop(sym)) transform(initializer(sym)) [ PatternMatcher.scala:675 ] ``` Now, the same error looks like this: ``` [error] -- Error: /******/dotty/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala:1032:22 [error] 1032 | "inlineVars" -> inlineVars [error] | ^^^^^^^^^^ [error] |Cannot prove that the value is fully initialized. Only initialized values may be used as arguments [error] | [error] |The unsafe promotion may cause the following problem: [error] |Calling the external method method apply may cause initialization errors. Calling trace: [error] | -> "inlineVars" -> inlineVars [ PatternMatcher.scala:1032 ] [error] | ^^^^^^^^^^ [error] | -> Inliner(plan) [ PatternMatcher.scala:700 ] [error] | ^^^^^^^^^^^^^ [error] | -> case plan: TestPlan => apply(plan) [ PatternMatcher.scala:492 ] [error] | ^^^^^^^^^^^ [error] | -> plan.scrutinee = apply(plan.scrutinee) [ PatternMatcher.scala:472 ] [error] | ^^^^^^^^^^^^^^^^^^^^^ [error] | -> def apply(tree: Tree): Tree = treeMap.transform(tree) [ PatternMatcher.scala:470 ] [error] | ^^^^^^^^^^^^^^^^^^^^^^^ [error] | -> if (toDrop(sym)) transform(initializer(sym)) [ PatternMatcher.scala:675 ] ``` This should improve the readability of the safe-init error messages. Review by @liufengyun
1 parent 5626f25 commit 84f8fc3

13 files changed

+57
-1
lines changed

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package init
55

66
import ast.tpd._
77
import core._
8+
import util.SourcePosition
89
import Decorators._, printing.SyntaxHighlighting
910
import Types._, Symbols._, Contexts._
1011

@@ -40,14 +41,28 @@ object Errors {
4041
i"$code\t$loc"
4142
else
4243
tree.show
44+
val positionMarkerLine = positionMarker(pos, indentCount + 3)
4345

44-
if (last != line) sb.append(prefix + line + "\n")
46+
if (last != line) sb.append(prefix + line + "\n" + positionMarkerLine )
4547

4648
last = line
4749
}
4850
sb.toString
4951
}
5052

53+
/** Used to underline source positions in the stack trace
54+
*/
55+
private def positionMarker(pos: SourcePosition, indent: Int): String = {
56+
val trimmed = pos.lineContent.takeWhile(c => c.isWhitespace).length
57+
val padding = pos.startColumnPadding.substring(trimmed).nn + (" " * indent)
58+
val carets =
59+
if (pos.startLine == pos.endLine)
60+
"^" * math.max(1, pos.endColumn - pos.startColumn)
61+
else "^"
62+
63+
return s"$padding$carets\n"
64+
}
65+
5166
/** Flatten UnsafePromotion errors
5267
*/
5368
def flatten: Errors = this match {

tests/init/neg/cycle-structure.check

+6
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@
33
| ^^^
44
| Access field A.this.b.x on a value with an unknown initialization status. Calling trace:
55
| -> val x = A(this) [ cycle-structure.scala:9 ]
6+
| ^^^^^^^
67
| -> case class A(b: B) { [ cycle-structure.scala:1 ]
8+
| ^
79
-- Error: tests/init/neg/cycle-structure.scala:8:15 --------------------------------------------------------------------
810
8 | val x1 = a.x // error
911
| ^^^
1012
| Access field B.this.a.x on a value with an unknown initialization status. Calling trace:
1113
| -> val x = A(this) [ cycle-structure.scala:9 ]
14+
| ^^^^^^^
1215
| -> case class A(b: B) { [ cycle-structure.scala:1 ]
16+
| ^
1317
| -> val x = B(this) [ cycle-structure.scala:3 ]
18+
| ^^^^^^^
1419
| -> case class B(a: A) { [ cycle-structure.scala:7 ]
20+
| ^

tests/init/neg/default-this.check

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
| ^^^^^^^
44
|Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. Calling trace:
55
| -> val result = updateThenCompare(5) [ default-this.scala:11 ]
6+
| ^^^^^^^^^^^^^^^^^^^^

tests/init/neg/default-this.check.new

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Error: tests/init/neg/default-this.scala:9:8 ------------------------------------------------------------------------
2+
9 | compare() // error
3+
^^^^^^^
4+
Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. Calling trace:
5+
-> val result = updateThenCompare(5) [ default-this.scala:11 ]
6+

tests/init/neg/enum-desugared.check

+4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
| The unsafe promotion may cause the following problem:
77
| Calling the external method method name may cause initialization errors. Calling trace:
88
| -> Array(this.LazyErrorId, this.NoExplanationID) // error // error [ enum-desugared.scala:17 ]
9+
| ^^^^^^^^^^^^^^^^
910
| -> override def productPrefix: String = this.name() [ enum-desugared.scala:29 ]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1012
-- Error: tests/init/neg/enum-desugared.scala:17:33 --------------------------------------------------------------------
1113
17 | Array(this.LazyErrorId, this.NoExplanationID) // error // error
1214
| ^^^^^^^^^^^^^^^^^^^^
@@ -15,4 +17,6 @@
1517
| The unsafe promotion may cause the following problem:
1618
| Calling the external method method ordinal may cause initialization errors. Calling trace:
1719
| -> Array(this.LazyErrorId, this.NoExplanationID) // error // error [ enum-desugared.scala:17 ]
20+
| ^^^^^^^^^^^^^^^^^^^^
1821
| -> def errorNumber: Int = this.ordinal() - 2 [ enum-desugared.scala:8 ]
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/init/neg/enum.check

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
| The unsafe promotion may cause the following problem:
77
| Calling the external method method name may cause initialization errors. Calling trace:
88
| -> NoExplanationID // error [ enum.scala:4 ]
9+
| ^

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

+8
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@
44
| Cannot prove that the value is fully initialized. May only assign fully initialized value.
55
| Calling trace:
66
| -> val c = new C [ inherit-non-hot.scala:19 ]
7+
| ^^^^^
78
| -> class C extends A { [ inherit-non-hot.scala:15 ]
9+
| ^
810
| -> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ]
11+
| ^^^
912
|
1013
| The unsafe promotion may cause the following problem:
1114
| Call method Foo.B.this.aCopy.toB on a value with an unknown initialization. Calling trace:
1215
| -> val c = new C [ inherit-non-hot.scala:19 ]
16+
| ^^^^^
1317
| -> class C extends A { [ inherit-non-hot.scala:15 ]
18+
| ^
1419
| -> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ]
20+
| ^^^
1521
| -> if b == null then b = new B(this) // error [ inherit-non-hot.scala:6 ]
22+
| ^^^^^^^^^^^
1623
| -> def getBAgain: B = aCopy.toB [ inherit-non-hot.scala:12 ]
24+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/init/neg/inlined-method.check

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
| ^^^^^^^
44
|Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. Calling trace:
55
| -> Assertion.failAssert(this) [ inlined-method.scala:2 ]
6+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/init/neg/local-warm4.check

+7
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@
33
| ^^^^
44
| Cannot prove that the value is fully initialized. May only assign fully initialized value. Calling trace:
55
| -> val a = new A(5) [ local-warm4.scala:26 ]
6+
| ^^^^^^^^
67
| -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ]
8+
| ^
79
| -> val b = new B(y) [ local-warm4.scala:10 ]
10+
| ^^^^^^^^
811
| -> class B(x: Int) extends A(x) { [ local-warm4.scala:13 ]
12+
| ^
913
| -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ]
14+
| ^
1015
| -> increment() [ local-warm4.scala:9 ]
16+
| ^^^^^^^^^^^
1117
| -> updateA() [ local-warm4.scala:21 ]
18+
| ^^^^^^^^^

tests/init/neg/t3273.check

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
| The unsafe promotion may cause the following problem:
77
| Access non-initialized value num1. Calling trace:
88
| -> val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error [ t3273.scala:4 ]
9+
| ^^^^
910
-- Error: tests/init/neg/t3273.scala:5:61 ------------------------------------------------------------------------------
1011
5 | val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error
1112
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -14,3 +15,4 @@
1415
| The unsafe promotion may cause the following problem:
1516
| Access non-initialized value num2. Calling trace:
1617
| -> val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error [ t3273.scala:5 ]
18+
| ^^^^

tests/init/neg/unsound2.check

+2
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
| ^^^
44
| Access field B.this.a.n on a value with an unknown initialization status. Calling trace:
55
| -> println(foo(x).getB) [ unsound2.scala:8 ]
6+
| ^^^^^^
67
| -> def foo(y: Int): B = if (y > 10) then B(bar(y - 1), foo(y - 1).getN) else B(bar(y), 10) [ unsound2.scala:2 ]
8+
| ^^^^^^^^^^^^^^^

tests/init/neg/unsound3.check

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
| ^^^^^^^^^^^^^^
44
| Access field C.this.foo().getC().b on a value with an unknown initialization status. Calling trace:
55
| -> val b = foo() [ unsound3.scala:12 ]
6+
| ^^^^^

tests/init/neg/unsound4.check

+2
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
| ^
44
| Access non-initialized value aAgain. Calling trace:
55
| -> val aAgain = foo(5) // error [ unsound4.scala:3 ]
6+
| ^^^^^^
67
| -> def foo(x: Int): A = if (x < 5) then this else foo(x - 1).aAgain [ unsound4.scala:2 ]
8+
| ^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)