Skip to content

MultiChoiceSetting for language features #9915

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
40 changes: 20 additions & 20 deletions compiler/src/dotty/tools/dotc/config/Settings.scala
Original file line number Diff line number Diff line change
@@ -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))

3 changes: 2 additions & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
@@ -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"),
24 changes: 24 additions & 0 deletions compiler/test/dotty/tools/dotc/SettingsTests.scala
Original file line number Diff line number Diff line change
@@ -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)

}
28 changes: 28 additions & 0 deletions compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions tests/neg-custom-args/i7575c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// tested with -language:_, which is illegal
// nopos-error
class C