Skip to content

Commit c81ce54

Browse files
committed
Don't strip CR from multiline interpolations
Forward port of bug fix from 2.12. Leaving an interpolated expression would sanitize an immediately following line separator.
1 parent 25cdd9e commit c81ce54

File tree

3 files changed

+88
-4
lines changed

3 files changed

+88
-4
lines changed

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,13 @@ object Scanners {
256256
case _ => false
257257
}
258258

259+
/** Are we in a `${ }` block? such that RBRACE exits back into multiline string. */
260+
private def inMultiLineInterpolatedExpression =
261+
currentRegion match {
262+
case InBraces(_, InString(true, _)) => true
263+
case _ => false
264+
}
265+
259266
/** read next token and return last offset
260267
*/
261268
def skipToken(): Offset = {
@@ -806,7 +813,8 @@ object Scanners {
806813
case ')' =>
807814
nextChar(); token = RPAREN
808815
case '}' =>
809-
nextChar(); token = RBRACE
816+
if (inMultiLineInterpolatedExpression) nextRawChar() else nextChar()
817+
token = RBRACE
810818
case '[' =>
811819
nextChar(); token = LBRACKET
812820
case ']' =>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package dotty.tools
2+
package dotc
3+
package parsing
4+
5+
import ast.untpd._
6+
import core.Constants._
7+
8+
import org.junit.Test
9+
10+
class ParserEdgeTest extends ParserTest {
11+
//
12+
// CR after right brace of interpolated expression was stripped.
13+
//
14+
@Test def `t9944 respect line separator`: Unit = {
15+
// avoid git stripping CR from a test file; also, inlining doesn't demonstrate the behavior.
16+
val CR = "\u000D"
17+
val NL = "\u000A"
18+
val triple = "\"" * 3
19+
val text = s"""class C { def g = "X" ; def f = s${triple}123${CR}${NL}$${g}${CR}${NL}456${triple} }"""
20+
def isStripped(s: String) = s.contains(NL) && !s.contains(CR)
21+
22+
// sanity check
23+
assert(text.linesIterator.size == 3, s"line count ${text.linesIterator.size}")
24+
assert(text.linesIterator.forall(!isStripped(_)))
25+
26+
val t = parseText(text)
27+
//println(t.show)
28+
assert(!t.existsSubTree {
29+
case Literal(const @ Constant(_)) if const.tag == StringTag => isStripped(const.stringValue)
30+
case st => false
31+
})
32+
}
33+
/* was:
34+
package <empty> {
35+
class C {
36+
def g = "X"
37+
def f =
38+
s"123\r\n{
39+
{
40+
g
41+
}
42+
}\n456"
43+
}
44+
}
45+
* now:
46+
package <empty> {
47+
class C {
48+
def g = "X"
49+
def f =
50+
s"123\r\n{
51+
{
52+
g
53+
}
54+
}\r\n456"
55+
}
56+
}
57+
* tests/run/t9944 expressed ordinarily, with CR as indicated:
58+
class C {
59+
def g = 42
60+
def f =
61+
s"""123^M
62+
|${ g }^M
63+
|123""".stripMargin
64+
}
65+
66+
object Test extends App {
67+
println(new C().f.getBytes.toList.map(c => f"$c%02x"))
68+
}
69+
* was:
70+
➜ dotr Test
71+
List(31, 32, 33, 0d, 0a, 34, 32, 0a, 31, 32, 33)
72+
*/
73+
}

compiler/test/dotty/tools/dotc/parsing/ParserTest.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ class ParserTest extends DottyTest {
2323
parsedTrees.clear()
2424
}
2525

26-
def parse(file: PlainFile): Tree = {
27-
//println("***** parsing " + file)
28-
val source = new SourceFile(file, Codec.UTF8)
26+
def parse(file: PlainFile): Tree = parseSource(new SourceFile(file, Codec.UTF8))
27+
28+
private def parseSource(source: SourceFile): Tree = {
29+
//println("***** parsing " + source.file)
2930
val parser = new Parser(source)
3031
val tree = parser.parse()
3132
parsed += 1
@@ -41,4 +42,6 @@ class ParserTest extends DottyTest {
4142
for (d <- dir.dirs)
4243
parseDir(d.path)
4344
}
45+
46+
def parseText(code: String): Tree = parseSource(SourceFile.virtual("<code>", code))
4447
}

0 commit comments

Comments
 (0)