From cf783ec1c6527017cf8e9be8a6361ead0d4c33da Mon Sep 17 00:00:00 2001 From: Martijn Hoekstra Date: Tue, 6 Aug 2019 16:13:24 +0200 Subject: [PATCH] improve error messages --- build.sbt | 3 +- .../scala/tools/nsc/ast/parser/Scanners.scala | 59 ++++++++++++------- .../reflect/FastStringInterpolator.scala | 8 ++- src/library/scala/StringContext.scala | 12 +++- test/files/neg/t3220-1.check | 25 ++++++++ test/files/neg/t3220-1.scala | 10 ++++ test/files/neg/t3220-2.check | 16 +++++ test/files/neg/t3220-2.scala | 8 +++ test/files/neg/t6631.check | 2 +- test/files/neg/t8266-invalid-interp.check | 4 +- test/files/run/t3220-213.check | 20 +++---- 11 files changed, 125 insertions(+), 42 deletions(-) create mode 100644 test/files/neg/t3220-1.check create mode 100644 test/files/neg/t3220-1.scala create mode 100644 test/files/neg/t3220-2.check create mode 100644 test/files/neg/t3220-2.scala diff --git a/build.sbt b/build.sbt index f02cda7c6eb1..d23f28f068e4 100644 --- a/build.sbt +++ b/build.sbt @@ -94,7 +94,8 @@ val mimaFilterSettings = Seq( ProblemFilters.exclude[MissingClassProblem]("scala.reflect.runtime.JavaMirrors$JavaMirror$typeTagCache$"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.api.TypeTags.TypeTagImpl"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.api.Universe.TypeTagImpl"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.StringContext.processUnicode") + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.StringContext.processUnicode"), + ProblemFilters.exclude[MissingClassProblem]("scala.StringContext$InvalidUnicodeEscapeException") ), ) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index ba4ba0a48e3d..7c9e0a4f567e 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -760,13 +760,20 @@ trait Scanners extends ScannersCommon { private def unclosedStringLit(): Unit = syntaxError("unclosed string literal") - private def replaceUnicodeEscapes(warn: Boolean): Unit = + private def replaceUnicodeEscapesInTriple(): Unit = if(strVal != null) { - val replaced = StringContext.processUnicode(strVal) - if(warn && replaced != strVal) { - deprecationWarning("Unicode escapes in triple quoted strings and raw interpolations are deprecated, use the literal character instead" , since="2.13.1") + try { + val replaced = StringContext.processUnicode(strVal) + if(replaced != strVal) { + val diffPosition = replaced.zip(strVal).zipWithIndex.collectFirst{ case ((r, o), i) if r != o => i}.getOrElse(replaced.length - 1) + deprecationWarning(offset + 3 + diffPosition, "Unicode escapes in triple quoted strings are deprecated, use the literal character instead", since="2.13.1") + } + strVal = replaced + } catch { + case ue: StringContext.InvalidUnicodeEscapeException => { + syntaxError(offset + ue.index, ue.getMessage()) + } } - strVal = replaced } @tailrec private def getRawStringLit(): Unit = { @@ -774,7 +781,7 @@ trait Scanners extends ScannersCommon { nextRawChar() if (isTripleQuote()) { setStrVal() - if(!currentRun.isScala214) replaceUnicodeEscapes(true) + if(!currentRun.isScala214) replaceUnicodeEscapesInTriple() token = STRINGLIT } else getRawStringLit() @@ -901,30 +908,34 @@ trait Scanners extends ScannersCommon { syntaxError(start, s"octal escape literals are unsupported: use $alt instead") putChar(oct.toChar) } else { - ch match { - case 'b' => putChar('\b') - case 't' => putChar('\t') - case 'n' => putChar('\n') - case 'f' => putChar('\f') - case 'r' => putChar('\r') - case '\"' => putChar('\"') - case '\'' => putChar('\'') - case '\\' => putChar('\\') - case 'u' => getUEscape() - case _ => invalidEscape() + if (ch == 'u') { + if (getUEscape()) nextChar() + } + else { + ch match { + case 'b' => putChar('\b') + case 't' => putChar('\t') + case 'n' => putChar('\n') + case 'f' => putChar('\f') + case 'r' => putChar('\r') + case '\"' => putChar('\"') + case '\'' => putChar('\'') + case '\\' => putChar('\\') + case _ => invalidEscape() + } + nextChar() } - nextChar() } } else { putChar(ch) nextChar() } - private def getUEscape(): Unit = { + private def getUEscape(): Boolean = { while (ch == 'u') nextChar() var codepoint = 0 var digitsRead = 0 - while(digitsRead < 4){ + while (digitsRead < 4) { if (digitsRead > 0) nextChar() val digit = digit2int(ch, 16) digitsRead += 1 @@ -932,10 +943,14 @@ trait Scanners extends ScannersCommon { codepoint = codepoint << 4 codepoint += digit } - else invalidUnicodeEscape(digitsRead) + else { + invalidUnicodeEscape(digitsRead) + return false + } } val found = codepoint.asInstanceOf[Char] putChar(found) + true } @@ -945,7 +960,7 @@ trait Scanners extends ScannersCommon { } protected def invalidUnicodeEscape(n: Int): Unit = { - syntaxError(charOffset -n, "invalid unicode escape") + syntaxError(charOffset - n, "invalid unicode escape") putChar(ch) } diff --git a/src/compiler/scala/tools/reflect/FastStringInterpolator.scala b/src/compiler/scala/tools/reflect/FastStringInterpolator.scala index 51863e8c9808..7c0a277a620b 100644 --- a/src/compiler/scala/tools/reflect/FastStringInterpolator.scala +++ b/src/compiler/scala/tools/reflect/FastStringInterpolator.scala @@ -36,10 +36,11 @@ trait FastStringInterpolator extends FormatInterpolator { else if (isRaw) { val processed = StringContext.processUnicode(stringVal) if(processed != stringVal){ - var diffindex = processed.zip(stringVal).zipWithIndex.collectFirst{ + val diffindex = processed.zip(stringVal).zipWithIndex.collectFirst { case ((p, o), i) if p != o => i }.getOrElse(processed.length - 1) - c.warning(lit.pos, "Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14") + + c.warning(lit.pos.withShift(diffindex), "Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14. Use the literal character instead.") } processed } @@ -48,7 +49,8 @@ trait FastStringInterpolator extends FormatInterpolator { treeCopy.Literal(lit, k).setType(ConstantType(k)) } catch { - case e: StringContext.InvalidEscapeException => c.abort(parts.head.pos.withShift(e.index), e.getMessage) + case ie: StringContext.InvalidEscapeException => c.abort(parts.head.pos.withShift(ie.index), ie.getMessage) + case iue: StringContext.InvalidUnicodeEscapeException => c.abort(parts.head.pos.withShift(iue.index), iue.getMessage) } val argsIndexed = args.toVector diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index 5d32d207d971..57c73b768919 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -324,11 +324,15 @@ object StringContext { class InvalidEscapeException(str: String, val index: Int) extends IllegalArgumentException( s"""invalid escape ${ require(index >= 0 && index < str.length) - val ok = s"""[\\b, \\t, \\n, \\f, \\r, \\, \\", \\', \\uxxxx]""" + val ok = s"""[\\b, \\t, \\n, \\f, \\r, \\\\, \\", \\', \\uxxxx]""" if (index == str.length - 1) "at terminal" else s"'\\${str(index + 1)}' not one of $ok at" } index $index in "$str". Use \\\\ for literal \\.""" ) + protected[scala] class InvalidUnicodeEscapeException(str: String, val escapeStart: Int, val index: Int) extends IllegalArgumentException( + s"""invalid unicode escape at index $index of $str""" + ) + private[this] def readUEscape(src: String, startindex: Int): (Char, Int) = { val len = src.length() def loop(uindex: Int): (Char, Int) = { @@ -340,14 +344,16 @@ object StringContext { val digitsRead = dindex (codepoint.asInstanceOf[Char], usRead + digitsRead) } + else if (dindex + uindex >= len) + throw new InvalidUnicodeEscapeException(src, startindex, dindex + uindex) else { val ch = src(dindex + uindex) val e = ch.asDigit if(e >= 0 && e <= 15) loopCP(dindex + 1, (codepoint << 4) + e) - else throw new InvalidEscapeException(src, startindex) + else throw new InvalidUnicodeEscapeException(src, startindex, dindex + uindex) } } - if(uindex > len) throw new InvalidEscapeException(src, startindex) + if(uindex >= len) throw new InvalidUnicodeEscapeException(src, startindex, uindex - 1) //allow one or more `u` characters between the //backslash and the code unit else if(src(uindex) == 'u') loop(uindex + 1) diff --git a/test/files/neg/t3220-1.check b/test/files/neg/t3220-1.check new file mode 100644 index 000000000000..60a1fa79825e --- /dev/null +++ b/test/files/neg/t3220-1.check @@ -0,0 +1,25 @@ +t3220-1.scala:2: error: invalid unicode escape + val badsingle = "foo \unope that's wrong" + ^ +t3220-1.scala:3: error: invalid unicode escape at index 6 of foo \unope that's wrong + val badtriple = """foo \unope that's wrong""" + ^ +t3220-1.scala:4: error: invalid unicode escape + val caretPos = "foo \u12x3 pos @ x" + ^ +t3220-1.scala:5: error: invalid unicode escape + val caretPos2 = "foo \uuuuuuu12x3 pos @ x" + ^ +t3220-1.scala:6: error: invalid unicode escape + val carPosTerm = "foo \u123" + ^ +t3220-1.scala:7: error: invalid unicode escape + val halfAnEscape = "foo \u12" + ^ +t3220-1.scala:8: error: invalid unicode escape + val halfAnEscapeChar = '\u45' + ^ +t3220-1.scala:9: error: invalid unicode escape + val `half An Identifier\u45` = "nope" + ^ +8 errors found diff --git a/test/files/neg/t3220-1.scala b/test/files/neg/t3220-1.scala new file mode 100644 index 000000000000..5935b352443f --- /dev/null +++ b/test/files/neg/t3220-1.scala @@ -0,0 +1,10 @@ +object Example { + val badsingle = "foo \unope that's wrong" + val badtriple = """foo \unope that's wrong""" + val caretPos = "foo \u12x3 pos @ x" + val caretPos2 = "foo \uuuuuuu12x3 pos @ x" + val carPosTerm = "foo \u123" + val halfAnEscape = "foo \u12" + val halfAnEscapeChar = '\u45' + val `half An Identifier\u45` = "nope" +} \ No newline at end of file diff --git a/test/files/neg/t3220-2.check b/test/files/neg/t3220-2.check new file mode 100644 index 000000000000..d4e5d1da5006 --- /dev/null +++ b/test/files/neg/t3220-2.check @@ -0,0 +1,16 @@ +t3220-2.scala:2: error: invalid unicode escape at index 6 of foo \unope that's wrong + val badInters1 = s"foo \unope that's wrong" + ^ +t3220-2.scala:3: error: invalid unicode escape at index 8 of foo \u12 + val badIntersEnd1 = s"foo \u12" + ^ +t3220-2.scala:4: error: invalid unicode escape at index 6 of foo \unope that's wrong + val badInterRaw1 = raw"foo \unope that's wrong" + ^ +t3220-2.scala:6: error: invalid unicode escape at index 6 of foo \unope that's wrong + val badInters3 = s"""foo \unope that's wrong""" + ^ +t3220-2.scala:7: error: invalid unicode escape at index 6 of foo \unope that's wrong + val badInterRaw3 = raw"""foo \unope that's wrong""" + ^ +5 errors found diff --git a/test/files/neg/t3220-2.scala b/test/files/neg/t3220-2.scala new file mode 100644 index 000000000000..79d2ca7449f1 --- /dev/null +++ b/test/files/neg/t3220-2.scala @@ -0,0 +1,8 @@ +object Example { + val badInters1 = s"foo \unope that's wrong" + val badIntersEnd1 = s"foo \u12" + val badInterRaw1 = raw"foo \unope that's wrong" + val badInterRawEnd1 = raw"foo \u12" + val badInters3 = s"""foo \unope that's wrong""" + val badInterRaw3 = raw"""foo \unope that's wrong""" +} \ No newline at end of file diff --git a/test/files/neg/t6631.check b/test/files/neg/t6631.check index f54e73556f62..7b848cc8db2e 100644 --- a/test/files/neg/t6631.check +++ b/test/files/neg/t6631.check @@ -1,4 +1,4 @@ -t6631.scala:2: error: invalid escape '\x' not one of [\b, \t, \n, \f, \r, \, \", \', \uxxxx] at index 0 in "\x". Use \\ for literal \. +t6631.scala:2: error: invalid escape '\x' not one of [\b, \t, \n, \f, \r, \\, \", \', \uxxxx] at index 0 in "\x". Use \\ for literal \. s"""\x""" ^ one error found diff --git a/test/files/neg/t8266-invalid-interp.check b/test/files/neg/t8266-invalid-interp.check index fca8db21f893..7b47703e88eb 100644 --- a/test/files/neg/t8266-invalid-interp.check +++ b/test/files/neg/t8266-invalid-interp.check @@ -1,10 +1,10 @@ t8266-invalid-interp.scala:4: error: Trailing '\' escapes nothing. f"a\", ^ -t8266-invalid-interp.scala:5: error: invalid escape '\x' not one of [\b, \t, \n, \f, \r, \, \", \', \uxxxx] at index 1 in "a\xc". Use \\ for literal \. +t8266-invalid-interp.scala:5: error: invalid escape '\x' not one of [\b, \t, \n, \f, \r, \\, \", \', \uxxxx] at index 1 in "a\xc". Use \\ for literal \. f"a\xc", ^ -t8266-invalid-interp.scala:7: error: invalid escape '\v' not one of [\b, \t, \n, \f, \r, \, \", \', \uxxxx] at index 1 in "a\vc". Use \\ for literal \. +t8266-invalid-interp.scala:7: error: invalid escape '\v' not one of [\b, \t, \n, \f, \r, \\, \", \', \uxxxx] at index 1 in "a\vc". Use \\ for literal \. f"a\vc" ^ three errors found diff --git a/test/files/run/t3220-213.check b/test/files/run/t3220-213.check index 5358aa80ad80..ce69caf6fdc7 100644 --- a/test/files/run/t3220-213.check +++ b/test/files/run/t3220-213.check @@ -1,21 +1,21 @@ -t3220-213.scala:9: warning: Unicode escapes in triple quoted strings and raw interpolations are deprecated, use the literal character instead +t3220-213.scala:9: warning: Unicode escapes in triple quoted strings are deprecated, use the literal character instead def inTripleQuoted = """\u000A""" - ^ -t3220-213.scala:42: warning: Unicode escapes in triple quoted strings and raw interpolations are deprecated, use the literal character instead + ^ +t3220-213.scala:42: warning: Unicode escapes in triple quoted strings are deprecated, use the literal character instead "tab unicode escape in triple quoted string" -> """tab\u0009tab""", - ^ -t3220-213.scala:10: warning: Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14 + ^ +t3220-213.scala:10: warning: Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14. Use the literal character instead. def inInterpolation = raw"\u000A" ^ -t3220-213.scala:11: warning: Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14 +t3220-213.scala:11: warning: Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14. Use the literal character instead. def inTripleQuotedInterpolation = raw"""\u000A""" ^ -t3220-213.scala:43: warning: Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14 +t3220-213.scala:43: warning: Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14. Use the literal character instead. "tab unicode escape in single quoted raw interpolator" -> raw"tab\u0009tab", - ^ -t3220-213.scala:44: warning: Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14 + ^ +t3220-213.scala:44: warning: Unicode escapes in raw interpolations are deprecated as of scala 2.13.1, and will be removed in scala 2.14. Use the literal character instead. "tab unicode escape in triple quoted raw interpolator" -> raw"""tab\u0009tab""" - ^ + ^ supported literals that result in tab tab: literal tab in single quoted string