Skip to content

Commit a56f9b9

Browse files
committed
Reject char literal of raw newline
Don't allow char literal to span a natural line ending, but do allow `'\u000A'` to mean `'\n'`. The spec was updated to reflect that meaning, and the direction is to eliminate Unicode escapes outside quotes. Update the test rig so that `// nopos-error` can count as `// anypos-error` since the test cannot be expressed on one line with a trailing comment.
1 parent 0dc07b0 commit a56f9b9

File tree

4 files changed

+78
-9
lines changed

4 files changed

+78
-9
lines changed

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -773,19 +773,25 @@ object Scanners {
773773
}
774774
fetchDoubleQuote()
775775
case '\'' =>
776-
def fetchSingleQuote() = {
776+
def fetchSingleQuote(): Unit = {
777777
nextChar()
778-
if (isIdentifierStart(ch))
778+
if isIdentifierStart(ch) then
779779
charLitOr { getIdentRest(); QUOTEID }
780-
else if (isOperatorPart(ch) && (ch != '\\'))
780+
else if isOperatorPart(ch) && ch != '\\' then
781781
charLitOr { getOperatorRest(); QUOTEID }
782782
else ch match {
783783
case '{' | '[' | ' ' | '\t' if lookaheadChar() != '\'' =>
784784
token = QUOTE
785-
case _ =>
785+
case _ if !isAtEnd && (ch != SU && ch != CR && ch != LF || isUnicodeEscape) =>
786+
val isEmptyCharLit = (ch == '\'')
786787
getLitChar()
787-
if (ch == '\'') finishCharLit()
788+
if ch == '\'' then
789+
if isEmptyCharLit then error("empty character literal (use '\\'' for single quote)")
790+
else finishCharLit()
791+
else if isEmptyCharLit then error("empty character literal")
788792
else error("unclosed character literal")
793+
case _ =>
794+
error("unclosed character literal")
789795
}
790796
}
791797
fetchSingleQuote()

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -694,15 +694,21 @@ trait ParallelTesting extends RunnerOrchestration { self =>
694694

695695
val errors = errorMap.get(key)
696696

697+
def missing = { echo(s"Error reported in ${pos1.source}, but no annotation found") ; false }
698+
697699
if (errors ne null) {
698700
if (errors == 1) errorMap.remove(key)
699701
else errorMap.put(key, errors - 1)
700702
true
701703
}
702-
else {
703-
echo(s"Error reported in ${pos1.source}, but no annotation found")
704-
false
705-
}
704+
else if key == "nopos" then
705+
missing
706+
else
707+
errorMap.get("nopos") match
708+
case null => missing
709+
case 1 => errorMap.remove("nopos") ; true
710+
case slack => if slack < 1 then missing
711+
else errorMap.put("nopos", slack - 1) ; true
706712
}
707713
}
708714

tests/neg/t6810.check

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- Error: tests/neg/t6810.scala:5:10 -----------------------------------------------------------------------------------
2+
5 | val y = '
3+
| ^
4+
| unclosed character literal
5+
-- Error: tests/neg/t6810.scala:12:10 ----------------------------------------------------------------------------------
6+
12 | val Y = "
7+
| ^
8+
| unclosed string literal
9+
-- Error: tests/neg/t6810.scala:13:0 -----------------------------------------------------------------------------------
10+
13 |" // error obviously not
11+
|^
12+
|unclosed string literal
13+
-- Error: tests/neg/t6810.scala:24:6 -----------------------------------------------------------------------------------
14+
24 | val `
15+
| ^
16+
| unclosed quoted identifier
17+
-- Error: tests/neg/t6810.scala:25:0 -----------------------------------------------------------------------------------
18+
25 |` = EOL // error not raw string literals aka triple-quoted, multiline strings
19+
|^
20+
|unclosed quoted identifier
21+
-- Error: tests/neg/t6810.scala:30:10 ----------------------------------------------------------------------------------
22+
30 | val b = '
23+
| ^
24+
| unclosed character literal

tests/neg/t6810.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
trait t6810 {
3+
val x = '\u000A' // char literals accept arbitrary unicode escapes
4+
// nopos-error so as not to interfere with the following bad syntax
5+
val y = '
6+
' // but not embedded EOL sequences not represented as escapes
7+
println(); // scanner firewall
8+
val z = '\n' // normally, expect this escape
9+
10+
val X = "\u000A" // it's the same as ordinary string literals
11+
// nopos-error so as not to interfere with the following bad syntax
12+
val Y = "
13+
" // error obviously not
14+
val Z = "\n" // normally, expect this escape
15+
16+
val A = """
17+
""" // which is what these are for
18+
val B = s"""
19+
""" // or the same for interpolated strings
20+
21+
import System.{lineSeparator => EOL}
22+
val `\u000A` = EOL // backquoted identifiers are arbitrary string literals
23+
// nopos-error so as not to interfere with the following bad syntax
24+
val `
25+
` = EOL // error not raw string literals aka triple-quoted, multiline strings
26+
27+
val firebreak = 42 // help parser recovery, could also use rbrace
28+
29+
val a = '\u000D' // similar treatment of CR
30+
val b = '' // nopos-error CR seen as EOL by scanner; FSR, error only on open quote, unlike `y`
31+
println(); // scanner firewall
32+
val c = '\r' // traditionally
33+
}

0 commit comments

Comments
 (0)