Skip to content

Commit e296c79

Browse files
committed
Improve Multi-line code message rendering
Improve rendering of messages with multi-line code positions. Two improvements: - Add a blank line between the error message proper and the code lines that follow it. - If there are more than 3 code lines following it, summarize them by printing the first and last line and a `...` in between.
1 parent 4247597 commit e296c79

39 files changed

+99
-25
lines changed

compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ trait MessageRendering {
2222
import Highlight.*
2323
import Offsets.*
2424

25+
/** The maximal number of lines of code that are shown in a message after the
26+
* `^` and error message.
27+
*/
28+
private inline val maxRenderedLinesAfterPoint = 3
29+
2530
/** Remove ANSI coloring from `str`, useful for getting real length of
2631
* strings
2732
*
@@ -64,9 +69,21 @@ trait MessageRendering {
6469
val lines = linesFrom(syntax)
6570
val (before, after) = pos.beforeAndAfterPoint
6671

72+
def compress(offsetsAndLines: List[(Int, String)]): List[(Int, String)] =
73+
if offsetsAndLines.isEmpty then offsetsAndLines
74+
else
75+
val compressedLines =
76+
if offsetsAndLines.length > maxRenderedLinesAfterPoint then
77+
offsetsAndLines.take(maxRenderedLinesAfterPoint - 2)
78+
++ List(
79+
(offsetsAndLines(maxRenderedLinesAfterPoint - 2)._1, "..."),
80+
offsetsAndLines.last)
81+
else offsetsAndLines
82+
compressedLines
83+
6784
(
6885
before.zip(lines).map(render),
69-
after.zip(lines.drop(before.length)).map(render),
86+
compress(after.zip(lines.drop(before.length))).map(render),
7087
maxLen
7188
)
7289
}
@@ -140,7 +157,7 @@ trait MessageRendering {
140157
*
141158
* @return aligned error message
142159
*/
143-
private def errorMsg(pos: SourcePosition, msg: String)(using Context, Level, Offset): String = {
160+
private def errorMsg(pos: SourcePosition, msg: String, addLine: Boolean)(using Context, Level, Offset): String = {
144161
val padding = msg.linesIterator.foldLeft(pos.startColumnPadding) { (pad, line) =>
145162
val lineLength = stripColor(line).length
146163
val maxPad = math.max(0, ctx.settings.pageWidth.value - offset - lineLength) - offset
@@ -149,9 +166,10 @@ trait MessageRendering {
149166
else pad
150167
}
151168

152-
msg.linesIterator
169+
val msgStr = msg.linesIterator
153170
.map { line => offsetBox + (if line.isEmpty then "" else padding + line) }
154171
.mkString(EOL)
172+
if addLine then msgStr ++ s"${EOL}$offsetBox" else msgStr
155173
}
156174

157175
// file.path or munge it to normalize for testing
@@ -280,7 +298,7 @@ trait MessageRendering {
280298
if pos.exists && pos1.exists && pos1.source.file.exists then
281299
val (srcBefore, srcAfter, offset) = sourceLines(pos1)
282300
val marker = positionMarker(pos1)
283-
val err = errorMsg(pos1, msg.message)
301+
val err = errorMsg(pos1, msg.message, srcAfter.nonEmpty)
284302
sb.append((srcBefore ::: marker :: err :: srcAfter).mkString(EOL))
285303

286304
if inlineStack.nonEmpty then

tests/neg-custom-args/captures/boundary-homebrew.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
|where: ?=> refers to a fresh root capability created in anonymous function of type (using l1²: boundary.Label[boundary.Label[Int]^]^): boundary.Label[Int]^ when checking argument to parameter body of method apply
1111
| ^ refers to the universal root capability
1212
| cap is a fresh root capability created in anonymous function of type (using l2: boundary.Label[Int]^'s2): Int of parameter parameter l2² of method $anonfun
13+
|
1314
20 | boundary.break(l2)(using l1)
1415
21 | 15
1516
|

tests/neg-custom-args/captures/boundary.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
|
1111
| where: ^ and cap refer to the universal root capability
1212
| ^² refers to a fresh root capability classified as Control in the type of value local
13+
|
1314
6 | boundary[Unit]: l2 ?=>
1415
7 | boundary.break(l2)(using l1) // error
1516
8 | ???
@@ -47,6 +48,7 @@
4748
|
4849
| where: ^ and cap² refer to the universal root capability
4950
| ^² and cap refer to a fresh root capability created in package <empty>
51+
|
5052
6 | boundary[Unit]: l2 ?=>
5153
7 | boundary.break(l2)(using l1) // error
5254
8 | ???

tests/neg-custom-args/captures/capt1.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
| Required: A
4242
|
4343
| Note that capability x is not included in capture set {}.
44+
|
4445
28 | def m() = if x == null then y else y
4546
|
4647
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/check-inferred.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
|
77
| Inferred type : () ->{Counter.this.count} Unit
88
| Externally visible type: () -> Unit
9+
|
910
19 | count.put(count.get + 1)
1011
-- Error: tests/neg-custom-args/captures/check-inferred.scala:20:13 ----------------------------------------------------
1112
20 | val decr = () => // error
@@ -15,6 +16,7 @@
1516
|
1617
| Inferred type : () ->{Counter.this.count} Unit
1718
| Externally visible type: () -> Unit
19+
|
1820
21 | count.put(count.get - 1)
1921
-- Error: tests/neg-custom-args/captures/check-inferred.scala:24:14 ----------------------------------------------------
2022
24 | val count = Ref(): Object^ // error // error

tests/neg-custom-args/captures/class-caps.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
| Required: (Int, Int) -> Int
66
|
77
| Note that capability Test.this.console is not included in capture set {}.
8+
|
89
19 | log(s"adding a ($a) to b ($b)")(using console)
910
20 | a + b
1011
|
@@ -16,6 +17,7 @@
1617
| Required: (Int, Int) -> Int
1718
|
1819
| Note that capability Test1.this.console is not included in capture set {}.
20+
|
1921
29 | log(s"adding a ($a) to b ($b)")(using console)
2022
30 | a + b
2123
|

tests/neg-custom-args/captures/classifiers-secondclass.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
|Note that capability f.write is not included in capture set {cap.only[Read]}.
88
|
99
|where: cap is a fresh root capability created in anonymous function of type (f²: Levels.File^): Unit when checking argument to parameter op of method parReduce
10+
|
1011
42 | f.write(42) // the error stems from here
1112
43 | a + b + f.read() // ok
1213
|
@@ -20,6 +21,7 @@
2021
|Note that capability f.write is not included in capture set {cap.only[Read]}.
2122
|
2223
|where: cap is a fresh root capability created in anonymous function of type (g²: Levels.File^): Unit when checking argument to parameter op of method parReduce
24+
|
2325
54 | f.write(42) // the error stems from here
2426
55 | a + b + f.read() + g.read() // ok
2527
|
@@ -33,6 +35,7 @@
3335
|Note that capability g.write is not included in capture set {cap.only[Read]}.
3436
|
3537
|where: cap is a fresh root capability created in anonymous function of type (g²: Levels.File^): Unit when checking argument to parameter op of method parReduce
38+
|
3639
57 | g.write(42) // the error stems from here
3740
58 | 0
3841
|

tests/neg-custom-args/captures/delayedRunops.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
| Required: () -> Unit
66
|
77
| Note that capability ops* is not included in capture set {}.
8+
|
89
13 | val ops1 = ops
910
14 | runOps(ops1)
1011
|
@@ -16,6 +17,7 @@
1617
| Required: () -> Unit
1718
|
1819
| Note that capability ops* is not included in capture set {}.
20+
|
1921
25 | val ops1: List[() ->{ops*} Unit] = ops
2022
26 | runOps(ops1)
2123
|

tests/neg-custom-args/captures/effect-swaps-explicit.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
| Required: Result[Future[T], Nothing]
77
|
88
| Note that capability fr is not included in capture set {}.
9+
|
910
65 | fr.await.ok
1011
|--------------------------------------------------------------------------------------------------------------------
1112
|Inline stack trace
@@ -27,6 +28,7 @@
2728
|
2829
|where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make
2930
| ^ refers to the universal root capability
31+
|
3032
70 | fr.await.ok
3133
|
3234
| longer explanation available when compiling with `-explain`
@@ -40,6 +42,7 @@
4042
|
4143
|where: ?=> refers to a fresh root capability created in method fail5 when checking argument to parameter body of method make
4244
| ^ refers to the universal root capability
45+
|
4346
74 | Future: fut ?=>
4447
75 | fr.await.ok
4548
|

tests/neg-custom-args/captures/effect-swaps.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
| Required: Result[Future[T], Nothing]
77
|
88
| Note that capability fr is not included in capture set {}.
9+
|
910
65 | fr.await.ok
1011
|--------------------------------------------------------------------------------------------------------------------
1112
|Inline stack trace
@@ -27,6 +28,7 @@
2728
|
2829
|where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make
2930
| ^ refers to the universal root capability
31+
|
3032
70 | fr.await.ok
3133
|
3234
| longer explanation available when compiling with `-explain`
@@ -40,6 +42,7 @@
4042
|
4143
|where: ?=> refers to a fresh root capability created in method fail5 when checking argument to parameter body of method make
4244
| ^ refers to the universal root capability
45+
|
4346
74 | Future: fut ?=>
4447
75 | fr.await.ok
4548
|

0 commit comments

Comments
 (0)