Skip to content

Commit 7d2cad5

Browse files
authored
Make sure that trace is shown correctly in the presence of invalid line numbers (#18930)
Make sure that trace is shown correctly in the presence of invalid line numbers (-1) in TASTy. Before: ```Scala Reading mutable state of object Array during initialization of object Parent. Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. Calling trace: -> object Parent { // error [ t9312.scala:11 ] ^ -> final val children = Set(Child1, Child2) [ t9312.scala:21 ] ^^^^^^^^^^^^^^^^^^^ -> BitmapIndexedSetNode.this.originalHashes.apply(index) ``` After: ```Scala Reading mutable state of object Array during initialization of object Parent. Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. Calling trace: ├── object Parent { // error [ t9312.scala:11 ] │ ^ ├── final val children = Set(Child1, Child2) [ t9312.scala:21 ] │ ^^^^^^^^^^^^^^^^^^^ ├── scala.collection.IterableFactory.apply ├── IterableFactory.this.from[A](elems) ├── scala.collection.immutable.Set.from ├── scala.collection.immutable.Set.newBuilder[E].addAll(it) ├── scala.collection.immutable.SetBuilderImpl.addAll ├── super.addAll(xs) ├── scala.collection.mutable.Growable.addAll ├── Growable.this.addOne(it.next()) ├── scala.collection.immutable.SetBuilderImpl.addOne ├── SetBuilderImpl.this.hashSetBuilder.addOne(elem) ├── scala.collection.immutable.HashSetBuilder.addOne ├── HashSetBuilder.this.update(HashSetBuilder.this.rootNode, elem, h, im, 0) ├── scala.collection.immutable.HashSetBuilder.update ├── bm.getHash(index) ├── scala.collection.immutable.BitmapIndexedSetNode.getHash └── BitmapIndexedSetNode.this.originalHashes.apply(index) ``` Ref: #18882
2 parents fdf8de3 + 395167a commit 7d2cad5

25 files changed

+293
-287
lines changed

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

+14-16
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ object Objects:
380380
case Some(theValue) =>
381381
theValue
382382
case _ =>
383-
report.warning("[Internal error] Value not found " + x.show + "\nenv = " + data.show + ". Calling trace:\n" + Trace.show, Trace.position)
383+
report.warning("[Internal error] Value not found " + x.show + "\nenv = " + data.show + ". " + Trace.show, Trace.position)
384384
Bottom
385385

386386
def getVal(x: Symbol)(using data: Data, ctx: Context): Option[Value] = data.getVal(x)
@@ -619,7 +619,7 @@ object Objects:
619619
def call(value: Value, meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log("call " + meth.show + ", this = " + value.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) {
620620
value match
621621
case Cold =>
622-
report.warning("Using cold alias. Calling trace:\n" + Trace.show, Trace.position)
622+
report.warning("Using cold alias. " + Trace.show, Trace.position)
623623
Bottom
624624

625625
case Bottom =>
@@ -792,15 +792,15 @@ object Objects:
792792
errorReadOtherStaticObject(State.currentObject, addr.owner)
793793
Bottom
794794
else if ref.isObjectRef && ref.klass.hasSource then
795-
report.warning("Access uninitialized field " + field.show + ". Call trace: " + Trace.show, Trace.position)
795+
report.warning("Access uninitialized field " + field.show + ". " + Trace.show, Trace.position)
796796
Bottom
797797
else
798798
// initialization error, reported by the initialization checker
799799
Bottom
800800
else if ref.hasVal(target) then
801801
ref.valValue(target)
802802
else if ref.isObjectRef && ref.klass.hasSource then
803-
report.warning("Access uninitialized field " + field.show + ". Call trace: " + Trace.show, Trace.position)
803+
report.warning("Access uninitialized field " + field.show + ". " + Trace.show, Trace.position)
804804
Bottom
805805
else
806806
// initialization error, reported by the initialization checker
@@ -847,7 +847,7 @@ object Objects:
847847
report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + Trace.show, Trace.position)
848848

849849
case Cold =>
850-
report.warning("Assigning to cold aliases is forbidden. Calling trace:\n" + Trace.show, Trace.position)
850+
report.warning("Assigning to cold aliases is forbidden. " + Trace.show, Trace.position)
851851

852852
case Bottom =>
853853

@@ -862,7 +862,7 @@ object Objects:
862862
else
863863
Heap.writeJoin(addr, rhs)
864864
else
865-
report.warning("Mutating a field before its initialization: " + field.show + ". Calling trace:\n" + Trace.show, Trace.position)
865+
report.warning("Mutating a field before its initialization: " + field.show + ". " + Trace.show, Trace.position)
866866
end match
867867

868868
Bottom
@@ -947,7 +947,7 @@ object Objects:
947947
Bottom
948948
end if
949949
case _ =>
950-
report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". Calling trace:\n" + Trace.show, Trace.position)
950+
report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position)
951951
Bottom
952952
else
953953
given Env.Data = env
@@ -959,17 +959,17 @@ object Objects:
959959
given Env.Data = fun.env
960960
eval(fun.code, fun.thisV, fun.klass)
961961
case Cold =>
962-
report.warning("Calling cold by-name alias. Call trace: \n" + Trace.show, Trace.position)
962+
report.warning("Calling cold by-name alias. " + Trace.show, Trace.position)
963963
Bottom
964964
case _: ValueSet | _: Ref | _: OfArray =>
965-
report.warning("[Internal error] Unexpected by-name value " + value.show + ". Calling trace:\n" + Trace.show, Trace.position)
965+
report.warning("[Internal error] Unexpected by-name value " + value.show + ". " + Trace.show, Trace.position)
966966
Bottom
967967
else
968968
value
969969

