diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index a57b2ee30ac9..0617f6238285 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -42,7 +42,7 @@ class ScalaSettings extends Settings.SettingGroup { val verbose: Setting[Boolean] = BooleanSetting("-verbose", "Output messages about what the compiler is doing.") withAbbreviation "--verbose" val version: Setting[Boolean] = BooleanSetting("-version", "Print product version and exit.") withAbbreviation "--version" val pageWidth: Setting[Int] = IntSetting("-pagewidth", "Set page width", 80) withAbbreviation "--page-width" - val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.") withAbbreviation "--language" + val language: Setting[List[String]] = MultiChoiceSetting("-language", "feature", "Enable one or more language features.", featureChoices) withAbbreviation "--language" val rewrite: Setting[Option[Rewrites]] = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with a `...-migration` source version, rewrites sources to migrate to new version.") withAbbreviation "--rewrite" val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.") withAbbreviation "--no-warnings" val fromTasty: Setting[Boolean] = BooleanSetting("-from-tasty", "Compile classes from tasty files. The arguments are .tasty or .jar files.") withAbbreviation "--from-tasty" @@ -225,4 +225,24 @@ class ScalaSettings extends Settings.SettingGroup { val docSnapshot: Setting[Boolean] = BooleanSetting("-doc-snapshot", "Generate a documentation snapshot for the current Dotty version") val wikiSyntax: Setting[Boolean] = BooleanSetting("-Xwiki-syntax", "Retains the Scala2 behavior of using Wiki Syntax in Scaladoc.") + + // derive choices for -language from scala.language aka scalaShadowing.language + private def featureChoices: List[String] = + List(scalaShadowing.language.getClass, scalaShadowing.language.experimental.getClass).flatMap { cls => + import java.lang.reflect.{Member, Modifier} + def isPublic(m: Member): Boolean = Modifier.isPublic(m.getModifiers) + def goodFields(nm: String): List[String] = + nm match { + case "experimental" => Nil + case s if s.contains("$") => + val adjusted = s.replace("$u002E", ".").replace("$minus", "-") + if adjusted.contains("$") then Nil else adjusted :: Nil + case _ => nm :: Nil + } + val isX = cls.getName.contains("experimental") + def prefixed(nm: String) = if isX then s"experimental.$nm" else nm + + cls.getDeclaredMethods.filter(isPublic).map(_.getName).map(prefixed) + ++ cls.getDeclaredFields.filter(isPublic).map(_.getName).flatMap(goodFields).map(prefixed) + } } diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index a351921e8e64..df8886a252ba 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -9,6 +9,7 @@ import annotation.tailrec import collection.mutable.ArrayBuffer import language.existentials import reflect.ClassTag +import scala.PartialFunction.cond import scala.util.{Success, Failure} object Settings { @@ -91,27 +92,16 @@ object Settings { def isMultivalue: Boolean = implicitly[ClassTag[T]] == ListTag def legalChoices: String = - choices match { + choices match case xs if xs.isEmpty => "" case r: Range => s"${r.head}..${r.last}" case xs: List[?] => xs.toString - } def isLegal(arg: Any): Boolean = - choices match { - case xs if xs.isEmpty => - arg match { - case _: T => true - case _ => false - } - case r: Range => - arg match { - case x: Int => r.head <= x && x <= r.last - case _ => false - } - case xs: List[?] => - xs.contains(arg) - } + choices match + case xs if xs.isEmpty => cond(arg) { case _: T => true } + case r: Range => cond(arg) { case x: Int => r.head <= x && x <= r.last } + case xs: List[?] => xs.contains(arg) def tryToSet(state: ArgsSummary): ArgsSummary = { val ArgsSummary(sstate, arg :: args, errors, warnings) = state @@ -132,14 +122,21 @@ object Settings { ArgsSummary(sstate, args, errors :+ msg, warnings) def missingArg = fail(s"missing argument for option $name", args) - def doSet(argRest: String) = ((implicitly[ClassTag[T]], args): @unchecked) match { + def doSet(argRest: String) = ((implicitly[ClassTag[T]], args): @unchecked) match case (BooleanTag, _) => update(true, args) case (OptionTag, _) => update(Some(propertyClass.get.getConstructor().newInstance()), args) + case (ListTag, _) if argRest.isEmpty => + missingArg + case (ListTag, _) if choices.nonEmpty => + val ss = argRest.split(",").toList + ss.find(s => !choices.exists(c => c.asInstanceOf[List[String]].contains(s))) match { + case Some(s) => fail(s"$s is not a valid choice for $name", args) + case None => update(ss, args) + } case (ListTag, _) => - if (argRest.isEmpty) missingArg - else update((argRest split ",").toList, args) + update((argRest split ",").toList, args) case (StringTag, _) if choices.nonEmpty && argRest.nonEmpty => if (!choices.contains(argRest)) fail(s"$arg is not a valid choice for $name", args) @@ -177,7 +174,7 @@ object Settings { } case (_, Nil) => missingArg - } + end doSet if (prefix != "" && arg.startsWith(prefix)) doSet(arg drop prefix.length) @@ -278,6 +275,9 @@ object Settings { def IntSetting(name: String, descr: String, default: Int, range: Seq[Int] = Nil): Setting[Int] = publish(Setting(name, descr, default, choices = range)) + def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String]): Setting[List[String]] = + publish(Setting(name, descr, Nil, helpArg, choices.map(c => List(c)))) + def MultiStringSetting(name: String, helpArg: String, descr: String): Setting[List[String]] = publish(Setting(name, descr, Nil, helpArg)) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index c7813a86e243..fd619b079e3e 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -158,7 +158,8 @@ class CompilationTests { compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-noindent", "-Xfatal-warnings")), compileFile("tests/neg-custom-args/extmethods-tparams.scala", defaultOptions.and("-deprecation", "-Xfatal-warnings")), compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-source", "3.1", "-feature", "-Xfatal-warnings")), - compileFile("tests/neg/i7575.scala", defaultOptions.withoutLanguageFeatures.and("-language:_")), + compileFile("tests/neg/i7575.scala", defaultOptions.withoutLanguageFeatures), + compileFile("tests/neg-custom-args/i7575c.scala", defaultOptions.and("-language:_")), compileFile("tests/neg-custom-args/kind-projector.scala", defaultOptions.and("-Ykind-projector")), compileFile("tests/neg-custom-args/typeclass-derivation2.scala", defaultOptions.and("-Yerased-terms")), compileFile("tests/neg-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"), diff --git a/compiler/test/dotty/tools/dotc/SettingsTests.scala b/compiler/test/dotty/tools/dotc/SettingsTests.scala index f0e1699358b2..969264abc211 100644 --- a/compiler/test/dotty/tools/dotc/SettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/SettingsTests.scala @@ -37,4 +37,28 @@ class SettingsTests { val options = Array("-encoding", "-d", outputDir.toString, source.toString) val reporter = Main.process(options, reporter = StoreReporter()) assertEquals(1, reporter.errorCount) + + //List(dynamics, existentials, higherKinds, implicitConversions, postfixOps, reflectiveCalls, Scala2Compat, noAutoTupling, strictEquality, adhocExtensions, dependent, 3.0-migration, 3.0, 3.1-migration, 3.1, experimental.macros, experimental.dependent) + @Test def `t9901 Settings detects available language features`: Unit = + val options = Array("-language:dynamics,strictEquality,experimental.macros") + val reporter = Main.process(options, reporter = StoreReporter()) + assertEquals(0, reporter.errorCount) + + @Test def `t9901 Settings detects one erroneous language feature`: Unit = + val options = Array("-language:dynamics,awesome,experimental") + val reporter = Main.process(options, reporter = StoreReporter()) + assertEquals(1, reporter.errorCount) + assertEquals("awesome is not a valid choice for -language", reporter.allErrors.head.message) + + @Test def `t9901 Settings excludes experimental`: Unit = + val options = Array("-language:dynamics,experimental") + val reporter = Main.process(options, reporter = StoreReporter()) + assertEquals(1, reporter.errorCount) + assertEquals("experimental is not a valid choice for -language", reporter.allErrors.head.message) + + @Test def `t9901 Settings includes funky strings`: Unit = + val options = Array("-language:3.1-migration") + val reporter = Main.process(options, reporter = StoreReporter()) + assertEquals(0, reporter.errorCount) + } diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index 60c4bfa57fc1..d1fccfc6c286 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -51,4 +51,32 @@ class ScalaSettingsTests: assertTrue("Has the feature", set.contains("implicitConversions")) assertTrue("Has the feature", set.contains("dynamics")) + @Test def `A multichoice setting is multivalued`: Unit = + class SUT extends SettingGroup: + val language: Setting[List[String]] = MultiChoiceSetting("-language", "feature", "Enable creature features.", List("foo", "bar", "baz")) + val sut = SUT() + val args = tokenize("-language:foo,baz") + val sumy = ArgsSummary(sut.defaultState, args, errors = Nil, warnings = Nil) + val res = sut.processArguments(sumy, processAll = true, skipped = Nil) + val set = sut.language.valueIn(res.sstate) + assertEquals(1, args.length) + assertTrue("No warnings!", res.warnings.isEmpty) + assertTrue("No errors!", res.errors.isEmpty) + assertTrue("Has the feature", set.contains("foo")) + assertTrue("Has the feature", set.contains("baz")) + + @Test def `A multichoice setting is restricted`: Unit = + class SUT extends SettingGroup: + val language: Setting[List[String]] = MultiChoiceSetting("-language", "feature", "Enable creature features.", List("foo", "bar", "baz")) + val sut = SUT() + val args = tokenize("-language:foo,bah") + val sumy = ArgsSummary(sut.defaultState, args, errors = Nil, warnings = Nil) + val res = sut.processArguments(sumy, processAll = true, skipped = Nil) + val set = sut.language.valueIn(res.sstate) + assertEquals(1, args.length) + assertTrue("No warnings!", res.warnings.isEmpty) + assertEquals(1, res.errors.size) + assertFalse("Has the foo feature", set.contains("foo")) + assertFalse("Has the bah feature", set.contains("bah")) + end ScalaSettingsTests diff --git a/tests/neg-custom-args/i7575c.scala b/tests/neg-custom-args/i7575c.scala new file mode 100644 index 000000000000..ed5b51df6ec8 --- /dev/null +++ b/tests/neg-custom-args/i7575c.scala @@ -0,0 +1,3 @@ +// tested with -language:_, which is illegal +// nopos-error +class C