Skip to content

Commit 3182f1e

Browse files
authored
Merge pull request #8268 from som-snytt/forward/9944
Don't strip CR from multiline interpolations
2 parents 4a085ed + c81ce54 commit 3182f1e

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)