Skip to content

Commit cc18792

Browse files
authored
Merge pull request #634 from scala/backport-lts-3.3-22945
Backport "Improve printing of strings" to 3.3 LTS
2 parents 22f4080 + b3d6db8 commit cc18792

File tree

6 files changed

+86
-40
lines changed

6 files changed

+86
-40
lines changed

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

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import ast.Trees.*
1111
import typer.Implicits.*
1212
import typer.ImportInfo
1313
import Variances.varianceSign
14-
import util.SourcePosition
14+
import util.{Chars, SourcePosition}
1515
import scala.util.control.NonFatal
1616
import scala.annotation.switch
1717
import config.{Config, Feature}
@@ -579,22 +579,10 @@ class PlainPrinter(_ctx: Context) extends Printer {
579579

580580
def toText(denot: Denotation): Text = toText(denot.symbol) ~ "/D"
581581

582-
private def escapedChar(ch: Char): String = (ch: @switch) match {
583-
case '\b' => "\\b"
584-
case '\t' => "\\t"
585-
case '\n' => "\\n"
586-
case '\f' => "\\f"
587-
case '\r' => "\\r"
588-
case '"' => "\\\""
589-
case '\'' => "\\\'"
590-
case '\\' => "\\\\"
591-
case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch).nn
592-
}
593-
594582
def toText(const: Constant): Text = const.tag match {
595-
case StringTag => stringText("\"" + escapedString(const.value.toString) + "\"")
583+
case StringTag => stringText(Chars.escapedString(const.value.toString, quoted = true))
596584
case ClazzTag => "classOf[" ~ toText(const.typeValue) ~ "]"
597-
case CharTag => literalText(s"'${escapedChar(const.charValue)}'")
585+
case CharTag => literalText(Chars.escapedChar(const.charValue))
598586
case LongTag => literalText(const.longValue.toString + "L")
599587
case DoubleTag => literalText(const.doubleValue.toString + "d")
600588
case FloatTag => literalText(const.floatValue.toString + "f")
@@ -612,7 +600,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
612600
~ (if param.isTypeParam then "" else ": ")
613601
~ toText(param.paramInfo)
614602

615-
protected def escapedString(str: String): String = str flatMap escapedChar
603+
protected final def escapedString(str: String): String = Chars.escapedString(str, quoted = false)
616604

617605
def dclsText(syms: List[Symbol], sep: String): Text = Text(syms map dclText, sep)
618606

compiler/src/dotty/tools/dotc/util/Chars.scala

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package dotty.tools.dotc.util
33
import scala.annotation.switch
44
import Character.{LETTER_NUMBER, LOWERCASE_LETTER, OTHER_LETTER, TITLECASE_LETTER, UPPERCASE_LETTER}
55
import Character.{MATH_SYMBOL, OTHER_SYMBOL}
6-
import Character.{isJavaIdentifierPart, isUnicodeIdentifierStart, isUnicodeIdentifierPart}
6+
import Character.{isISOControl as isControl, isJavaIdentifierPart, isUnicodeIdentifierStart, isUnicodeIdentifierPart}
7+
import java.lang.StringBuilder
78

89
/** Contains constants and classifier methods for characters */
910
object Chars:
@@ -110,3 +111,59 @@ object Chars:
110111

111112
/** Would the character be encoded by `NameTransformer.encode`? */
112113
def willBeEncoded(c: Char): Boolean = !isJavaIdentifierPart(c)
114+
115+
private inline def requiresFormat(c: Char): Boolean = (c: @switch) match
116+
case '\b' | '\t' | '\n' | '\f' | '\r' | '"' | '\'' | '\\' => true
117+
case c => isControl(c)
118+
119+
def escapedString(text: String, quoted: Boolean): String =
120+
inline def doBuild: String =
121+
val b = StringBuilder(text.length + 16)
122+
if quoted then
123+
b.append('"')
124+
var i = 0
125+
while i < text.length do
126+
escapedChar(b, text.charAt(i))
127+
i += 1
128+
if quoted then
129+
b.append('"')
130+
b.toString
131+
var i = 0
132+
while i < text.length do
133+
if requiresFormat(text.charAt(i)) then return doBuild
134+
i += 1
135+
if quoted then "\"" + text + "\""
136+
else text
137+
138+
def escapedChar(ch: Char): String =
139+
if requiresFormat(ch) then
140+
val b = StringBuilder().append('\'')
141+
escapedChar(b, ch)
142+
b.append('\'').toString
143+
else
144+
"'" + ch + "'"
145+
146+
private def escapedChar(b: StringBuilder, c: Char): Unit =
147+
inline def quadNibble(x: Int, i: Int): Unit =
148+
if i < 4 then
149+
quadNibble(x >> 4, i + 1)
150+
val n = x & 0xF
151+
val c = if (n < 10) '0' + n else 'a' + (n - 10)
152+
b.append(c.toChar)
153+
val replace = (c: @switch) match
154+
case '\b' => "\\b"
155+
case '\t' => "\\t"
156+
case '\n' => "\\n"
157+
case '\f' => "\\f"
158+
case '\r' => "\\r"
159+
case '"' => "\\\""
160+
case '\'' => "\\\'"
161+
case '\\' => "\\\\"
162+
case c =>
163+
if isControl(c) then
164+
b.append("\\u")
165+
quadNibble(c.toInt, 0)
166+
else
167+
b.append(c)
168+
return
169+
b.append(replace)

compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package scala.quoted
22
package runtime.impl.printers
33

4+
import dotty.tools.dotc.util.Chars
5+
46
import scala.annotation.switch
7+
import scala.collection.mutable
8+
9+
import java.lang.StringBuilder
510

611
/** Printer for fully elaborated representation of the source code */
712
object SourceCode {
@@ -97,7 +102,7 @@ object SourceCode {
97102
this += lineBreak() += "}"
98103
}
99104

100-
def result(): String = sb.result()
105+
def result(): String = sb.toString
101106

102107
private def lineBreak(): String = "\n" + (" " * indent)
103108
private def doubleLineBreak(): String = "\n\n" + (" " * indent)
@@ -438,7 +443,7 @@ object SourceCode {
438443
case _ =>
439444
inParens {
440445
printTree(term)
441-
this += (if (dotty.tools.dotc.util.Chars.isOperatorPart(sb.last)) " : " else ": ")
446+
this += (if Chars.isOperatorPart(sb.charAt(sb.length - 1)) then " : " else ": ")
442447
def printTypeOrAnnots(tpe: TypeRepr): Unit = tpe match {
443448
case AnnotatedType(tp, annot) if tp == term.tpe =>
444449
printAnnotation(annot)
@@ -957,9 +962,6 @@ object SourceCode {
957962

958963
}
959964

960-
inline private val qc = '\''
961-
inline private val qSc = '"'
962-
963965
def printConstant(const: Constant): this.type = const match {
964966
case UnitConstant() => this += highlightLiteral("()")
965967
case NullConstant() => this += highlightLiteral("null")
@@ -970,8 +972,8 @@ object SourceCode {
970972
case LongConstant(v) => this += highlightLiteral(v.toString + "L")
971973
case FloatConstant(v) => this += highlightLiteral(v.toString + "f")
972974
case DoubleConstant(v) => this += highlightLiteral(v.toString)
973-
case CharConstant(v) => this += highlightString(s"${qc}${escapedChar(v)}${qc}")
974-
case StringConstant(v) => this += highlightString(s"${qSc}${escapedString(v)}${qSc}")
975+
case CharConstant(v) => this += highlightString(Chars.escapedChar(v))
976+
case StringConstant(v) => this += highlightString(Chars.escapedString(v, quoted = true))
975977
case ClassOfConstant(v) =>
976978
this += "classOf"
977979
inSquare(printType(v))
@@ -1444,22 +1446,8 @@ object SourceCode {
14441446
private def +=(x: Char): this.type = { sb.append(x); this }
14451447
private def +=(x: String): this.type = { sb.append(x); this }
14461448

1447-
private def escapedChar(ch: Char): String = (ch: @switch) match {
1448-
case '\b' => "\\b"
1449-
case '\t' => "\\t"
1450-
case '\n' => "\\n"
1451-
case '\f' => "\\f"
1452-
case '\r' => "\\r"
1453-
case '"' => "\\\""
1454-
case '\'' => "\\\'"
1455-
case '\\' => "\\\\"
1456-
case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch).nn
1457-
}
1458-
1459-
private def escapedString(str: String): String = str flatMap escapedChar
1460-
1461-
private[this] val names = collection.mutable.Map.empty[Symbol, String]
1462-
private[this] val namesIndex = collection.mutable.Map.empty[String, Int]
1449+
private val names = collection.mutable.Map.empty[Symbol, String]
1450+
private val namesIndex = collection.mutable.Map.empty[String, Int]
14631451

14641452
private def splicedName(sym: Symbol): Option[String] = {
14651453
if sym.owner.isClassDef then None

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dotty
22
package tools
33
package dotc
4+
package printing
45

56
import scala.language.unsafeNulls
67

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[[syntax trees at end of parser]] // tests/printing/untyped/strings.scala
2+
package <empty> {
3+
class C {
4+
def chars = "\b\t\n\f\r\"\'\\a\u0003"
5+
def greeting = "hello, world"
6+
}
7+
}
8+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
class C:
3+
def chars = "\b\t\n\f\r\"\'\\\u0061\u0003"
4+
def greeting = "hello, world"

0 commit comments

Comments
 (0)