970970
case _ =>
971971
if isByNameParam(sym) then
972-
report.warning("Calling cold by-name alias. Call trace: \n" + Trace.show, Trace.position)
972+
report.warning("Calling cold by-name alias. " + Trace.show, Trace.position)
973973
Bottom
974974
else
975975
Cold
@@ -994,10 +994,10 @@ object Objects:
994994
else
995995
Heap.writeJoin(addr, value)
996996
case _ =>
997-
report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". Calling trace:\n" + Trace.show, Trace.position)
997+
report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position)
998998

999999
case _ =>
1000-
report.warning("Assigning to variables in outer scope. Calling trace:\n" + Trace.show, Trace.position)
1000+
report.warning("Assigning to variables in outer scope. " + Trace.show, Trace.position)
10011001

10021002
Bottom
10031003
}
@@ -1726,15 +1726,13 @@ object Objects:
17261726
def errorMutateOtherStaticObject(currentObj: ClassSymbol, otherObj: ClassSymbol)(using Trace, Context) =
17271727
val msg =
17281728
s"Mutating ${otherObj.show} during initialization of ${currentObj.show}.\n" +
1729-
"Mutating other static objects during the initialization of one static object is forbidden. " +
1730-
"Calling trace:\n" + Trace.show
1729+
"Mutating other static objects during the initialization of one static object is forbidden. " + Trace.show
17311730

17321731
report.warning(msg, Trace.position)
17331732

17341733
def errorReadOtherStaticObject(currentObj: ClassSymbol, otherObj: ClassSymbol)(using Trace, Context) =
17351734
val msg =
17361735
"Reading mutable state of " + otherObj.show + " during initialization of " + currentObj.show + ".\n" +
1737-
"Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. " +
1738-
"Calling trace: " + Trace.show
1736+
"Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. " + Trace.show
17391737

17401738
report.warning(msg, Trace.position)

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

+18-10
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@ object Trace:
2222

2323
val empty: Trace = Vector.empty
2424

25+
val EMPTY_PADDING = " "
26+
val CONNECTING_INDENT = "\u2502 " // "| "
27+
val CHILD = "\u251c\u2500\u2500 " // "|-- "
28+
val LAST_CHILD = "\u2514\u2500\u2500 " // "\-- "
29+
2530
extension (trace: Trace)
2631
def add(node: Tree): Trace = trace :+ node
2732
def toVector: Vector[Tree] = trace
2833
def ++(trace2: Trace): Trace = trace ++ trace2
2934

30-
def show(using trace: Trace, ctx: Context): String = buildStacktrace(trace, "\n")
35+
def show(using trace: Trace, ctx: Context): String = buildStacktrace(trace, "Calling trace:" + System.lineSeparator())
3136

3237
def position(using trace: Trace): Tree = trace.last
3338

