From abb58dcef1b4758f4e3af26c030e1f0630b8d145 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sun, 27 Apr 2014 00:08:54 -0700 Subject: SI-6476 Improve error on escapement Behavior of escape processing under string interpolation can be confusing. This commit improves the exception message so you know at least what sort of escapes are handled. This came up on SO in the form `s"\d".r`, where it may not be obvious what is throwing and how to work around it. ``` scala> s"\d".r scala.StringContext$InvalidEscapeException: invalid escape '\d' not one of [\b, \t, \n, \f, \r, \\, \", \'] at index 0 in "\d". Use \\ for literal \. scala> s"a\" scala.StringContext$InvalidEscapeException: invalid escape at terminal index 1 in "a\". Use \\ for literal \. ``` Referencing SI-6476 because that has become the magnet ticket for "escape processing under string interpolation, huh?" This doesn't address `$"` and doesn't handle the more interesting parse error `s"a\"b"`. --- src/library/scala/StringContext.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index c0468e8b02..0b08182102 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -175,8 +175,13 @@ object StringContext { * @param str The offending string * @param idx The index of the offending backslash character in `str`. */ - class InvalidEscapeException(str: String, @deprecatedName('idx) val index: Int) - extends IllegalArgumentException("invalid escape character at index "+index+" in \""+str+"\"") + class InvalidEscapeException(str: String, @deprecatedName('idx) val index: Int) extends IllegalArgumentException( + s"""invalid escape ${ + require(index >= 0 && index < str.length) + val ok = """[\b, \t, \n, \f, \r, \\, \", \']""" + if (index == str.length - 1) "at terminal" else s"'\\${str(index + 1)}' not one of $ok at" + } index $index in "$str". Use \\\\ for literal \\.""" + ) /** Expands standard Scala escape sequences in a string. * Escape sequences are: -- cgit v1.2.3 From b4f506749189b337bd25633d521b6b917108ee15 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 18 Jul 2014 22:34:01 -0700 Subject: SI-6476 Documentation And adjust the test. --- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 2 ++ test/files/run/t6631.scala | 18 ------------------ test/junit/scala/StringContextTest.scala | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 18 deletions(-) delete mode 100644 test/files/run/t6631.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 883fd31dbc..90bc53721d 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -1254,6 +1254,8 @@ self => } if (in.token == STRINGLIT) partsBuf += literal() + // Documenting that it is intentional that the ident is not rooted for purposes of virtualization + //val t1 = atPos(o2p(start)) { Select(Select (Ident(nme.ROOTPKG), nme.scala_), nme.StringContext) } val t1 = atPos(o2p(start)) { Ident(nme.StringContext) } val t2 = atPos(start) { Apply(t1, partsBuf.toList) } t2 setPos t2.pos.makeTransparent diff --git a/test/files/run/t6631.scala b/test/files/run/t6631.scala deleted file mode 100644 index e472b83d50..0000000000 --- a/test/files/run/t6631.scala +++ /dev/null @@ -1,18 +0,0 @@ -import reflect.ClassTag - -object Test extends App { - def intercept[T <: Throwable : ClassTag](act: => Any) = try { - act - } catch { - case x: Throwable => - val cls = implicitly[ClassTag[T]].runtimeClass - assert(cls.isInstance(x), (x.getClass, x, cls).toString) - } - assert(s"""\f\r\n\t""" == "\f\r\n\t") - - import StringContext.InvalidEscapeException - intercept[InvalidEscapeException](s"""\""") - intercept[InvalidEscapeException](s"""\x""") - intercept[InvalidEscapeException](s"\") - -} diff --git a/test/junit/scala/StringContextTest.scala b/test/junit/scala/StringContextTest.scala index 5abfe90cd1..bb0e8c4252 100644 --- a/test/junit/scala/StringContextTest.scala +++ b/test/junit/scala/StringContextTest.scala @@ -48,4 +48,18 @@ class StringContextTest { val res = processEscapes(s) assertEquals("Scala", res) } + + @Test def t6631_baseline() = assertEquals("\f\r\n\t", s"""\f\r\n\t""") + + @Test def t6631_badEscape() = assertThrows[InvalidEscapeException] { + s"""\x""" + } + + // verifying that the standard interpolators can be supplanted + @Test def antiHijack_?() = { + object AllYourStringsAreBelongToMe { case class StringContext(args: Any*) { def s(args: Any) = "!!!!" } } + import AllYourStringsAreBelongToMe._ + //assertEquals("????", s"????") + assertEquals("!!!!", s"????") // OK to hijack core interpolator ids + } } -- cgit v1.2.3 From ccbb84bb87a9dd828fa3aca1a1a14487e327ec5b Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sat, 19 Jul 2014 19:26:39 -0700 Subject: SI-6476 Unitize procedures, readability Strictly trivial updates for readability. I used to prefer procedure syntax, but since it was scheduled for removal, I can't scan a def without an equals sign. --- .../scala/tools/nsc/ast/parser/Parsers.scala | 36 ++++++++++------------ .../scala/tools/nsc/ast/parser/Scanners.scala | 16 +++++----- 2 files changed, 24 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 90bc53721d..9350a39cbe 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -204,13 +204,11 @@ self => override def newScanner() = new UnitScanner(unit, patches) - override def warning(offset: Offset, msg: String) { + override def warning(offset: Offset, msg: String): Unit = reporter.warning(o2p(offset), msg) - } - override def deprecationWarning(offset: Offset, msg: String) { + override def deprecationWarning(offset: Offset, msg: String): Unit = currentRun.reporting.deprecationWarning(o2p(offset), msg) - } private var smartParsing = false @inline private def withSmartParsing[T](body: => T): T = { @@ -226,12 +224,12 @@ self => for ((offset, msg) <- syntaxErrors) reporter.error(o2p(offset), msg) - override def syntaxError(offset: Offset, msg: String) { + override def syntaxError(offset: Offset, msg: String): Unit = { if (smartParsing) syntaxErrors += ((offset, msg)) else reporter.error(o2p(offset), msg) } - override def incompleteInputError(msg: String) { + override def incompleteInputError(msg: String): Unit = { val offset = source.content.length - 1 if (smartParsing) syntaxErrors += ((offset, msg)) else currentRun.reporting.incompleteInputError(o2p(offset), msg) @@ -544,27 +542,25 @@ self => } def warning(offset: Offset, msg: String): Unit def incompleteInputError(msg: String): Unit - private def syntaxError(pos: Position, msg: String, skipIt: Boolean) { - syntaxError(pos pointOrElse in.offset, msg, skipIt) - } def syntaxError(offset: Offset, msg: String): Unit - def syntaxError(msg: String, skipIt: Boolean) { + + private def syntaxError(pos: Position, msg: String, skipIt: Boolean): Unit = + syntaxError(pos pointOrElse in.offset, msg, skipIt) + def syntaxError(msg: String, skipIt: Boolean): Unit = syntaxError(in.offset, msg, skipIt) - } - def syntaxError(offset: Offset, msg: String, skipIt: Boolean) { + def syntaxError(offset: Offset, msg: String, skipIt: Boolean): Unit = { if (offset > lastErrorOffset) { syntaxError(offset, msg) - // no more errors on this token. - lastErrorOffset = in.offset + lastErrorOffset = in.offset // no more errors on this token. } if (skipIt) skip(UNDEF) } - def warning(msg: String) { warning(in.offset, msg) } + def warning(msg: String): Unit = warning(in.offset, msg) - def syntaxErrorOrIncomplete(msg: String, skipIt: Boolean) { + def syntaxErrorOrIncomplete(msg: String, skipIt: Boolean): Unit = { if (in.token == EOF) incompleteInputError(msg) else @@ -1233,15 +1229,15 @@ self => skipIt = true)(EmptyTree) // Like Swiss cheese, with holes def stringCheese: Tree = atPos(in.offset) { - val start = in.offset + val start = in.offset val interpolator = in.name.encoded // ident() for INTERPOLATIONID val partsBuf = new ListBuffer[Tree] - val exprBuf = new ListBuffer[Tree] + val exprsBuf = new ListBuffer[Tree] in.nextToken() while (in.token == STRINGPART) { partsBuf += literal() - exprBuf += ( + exprsBuf += ( if (inPattern) dropAnyBraces(pattern()) else in.token match { case IDENTIFIER => atPos(in.offset)(Ident(ident())) @@ -1260,7 +1256,7 @@ self => val t2 = atPos(start) { Apply(t1, partsBuf.toList) } t2 setPos t2.pos.makeTransparent val t3 = Select(t2, interpolator) setPos t2.pos - atPos(start) { Apply(t3, exprBuf.toList) } + atPos(start) { Apply(t3, exprsBuf.toList) } } if (inPattern) stringCheese else withPlaceholders(stringCheese, isAny = true) // strinterpolator params are Any* by definition diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 572497ac90..5ae9fd3f56 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -688,9 +688,11 @@ trait Scanners extends ScannersCommon { setStrVal() nextChar() token = STRINGLIT - } else syntaxError("unclosed string literal") + } else unclosedStringLit() } + private def unclosedStringLit(): Unit = syntaxError("unclosed string literal") + private def getRawStringLit(): Unit = { if (ch == '\"') { nextRawChar() @@ -764,7 +766,7 @@ trait Scanners extends ScannersCommon { if (multiLine) incompleteInputError("unclosed multi-line string literal") else - syntaxError("unclosed string literal") + unclosedStringLit() } else { putChar(ch) @@ -1068,21 +1070,19 @@ trait Scanners extends ScannersCommon { // Errors ----------------------------------------------------------------- - /** generate an error at the given offset - */ - def syntaxError(off: Offset, msg: String) { + /** generate an error at the given offset */ + def syntaxError(off: Offset, msg: String): Unit = { error(off, msg) token = ERROR } - /** generate an error at the current token offset - */ + /** generate an error at the current token offset */ def syntaxError(msg: String): Unit = syntaxError(offset, msg) def deprecationWarning(msg: String): Unit = deprecationWarning(offset, msg) /** signal an error where the input ended in the middle of a token */ - def incompleteInputError(msg: String) { + def incompleteInputError(msg: String): Unit = { incompleteInputError(offset, msg) token = EOF } -- cgit v1.2.3 From eff9a584a68c65935c313a6f26d7d4f778a61703 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sat, 19 Jul 2014 19:57:02 -0700 Subject: SI-6476 Unitize ALL the procedures! --- .../scala/tools/nsc/ast/parser/Parsers.scala | 18 +++++------ .../scala/tools/nsc/ast/parser/Scanners.scala | 35 +++++++++++----------- 2 files changed, 26 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 9350a39cbe..c90f0d0173 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -154,8 +154,8 @@ self => def unit = global.currentUnit // suppress warnings; silent abort on errors - def warning(offset: Offset, msg: String) {} - def deprecationWarning(offset: Offset, msg: String) {} + def warning(offset: Offset, msg: String): Unit = () + def deprecationWarning(offset: Offset, msg: String): Unit = () def syntaxError(offset: Offset, msg: String): Unit = throw new MalformedInput(offset, msg) def incompleteInputError(msg: String): Unit = throw new MalformedInput(source.content.length - 1, msg) @@ -333,7 +333,7 @@ self => */ private var inScalaPackage = false private var currentPackage = "" - def resetPackage() { + def resetPackage(): Unit = { inScalaPackage = false currentPackage = "" } @@ -512,7 +512,7 @@ self => finally inFunReturnType = saved } - protected def skip(targetToken: Token) { + protected def skip(targetToken: Token): Unit = { var nparens = 0 var nbraces = 0 while (true) { @@ -715,7 +715,7 @@ self => /** Convert tree to formal parameter. */ def convertToParam(tree: Tree): ValDef = atPos(tree.pos) { - def removeAsPlaceholder(name: Name) { + def removeAsPlaceholder(name: Name): Unit = { placeholderParams = placeholderParams filter (_.name != name) } def errorParam = makeParam(nme.ERROR, errorTypeTree setPos o2p(tree.pos.end)) @@ -1264,21 +1264,21 @@ self => /* ------------- NEW LINES ------------------------------------------------- */ - def newLineOpt() { + def newLineOpt(): Unit = { if (in.token == NEWLINE) in.nextToken() } - def newLinesOpt() { + def newLinesOpt(): Unit = { if (in.token == NEWLINE || in.token == NEWLINES) in.nextToken() } - def newLineOptWhenFollowedBy(token: Offset) { + def newLineOptWhenFollowedBy(token: Offset): Unit = { // note: next is defined here because current == NEWLINE if (in.token == NEWLINE && in.next.token == token) newLineOpt() } - def newLineOptWhenFollowing(p: Token => Boolean) { + def newLineOptWhenFollowing(p: Token => Boolean): Unit = { // note: next is defined here because current == NEWLINE if (in.token == NEWLINE && p(in.next.token)) newLineOpt() } diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 5ae9fd3f56..0248806fff 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -113,7 +113,7 @@ trait Scanners extends ScannersCommon { case SU | CR | LF => case _ => nextChar() ; skipLineComment() } - private def maybeOpen() { + private def maybeOpen(): Unit = { putCommentChar() if (ch == '*') { putCommentChar() @@ -137,7 +137,7 @@ trait Scanners extends ScannersCommon { def skipDocComment(): Unit = skipNestedComments() def skipBlockComment(): Unit = skipNestedComments() - private def skipToCommentEnd(isLineComment: Boolean) { + private def skipToCommentEnd(isLineComment: Boolean): Unit = { nextChar() if (isLineComment) skipLineComment() else { @@ -185,7 +185,7 @@ trait Scanners extends ScannersCommon { /** append Unicode character to "cbuf" buffer */ - protected def putChar(c: Char) { + protected def putChar(c: Char): Unit = { // assert(cbuf.size < 10000, cbuf) cbuf.append(c) } @@ -196,7 +196,7 @@ trait Scanners extends ScannersCommon { protected def emitIdentifierDeprecationWarnings = true /** Clear buffer and set name and token */ - private def finishNamed(idtoken: Token = IDENTIFIER) { + private def finishNamed(idtoken: Token = IDENTIFIER): Unit = { name = newTermName(cbuf.toString) cbuf.clear() token = idtoken @@ -215,7 +215,7 @@ trait Scanners extends ScannersCommon { } /** Clear buffer and set string */ - private def setStrVal() { + private def setStrVal(): Unit = { strVal = cbuf.toString cbuf.clear() } @@ -270,7 +270,7 @@ trait Scanners extends ScannersCommon { /** Produce next token, filling TokenData fields of Scanner. */ - def nextToken() { + def nextToken(): Unit = { val lastToken = token // Adapt sepRegions according to last token (lastToken: @switch) match { @@ -341,7 +341,7 @@ trait Scanners extends ScannersCommon { prev copyFrom this val nextLastOffset = charOffset - 1 fetchToken() - def resetOffset() { + def resetOffset(): Unit = { offset = prev.offset lastOffset = prev.lastOffset } @@ -399,7 +399,7 @@ trait Scanners extends ScannersCommon { /** read next token, filling TokenData fields of Scanner. */ - protected final def fetchToken() { + protected final def fetchToken(): Unit = { offset = charOffset - 1 (ch: @switch) match { @@ -604,7 +604,7 @@ trait Scanners extends ScannersCommon { // Identifiers --------------------------------------------------------------- - private def getBackquotedIdent() { + private def getBackquotedIdent(): Unit = { nextChar() getLitChars('`') if (ch == '`') { @@ -664,7 +664,7 @@ trait Scanners extends ScannersCommon { else finishNamed() } - private def getIdentOrOperatorRest() { + private def getIdentOrOperatorRest(): Unit = { if (isIdentifierPart(ch)) getIdentRest() else ch match { @@ -859,7 +859,7 @@ trait Scanners extends ScannersCommon { /** read fractional part and exponent of floating point number * if one is present. */ - protected def getFraction() { + protected def getFraction(): Unit = { token = DOUBLELIT while ('0' <= ch && ch <= '9') { putChar(ch) @@ -968,14 +968,13 @@ trait Scanners extends ScannersCommon { def floatVal: Double = floatVal(negated = false) - def checkNoLetter() { + def checkNoLetter(): Unit = { if (isIdentifierPart(ch) && ch >= ' ') syntaxError("Invalid literal number") } - /** Read a number into strVal and set base - */ - protected def getNumber() { + /** Read a number into strVal and set base */ + protected def getNumber(): Unit = { val base1 = if (base < 10) 10 else base // Read 8,9's even if format is octal, produce a malformed number error afterwards. // At this point, we have already read the first digit, so to tell an innocent 0 apart @@ -1054,7 +1053,7 @@ trait Scanners extends ScannersCommon { /** Parse character literal if current character is followed by \', * or follow with given op and return a symbol literal token */ - def charLitOr(op: () => Unit) { + def charLitOr(op: () => Unit): Unit = { putChar(ch) nextChar() if (ch == '\'') { @@ -1134,7 +1133,7 @@ trait Scanners extends ScannersCommon { /** Initialization method: read first char, then first token */ - def init() { + def init(): Unit = { nextChar() nextToken() } @@ -1490,6 +1489,6 @@ trait Scanners extends ScannersCommon { // when skimming through the source file trying to heal braces override def emitIdentifierDeprecationWarnings = false - override def error(offset: Offset, msg: String) {} + override def error(offset: Offset, msg: String): Unit = () } } -- cgit v1.2.3