Skip to content

Commit d105620

Browse files
committed
MultiChoiceSetting for language features
1 parent c20be38 commit d105620

File tree

6 files changed

+98
-22
lines changed

6 files changed

+98
-22
lines changed

Diff for: compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+21-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class ScalaSettings extends Settings.SettingGroup {
4242
val verbose: Setting[Boolean] = BooleanSetting("-verbose", "Output messages about what the compiler is doing.") withAbbreviation "--verbose"
4343
val version: Setting[Boolean] = BooleanSetting("-version", "Print product version and exit.") withAbbreviation "--version"
4444
val pageWidth: Setting[Int] = IntSetting("-pagewidth", "Set page width", 80) withAbbreviation "--page-width"
45-
val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.") withAbbreviation "--language"
45+
val language: Setting[List[String]] = MultiChoiceSetting("-language", "feature", "Enable one or more language features.", featureChoices) withAbbreviation "--language"
4646
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"
4747
val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.") withAbbreviation "--no-warnings"
4848
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 {
225225
val docSnapshot: Setting[Boolean] = BooleanSetting("-doc-snapshot", "Generate a documentation snapshot for the current Dotty version")
226226

227227
val wikiSyntax: Setting[Boolean] = BooleanSetting("-Xwiki-syntax", "Retains the Scala2 behavior of using Wiki Syntax in Scaladoc.")
228+
229+
// derive choices for -language from scala.language aka scalaShadowing.language
230+
private def featureChoices: List[String] =
231+
List(scalaShadowing.language.getClass, scalaShadowing.language.experimental.getClass).flatMap { cls =>
232+
import java.lang.reflect.{Member, Modifier}
233+
def isPublic(m: Member): Boolean = Modifier.isPublic(m.getModifiers)
234+
def goodFields(nm: String): List[String] =
235+
nm match {
236+
case "experimental" => Nil
237+
case s if s.contains("$") =>
238+
val adjusted = s.replace("$u002E", ".").replace("$minus", "-")
239+
if adjusted.contains("$") then Nil else adjusted :: Nil
240+
case _ => nm :: Nil
241+
}
242+
val isX = cls.getName.contains("experimental")
243+
def prefixed(nm: String) = if isX then s"experimental.$nm" else nm
244+
245+
cls.getDeclaredMethods.filter(isPublic).map(_.getName).map(prefixed)
246+
++ cls.getDeclaredFields.filter(isPublic).map(_.getName).flatMap(goodFields).map(prefixed)
247+
}
228248
}

Diff for: compiler/src/dotty/tools/dotc/config/Settings.scala

+20-20
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import annotation.tailrec
99
import collection.mutable.ArrayBuffer
1010
import language.existentials
1111
import reflect.ClassTag
12+
import scala.PartialFunction.cond
1213
import scala.util.{Success, Failure}
1314

1415
object Settings {
@@ -91,27 +92,16 @@ object Settings {
9192
def isMultivalue: Boolean = implicitly[ClassTag[T]] == ListTag
9293

9394
def legalChoices: String =
94-
choices match {
95+
choices match
9596
case xs if xs.isEmpty => ""
9697
case r: Range => s"${r.head}..${r.last}"
9798
case xs: List[?] => xs.toString
98-
}
9999

100100
def isLegal(arg: Any): Boolean =
101-
choices match {
102-
case xs if xs.isEmpty =>
103-
arg match {
104-
case _: T => true
105-
case _ => false
106-
}
107-
case r: Range =>
108-
arg match {
109-
case x: Int => r.head <= x && x <= r.last
110-
case _ => false
111-
}
112-
case xs: List[?] =>
113-
xs.contains(arg)
114-
}
101+
choices match
102+
case xs if xs.isEmpty => cond(arg) { case _: T => true }
103+
case r: Range => cond(arg) { case x: Int => r.head <= x && x <= r.last }
104+
case xs: List[?] => xs.contains(arg)
115105

116106
def tryToSet(state: ArgsSummary): ArgsSummary = {
117107
val ArgsSummary(sstate, arg :: args, errors, warnings) = state
@@ -132,14 +122,21 @@ object Settings {
132122
ArgsSummary(sstate, args, errors :+ msg, warnings)
133123
def missingArg =
134124
fail(s"missing argument for option $name", args)
135-
def doSet(argRest: String) = ((implicitly[ClassTag[T]], args): @unchecked) match {
125+
def doSet(argRest: String) = ((implicitly[ClassTag[T]], args): @unchecked) match
136126
case (BooleanTag, _) =>
137127
update(true, args)
138128
case (OptionTag, _) =>
139129
update(Some(propertyClass.get.getConstructor().newInstance()), args)
130+
case (ListTag, _) if argRest.isEmpty =>
131+
missingArg
132+
case (ListTag, _) if choices.nonEmpty =>
133+
val ss = argRest.split(",").toList
134+
ss.find(s => !choices.exists(c => c.asInstanceOf[List[String]].contains(s))) match {
135+
case Some(s) => fail(s"$s is not a valid choice for $name", args)
136+
case None => update(ss, args)
137+
}
140138
case (ListTag, _) =>
141-
if (argRest.isEmpty) missingArg
142-
else update((argRest split ",").toList, args)
139+
update((argRest split ",").toList, args)
143140
case (StringTag, _) if choices.nonEmpty && argRest.nonEmpty =>
144141
if (!choices.contains(argRest))
145142
fail(s"$arg is not a valid choice for $name", args)
@@ -177,7 +174,7 @@ object Settings {
177174
}
178175
case (_, Nil) =>
179176
missingArg
180-
}
177+
end doSet
181178

182179
if (prefix != "" && arg.startsWith(prefix))
183180
doSet(arg drop prefix.length)
@@ -278,6 +275,9 @@ object Settings {
278275
def IntSetting(name: String, descr: String, default: Int, range: Seq[Int] = Nil): Setting[Int] =
279276
publish(Setting(name, descr, default, choices = range))
280277

278+
def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String]): Setting[List[String]] =
279+
publish(Setting(name, descr, Nil, helpArg, choices.map(c => List(c))))
280+
281281
def MultiStringSetting(name: String, helpArg: String, descr: String): Setting[List[String]] =
282282
publish(Setting(name, descr, Nil, helpArg))
283283

Diff for: compiler/test/dotty/tools/dotc/CompilationTests.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ class CompilationTests {
158158
compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-noindent", "-Xfatal-warnings")),
159159
compileFile("tests/neg-custom-args/extmethods-tparams.scala", defaultOptions.and("-deprecation", "-Xfatal-warnings")),
160160
compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-source", "3.1", "-feature", "-Xfatal-warnings")),
161-
compileFile("tests/neg/i7575.scala", defaultOptions.withoutLanguageFeatures.and("-language:_")),
161+
compileFile("tests/neg/i7575.scala", defaultOptions.withoutLanguageFeatures),
162+
compileFile("tests/neg-custom-args/i7575c.scala", defaultOptions.and("-language:_")),
162163
compileFile("tests/neg-custom-args/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
163164
compileFile("tests/neg-custom-args/typeclass-derivation2.scala", defaultOptions.and("-Yerased-terms")),
164165
compileFile("tests/neg-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),

Diff for: compiler/test/dotty/tools/dotc/SettingsTests.scala

+24
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,28 @@ class SettingsTests {
3737
val options = Array("-encoding", "-d", outputDir.toString, source.toString)
3838
val reporter = Main.process(options, reporter = StoreReporter())
3939
assertEquals(1, reporter.errorCount)
40+
41+
//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)
42+
@Test def `t9901 Settings detects available language features`: Unit =
43+
val options = Array("-language:dynamics,strictEquality,experimental.macros")
44+
val reporter = Main.process(options, reporter = StoreReporter())
45+
assertEquals(0, reporter.errorCount)
46+
47+
@Test def `t9901 Settings detects one erroneous language feature`: Unit =
48+
val options = Array("-language:dynamics,awesome,experimental")
49+
val reporter = Main.process(options, reporter = StoreReporter())
50+
assertEquals(1, reporter.errorCount)
51+
assertEquals("awesome is not a valid choice for -language", reporter.allErrors.head.message)
52+
53+
@Test def `t9901 Settings excludes experimental`: Unit =
54+
val options = Array("-language:dynamics,experimental")
55+
val reporter = Main.process(options, reporter = StoreReporter())
56+
assertEquals(1, reporter.errorCount)
57+
assertEquals("experimental is not a valid choice for -language", reporter.allErrors.head.message)
58+
59+
@Test def `t9901 Settings includes funky strings`: Unit =
60+
val options = Array("-language:3.1-migration")
61+
val reporter = Main.process(options, reporter = StoreReporter())
62+
assertEquals(0, reporter.errorCount)
63+
4064
}

Diff for: compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala

+28
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,32 @@ class ScalaSettingsTests:
5151
assertTrue("Has the feature", set.contains("implicitConversions"))
5252
assertTrue("Has the feature", set.contains("dynamics"))
5353

54+
@Test def `A multichoice setting is multivalued`: Unit =
55+
class SUT extends SettingGroup:
56+
val language: Setting[List[String]] = MultiChoiceSetting("-language", "feature", "Enable creature features.", List("foo", "bar", "baz"))
57+
val sut = SUT()
58+
val args = tokenize("-language:foo,baz")
59+
val sumy = ArgsSummary(sut.defaultState, args, errors = Nil, warnings = Nil)
60+
val res = sut.processArguments(sumy, processAll = true, skipped = Nil)
61+
val set = sut.language.valueIn(res.sstate)
62+
assertEquals(1, args.length)
63+
assertTrue("No warnings!", res.warnings.isEmpty)
64+
assertTrue("No errors!", res.errors.isEmpty)
65+
assertTrue("Has the feature", set.contains("foo"))
66+
assertTrue("Has the feature", set.contains("baz"))
67+
68+
@Test def `A multichoice setting is restricted`: Unit =
69+
class SUT extends SettingGroup:
70+
val language: Setting[List[String]] = MultiChoiceSetting("-language", "feature", "Enable creature features.", List("foo", "bar", "baz"))
71+
val sut = SUT()
72+
val args = tokenize("-language:foo,bah")
73+
val sumy = ArgsSummary(sut.defaultState, args, errors = Nil, warnings = Nil)
74+
val res = sut.processArguments(sumy, processAll = true, skipped = Nil)
75+
val set = sut.language.valueIn(res.sstate)
76+
assertEquals(1, args.length)
77+
assertTrue("No warnings!", res.warnings.isEmpty)
78+
assertEquals(1, res.errors.size)
79+
assertFalse("Has the foo feature", set.contains("foo"))
80+
assertFalse("Has the bah feature", set.contains("bah"))
81+
5482
end ScalaSettingsTests

Diff for: tests/neg-custom-args/i7575c.scala

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// tested with -language:_, which is illegal
2+
// nopos-error
3+
class C

0 commit comments

Comments
 (0)