Skip to content

Commit 9852984

Browse files
committed
Fix Text wrapping
There was a problem with how some text wrapped. I noticed it in the printing of big tuple types and I started to write unit tests for those, but there turned out to also be a checkfile that demonstrates the fix: tests/neg/i14127.check: - | Int - |, Int, Int)] was found for parameter x [...] - | Int - |, Int, Int)]: + | Int, Int, Int)] was found for parameter [...] + | Int, Int, Int)]: The fix is to make appendIndented, which is used by append, use Fluid instead of switching to Vertical. It seems to me that, pre-layout, Vertical means "I want my elements to be vertical", i.e. wrap every one, and it's layout that takes that into account. Fluid instead means "wrap where necessary, pack where possible". Post-layout, instead, it looks like Fluid means one-per-line, as it's consumed by print. So the fix is to switch appendIndented from Vertical to Fluid. Following that I made some improvements like not needless boxing non-splittable text, like Str, into Closed and merging non-closed Fluid text during concat (`~`). That fixed the extra wrapping, but it meant that the ", " separator could be the text that is wrapped and indented, which didn't look nice. So I changed Text.apply to close the separator to the previous element. That encouraged lines ending with ", ", that is with a trailing space, so I made print strip trailing spaces. The change from Vertical to Fluid made the last parenthesis in tests/neg/i9185.check not wrap, which is fine, but that pulled the trailing "failed with" up to the previous line. As that's not code, I decided to move it down and a line away from the code.
1 parent bf808b3 commit 9852984

29 files changed

+255
-154
lines changed

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
111111
protected def refinementNameString(tp: RefinedType): String = nameString(tp.refinedName)
112112

113113
/** String representation of a refinement */
114-
protected def toTextRefinement(rt: RefinedType): Closed =
114+
protected def toTextRefinement(rt: RefinedType): Text =
115115
(refinementNameString(rt) ~ toTextRHS(rt.refinedInfo)).close
116116