@@ -41,8 +46,8 @@ object Trace:
4146
var lastLineNum = -1
4247
var lines: mutable.ArrayBuffer[String] = new mutable.ArrayBuffer
4348
trace.foreach { tree =>
49+
val isLastTraceItem = tree `eq` trace.last
4450
val pos = tree.sourcePos
45-
val prefix = "-> "
4651
val line =
4752
if pos.source.exists then
4853
val loc = "[ " + pos.source.file.name + ":" + (pos.line + 1) + " ]"
@@ -52,19 +57,22 @@ object Trace:
5257
tree match
5358
case defDef: DefTree =>
5459
// The definition can be huge, avoid printing the whole definition.
55-
defDef.symbol.show
60+
defDef.symbol.showFullName
5661
case _ =>
57-
tree.show
62+
tree.show.split(System.lineSeparator(), 2).nn.head.nn
63+
5864
val positionMarkerLine =
5965
if pos.exists && pos.source.exists then
60-
positionMarker(pos)
61-
else ""
66+
(if isLastTraceItem then EMPTY_PADDING else CONNECTING_INDENT)+ positionMarker(pos)
67+
else
68+
""
6269

6370
// always use the more precise trace location
64-
if lastLineNum == pos.line then
71+
if lastLineNum >= 0 && lastLineNum == pos.line then
6572
lines.dropRightInPlace(1)
6673

67-
lines += (prefix + line + "\n" + positionMarkerLine)
74+
val prefix = if isLastTraceItem then LAST_CHILD else CHILD
75+
lines += (prefix + line + System.lineSeparator() + positionMarkerLine)
6876

6977
lastLineNum = pos.line
7078
}
@@ -78,10 +86,10 @@ object Trace:
7886
*/
7987
private def positionMarker(pos: SourcePosition): String =
8088
val trimmed = pos.source.lineContent(pos.start).takeWhile(c => c.isWhitespace).length
81-
val padding = pos.startColumnPadding.substring(trimmed).nn + " "
89+
val padding = pos.startColumnPadding.substring(trimmed).nn
8290
val carets =
8391
if (pos.startLine == pos.endLine)
8492
"^" * math.max(1, pos.endColumn - pos.startColumn)
8593
else "^"
8694

87-
s"$padding$carets\n"
95+
s"$padding$carets" + System.lineSeparator()

tests/init-global/neg/global-cycle1.check

