@@ -7,12 +7,12 @@ import dotty.tools.io.{AbstractFile, Directory, JarArchive, PlainDirectory}
77
88import annotation .tailrec
99import annotation .internal .unshared
10- import collection .mutable .ArrayBuffer
11- import collection .mutable
10+ import collection .mutable , mutable .ArrayBuffer
1211import reflect .ClassTag
1312import scala .util .{Success , Failure }
14- import dotty .tools .dotc .config .Settings .Setting .ChoiceWithHelp
15- import dotty .tools .dotc .util .chaining .*
13+
14+ import config .Settings .Setting .ChoiceWithHelp
15+ import util .chaining .*
1616
1717object Settings :
1818
@@ -55,27 +55,37 @@ object Settings:
5555 case class ArgsSummary (sstate : SettingsState , arguments : List [String ], errors : List [String ], warnings : List [String ])
5656
5757 extension (summary : ArgsSummary )
58- /** Add an error and drop an argument. If erroring on bad `-o junk`, the next arg is collected by processArgs. */
58+ /** Error and update arguments */
59+ def fail (msg : String , arguments : List [String ]): ArgsSummary =
60+ summary.copy(arguments = arguments, errors = summary.errors :+ msg)
61+ /** Error without updating arguments */
5962 def fail (msg : String ): ArgsSummary =
60- summary.copy(arguments = summary.arguments.drop(1 ), errors = summary.errors :+ msg)
61- /** Warn without skipping. */
63+ summary.copy(errors = summary.errors :+ msg)
64+ /** Warn and update arguments */
65+ def warn (msg : String , arguments : List [String ]): ArgsSummary =
66+ summary.copy(arguments = arguments, warnings = summary.warnings :+ msg)
67+ /** Warn without updating arguments */
6268 def warn (msg : String ): ArgsSummary =
6369 summary.copy(warnings = summary.warnings :+ msg)
64- /** Warn and skip. */
65- def skip ( msg : String ): ArgsSummary =
66- summary.copy(arguments = summary.arguments.drop( 1 ), warnings = summary.warnings :+ msg )
67- /** Skip without warning. */
68- def skip ( ): ArgsSummary = summary.copy(arguments = summary.arguments.drop( 1 ))
69- def updated ( sstate : SettingsState ) : ArgsSummary = summary.copy(sstate = sstate )
70+ /** Update state and shift arguments */
71+ def updated ( sstate : SettingsState , arguments : List [ String ] ): ArgsSummary =
72+ summary.copy(sstate = sstate, arguments = arguments )
73+ /** Only shift arguments */
74+ def shifted ( arguments : List [ String ] ): ArgsSummary =
75+ summary.copy(arguments = arguments )
7076 /** Warn and skip, prepending the alternative arguments as substitution. */
71- def deprecated (msg : String , altArgs : List [String ] = Nil ): ArgsSummary =
72- summary.copy(arguments = altArgs ++ summary.arguments.drop( 1 ) , warnings = summary.warnings :+ msg)
77+ def deprecated (msg : String , altArgs : List [String ], args : List [ String ] ): ArgsSummary =
78+ summary.copy(arguments = altArgs ++ args , warnings = summary.warnings :+ msg)
7379
7480 @ unshared
7581 val settingCharacters = " [a-zA-Z0-9_\\ -]*" .r
7682 def validateSettingString (name : String ): Unit =
7783 assert(settingCharacters.matches(name), s " Setting string $name contains invalid characters " )
7884
85+ val validTags = List (BooleanTag , IntTag , StringTag , ListTag , VersionTag , OptionTag , OutputTag )
86+ def validateSettingTag (ct : ClassTag [? ]): Unit =
87+ assert(validTags.contains(ct), s " Unsupported option value $ct" )
88+
7989 /** List of setting-value pairs that are required for another setting to be valid.
8090 * For example, `s = Setting(..., depends = List(YprofileEnabled -> true))`
8191 * means that `s` requires `YprofileEnabled` to be set to `true`.
@@ -105,6 +115,8 @@ object Settings:
105115 aliases.foreach(validateSettingString)
106116 assert(name.startsWith(s " - ${category.prefixLetter}" ), s " Setting $name does not start with category - $category" )
107117 assert(legacyArgs || ! choices.exists(_.contains(" " )), s " Empty string is not supported as a choice for setting $name" )
118+ validateSettingTag(ct)
119+
108120 // Without the following assertion, it would be easy to mistakenly try to pass a file to a setting that ignores invalid args.
109121 // Example: -opt Main.scala would be interpreted as -opt:Main.scala, and the source file would be ignored.
110122 assert(! (ct == ListTag && ignoreInvalidArgs), s " Ignoring invalid args is not supported for multivalue settings: $name" )
@@ -132,7 +144,7 @@ object Settings:
132144
133145 // Updates the state from the next arg if this setting is applicable.
134146 def tryToSet (state : ArgsSummary ): ArgsSummary =
135- val ArgsSummary (sstate, arg :: args, errors, warnings ) = state : @ unchecked
147+ val ArgsSummary (sstate, arg :: args, _, _ ) = state : @ unchecked
136148 def changed = sstate.wasChanged(idx)
137149
138150 /** Updates the value in state.
@@ -149,42 +161,32 @@ object Settings:
149161 val altArg1 =
150162 if altArg.isEmpty then List (replacedBy)
151163 else List (s " $replacedBy: $altArg" )
152- state.deprecated(deprecatedMsg, altArg1)
164+ state.deprecated(deprecatedMsg, altArg1, args) // retry with reconstructed arg
153165 case Some (Deprecation (msg, _)) =>
154- state.deprecated(s " Option $name is deprecated: $msg" )
166+ state.updated(updateIn(sstate, value), args) // allow but warn
167+ .warn(s " Option $name is deprecated: $msg" )
155168 case None =>
156- ArgsSummary (updateIn(sstate, value), args, errors, warnings )
169+ state.updated (updateIn(sstate, value), args)
157170 end update
158171
159- def ignoreValue (args : List [String ]): ArgsSummary =
160- ArgsSummary (sstate, args, errors, warnings)
161-
162- def missingArg =
163- val msg = s " missing argument for option $name"
164- if ignoreInvalidArgs then state.warn(msg + " , the tag was ignored" ) else state.fail(msg)
165-
166- def invalidChoices (invalid : List [String ]) =
167- val msg = s " invalid choice(s) for $name: ${invalid.mkString(" ," )}"
168- if ignoreInvalidArgs then state.warn(msg + " , the tag was ignored" ) else state.fail(msg)
169-
170172 def setBoolean (argValue : String , args : List [String ]) =
171173 def checkAndSet (v : Boolean ) =
172174 val dubious = changed && v != valueIn(sstate).asInstanceOf [Boolean ]
173- def updated = update(v, argValue, args)
174175 if dubious then
175176 if preferPrevious then
176- state.warn(s " Conflicting value for Boolean flag $name" )
177+ state.warn(s " Ignoring conflicting value for Boolean flag $name" , args )
177178 else
178- updated.warn(s " Conflicting value for Boolean flag $name" ) // error instead?
179- else updated
179+ update(v, argValue, args).warn(s " Conflicting value for Boolean flag $name" )
180+ else
181+ update(v, argValue, args)
180182 if argValue.isEmpty || argValue.equalsIgnoreCase(" true" ) then checkAndSet(true )
181183 else if argValue.equalsIgnoreCase(" false" ) then checkAndSet(false )
182- else state.fail(s " $argValue is not a valid choice for Boolean flag $name" )
184+ else state.fail(s " $argValue is not a valid choice for Boolean flag $name" , args )
183185
184186 def setString (argValue : String , args : List [String ]) =
185187 choices match
186188 case Some (choices) if ! choices.contains(argValue) =>
187- state.fail(s " $argValue is not a valid choice for $name" )
189+ state.fail(s " $argValue is not a valid choice for $name" , args )
188190 case _ =>
189191 if changed && argValue != valueIn(sstate).asInstanceOf [String ] then
190192 update(argValue, argValue, args).warn(s " Option $name was updated " )
@@ -195,32 +197,35 @@ object Settings:
195197 argValue.toIntOption.map: intValue =>
196198 choices match
197199 case Some (r : Range ) if intValue < r.head || r.last < intValue =>
198- state.fail(s " $argValue is out of legal range ${r.head}.. ${r.last} for $name" )
200+ state.fail(s " $argValue is out of legal range ${r.head}.. ${r.last} for $name" , args )
199201 case Some (choices) if ! choices.contains(intValue) =>
200- state.fail(s " $argValue is not a valid choice for $name" )
202+ state.fail(s " $argValue is not a valid choice for $name" , args )
201203 case _ =>
202204 val dubious = changed && intValue != valueIn(sstate).asInstanceOf [Int ]
203205 val updated = update(intValue, argValue, args)
204206 if dubious then updated.warn(s " Option $name was updated " ) else updated
205207 .getOrElse:
206- state.fail(s " $argValue is not an integer argument for $name" )
208+ state.fail(s " $argValue is not an integer argument for $name" , args )
207209
208210 def setOutput (arg : String , args : List [String ]) =
209211 val path = Directory (arg)
210212 val isJar = path.ext.isJar
211213 if (! isJar && ! path.isDirectory) then
212- state.fail(s " ' $arg' does not exist or is not a directory or .jar file " )
214+ state.fail(s " ' $arg' does not exist or is not a directory or .jar file " , args )
213215 else
214216 /* Side effect, do not change this method to evaluate eagerly */
215217 def output = if (isJar) JarArchive .create(path) else new PlainDirectory (path)
216- if changed && output != valueIn(sstate).asInstanceOf [AbstractFile ] then
217- update(output, arg, args).skip(s " Option $name was updated " )
218- else
219- update(output, arg, args)
218+ val dubious = changed && output != valueIn(sstate).asInstanceOf [AbstractFile ]
219+ val updated = update(output, arg, args)
220+ if dubious then updated.warn(s " Option $name was updated " ) else updated
220221
221222 // argRest is the remainder of -foo:bar if any. This setting will receive a value from argRest or args.head.
222223 // useArg means use argRest even if empty.
223224 def doSet (argRest : String , useArg : Boolean ): ArgsSummary =
225+ def missingArg =
226+ val msg = s " missing argument for option $name"
227+ if ignoreInvalidArgs then state.warn(s " $msg, the tag was ignored " , args) else state.fail(msg, args)
228+
224229 if ct == BooleanTag then setBoolean(argRest, args)
225230 else if ct == OptionTag then update(Some (propertyClass.get.getConstructor().newInstance()), " " , args)
226231 else
@@ -233,24 +238,20 @@ object Settings:
233238 else return missingArg
234239 def doSet (arg : String , args : List [String ]) =
235240 ct match
236- case _ if preferPrevious && changed => state.skip (s " Ignoring update of option $name" )
241+ case _ if preferPrevious && changed => state.warn (s " Ignoring update of option $name" , args )
237242 case ListTag => setMultivalue(arg, args)
238243 case StringTag => setString(arg, args)
239- case OutputTag =>
240- if changed && preferPrevious then
241- state.skip() // do not risk side effects e.g. overwriting a jar
242- else
243- setOutput(arg, args)
244+ case OutputTag => setOutput(arg, args)
244245 case IntTag => setInt(arg, args)
245246 case VersionTag => setVersion(arg, args)
246- case _ => missingArg
247+ case _ => state.fail( s " unknown $ct " , args)
247248 doSet(arg1, args1)
248249 end doSet
249250
250251 def setVersion (arg : String , args : List [String ]) =
251252 ScalaVersion .parse(arg) match
252253 case Success (v) => update(v, arg, args)
253- case Failure (e) => state.fail(e.getMessage)
254+ case Failure (e) => state.fail(e.getMessage, args )
254255
255256 def setMultivalue (arg : String , args : List [String ]) =
256257 val split = arg.split(" ," ).toList
@@ -262,6 +263,10 @@ object Settings:
262263 else
263264 actual
264265 update(updated, arg, args)
266+ def invalidChoices (invalid : List [String ]) =
267+ val msg = s " invalid choice(s) for $name: ${invalid.mkString(" ," )}"
268+ if ignoreInvalidArgs then state.warn(s " $msg, the tag was ignored " , args) else state.fail(msg, args)
269+
265270 choices match
266271 case Some (choices) =>
267272 split.partition(choices.contains) match
@@ -274,6 +279,7 @@ object Settings:
274279 case invalid => invalidChoices(invalid)
275280 case none => invalidChoices(invalid)
276281 case none => setOrUpdate(split)
282+ end setMultivalue
277283
278284 def matches : Boolean =
279285 val name = arg.takeWhile(_ != ':' )
@@ -282,7 +288,7 @@ object Settings:
282288 if matches then
283289 deprecation match
284290 case Some (Deprecation (msg, _)) if ignoreInvalidArgs => // a special case for Xlint
285- state.deprecated (s " Option $name is deprecated: $msg" )
291+ state.warn (s " Option $name is deprecated: $msg" , args )
286292 case _ =>
287293 prefix match
288294 case Some (prefix) =>
@@ -299,25 +305,6 @@ object Settings:
299305 end tryToSet
300306 end Setting
301307
302- /**
303- * Class used for deprecating purposes.
304- * It contains all necessary information to deprecate given option.
305- * Scala Settings are considered deprecated when this object is present at their creation site.
306- *
307- * @param msg deprecation message that will be displayed in following format: s"Option $name is deprecated: $msg"
308- * @param replacedBy option that is substituting current option
309- */
310- case class Deprecation (
311- msg : String ,
312- replacedBy : Option [String ] = None ,
313- )
314-
315- object Deprecation :
316- def renamed (replacement : String ) = Some (Deprecation (s " Use $replacement instead. " , Some (replacement)))
317- def removed (removedVersion : Option [String ] = None ) =
318- val msg = removedVersion.map(" in " + _).getOrElse(" ." )
319- Some (Deprecation (s " Scheduled for removal $msg" , None ))
320-
321308 object Setting :
322309 extension [T ](setting : Setting [T ])
323310 def value (using Context ): T = setting.valueIn(ctx.settingsState)
@@ -341,6 +328,25 @@ object Settings:
341328 Setting (RootSetting , name, " internal" , default = value)(- 1 )
342329 end Setting
343330
331+ /**
332+ * Class used for deprecating purposes.
333+ * It contains all necessary information to deprecate given option.
334+ * Scala Settings are considered deprecated when this object is present at their creation site.
335+ *
336+ * @param msg deprecation message that will be displayed in following format: s"Option $name is deprecated: $msg"
337+ * @param replacedBy option that is substituting current option
338+ */
339+ case class Deprecation (
340+ msg : String ,
341+ replacedBy : Option [String ] = None ,
342+ )
343+
344+ object Deprecation :
345+ def renamed (replacement : String ) = Some (Deprecation (s " Use $replacement instead. " , Some (replacement)))
346+ def removed (removedVersion : Option [String ] = None ) =
347+ val msg = removedVersion.map(" in " + _).getOrElse(" ." )
348+ Some (Deprecation (s " Scheduled for removal $msg" , None ))
349+
344350 class SettingGroup :
345351
346352 @ unshared
@@ -361,7 +367,7 @@ object Settings:
361367 private def checkDependenciesOfSetting (state : ArgsSummary , setting : Setting [? ]) =
362368 setting.depends.foldLeft(state): (s, dep) =>
363369 val (depSetting, reqValue) = dep
364- if (depSetting.valueIn(state .sstate) == reqValue) s
370+ if (depSetting.valueIn(s .sstate) == reqValue) s
365371 else s.fail(s " incomplete option ${setting.name} (requires ${depSetting.name}) " )
366372
367373 /** Iterates over the arguments applying them to settings where applicable.
@@ -381,26 +387,24 @@ object Settings:
381387 */
382388 @ tailrec
383389 final def processArguments (state : ArgsSummary , processAll : Boolean , skipped : List [String ]): ArgsSummary =
384- def stateWithArgs (args : List [String ]) = state.copy(arguments = args)
385390 state.arguments match
386391 case Nil =>
387- checkDependencies(stateWithArgs (skipped))
392+ checkDependencies(state.shifted (skipped))
388393 case " --" :: args =>
389- checkDependencies(stateWithArgs (skipped ++ args))
390- case arg :: _ if arg.startsWith(" -" ) =>
394+ checkDependencies(state.shifted (skipped ++ args))
395+ case arg :: args if arg.startsWith(" -" ) =>
391396 // find a setting to consume the next arg
392397 @ tailrec def loop (settings : List [Setting [? ]]): ArgsSummary = settings match
393398 case setting :: settings =>
394399 val state1 = setting.tryToSet(state)
395400 if state1 ne state then state1
396401 else loop(settings)
397402 case Nil =>
398- state.skip (s " bad option ' $arg' was ignored " )
403+ state.warn (s " bad option ' $arg' was ignored " , args )
399404 processArguments(loop(allSettings.toList), processAll, skipped)
400405 case arg :: args =>
401- if processAll then processArguments(stateWithArgs (args), processAll, skipped :+ arg)
406+ if processAll then processArguments(state.shifted (args), processAll, skipped :+ arg)
402407 else state
403- end processArguments
404408
405409 def processArguments (arguments : List [String ], processAll : Boolean , settingsState : SettingsState = defaultState): ArgsSummary =
406410 processArguments(ArgsSummary (settingsState, arguments, errors = Nil , warnings = Nil ), processAll, skipped = Nil )
0 commit comments