117117
protected def argText(arg: Type): Text = homogenizeArg(arg) match {

compiler/src/dotty/tools/dotc/printing/Texts.scala

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@ object Texts {
1515
case Vertical(relems) => relems.isEmpty
1616
}
1717

18+
// Str Ver Clo Flu
19+
// isVertical F T F F
20+
// isClosed F T T F
21+
// isFluid F F T T
22+
// isSplittable F F F T
1823
def isVertical: Boolean = isInstanceOf[Vertical]
1924
def isClosed: Boolean = isVertical || isInstanceOf[Closed]
2025
def isFluid: Boolean = isInstanceOf[Fluid]
2126
def isSplittable: Boolean = isFluid && !isClosed
2227

23-
def close: Closed = new Closed(relems)
28+
def close: Text = if isSplittable then Closed(relems) else this
2429

2530
def remaining(width: Int): Int = this match {
2631
case Str(s, _) =>
@@ -53,7 +58,7 @@ object Texts {
5358
}
5459

5560
private def appendIndented(that: Text)(width: Int): Text =
56-
Vertical(that.layout(width - indentMargin).indented :: this.relems)
61+
Fluid(that.layout(width - indentMargin).indented :: this.relems)
5762

5863
private def append(width: Int)(that: Text): Text =
5964
if (this.isEmpty) that.layout(width)
@@ -113,7 +118,7 @@ object Texts {
113118
sb.append("|")
114119
}
115120
}
116-
sb.append(s)
121+
sb.append(s.replaceAll("[ ]+$", ""))
117122
case _ =>
118123
var follow = false
119124
for (elem <- relems.reverse) {
@@ -138,7 +143,13 @@ object Texts {
138143
def ~ (that: Text): Text =
139144
if (this.isEmpty) that
140145
else if (that.isEmpty) this
141-
else Fluid(that :: this :: Nil)
146+
else this match
147+
case Fluid(relems1) if !isClosed => that match
148+
case Fluid(relems2) if !that.isClosed => Fluid(relems2 ++ relems1)
149+
case _ => Fluid(that +: relems1)
150+
case _ => that match
151+
case Fluid(relems2) if !that.isClosed => Fluid(relems2 :+ this)
152+
case _ => Fluid(that :: this :: Nil)
142153

143154
def ~~ (that: Text): Text =
144155
if (this.isEmpty) that
@@ -161,9 +172,9 @@ object Texts {
161172
def apply(xs: Traversable[Text], sep: String = " "): Text =
162173
if (sep == "\n") lines(xs)
163174
else {
164-
val ys = xs filterNot (_.isEmpty)
175+
val ys = xs.filterNot(_.isEmpty)
165176
if (ys.isEmpty) Str("")
166-
else ys reduce (_ ~ sep ~ _)
177+
else ys.reduceRight((a, b) => (a ~ sep).close ~ b)
167178
}
168179

169180
/** The given texts `xs`, each on a separate line */
@@ -176,12 +187,16 @@ object Texts {
176187

177188
case class Str(s: String, lineRange: LineRange = EmptyLineRange) extends Text {
178189
override def relems: List[Text] = List(this)
190+
override def toString = this match
191+
case Str(s, EmptyLineRange) => s"Str($s)"
192+
case Str(s, lineRange) => s"Str($s, $lineRange)"
179193
}
180194

181195
case class Vertical(relems: List[Text]) extends Text
182196
case class Fluid(relems: List[Text]) extends Text
183197

184-
class Closed(relems: List[Text]) extends Fluid(relems)
198+
class Closed(relems: List[Text]) extends Fluid(relems):
199+
override def productPrefix = "Closed"
185200

186201
implicit def stringToText(s: String): Text = Str(s)
187202

compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,9 @@ object ErrorReporting {
187187
|The tests were made under $constraintText"""
188188

189189
def whyFailedStr(fail: FailedExtension) =
190-
i""" failed with
190+
i"""
191+
|
192+
| failed with:
191193
|
192194
|${fail.whyFailed.message.indented(8)}"""
193195

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,9 +568,9 @@ object Implicits:
568568
if reasons.length > 1 then
569569
reasons.mkString("\n\t* ", "\n\t* ", "")
570570
else
571-
reasons.mkString
571+
reasons.mkString(" ", "", "")
572572

573-
def explanation(using Context) = em"Failed to synthesize an instance of type ${clarify(expectedType)}: ${formatReasons}"
573+
def explanation(using Context) = em"Failed to synthesize an instance of type ${clarify(expectedType)}:${formatReasons}"
574574

575575
end Implicits
576576

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package dotty.tools
2+
package dotc
3+
4+
import core.*, Decorators.*, Symbols.*
5+
import printing.Texts.*
6+
7+
import org.junit.Test
8+
9+
class TupleShowTests extends DottyTest:
10+
def IntType = defn.IntType
11+
def LongType = defn.LongType
12+
def ShortType = defn.ShortType
13+
def Types_10 = List.fill(5)(IntType) ::: List.fill(5)(LongType)
14+
def Types_20 = Types_10 ::: Types_10
15+
16+
val tup0 = defn.tupleType(Nil)
17+
val tup1 = defn.tupleType(IntType :: Nil)
18+
val tup2 = defn.tupleType(IntType :: LongType :: Nil)
19+
val tup3 = defn.tupleType(IntType :: LongType :: ShortType :: Nil)
20+
val tup21 = defn.tupleType(Types_20 ::: IntType :: Nil)
21+
val tup22 = defn.tupleType(Types_20 ::: IntType :: LongType :: Nil)
22+
val tup23 = defn.tupleType(Types_20 ::: IntType :: LongType :: ShortType :: Nil)
23+
val tup24 = defn.tupleType(Types_20 ::: IntType :: LongType :: ShortType :: ShortType :: Nil)
24+
25+
@Test def tup0_show = chkEq("EmptyTuple.type", i"$tup0")
26+
@Test def tup1_show = chkEq("Tuple1[Int]", i"$tup1")
27+
@Test def tup2_show = chkEq("(Int, Long)", i"$tup2")
28+
@Test def tup3_show = chkEq("(Int, Long, Short)", i"$tup3")
29+
@Test def tup21_show = chkEq(res21, i"$tup21")
30+
@Test def tup22_show = chkEq(res22, i"$tup22")
31+
@Test def tup23_show = chkEq(res23, i"$tup23")
32+
@Test def tup24_show = chkEq(res24, i"$tup24")
33+
34+
@Test def tup3_text =
35+
val obt = tup3.toText(ctx.printer)
36+
val exp = Fluid(List(
37+
Str(")"),
38+
Str("Short"),
39+
Closed(List(Str(", "), Str("Long"))),
40+
Closed(List(Str(", "), Str("Int"))),
41+
Str("("),
42+
))
43+
chkEq(exp, obt)
44+
45+
@Test def tup3_layout10 =
46+
val obt = tup3.toText(ctx.printer).layout(10)
47+
val exp = Fluid(List(
48+
Str(" Short)"),
49+
Str(" Long, "),
50+
Str("(Int, "),
51+
))
52+
chkEq(exp, obt)
53+
54+
@Test def tup3_show10 = chkEq("(Int,\n Long,\n Short)", tup3.toText(ctx.printer).mkString(10, false))
55+
56+
val res21 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int,
57+
| Int, Long, Long, Long, Long, Long, Int)""".stripMargin
58+
59+
val res22 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int,
60+
| Int, Long, Long, Long, Long, Long, Int, Long)""".stripMargin
61+
62+
val res23 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int,
63+
| Int, Long, Long, Long, Long, Long, Int, Long, Short)""".stripMargin
64+
65+
val res24 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int,
66+
| Int, Long, Long, Long, Long, Long, Int, Long, Short, Short)""".stripMargin
67+
68+
def chkEq[A](expected: A, obtained: A) = assert(expected == obtained, diff(s"$expected", s"$obtained"))
69+
70+
def diff(exp: String, obt: String) =
71+
val min = math.min(exp.length, obt.length)
72+
val pre =
73+
var i = 0
74+
while i < min && exp(i) == obt(i) do i += 1
75+
exp.take(i)
76+
val suf =
77+
val max = min - pre.length - 1
78+
var i = 0
79+
while i <= max && exp(exp.length - 1 - i) == obt(obt.length - 1 - i) do i += 1
80+
exp.drop(exp.length - 1)
81+
82+
import scala.io.AnsiColor.*
83+
val ellip = BLACK + BOLD + "..." + RESET
84+
val compactPre = if pre.length <= 20 then pre else ellip + pre.drop(pre.length - 20)
85+
val compactSuf = if suf.length <= 20 then suf else suf.take(20) + ellip
86+
def extractDiff(s: String) = s.slice(pre.length, s.length - suf.length)
87+
s"""|Comparison Failure:
88+
| expected: $compactPre${CYAN }${extractDiff(exp)}$RESET$compactSuf
89+
| obtained: $compactPre$MAGENTA${extractDiff(obt)}$RESET$compactSuf
90+
|""".stripMargin

compiler/test/dotty/tools/dotc/printing/PrintingTest.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import scala.io.Source
2121
import org.junit.Test
2222
import scala.util.Using
2323
import java.io.File
24+
2425
class PrintingTest {
2526

2627
def options(phase: String, flags: List[String]) =
@@ -45,7 +46,7 @@ class PrintingTest {
4546
}
4647

4748
val actualLines = byteStream.toString(StandardCharsets.UTF_8.name).linesIterator
48-
FileDiff.checkAndDump(path.toString, actualLines.toIndexedSeq, checkFilePath)
49+
FileDiff.checkAndDumpOrUpdate(path.toString, actualLines.toIndexedSeq, checkFilePath)
4950
}
5051

5152
def testIn(testsDir: String, phase: String) =

compiler/test/dotty/tools/vulpix/FileDiff.scala

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,6 @@ object FileDiff {
5050
outFile.writeAll(content.mkString("", EOL, EOL))
5151
}
5252

53-
def checkAndDump(sourceTitle: String, actualLines: Seq[String], checkFilePath: String): Boolean = {
54-
val outFilePath = checkFilePath + ".out"
55-
FileDiff.check(sourceTitle, actualLines, checkFilePath) match {
56-
case Some(msg) =>
57-
FileDiff.dump(outFilePath, actualLines)
58-
println(msg)
59-
println(FileDiff.diffMessage(checkFilePath, outFilePath))
60-
false
61-
case _ =>
62-
val jOutFilePath = Paths.get(outFilePath)
63-
Files.deleteIfExists(jOutFilePath)
64-
true
65-
}
66-
}
67-
6853
def checkAndDumpOrUpdate(sourceTitle: String, actualLines: Seq[String], checkFilePath: String): Boolean = {
6954
val outFilePath = checkFilePath + ".out"
7055
FileDiff.check(sourceTitle, actualLines, checkFilePath) match {

tests/neg/enum-values.check

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
| meaning a values array is not defined.
77
| An extension method was tried, but could not be fully constructed:
88
|
9-
| example.Extensions.values(Tag) failed with
9+
| example.Extensions.values(Tag)
10+
|
11+
| failed with:
1012
|
1113
| Found: example.Tag.type
1214
| Required: Nothing
@@ -18,7 +20,9 @@
1820
| meaning a values array is not defined.
1921
| An extension method was tried, but could not be fully constructed:
2022
|
21-
| example.Extensions.values(ListLike) failed with
23+
| example.Extensions.values(ListLike)
24+
|
25+
| failed with:
2226
|
2327
| Found: Array[example.Tag[?]]
2428
| Required: Array[example.ListLike[?]]
@@ -30,7 +34,9 @@
3034
| meaning a values array is not defined.
3135
| An extension method was tried, but could not be fully constructed:
3236
|
33-
| example.Extensions.values(TypeCtorsK) failed with
37+
| example.Extensions.values(TypeCtorsK)
38+
|
39+
| failed with:
3440
|
3541
| Found: Array[example.Tag[?]]
3642
| Required: Array[example.TypeCtorsK[?[_$1]]]
@@ -63,7 +69,9 @@
6369
| value values is not a member of object example.NotAnEnum.
6470
| An extension method was tried, but could not be fully constructed:
6571
|
66-
| example.Extensions.values(NotAnEnum) failed with
72+
| example.Extensions.values(NotAnEnum)
73+
|
74+
| failed with:
6775
|
6876
| Found: example.NotAnEnum.type
6977
| Required: Nothing

tests/neg/i10901.check

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
| value º is not a member of object BugExp4Point2D.IntT.
55
| An extension method was tried, but could not be fully constructed:
66
|
7-
| º(x) failed with
7+
| º(x)
8+
|
9+
| failed with:
810
|
911
| Ambiguous overload. The overloaded alternatives of method º in object dsl with types
1012
| [T1, T2]
@@ -22,7 +24,9 @@
2224
|value º is not a member of object BugExp4Point2D.IntT.
2325
|An extension method was tried, but could not be fully constructed:
2426
|
25-
| º(x) failed with
27+
| º(x)
28+
|
29+
| failed with:
2630
|
2731
| Ambiguous overload. The overloaded alternatives of method º in object dsl with types
2832
| [T1, T2]
@@ -36,6 +40,8 @@
3640
| value foo is not a member of String.
3741
| An extension method was tried, but could not be fully constructed:
3842
|
39-
| Test.foo("abc")(/* missing */summon[C]) failed with
43+
| Test.foo("abc")(/* missing */summon[C])
44+
|
45+
| failed with:
4046
|
4147
| No given instance of type C was found for parameter x$2 of method foo in object Test

tests/neg/i13558.check

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
| value id is not a member of testcode.A.
55
| An extension method was tried, but could not be fully constructed:
66
|
7-
| testcode.ExtensionA.id(a) failed with
7+
| testcode.ExtensionA.id(a)
8+
|
9+
| failed with:
810
|
911
| Reference to id is ambiguous,
1012
| it is both imported by import testcode.ExtensionB._
@@ -15,7 +17,9 @@
1517
| value id is not a member of testcode.A.
1618
| An extension method was tried, but could not be fully constructed:
1719
|
18-
| testcode.ExtensionB.id(a) failed with
20+
| testcode.ExtensionB.id(a)
21+
|
22+
| failed with:
1923
|
2024
| Reference to id is ambiguous,
2125
| it is both imported by import testcode.ExtensionA._

tests/neg/i14127.check

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
-- Error: tests/neg/i14127.scala:6:55 ----------------------------------------------------------------------------------
22
6 | *: Int *: Int *: Int *: Int *: Int *: EmptyTuple)]] // error
33
| ^
4-
|No given instance of type deriving.Mirror.Of[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
5-
| Int
6-
|, Int, Int)] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
7-
| Int
8-
|, Int, Int)]:
4+
|No given instance of type deriving.Mirror.Of[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
5+
| Int, Int, Int)] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
6+
| Int, Int, Int)]:
97
| * class *: is not a generic product because it reduces to a tuple with arity 23, expected arity <= 22
108
| * class *: is not a generic sum because it does not have subclasses

