Skip to content

Commit 12ff02f

Browse files
authored
Merge pull request #10804 from prolativ/validate-settings-with-choices
Fix #10756: Validate enumerative scalac settings
2 parents 46290f9 + 8b54d45 commit 12ff02f

File tree

2 files changed

+94
-30
lines changed

2 files changed

+94
-30
lines changed

compiler/src/dotty/tools/dotc/config/Settings.scala

+24-30
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ object Settings {
5959
description: String,
6060
default: T,
6161
helpArg: String = "",
62-
choices: Seq[T] = Nil,
62+
choices: Option[Seq[T]] = None,
6363
prefix: String = "",
6464
aliases: List[String] = Nil,
6565
depends: List[(Setting[?], Any)] = Nil,
@@ -92,25 +92,10 @@ object Settings {
9292

9393
def legalChoices: String =
9494
choices match {
95-
case xs if xs.isEmpty => ""
96-
case r: Range => s"${r.head}..${r.last}"
97-
case xs: List[?] => xs.toString
98-
}
99-
100-
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)
95+
case Some(xs) if xs.isEmpty => ""
96+
case Some(r: Range) => s"${r.head}..${r.last}"
97+
case Some(xs) => xs.mkString(", ")
98+
case None => ""
11499
}
115100

116101
def tryToSet(state: ArgsSummary): ArgsSummary = {
@@ -132,6 +117,12 @@ object Settings {
132117
ArgsSummary(sstate, args, errors :+ msg, warnings)
133118
def missingArg =
134119
fail(s"missing argument for option $name", args)
120+
def setString(argValue: String, args: List[String]) =
121+
choices match
122+
case Some(xs) if !xs.contains(argValue) =>
123+
fail(s"$argValue is not a valid choice for $name", args)
124+
case _ =>
125+
update(argValue, args)
135126
def doSet(argRest: String) = ((implicitly[ClassTag[T]], args): @unchecked) match {
136127
case (BooleanTag, _) =>
137128
update(true, args)
@@ -140,13 +131,11 @@ object Settings {
140131
case (ListTag, _) =>
141132
if (argRest.isEmpty) missingArg
142133
else update((argRest split ",").toList, args)
143-
case (StringTag, _) if choices.nonEmpty && argRest.nonEmpty =>
144-
if (!choices.contains(argRest))
145-
fail(s"$arg is not a valid choice for $name", args)
146-
else update(argRest, args)
134+
case (StringTag, _) if argRest.nonEmpty =>
135+
setString(argRest, args)
147136
case (StringTag, arg2 :: args2) =>
148137
if (arg2 startsWith "-") missingArg
149-
else update(arg2, args2)
138+
else setString(arg2, args2)
150139
case (OutputTag, arg :: args) =>
151140
val path = Directory(arg)
152141
val isJar = path.extension == "jar"
@@ -160,8 +149,10 @@ object Settings {
160149
try {
161150
val x = arg2.toInt
162151
choices match {
163-
case r: Range if x < r.head || r.last < x =>
164-
fail(s"$arg2 is out of legal range $legalChoices for $name", args2)
152+
case Some(r: Range) if x < r.head || r.last < x =>
153+
fail(s"$arg2 is out of legal range ${r.head}..${r.last} for $name", args2)
154+
case Some(xs) if !xs.contains(x) =>
155+
fail(s"$arg2 is not a valid choice for $name", args)
165156
case _ =>
166157
update(x, args2)
167158
}
@@ -273,10 +264,13 @@ object Settings {
273264
publish(Setting(name, descr, default, helpArg, aliases = aliases))
274265

275266
def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String): Setting[String] =
276-
publish(Setting(name, descr, default, helpArg, choices))
267+
publish(Setting(name, descr, default, helpArg, Some(choices)))
268+
269+
def IntSetting(name: String, descr: String, default: Int): Setting[Int] =
270+
publish(Setting(name, descr, default))
277271

278-
def IntSetting(name: String, descr: String, default: Int, range: Seq[Int] = Nil): Setting[Int] =
279-
publish(Setting(name, descr, default, choices = range))
272+
def IntChoiceSetting(name: String, descr: String, choices: Seq[Int], default: Int): Setting[Int] =
273+
publish(Setting(name, descr, default, choices = Some(choices)))
280274

281275
def MultiStringSetting(name: String, helpArg: String, descr: String): Setting[List[String]] =
282276
publish(Setting(name, descr, Nil, helpArg))

compiler/test/dotty/tools/dotc/SettingsTests.scala

+70
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package dotc
44
import reporting.StoreReporter
55
import vulpix.TestConfiguration
66

7+
import core.Contexts.{Context, ContextBase}
8+
import dotty.tools.dotc.config.Settings._
79
import dotty.tools.vulpix.TestConfiguration.mkClasspath
810

911
import java.nio.file._
@@ -37,4 +39,72 @@ class SettingsTests {
3739
val options = Array("-encoding", "-d", outputDir.toString, source.toString)
3840
val reporter = Main.process(options, reporter = StoreReporter())
3941
assertEquals(1, reporter.errorCount)
42+
43+
@Test def acceptUnconstrained: Unit =
44+
object Settings extends SettingGroup:
45+
val foo = StringSetting("-foo", "foo", "Foo", "a")
46+
val bar = IntSetting("-bar", "Bar", 0)
47+
48+
inContext {
49+
val args = List("-foo", "b", "-bar", "1")
50+
val summary = Settings.processArguments(args, true)
51+
assertTrue(summary.errors.isEmpty)
52+
assertEquals("b", Settings.foo.value)
53+
assertEquals(1, Settings.bar.value)
54+
}
55+
56+
@Test def validateChoices: Unit =
57+
object Settings extends SettingGroup:
58+
val foo = ChoiceSetting("-foo", "foo", "Foo", List("a", "b"), "a")
59+
val bar = IntChoiceSetting("-bar", "Bar", List(0, 1, 2), 0)
60+
val baz = IntChoiceSetting("-baz", "Baz", 0 to 10, 10)
61+
62+
val quux = ChoiceSetting("-quux", "quux", "Quux", List(), "")
63+
val quuz = IntChoiceSetting("-quuz", "Quuz", List(), 0)
64+
65+
inContext {
66+
val args = List("-foo", "b", "-bar", "1", "-baz", "5")
67+
val summary = Settings.processArguments(args, true)
68+
assertTrue(summary.errors.isEmpty)
69+
assertEquals("b", Settings.foo.value)
70+
assertEquals(1, Settings.bar.value)
71+
assertEquals(5, Settings.baz.value)
72+
}
73+
74+
inContext {
75+
val args = List("-foo:b")
76+
val summary = Settings.processArguments(args, true)
77+
assertTrue(summary.errors.isEmpty)
78+
assertEquals("b", Settings.foo.value)
79+
}
80+
81+
inContext {
82+
val args = List("-foo", "c", "-bar", "3", "-baz", "-1")
83+
val summary = Settings.processArguments(args, true)
84+
val expectedErrors = List(
85+
"c is not a valid choice for -foo",
86+
"3 is not a valid choice for -bar",
87+
"-1 is out of legal range 0..10 for -baz"
88+
)
89+
assertEquals(expectedErrors, summary.errors)
90+
}
91+
92+
inContext {
93+
val args = List("-foo:c")
94+
val summary = Settings.processArguments(args, true)
95+
val expectedErrors = List("c is not a valid choice for -foo")
96+
assertEquals(expectedErrors, summary.errors)
97+
}
98+
99+
inContext {
100+
val args = List("-quux", "a", "-quuz", "0")
101+
val summary = Settings.processArguments(args, true)
102+
val expectedErrors = List(
103+
"a is not a valid choice for -quux",
104+
"0 is not a valid choice for -quuz",
105+
)
106+
assertEquals(expectedErrors, summary.errors)
107+
}
108+
109+
private def inContext(f: Context ?=> Unit) = f(using (new ContextBase).initialCtx.fresh)
40110
}

0 commit comments

Comments
 (0)