Skip to content

Allow \" in single-quoted interpolated string literals #11751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,7 @@ object Scanners {
getRawStringLit()
}

// for interpolated strings
@annotation.tailrec private def getStringPart(multiLine: Boolean): Unit =
if (ch == '"')
if (multiLine) {
Expand All @@ -1069,6 +1070,14 @@ object Scanners {
setStrVal()
token = STRINGLIT
}
else if (ch == '\\' && !multiLine) {
putChar(ch)
nextRawChar()
if (ch == '"' || ch == '\\')
putChar(ch)
nextRawChar()
getStringPart(multiLine)
}
else if (ch == '$') {
nextRawChar()
if (ch == '$' || ch == '"') {
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/internals/syntax-3.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ stringElement ::= printableChar \ (‘"’ | ‘\’)
| charEscapeSeq
multiLineChars ::= {[‘"’] [‘"’] char \ ‘"’} {‘"’}
processedStringLiteral
::= alphaid ‘"’ {printableChar \ (‘"’ | ‘$’) | escape} ‘"’
::= alphaid ‘"’ {[‘\’] processedStringPart | ‘\\’ | ‘\"’} ‘"’
| alphaid ‘"""’ {[‘"’] [‘"’] char \ (‘"’ | ‘$’) | escape} {‘"’} ‘"""’
processedStringPart
::= printableChar \ (‘"’ | ‘$’ | ‘\’) | escape
escape ::= ‘$$’
| ‘$’ letter { letter | digit }
| ‘{’ Block [‘;’ whiteSpace stringFormat whiteSpace] ‘}’
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ stringElement ::= printableChar \ (‘"’ | ‘\’)
| charEscapeSeq
multiLineChars ::= {[‘"’] [‘"’] char \ ‘"’} {‘"’}
processedStringLiteral
::= alphaid ‘"’ {printableChar \ (‘"’ | ‘$’) | escape} ‘"’
::= alphaid ‘"’ {[‘\’] processedStringPart | ‘\\’ | ‘\"’} ‘"’
| alphaid ‘"""’ {[‘"’] [‘"’] char \ (‘"’ | ‘$’) | escape} {‘"’} ‘"""’
processedStringPart
::= printableChar \ (‘"’ | ‘$’ | ‘\’) | escape
escape ::= ‘$$’
| ‘$’ letter { letter | digit }
| ‘{’ Block [‘;’ whiteSpace stringFormat whiteSpace] ‘}’
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/reference/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ stringElement ::= printableChar \ (‘"’ | ‘\’)
| charEscapeSeq
multiLineChars ::= {[‘"’] [‘"’] char \ ‘"’} {‘"’}
processedStringLiteral
::= alphaid ‘"’ {printableChar \ (‘"’ | ‘$’) | escape} ‘"’
::= alphaid ‘"’ {[‘\’] processedStringPart | ‘\\’ | ‘\"’} ‘"’
| alphaid ‘"""’ {[‘"’] [‘"’] char \ (‘"’ | ‘$’) | escape} {‘"’} ‘"""’
processedStringPart
::= printableChar \ (‘"’ | ‘$’ | ‘\’) | escape
escape ::= ‘$$’
| ‘$’ letter { letter | digit }
| ‘{’ Block [‘;’ whiteSpace stringFormat whiteSpace] ‘}’
Expand Down
9 changes: 9 additions & 0 deletions tests/neg/t6476.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// only the last one doesn't parse
class C {
s"""\ """
s"""\\"""
s"""\"""
s"\ "
s"\\"
s"\" // error
} // error (should not be one)
8 changes: 8 additions & 0 deletions tests/neg/t6476b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class C {
val sa = s"""\""" // error: invalid escape
val sb = s"""\\"""
val sc = s"""\ """ // error: invalid escape
val ra = raw"""\"""
val rb = raw"""\\"""
val rc = raw"""\ """
}
13 changes: 13 additions & 0 deletions tests/run/t6476.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"Hello", Alice
"Hello", Alice
\"Hello\", Alice
\"Hello\", Alice
\"Hello\", Alice
\"Hello\", Alice
\TILT\
\\TILT\\
\\TILT\\
\TILT\
\\TILT\\
\\TILT\\
\TILT\
23 changes: 23 additions & 0 deletions tests/run/t6476.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
object Test {
def main(args: Array[String]): Unit = {
val person = "Alice"
println(s"\"Hello\", $person")
println(s"""\"Hello\", $person""")

println(f"\"Hello\", $person")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this one was wrongly interpreted as \" instead of ". The check file differs with the one in Scala 2.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed #11750 about that

println(f"""\"Hello\", $person""")

println(raw"\"Hello\", $person")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions: Now that we have $" as way to escape " in any string interpolator, do we really need to special case \" in raw interpolators? Cant we just assume that raw"\" is "\"? How can we end with a \ in a raw now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need to special case \" in raw interpolators?

Maybe I misunderstand, but there's no special case or change in the raw interpolator. The change here is that \" no longer closes the string literal, but the interpretation of \" is unchanged (raw"""\"""" and s"""\"""" are the same as before).

In my opinion we should do this change change and fix scala/bug#6476, even if we have $" now. This issue affected a lot of people, and will keep affecting a lot of people in the future, especially beginners (https://stackoverflow.com/questions/21086263/how-to-insert-double-quotes-into-string-with-interpolation-in-scala).

See also the (updated) PR description of scala/scala#8830 (comment).

Cant we just assume that raw"\" is "\"?

I'm not sure what you mean by that.

How can we end with a \ in a raw now?

Using triple-quote. The 2.13 compiler will even tell you that:

scala> raw"c:\"
           ^
       error: unclosed string literal; note that `\"` no longer closes single-quoted interpolated string literals since 2.13.6, you can use a triple-quoted string instead

If desired, I can include this message in Scala 3 as well.

println(raw"""\"Hello\", $person""")

println(s"\\TILT\\")
println(f"\\TILT\\")
println(raw"\\TILT\\")

println(s"""\\TILT\\""")
println(f"""\\TILT\\""")
println(raw"""\\TILT\\""")

println(raw"""\TILT\""")
}
}