From 6598cce01a7ba3a5c8cf76a17408b5dd2e88fb4d Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 11 Jun 2020 20:22:39 -0700 Subject: [PATCH 1/5] Unleash old literals test with compromises --- tests/neg/literals.scala | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/tests/neg/literals.scala b/tests/neg/literals.scala index 8e062c19351b..6d57d78dc6b7 100644 --- a/tests/neg/literals.scala +++ b/tests/neg/literals.scala @@ -1,11 +1,6 @@ -trait RejectedLiterals { - def missingHex: Int = { 0x } // error: invalid literal number -} -/* -// nsc: -Ywarn-octal-literal -Xfatal-warnings -deprecation trait RejectedLiterals { - def missingHex: Int = { 0x } // line 4: was: not reported, taken as zero + def missingHex: Int = { 0x } // error: invalid literal number def leadingZeros: Int = { 01 } // line 6: no leading zero @@ -13,17 +8,17 @@ trait RejectedLiterals { def zeroOfNine: Int = { 09 } // line 10: no leading zero - def orphanDot: Int = { 9. } // line 12: ident expected + def orphanDot: Int = { 9. } // error: ident expected - def zeroOfNineDot: Int = { 09. } // line 14: malformed integer, ident expected + def zeroOfNineDot: Int = { 09. } // error: malformed integer, ident expected - def noHexFloat: Double = { 0x1.2 } // line 16: ';' expected but double literal found. + def noHexFloat: Double = { 0x1.2 } // error: ';' expected but double literal found. } trait Braceless { - def missingHex: Int = 0x // line 22: was: not reported, taken as zero + def missingHex: Int = 0x // error: was: not reported, taken as zero def leadingZeros: Int = 01 // line 24: no leading zero @@ -31,28 +26,27 @@ trait Braceless { def zeroOfNine: Int = 09 // line 28: no leading zero - def orphanDot: Int = 9. // line 30: ident expected + def orphanDot: Int = 9. // should be: ident expected - def zeroOfNineDot: Int = 09. // line 32: malformed integer, ident expected + def zeroOfNineDot: Int = 09. // error: an identifier expected, but 'def' found, shoule be ident expected - def noHexFloat: Double = 0x1.2 // line 34: ';' expected but double literal found. + def noHexFloat: Double = 0x1.2 // should be: ';' expected but double literal found. } trait MoreSadness { - def tooTiny: Float = { 0.7e-45f } // floating point number too small + def tooTiny: Float = { 0.7e-45f } // error: floating point number too small - def twoTiny: Double = { 2.0e-324 } // double precision floating point number too small + def twoTiny: Double = { 2.0e-324 } // error: double precision floating point number too small - def tooHuge: Float = { 3.4028236E38f } // floating point number too large + def tooHuge: Float = { 3.4028236E38f } // error: floating point number too large - def twoHuge: Double = { 1.7976931348623159e308 } // double precision floating point number too large -} + def twoHuge: Double = { 1.7976931348623159e308 } // error: double precision floating point number too large +} // error: Found: Unit etc, sad trait Lengthy { def bad = 1l def worse = 123l -} -*/ +} // error: Found: Unit etc, sad // anypos-error: '}' expected, but eof found, sad From 7b27c0513dbdabe7661cfaa3f4f44759667a80f7 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 22 Mar 2022 00:43:45 -0700 Subject: [PATCH 2/5] Forward port lint 1l and getNumber tweak An additional message is emitted on `0_x` because it asks to continue, but that is helpful. --- .../dotty/tools/dotc/parsing/Scanners.scala | 65 ++++++++++--------- tests/neg/t6124.check | 6 +- tests/neg/t6124.scala | 2 +- 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 12d6e34c1e98..4fdd535bdc38 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -21,6 +21,8 @@ import config.Feature.migrateTo3 import config.SourceVersion.`3.0` import reporting.{NoProfile, Profile} +import java.lang.Character.isDigit + object Scanners { /** Offset into source character array */ @@ -152,9 +154,9 @@ object Scanners { strVal = litBuf.toString litBuf.clear() - @inline def isNumberSeparator(c: Char): Boolean = c == '_' + inline def isNumberSeparator(c: Char): Boolean = c == '_' - @inline def removeNumberSeparators(s: String): String = if (s.indexOf('_') == -1) s else s.replace("_", "") + inline def removeNumberSeparators(s: String): String = if s.indexOf('_') == -1 then s else s.replace("_", "") // disallow trailing numeric separator char, but continue lexing def checkNoTrailingSeparator(): Unit = @@ -887,7 +889,7 @@ object Scanners { //case 'b' | 'B' => base = 2 ; nextChar() case _ => base = 10 ; putChar('0') } - if (base != 10 && !isNumberSeparator(ch) && digit2int(ch, base) < 0) + if base != 10 && !isNumberSeparator(ch) && digit2int(ch, base) < 0 then error("invalid literal number") } fetchLeadingZero() @@ -967,7 +969,7 @@ object Scanners { case '.' => nextChar() if ('0' <= ch && ch <= '9') { - putChar('.'); getFraction(); setStrVal() + putChar('.'); getFraction() } else token = DOT @@ -1427,7 +1429,7 @@ object Scanners { /** read fractional part and exponent of floating point number * if one is present. */ - protected def getFraction(): Unit = { + protected def getFraction(): Unit = token = DECILIT while ('0' <= ch && ch <= '9' || isNumberSeparator(ch)) { putChar(ch) @@ -1465,41 +1467,44 @@ object Scanners { token = FLOATLIT } checkNoLetter() - } + setStrVal() + end getFraction def checkNoLetter(): Unit = if (isIdentifierPart(ch) && ch >= ' ') error("Invalid literal number") /** Read a number into strVal and set base */ - protected def getNumber(): Unit = { - while (isNumberSeparator(ch) || digit2int(ch, base) >= 0) { - putChar(ch) - nextChar() - } - checkNoTrailingSeparator() - token = INTLIT - if (base == 10 && ch == '.') { - val lch = lookaheadChar() - if ('0' <= lch && lch <= '9') { - putChar('.') - nextChar() - getFraction() - } - } - else (ch: @switch) match { - case 'e' | 'E' | 'f' | 'F' | 'd' | 'D' => - if (base == 10) getFraction() - case 'l' | 'L' => + protected def getNumber(): Unit = + def consumeDigits(): Unit = + while isNumberSeparator(ch) || digit2int(ch, base) >= 0 do + putChar(ch) nextChar() - token = LONGLIT - case _ => - } + // at dot with digit following + def restOfNonIntegralNumber(): Unit = + putChar('.') + nextChar() + getFraction() + // 1l is an acknowledged bad practice + def lintel(): Unit = + if ch == 'l' then + val msg = "Lowercase el for long is not recommended because it is easy to confuse with numeral 1; use uppercase L instead" + report.deprecationWarning(msg, sourcePos(offset + litBuf.length)) + // after int: 5e7f, 42L, 42.toDouble but not 42b. + def restOfNumber(): Unit = + ch match + case 'e' | 'E' | 'f' | 'F' | 'd' | 'D' => getFraction() + case 'l' | 'L' => lintel() ; token = LONGLIT ; setStrVal() ; nextChar() + case _ => token = INTLIT ; setStrVal() ; checkNoLetter() + + // consume leading digits + consumeDigits() checkNoTrailingSeparator() - setStrVal() - } + val detectedFloat: Boolean = base == 10 && ch == '.' && isDigit(lookaheadChar()) + if detectedFloat then restOfNonIntegralNumber() else restOfNumber() + end getNumber private def finishCharLit(): Unit = { nextChar() diff --git a/tests/neg/t6124.check b/tests/neg/t6124.check index 871533bf80c8..6d74a12a2d8a 100644 --- a/tests/neg/t6124.check +++ b/tests/neg/t6124.check @@ -43,9 +43,13 @@ | ^ | trailing separator is not allowed -- Error: tests/neg/t6124.scala:24:12 ---------------------------------------------------------------------------------- -24 | val x5 = 0_x52 // error +24 | val x5 = 0_x52 // error // error | ^ | trailing separator is not allowed +-- Error: tests/neg/t6124.scala:24:11 ---------------------------------------------------------------------------------- +24 | val x5 = 0_x52 // error // error + | ^ + | Invalid literal number -- Error: tests/neg/t6124.scala:26:13 ---------------------------------------------------------------------------------- 26 | val x8 = 0x52_ // error | ^ diff --git a/tests/neg/t6124.scala b/tests/neg/t6124.scala index c8d1944dd95e..eff62869c7fe 100644 --- a/tests/neg/t6124.scala +++ b/tests/neg/t6124.scala @@ -21,7 +21,7 @@ trait T { val x1 = _52 // error val x3 = 52_ // error - val x5 = 0_x52 // error + val x5 = 0_x52 // error // error val x6 = 0x_52 val x8 = 0x52_ // error val x9 = 0_52 From 8d9b9b8bcfde423f00c5ac4b6093359165b95c9a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 22 Mar 2022 13:03:20 -0700 Subject: [PATCH 3/5] Avoid skip on numeric literal badly decoded Align with Scala 2 and improve subsequent parsing. --- .../src/dotty/tools/dotc/parsing/Parsers.scala | 15 ++++++--------- tests/neg/literals.scala | 4 ++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index e8ecf1c88f8c..f475c8fd0f36 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1182,9 +1182,9 @@ object Parsers { case EXPOLIT => return Number(digits, NumberKind.Floating) case _ => } - import scala.util.FromDigits._ + import scala.util.FromDigits.* val value = - try token match { + try token match case INTLIT => intFromDigits(digits, in.base) case LONGLIT => longFromDigits(digits, in.base) case FLOATLIT => floatFromDigits(digits) @@ -1194,15 +1194,11 @@ object Parsers { case TRUE => true case FALSE => false case NULL => null - case _ => - syntaxErrorOrIncomplete(IllegalLiteral()) - null - } - catch { - case ex: FromDigitsException => syntaxErrorOrIncomplete(ex.getMessage) - } + case _ => syntaxErrorOrIncomplete(IllegalLiteral()); null + catch case ex: FromDigitsException => syntaxError(ex.getMessage) Literal(Constant(value)) } + end literalOf if (inStringInterpolation) { val t = in.token match { @@ -1252,6 +1248,7 @@ object Parsers { } } } + end literal private def interpolatedString(inPattern: Boolean = false): Tree = atSpan(in.offset) { val segmentBuf = new ListBuffer[Tree] diff --git a/tests/neg/literals.scala b/tests/neg/literals.scala index 6d57d78dc6b7..3dd76a5ca176 100644 --- a/tests/neg/literals.scala +++ b/tests/neg/literals.scala @@ -42,11 +42,11 @@ trait MoreSadness { def tooHuge: Float = { 3.4028236E38f } // error: floating point number too large def twoHuge: Double = { 1.7976931348623159e308 } // error: double precision floating point number too large -} // error: Found: Unit etc, sad +} trait Lengthy { def bad = 1l def worse = 123l -} // error: Found: Unit etc, sad // anypos-error: '}' expected, but eof found, sad +} From f9e66930ab99b34c3add43a8fdde701924b79cbe Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 22 Mar 2022 13:14:01 -0700 Subject: [PATCH 4/5] Avoid emitting INTLIT in edge case error --- compiler/src/dotty/tools/dotc/parsing/Scanners.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 4fdd535bdc38..0a57f82354e2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -891,9 +891,10 @@ object Scanners { } if base != 10 && !isNumberSeparator(ch) && digit2int(ch, base) < 0 then error("invalid literal number") + else + getNumber() } fetchLeadingZero() - getNumber() case '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => base = 10 getNumber() From 533d613be489eb0ca76ae373f9ad3481beccbb39 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 23 Jun 2022 07:58:00 -0700 Subject: [PATCH 5/5] Longlit deprecation is -Xlint:longlit --- .../src/dotty/tools/dotc/config/ScalaSettings.scala | 8 +++++++- compiler/src/dotty/tools/dotc/config/Settings.scala | 8 ++++++-- compiler/src/dotty/tools/dotc/parsing/Scanners.scala | 3 ++- tests/neg/longlit.scala | 11 +++++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 tests/neg/longlit.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8a66b5abca8a..6c20052367e2 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -10,7 +10,7 @@ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.rewrites.Rewrites import dotty.tools.io.{AbstractFile, Directory, JDK9Reflectors, PlainDirectory} -import scala.util.chaining._ +import scala.util.chaining.given class ScalaSettings extends SettingGroup with AllScalaSettings @@ -156,6 +156,8 @@ private sealed trait WarningSettings: self: SettingGroup => val Whelp: Setting[Boolean] = BooleanSetting("-W", "Print a synopsis of warning options.") val XfatalWarnings: Setting[Boolean] = BooleanSetting("-Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings")) + val Xlint: Setting[List[String]] = EnumSetting("-Wlint", "warning", "Enable recommended warnings.", LintWarning.values, default = Nil, aliases = List("-Xlint")) + def isLintEnabled(w: LintWarning)(using Context): Boolean = Xlint.value.contains(w.toString) val Wunused: Setting[List[String]] = MultiChoiceSetting( name = "-Wunused", @@ -339,3 +341,7 @@ private sealed trait YSettings: val YforceInlineWhileTyping: Setting[Boolean] = BooleanSetting("-Yforce-inline-while-typing", "Make non-transparent inline methods inline when typing. Emulates the old inlining behavior of 3.0.0-M3.") end YSettings +/** Warnings that may be queried with `ctx.settings.isLintEnabled`. */ +enum LintWarning: + case deprecation, longLit +end LintWarning diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 277833afbd5d..e22625614300 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -9,7 +9,7 @@ import dotty.tools.io.{AbstractFile, Directory, JarArchive, PlainDirectory} import annotation.tailrec import collection.mutable.ArrayBuffer -import reflect.ClassTag +import reflect.{ClassTag, Enum} import scala.util.{Success, Failure} object Settings: @@ -61,7 +61,8 @@ object Settings: prefix: String = "", aliases: List[String] = Nil, depends: List[(Setting[?], Any)] = Nil, - propertyClass: Option[Class[?]] = None)(private[Settings] val idx: Int) { + propertyClass: Option[Class[?]] = None, + )(private[Settings] val idx: Int) { private var changed: Boolean = false @@ -265,6 +266,9 @@ object Settings: def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: List[String], aliases: List[String] = Nil): Setting[List[String]] = publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases)) + def EnumSetting[E <: Enum](name: String, helpArg: String, descr: String, choices: Array[E], default: List[String], aliases: List[String] = Nil): Setting[List[String]] = + publish(Setting(name, descr, default, helpArg, Some(choices.toList.map(_.toString)), aliases = aliases)) + def IntSetting(name: String, descr: String, default: Int, aliases: List[String] = Nil): Setting[Int] = publish(Setting(name, descr, default, aliases = aliases)) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 0a57f82354e2..b2fabe44b920 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -11,6 +11,7 @@ import util.Chars._ import util.{SourcePosition, CharBuffer} import util.Spans.Span import config.Config +import config.LintWarning import Tokens._ import scala.annotation.{switch, tailrec} import scala.collection.mutable @@ -1488,7 +1489,7 @@ object Scanners { getFraction() // 1l is an acknowledged bad practice def lintel(): Unit = - if ch == 'l' then + if ch == 'l' && ctx.settings.isLintEnabled(LintWarning.longLit) then val msg = "Lowercase el for long is not recommended because it is easy to confuse with numeral 1; use uppercase L instead" report.deprecationWarning(msg, sourcePos(offset + litBuf.length)) // after int: 5e7f, 42L, 42.toDouble but not 42b. diff --git a/tests/neg/longlit.scala b/tests/neg/longlit.scala new file mode 100644 index 000000000000..b652602214d6 --- /dev/null +++ b/tests/neg/longlit.scala @@ -0,0 +1,11 @@ +// scalac: -Werror -Xlint:longLit,deprecation -deprecation + +trait DejectedLiterals: + + def bad = 1l // error + + def worse = 123l // error + + def worstest = 32l // error + +// Lowercase el for long is not recommended because it is easy to confuse with numeral 1; use uppercase L instead