tests/neg/i14432.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-- Error: tests/neg/i14432.scala:13:33 ---------------------------------------------------------------------------------
22
13 |val mFoo = summon[Mirror.Of[Foo]] // error: no mirror found
33
| ^
4-
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
4+
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
55
| * class Foo is not a generic product because the constructor of class Foo is innaccessible from the calling scope.
66
| * class Foo is not a generic sum because it is not a sealed class

tests/neg/i14432a.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-- Error: tests/neg/i14432a.scala:14:43 --------------------------------------------------------------------------------
22
14 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
33
| ^
4-
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
4+
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
55
| * class Foo is not a generic product because the constructor of class Foo is innaccessible from the calling scope.
66
| * class Foo is not a generic sum because it is not a sealed class

tests/neg/i14432b.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-- Error: tests/neg/i14432b.scala:15:43 --------------------------------------------------------------------------------
22
15 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
33
| ^
4-
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
4+
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
55
| * class Foo is not a generic product because the constructor of class Foo is innaccessible from the calling scope.
66
| * class Foo is not a generic sum because it is not a sealed class

tests/neg/i14432c.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
-- Error: tests/neg/i14432c.scala:16:43 --------------------------------------------------------------------------------
66
16 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror
77
| ^
8-
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
8+
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
99
| * class Foo is not a generic product because the constructor of class Foo is innaccessible from the calling scope.
1010
| * class Foo is not a generic sum because it is not a sealed class

tests/neg/i14432d.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-- Error: tests/neg/i14432d.scala:17:45 --------------------------------------------------------------------------------
22
17 | val mFoo = summon[Mirror.Of[example.Foo]] // error
33
| ^
4-
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
4+
|No given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[example.Foo]:
55
| * class Foo is not a generic product because the constructor of class Foo is innaccessible from the calling scope.
66
| * class Foo is not a generic sum because it is not a sealed class

0 commit comments

Comments
 (0)