+13-13
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22
1 |object A { // error
33
| ^
44
| Cyclic initialization: object A -> object B -> object A. Calling trace:
5-
| -> object A { // error [ global-cycle1.scala:1 ]
6-
| ^
7-
| -> val a: Int = B.b [ global-cycle1.scala:2 ]
8-
| ^
9-
| -> object B { [ global-cycle1.scala:5 ]
10-
| ^
11-
| -> val b: Int = A.a // error [ global-cycle1.scala:6 ]
12-
| ^
5+
| ├── object A { // error [ global-cycle1.scala:1 ]
6+
| ^
7+
| ├── val a: Int = B.b [ global-cycle1.scala:2 ]
8+
| ^
9+
| ├── object B { [ global-cycle1.scala:5 ]
10+
| ^
11+
| └── val b: Int = A.a // error [ global-cycle1.scala:6 ]
12+
| ^
1313
-- Error: tests/init-global/neg/global-cycle1.scala:6:17 ---------------------------------------------------------------
1414
6 | val b: Int = A.a // error
1515
| ^^^
16-
| Access uninitialized field value a. Call trace:
17-
| -> object B { [ global-cycle1.scala:5 ]
18-
| ^
19-
| -> val b: Int = A.a // error [ global-cycle1.scala:6 ]
20-
| ^^^
16+
| Access uninitialized field value a. Calling trace:
17+
| ├── object B { [ global-cycle1.scala:5 ]
18+
| ^
19+
| └── val b: Int = A.a // error [ global-cycle1.scala:6 ]
20+
| ^^^

tests/init-global/neg/line-spacing.check

+9-9
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
3 | B
33
4 | .s.length // error
44
| ^
5-
| Access uninitialized field value s. Call trace:
6-
| -> object B { [ line-spacing.scala:7 ]
7-
| ^
8-
| -> val s: String = s"${A.a}a" [ line-spacing.scala:8 ]
9-
| ^^^
10-
| -> def a: Int = [ line-spacing.scala:2 ]
11-
| ^
12-
| -> .s.length // error [ line-spacing.scala:4 ]
13-
| ^
5+
| Access uninitialized field value s. Calling trace:
6+
| ├── object B { [ line-spacing.scala:7 ]
7+
| ^
8+
| ├── val s: String = s"${A.a}a" [ line-spacing.scala:8 ]
9+
| ^^^
10+
| ├── def a: Int = [ line-spacing.scala:2 ]
11+
| ^
12+
| └── .s.length // error [ line-spacing.scala:4 ]
13+
| ^

tests/init-global/neg/patmat-unapplySeq.check

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
8 | def apply(i: Int): Box = array(i) // error
33
| ^^^^^^^^
44
|Reading mutable state of object A during initialization of object B.
5-
|Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. Calling trace:
6-
|-> object B: [ patmat-unapplySeq.scala:15 ]
7-
| ^
8-
|-> case A(b) => [ patmat-unapplySeq.scala:17 ]
9-
| ^^^^
10-
|-> def apply(i: Int): Box = array(i) // error [ patmat-unapplySeq.scala:8 ]
11-
| ^^^^^^^^
5+
|Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. Calling trace:
6+
|├── object B: [ patmat-unapplySeq.scala:15 ]
7+
| ^
8+
|├── case A(b) => [ patmat-unapplySeq.scala:17 ]
9+
| ^^^^
10+
|└── def apply(i: Int): Box = array(i) // error [ patmat-unapplySeq.scala:8 ]
11+
| ^^^^^^^^

tests/init/neg/closureLeak.check

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
11 | l.foreach(a => a.addX(this)) // error
33
| ^^^^^^^^^^^^^^^^^
44
|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-
| ^^^^^^^^^^^^^^^^^
5+
|├── class Outer { [ closureLeak.scala:1 ]
6+
| ^
7+
|└── l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ]
8+
| ^^^^^^^^^^^^^^^^^
99
|
1010
|Promoting the value to transitively initialized (Hot) failed due to the following problem:
1111
|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).
1212
|Non initialized field(s): value p. Promotion trace:
13-
|-> l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ]
14-
| ^^^^
13+
|└── l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ]
14+
| ^^^^

tests/init/neg/cycle-structure.check

+16-16
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@
22
3 | val x = B(this) // error
33
| ^^^^^^^
44
| Problematic object instantiation: arg 1 is not transitively initialized (Hot). Calling trace:
5-
| -> case class A(b: B) { [ cycle-structure.scala:1 ]
6-
| ^
7-
| -> val x = B(this) // error [ cycle-structure.scala:3 ]
8-
| ^^^^^^^
5+
| ├── case class A(b: B) { [ cycle-structure.scala:1 ]
6+
| ^
7+
| └── val x = B(this) // error [ cycle-structure.scala:3 ]
8+
| ^^^^^^^
99
|
1010
| It leads to the following error during object initialization:
1111
| Access field value x on an uninitialized (Cold) object. Calling trace:
12-
| -> case class B(a: A) { [ cycle-structure.scala:7 ]
13-
| ^
14-
| -> val x1 = a.x [ cycle-structure.scala:8 ]
15-
| ^^^
12+
| ├── case class B(a: A) { [ cycle-structure.scala:7 ]
13+
| ^
14+
| └── val x1 = a.x [ cycle-structure.scala:8 ]
15+
| ^^^
1616
-- Error: tests/init/neg/cycle-structure.scala:9:13 --------------------------------------------------------------------
1717
9 | val x = A(this) // error
1818
| ^^^^^^^
1919
| Problematic object instantiation: arg 1 is not transitively initialized (Hot). Calling trace:
20-
| -> case class B(a: A) { [ cycle-structure.scala:7 ]
21-
| ^
22-
| -> val x = A(this) // error [ cycle-structure.scala:9 ]
23-
| ^^^^^^^
20+
| ├── case class B(a: A) { [ cycle-structure.scala:7 ]
21+
| ^
22+
| └── val x = A(this) // error [ cycle-structure.scala:9 ]
23+
| ^^^^^^^
2424
|
2525
| It leads to the following error during object initialization:
2626
| Access field value x on an uninitialized (Cold) object. Calling trace:
27-
| -> case class A(b: B) { [ cycle-structure.scala:1 ]
28-
| ^
29-
| -> val x1 = b.x [ cycle-structure.scala:2 ]
30-
| ^^^
27+
| ├── case class A(b: B) { [ cycle-structure.scala:1 ]
28+
| ^
29+
| └── val x1 = b.x [ cycle-structure.scala:2 ]
30+
| ^^^

tests/init/neg/default-this.check

+8-8
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
| ^^^^^^^
44
|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).
55
|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-
| ^^^^^^^
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

+8-8
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
3 | val b = new B(this) // error
33
| ^^^^^^^^^^^
44
| Problematic object instantiation: arg 1 is not transitively initialized (Hot). Calling trace:
5-
| -> class A: [ i15363.scala:1 ]
6-
| ^
7-
| -> val b = new B(this) // error [ i15363.scala:3 ]
8-
| ^^^^^^^^^^^
5+
| ├── class A: [ i15363.scala:1 ]
6+
| ^
7+
| └── val b = new B(this) // error [ i15363.scala:3 ]
8+
| ^^^^^^^^^^^
99
|
1010
| It leads to the following error during object initialization:
1111
| Access field value m on an uninitialized (Cold) object. Calling trace:
12-
| -> class B(a: A): [ i15363.scala:7 ]
13-
| ^
14-
| -> val x = a.m [ i15363.scala:8 ]
15-
| ^^^
12+
| ├── class B(a: A): [ i15363.scala:7 ]
13+
| ^
14+
| └── val x = a.m [ i15363.scala:8 ]
15+
| ^^^

tests/init/neg/i15459.check

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
| ^^^^
44
|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).
55
|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-
| ^^^^
6+
|├── class Sub extends Sup: [ i15459.scala:5 ]
7+
| ^
8+
|├── class Sup: [ i15459.scala:1 ]
9+
| ^
10+
|└── println(this) // error [ i15459.scala:3 ]
11+
| ^^^^

0 commit comments

Comments
 (0)