Skip to content

Commit 2be34c3

Browse files
committed
Don't differentiate between - and -- prefixes in compiler options
1 parent e265749 commit 2be34c3

File tree

6 files changed

+104
-66
lines changed

6 files changed

+104
-66
lines changed

modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala

+9-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import scala.build.input.{ScalaCliInvokeData, SubCommand}
2121
import scala.build.internal.util.WarningMessages
2222
import scala.build.internal.{Constants, Runner}
2323
import scala.build.internals.{EnvVar, FeatureType}
24+
import scala.build.options.ScalacOpt.noDashPrefixes
2425
import scala.build.options.{BuildOptions, ScalacOpt, Scope}
2526
import scala.build.{Artifacts, Directories, Logger, Positioned, ReplArtifacts}
2627
import scala.cli.commands.default.LegacyScalaOptions
@@ -175,9 +176,13 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T],
175176
val logger = options.global.logging.logger
176177
sharedOptions(options).foreach { so =>
177178
val scalacOpts = so.scalacOptions.toScalacOptShadowingSeq
178-
if scalacOpts.keys.contains(ScalacOpt(YScriptRunnerOption)) then
179-
logger.message(
180-
LegacyScalaOptions.yScriptRunnerWarning(scalacOpts.getOption(YScriptRunnerOption))
179+
scalacOpts.keys
180+
.find(k =>
181+
k == ScalacOpt(s"-$YScriptRunnerOption") || k == ScalacOpt(s"--$YScriptRunnerOption")
182+
)
183+
.map(_.value)
184+
.foreach(k =>
185+
logger.message(LegacyScalaOptions.yScriptRunnerWarning(k, scalacOpts.getOption(k)))
181186
)
182187
}
183188
}
@@ -192,7 +197,7 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T],
192197
shared <- sharedOptions(options)
193198
scalacOptions = shared.scalacOptions
194199
updatedScalacOptions = scalacOptions.withScalacExtraOptions(shared.scalacExtra)
195-
if updatedScalacOptions.exists(ScalacOptions.ScalacPrintOptions)
200+
if updatedScalacOptions.map(_.noDashPrefixes).exists(ScalacOptions.ScalacPrintOptions)
196201
logger = shared.logger
197202
fixedBuildOptions = buildOptions.copy(scalaOptions =
198203
buildOptions.scalaOptions.copy(defaultScalaVersion = Some(ScalaCli.getDefaultScalaVersion))

modules/cli/src/main/scala/scala/cli/commands/default/LegacyScalaOptions.scala

+2-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import scala.cli.commands.default.LegacyScalaOptions.*
1111
import scala.cli.commands.package0.Package
1212
import scala.cli.commands.shared.HelpGroup
1313
import scala.cli.commands.shared.HelpMessages.PowerString
14-
import scala.cli.commands.shared.ScalacOptions.YScriptRunnerOption
1514
import scala.cli.commands.tags
1615

1716
/** Options covering backwards compatibility with the old scala runner.
@@ -168,7 +167,7 @@ object LegacyScalaOptions {
168167
implicit lazy val parser: Parser[LegacyScalaOptions] = Parser.derive
169168
implicit lazy val help: Help[LegacyScalaOptions] = Help.derive
170169

171-
def yScriptRunnerWarning(yScriptRunnerValue: Option[String]): String = {
170+
def yScriptRunnerWarning(yScriptRunnerKey: String, yScriptRunnerValue: Option[String]): String = {
172171
val valueSpecificMsg = yScriptRunnerValue match {
173172
case Some(v @ "default") =>
174173
s"scala.tools.nsc.DefaultScriptRunner (the $v script runner) is no longer available."
@@ -185,7 +184,7 @@ object LegacyScalaOptions {
185184
s"Using $className as the script runner is no longer supported and will not be attempted."
186185
case _ => ""
187186
}
188-
s"""Deprecated option '$YScriptRunnerOption' is ignored.
187+
s"""Deprecated option '$yScriptRunnerKey' is ignored.
189188
|The script runner can no longer be picked as before.
190189
|$valueSpecificMsg""".stripMargin
191190
}

modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala

+53-42
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import caseapp.core.{Arg, Error}
88
import com.github.plokhotnyuk.jsoniter_scala.core.*
99
import com.github.plokhotnyuk.jsoniter_scala.macros.*
1010

11+
import scala.build.options.ScalacOpt.noDashPrefixes
1112
import scala.cli.commands.tags
1213

1314
// format: off
@@ -26,6 +27,13 @@ final case class ScalacOptions(
2627
// format: on
2728

2829
object ScalacOptions {
30+
extension (opt: String) {
31+
private def hasValidScalacOptionDashes: Boolean =
32+
opt.startsWith("-") && opt.length > 1 && (
33+
if opt.length > 2 then opt.charAt(2) != '-'
34+
else opt.charAt(1) != '-'
35+
)
36+
}
2937

3038
private val scalacOptionsArg = Arg("scalacOption").copy(
3139
extraNames = Seq(Name("scala-opt"), Name("O"), Name("scala-option")),
@@ -37,56 +45,54 @@ object ScalacOptions {
3745
origin = Some("ScalacOptions")
3846
)
3947
// .withIsFlag(true) // The scalac options we handle accept no value after the -… argument
40-
val YScriptRunnerOption = "-Yscriptrunner"
41-
private val scalacOptionsPurePrefixes =
42-
Set("-V", "-W", "-X", "-Y")
43-
private val scalacOptionsPrefixes =
44-
Set("-P") ++ scalacOptionsPurePrefixes
48+
val YScriptRunnerOption = "Yscriptrunner"
49+
private val scalacOptionsPurePrefixes = Set("V", "W", "X", "Y")
50+
private val scalacOptionsPrefixes = Set("P") ++ scalacOptionsPurePrefixes
4551
private val scalacAliasedOptions = // these options don't require being passed after -O and accept an arg
4652
Set(
47-
"-encoding",
48-
"-release",
49-
"-color",
50-
"-g",
51-
"-language",
52-
"-opt",
53-
"-target",
54-
"-source",
53+
"encoding",
54+
"release",
55+
"color",
56+
"g",
57+
"language",
58+
"opt",
59+
"target",
60+
"source",
5561
YScriptRunnerOption
5662
)
5763
private val scalacNoArgAliasedOptions = // these options don't require being passed after -O and don't accept an arg
5864
Set(
59-
"-unchecked",
60-
"-nowarn",
61-
"-feature",
62-
"-deprecation",
63-
"-rewrite",
64-
"-old-syntax",
65-
"-new-syntax",
66-
"-indent",
67-
"-no-indent"
65+
"unchecked",
66+
"nowarn",
67+
"feature",
68+
"deprecation",
69+
"rewrite",
70+
"old-syntax",
71+
"new-syntax",
72+
"indent",
73+
"no-indent"
6874
)
6975

7076
/** This includes all the scalac options which disregard inputs and print a help and/or context
7177
* message instead.
7278
*/
7379
val ScalacPrintOptions: Set[String] =
7480
scalacOptionsPurePrefixes ++ Set(
75-
"-help",
76-
"-opt:help",
77-
"-Xshow-phases",
78-
"-Xsource:help",
79-
"-Xplugin-list",
80-
"-Xmixin-force-forwarders:help",
81-
"-Xlint:help",
82-
"-Vphases"
81+
"help",
82+
"opt:help",
83+
"Xshow-phases",
84+
"Xsource:help",
85+
"Xplugin-list",
86+
"Xmixin-force-forwarders:help",
87+
"Xlint:help",
88+
"Vphases"
8389
)
8490

8591
/** This includes all the scalac options which are redirected to native Scala CLI options. */
86-
val ScalaCliRedirectedOptions = Set(
87-
"-classpath",
88-
"-cp", // redirected to --extra-jars
89-
"-d" // redirected to --compilation-output
92+
val ScalaCliRedirectedOptions: Set[String] = Set(
93+
"classpath",
94+
"cp", // redirected to --extra-jars
95+
"d" // redirected to --compilation-output
9096
)
9197
val ScalacDeprecatedOptions: Set[String] = Set(
9298
YScriptRunnerOption // old 'scala' runner specific, no longer supported
@@ -109,15 +115,20 @@ object ScalacOptions {
109115
): Either[(Error, List[String]), Option[(Option[List[String]], List[String])]] =
110116
args match {
111117
case h :: t
112-
if scalacOptionsPrefixes.exists(h.startsWith) &&
113-
!ScalacDeprecatedOptions.contains(h) =>
118+
if h.hasValidScalacOptionDashes &&
119+
scalacOptionsPrefixes.exists(h.noDashPrefixes.startsWith) &&
120+
!ScalacDeprecatedOptions.contains(h.noDashPrefixes) =>
114121
Right(Some((Some(acc.getOrElse(Nil) :+ h), t)))
115-
case h :: t if scalacNoArgAliasedOptions.contains(h) =>
122+
case h :: t
123+
if h.hasValidScalacOptionDashes &&
124+
scalacNoArgAliasedOptions.contains(h.noDashPrefixes) =>
116125
Right(Some((Some(acc.getOrElse(Nil) :+ h), t)))
117126
case h :: t
118-
if scalacAliasedOptions.exists(o => h.startsWith(o + ":")) &&
127+
if h.hasValidScalacOptionDashes &&
128+
scalacAliasedOptions.exists(o => h.noDashPrefixes.startsWith(o + ":")) &&
119129
h.count(_ == ':') == 1 => Right(Some((Some(acc.getOrElse(Nil) :+ h), t)))
120-
case h :: t if scalacAliasedOptions.contains(h) =>
130+
case h :: t
131+
if h.hasValidScalacOptionDashes && scalacAliasedOptions.contains(h.noDashPrefixes) =>
121132
// check if the next scalac arg is a different option or a param to the current option
122133
val maybeOptionArg = t.headOption.filter(!_.startsWith("-"))
123134
// if it's a param, it'll be treated as such and considered already parsed
@@ -131,8 +142,8 @@ object ScalacOptions {
131142
}
132143

133144
implicit lazy val parser: Parser[ScalacOptions] = {
134-
val baseParser = scalacOptionsArgument :: NilParser
135-
implicit val p = ArgFileOption.parser
145+
val baseParser = scalacOptionsArgument :: NilParser
146+
implicit val p: Parser[List[ArgFileOption]] = ArgFileOption.parser
136147
baseParser.addAll[List[ArgFileOption]].to[ScalacOptions]
137148
}
138149

@@ -143,7 +154,7 @@ object ScalacOptions {
143154
case class ArgFileOption(file: String) extends AnyVal
144155

145156
object ArgFileOption {
146-
val arg = Arg(
157+
val arg: Arg = Arg(
147158
name = Name("args-file"),
148159
valueDescription = Some(ValueDescription("@arguments-file")),
149160
helpMessage = Some(HelpMessage("File with scalac options.")),

modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package scala.cli.commands.util
22

33
import scala.build.Logger
4-
import scala.build.options.ScalacOpt.filterScalacOptionKeys
4+
import scala.build.options.ScalacOpt.{filterScalacOptionKeys, noDashPrefixes}
55
import scala.build.options.{ScalacOpt, ShadowingSeq}
66
import scala.cli.commands.bloop.BloopExit
77
import scala.cli.commands.default.LegacyScalaOptions
@@ -33,9 +33,13 @@ object ScalacOptionsUtil {
3333

3434
extension (opts: ShadowingSeq[ScalacOpt]) {
3535
def filterNonRedirected: ShadowingSeq[ScalacOpt] =
36-
opts.filterScalacOptionKeys(!ScalacOptions.ScalaCliRedirectedOptions.contains(_))
36+
opts.filterScalacOptionKeys(k =>
37+
!ScalacOptions.ScalaCliRedirectedOptions.contains(k.noDashPrefixes)
38+
)
3739
def filterNonDeprecated: ShadowingSeq[ScalacOpt] =
38-
opts.filterScalacOptionKeys(!ScalacOptions.ScalacDeprecatedOptions.contains(_))
40+
opts.filterScalacOptionKeys(k =>
41+
!ScalacOptions.ScalacDeprecatedOptions.contains(k.noDashPrefixes)
42+
)
3943
def getOption(key: String): Option[String] =
4044
opts.get(ScalacOpt(key)).headOption.map(_.value)
4145
}

modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala

+17-6
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,26 @@ trait CompileScalacCompatTestDefinitions { _: CompileTestDefinitions =>
4040
for {
4141
mode <- modes
4242
if actualScalaVersion == Constants.scala3Next
43+
dashPrefix <- Seq("-", "--")
4344
syntaxVariant <- Seq(
44-
Seq(Seq("-color:never"), Seq("-language:noAutoTupling"), Seq("-language:strictEquality")),
4545
Seq(
46-
Seq("-color", "never"),
47-
Seq("-language", "noAutoTupling"),
48-
Seq("-language", "strictEquality")
46+
Seq(s"${dashPrefix}color:never"),
47+
Seq(s"${dashPrefix}language:noAutoTupling"),
48+
Seq(s"${dashPrefix}language:strictEquality")
4949
),
50-
Seq(Seq("-color:never"), Seq("\"-language:noAutoTupling,strictEquality\"")),
51-
Seq(Seq("-color", "never"), Seq("-language", "\"noAutoTupling,strictEquality\""))
50+
Seq(
51+
Seq(s"${dashPrefix}color", "never"),
52+
Seq(s"${dashPrefix}language", "noAutoTupling"),
53+
Seq(s"${dashPrefix}language", "strictEquality")
54+
),
55+
Seq(
56+
Seq(s"${dashPrefix}color:never"),
57+
Seq(s"\"${dashPrefix}language:noAutoTupling,strictEquality\"")
58+
),
59+
Seq(
60+
Seq(s"${dashPrefix}color", "never"),
61+
Seq(s"${dashPrefix}language", "\"noAutoTupling,strictEquality\"")
62+
)
5263
)
5364
(cliOpts, directiveOpts) = {
5465
val (initialCliOpts, initialDirectiveOpts) = mode match {

modules/options/src/main/scala/scala/build/options/ScalacOpt.scala

+16-8
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
package scala.build.options
22

3+
import scala.build.options.ScalacOpt.noDashPrefixes
4+
35
final case class ScalacOpt(value: String) {
46

57
/** @return raw key for the option (if valid) */
68
private[options] def key: Option[String] =
7-
if value.startsWith("-") then Some(value.takeWhile(_ != ':'))
9+
if value.startsWith("-") || value.startsWith("--") then Some(value.takeWhile(_ != ':'))
810
else Some("@").filter(value.startsWith)
911

1012
/** @return raw key for the option (only if the key can be shadowed from the CLI) */
1113
private[options] def shadowableKey: Option[String] = key match
1214
case Some(key)
13-
if ScalacOpt.repeatingKeys.exists(rKey => rKey.startsWith(key + ":") || rKey == key) => None
15+
if ScalacOpt.repeatingKeys
16+
.exists(rKey =>
17+
rKey.startsWith(key.noDashPrefixes + ":") || rKey == key.noDashPrefixes
18+
) => None
1419
case otherwise => otherwise
1520
}
1621

1722
object ScalacOpt {
23+
extension (opt: String) {
24+
def noDashPrefixes: String = opt.stripPrefix("--").stripPrefix("-")
25+
}
1826
private val repeatingKeys = Set(
19-
"-Xplugin",
20-
"-P", // plugin options
21-
"-language",
22-
"-Wconf"
27+
"Xplugin",
28+
"P", // plugin options
29+
"language",
30+
"Wconf"
2331
)
2432

2533
implicit val hashedType: HashedType[ScalacOpt] = {
@@ -32,12 +40,12 @@ object ScalacOpt {
3240
seq => groupCliOptions(seq.map(_.value))
3341
)
3442

35-
// Groups options (starting with `-` or `@`) with option arguments that follow
43+
// Groups options (starting with `-`, `--` or `@`) with option arguments that follow
3644
def groupCliOptions(opts: Seq[String]): Seq[Int] =
3745
opts
3846
.zipWithIndex
3947
.collect {
40-
case (opt, idx) if opt.startsWith("-") || opt.startsWith("@") =>
48+
case (opt, idx) if opt.startsWith("-") || opt.startsWith("--") || opt.startsWith("@") =>
4149
idx
4250
}
4351

0 commit comments

Comments
 (0)