From 4673667d657bcbbb9be71a0d26f1c2ac797c2806 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Mon, 28 Oct 2024 14:17:03 +0100 Subject: [PATCH 1/8] Extract `scalac` options checks from `SipScalaTests` to the `compile` sub-command test suite --- .../CompileScalacCompatTestDefinitions.scala | 91 +++++++++++++++++++ .../integration/CompileTestDefinitions.scala | 1 + .../RunScalacCompatTestDefinitions.scala | 1 + .../scala/cli/integration/SipScalaTests.scala | 65 ------------- 4 files changed, 93 insertions(+), 65 deletions(-) create mode 100644 modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala diff --git a/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala new file mode 100644 index 0000000000..7ea213e981 --- /dev/null +++ b/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala @@ -0,0 +1,91 @@ +package scala.cli.integration + +import com.eed3si9n.expecty.Expecty.expect + +import scala.util.Properties + +/** For the `run` counterpart, refer to [[RunScalacCompatTestDefinitions]] */ +trait CompileScalacCompatTestDefinitions { _: CompileTestDefinitions => + if (actualScalaVersion.startsWith("3")) + test("consecutive -language:* flags are not ignored") { + val sourceFileName = "example.scala" + TestInputs(os.rel / sourceFileName -> + s"""//> using scala $actualScalaVersion + |//> using options -color:never -language:noAutoTupling -language:strictEquality + |case class Cat(name: String) + |case class Dog(name: String) + |def strictEquality(c: Cat, d: Dog):Boolean = c == d + |def takesTuple(tpl: Tuple) = ??? + |def withTuple() = takesTuple(1, 2) + |""".stripMargin).fromRoot { root => + val res = os.proc(TestUtil.cli, "compile", sourceFileName) + .call(cwd = root, check = false, stderr = os.Pipe) + expect(res.exitCode == 1) + val errOutput = res.err.trim() + val expectedStrictEqualityError = + " Values of types Cat and Dog cannot be compared with == or !=" + expect(errOutput.contains(expectedStrictEqualityError)) + val expectedNoAutoTuplingError = + "too many arguments for method takesTuple: (tpl: Tuple): Nothing" + expect(errOutput.trim().contains(expectedNoAutoTuplingError)) + } + } + + for { + useDirective <- Seq(true, false) + if !Properties.isWin + optionsSource = if (useDirective) "using directive" else "command line" + } test(s"consecutive -Wconf:* flags are not ignored (passed via $optionsSource)") { + val sv = actualScalaVersion + val sourceFileName = "example.scala" + val warningConfOptions = Seq("-Wconf:cat=deprecation:e", "-Wconf:any:s") + val maybeDirectiveString = + if (useDirective) s"//> using options ${warningConfOptions.mkString(" ")}" else "" + TestInputs(os.rel / sourceFileName -> + s"""//> using scala $sv + |$maybeDirectiveString + |object WConfExample extends App { + | @deprecated("This method will be removed", "1.0.0") + | def oldMethod(): Unit = println("This is an old method.") + | oldMethod() + |} + |""".stripMargin).fromRoot { root => + val localBin = root / "local-bin" + os.proc( + TestUtil.cs, + "install", + "--install-dir", + localBin, + s"scalac:$sv" + ).call(cwd = root) + val cliRes = + os.proc( + TestUtil.cli, + "compile", + sourceFileName, + "--server=false", + if (useDirective) Nil else warningConfOptions + ) + .call(cwd = root, check = false, stderr = os.Pipe) + val scalacRes = os.proc(localBin / "scalac", warningConfOptions, sourceFileName) + .call(cwd = root, check = false, stderr = os.Pipe) + expect(scalacRes.exitCode == cliRes.exitCode) + val scalacResErr = scalacRes.err.trim() + if (sv != Constants.scala3Lts) { + // TODO run this check for LTS when -Wconf gets fixed there + val cliResErr = + cliRes.err.trim().linesIterator.toList + // skip potentially irrelevant logs + .dropWhile(_.contains("Check")) + .mkString(System.lineSeparator()) + expect(cliResErr == scalacResErr) + } + else expect( + TestUtil.removeAnsiColors(cliRes.err.trim()) + .contains( + "method oldMethod in object WConfExample is deprecated since 1.0.0: This method will be removed" + ) + ) + } + } +} diff --git a/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala index c60703d020..7ef9e63ec5 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala @@ -10,6 +10,7 @@ abstract class CompileTestDefinitions extends ScalaCliSuite with TestScalaVersionArgs with CompilerPluginTestDefinitions + with CompileScalacCompatTestDefinitions with SemanticDbTestDefinitions { _: TestScalaVersion => protected lazy val extraOptions: Seq[String] = scalaVersionArgs ++ TestUtil.extraOptions diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala index 1051cdf3bb..3439e9db8c 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala @@ -7,6 +7,7 @@ import java.io.File import scala.jdk.CollectionConverters.* import scala.util.Properties +/** For the `compile` counterpart, refer to [[CompileScalacCompatTestDefinitions]] */ trait RunScalacCompatTestDefinitions { _: RunTestDefinitions => diff --git a/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala b/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala index e0e637f610..45cd3c5674 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala @@ -496,71 +496,6 @@ class SipScalaTests extends ScalaCliSuite with SbtTestHelper with MillTestHelper } } - test("consecutive -language:* flags are not ignored") { - val sourceFileName = "example.scala" - TestInputs(os.rel / sourceFileName -> - """//> using scala 3.3.1 - |//> using options -Yexplicit-nulls -language:fewerBraces -language:strictEquality - |def repro[A](as: List[A]): List[A] = - | as match - | case Nil => Nil - | case _ => ??? - |""".stripMargin).fromRoot { root => - val res = os.proc(TestUtil.cli, "compile", sourceFileName) - .call(cwd = root, check = false, stderr = os.Pipe) - expect(res.exitCode == 1) - val expectedError = - "Values of types object scala.collection.immutable.Nil and List[A] cannot be compared with == or !=" - expect(res.err.trim().contains(expectedError)) - } - } - - for { - useDirective <- Seq(true, false) - if !Properties.isWin - optionsSource = if (useDirective) "using directive" else "command line" - } test(s"consecutive -Wconf:* flags are not ignored (passed via $optionsSource)") { - val sv = "3.5.2" - val sourceFileName = "example.scala" - val warningConfOptions = Seq("-Wconf:cat=deprecation:e", "-Wconf:any:s") - val maybeDirectiveString = - if (useDirective) s"//> using options ${warningConfOptions.mkString(" ")}" else "" - TestInputs(os.rel / sourceFileName -> - s"""//> using scala $sv - |$maybeDirectiveString - |object WConfExample extends App { - | @deprecated("This method will be removed", "1.0.0") - | def oldMethod(): Unit = println("This is an old method.") - | oldMethod() - |} - |""".stripMargin).fromRoot { root => - val localCache = root / "local-cache" - val localBin = root / "local-bin" - os.proc( - TestUtil.cs, - "install", - "--cache", - localCache, - "--install-dir", - localBin, - s"scalac:$sv" - ).call(cwd = root) - val cliRes = - os.proc( - TestUtil.cli, - "compile", - sourceFileName, - "--server=false", - if (useDirective) Nil else warningConfOptions - ) - .call(cwd = root, check = false, stderr = os.Pipe) - val scalacRes = os.proc(localBin / "scalac", warningConfOptions, sourceFileName) - .call(cwd = root, check = false, stderr = os.Pipe) - expect(scalacRes.exitCode == cliRes.exitCode) - expect(cliRes.err.trim() == scalacRes.err.trim()) - } - } - for { sv <- Seq(Constants.scala212, Constants.scala213, Constants.scala3NextRc) code = From 6adc37b786896d15641e10db1efe93cf5bf17f7e Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Tue, 29 Oct 2024 11:13:20 +0100 Subject: [PATCH 2/8] Support missing syntax variants for compiler options --- .../cli/commands/shared/ScalacOptions.scala | 17 ++++- .../CompileScalacCompatTestDefinitions.scala | 72 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala index ce73e08edc..3254f3698c 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala @@ -41,9 +41,19 @@ object ScalacOptions { private val scalacOptionsPurePrefixes = Set("-V", "-W", "-X", "-Y") private val scalacOptionsPrefixes = - Set("-g", "-language", "-opt", "-P", "-target", "-source") ++ scalacOptionsPurePrefixes + Set("-P") ++ scalacOptionsPurePrefixes private val scalacAliasedOptions = // these options don't require being passed after -O and accept an arg - Set("-encoding", "-release", "-color", YScriptRunnerOption) + Set( + "-encoding", + "-release", + "-color", + "-g", + "-language", + "-opt", + "-target", + "-source", + YScriptRunnerOption + ) private val scalacNoArgAliasedOptions = // these options don't require being passed after -O and don't accept an arg Set( "-unchecked", @@ -104,6 +114,9 @@ object ScalacOptions { Right(Some((Some(acc.getOrElse(Nil) :+ h), t))) case h :: t if scalacNoArgAliasedOptions.contains(h) => Right(Some((Some(acc.getOrElse(Nil) :+ h), t))) + case h :: t + if scalacAliasedOptions.exists(o => h.startsWith(o + ":")) && + h.count(_ == ':') == 1 => Right(Some((Some(acc.getOrElse(Nil) :+ h), t))) case h :: t if scalacAliasedOptions.contains(h) => // check if the next scalac arg is a different option or a param to the current option val maybeOptionArg = t.headOption.filter(!_.startsWith("-")) diff --git a/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala index 7ea213e981..e7041d188f 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala @@ -31,6 +31,78 @@ trait CompileScalacCompatTestDefinitions { _: CompileTestDefinitions => } } + // Given the vast number of ways compiler options can be passed from the CLI, + // we test them all (or most, at the very least), as a (perhaps overkill) sanity check. + // Pieces of the existing `-language:*` test are reused, but kept separate for clarity. + { + val modes @ Seq(viaDirective, viaCli, viaCliWithExplicitOpt, mixed, mixedWithExplicitOpt) = + Seq("directive", "cli", "cli with -O", "mixed", "mixed with -O") + for { + mode <- modes + if actualScalaVersion == Constants.scala3Next + syntaxVariant <- Seq( + Seq(Seq("-color:never"), Seq("-language:noAutoTupling"), Seq("-language:strictEquality")), + Seq( + Seq("-color", "never"), + Seq("-language", "noAutoTupling"), + Seq("-language", "strictEquality") + ), + Seq(Seq("-color:never"), Seq("\"-language:noAutoTupling,strictEquality\"")), + Seq(Seq("-color", "never"), Seq("-language", "\"noAutoTupling,strictEquality\"")) + ) + (cliOpts, directiveOpts) = { + val (initialCliOpts, initialDirectiveOpts) = mode match { + case m if m == mixed => syntaxVariant.splitAt(syntaxVariant.length - 1) + case m if m == mixedWithExplicitOpt => + val (initialCliOpts, initialDirectiveOpts) = + syntaxVariant.splitAt(syntaxVariant.length - 1) + initialCliOpts.map(_.flatMap(o => Seq("-O", o))) -> initialDirectiveOpts + case c if c == viaCli => syntaxVariant -> Nil + case c if c == viaCliWithExplicitOpt => + syntaxVariant.map(_.flatMap(o => Seq("-O", o))) -> Nil + case _ => Nil -> syntaxVariant + } + initialCliOpts.flatten.map(_.filter(_ != '"')) -> initialDirectiveOpts.flatten + } + cliOptsString = cliOpts.mkString(" ") + directiveOptsString = directiveOpts.mkString(" ") + includeDirective = + (mode == viaDirective || mode == mixed || mode == mixedWithExplicitOpt) && directiveOpts.nonEmpty + directiveString = if (includeDirective) s"//> using options $directiveOptsString" else "" + allOptsString = mode match { + case m if m.startsWith(mixed) => + s"opts passed via command line: $cliOptsString, opts passed via directive: $directiveString" + case c if c.startsWith(viaCli) => + s"opts passed via command line: $cliOptsString" + case _ => + s"opts passed via directive: $directiveString" + } + } test(s"compiler options passed in $mode mode: $allOptsString") { + val sourceFileName = "example.scala" + TestInputs(os.rel / sourceFileName -> + s"""//> using scala $actualScalaVersion + |$directiveString + |case class Cat(name: String) + |case class Dog(name: String) + |def strictEquality(c: Cat, d: Dog):Boolean = c == d + |def takesTuple(tpl: Tuple) = ??? + |def withTuple() = takesTuple(1, 2) + |""".stripMargin).fromRoot { root => + val res = os.proc(TestUtil.cli, "compile", sourceFileName, cliOpts) + .call(cwd = root, check = false, stderr = os.Pipe) + println(res.err.trim()) + expect(res.exitCode == 1) + val errOutput = res.err.trim() + val expectedStrictEqualityError = + "Values of types Cat and Dog cannot be compared with == or !=" + expect(errOutput.contains(expectedStrictEqualityError)) + val expectedNoAutoTuplingError = + "too many arguments for method takesTuple: (tpl: Tuple): Nothing" + expect(errOutput.trim().contains(expectedNoAutoTuplingError)) + } + } + } + for { useDirective <- Seq(true, false) if !Properties.isWin From dfd06b92913ce2513f23d42b5e441591486f6629 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Tue, 29 Oct 2024 15:21:32 +0100 Subject: [PATCH 3/8] Don't differentiate between `-` and `--` prefixes in compiler options --- .../scala/cli/commands/ScalaCommand.scala | 13 ++- .../commands/default/LegacyScalaOptions.scala | 5 +- .../cli/commands/shared/ScalacOptions.scala | 95 +++++++++++-------- .../cli/commands/util/ScalacOptionsUtil.scala | 10 +- .../CompileScalacCompatTestDefinitions.scala | 23 +++-- .../scala/scala/build/options/ScalacOpt.scala | 24 +++-- 6 files changed, 104 insertions(+), 66 deletions(-) diff --git a/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala b/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala index 93693edcae..bedc39b873 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala @@ -21,6 +21,7 @@ import scala.build.input.{ScalaCliInvokeData, SubCommand} import scala.build.internal.util.WarningMessages import scala.build.internal.{Constants, Runner} import scala.build.internals.{EnvVar, FeatureType} +import scala.build.options.ScalacOpt.noDashPrefixes import scala.build.options.{BuildOptions, ScalacOpt, Scope} import scala.build.{Artifacts, Directories, Logger, Positioned, ReplArtifacts} import scala.cli.commands.default.LegacyScalaOptions @@ -175,9 +176,13 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T], val logger = options.global.logging.logger sharedOptions(options).foreach { so => val scalacOpts = so.scalacOptions.toScalacOptShadowingSeq - if scalacOpts.keys.contains(ScalacOpt(YScriptRunnerOption)) then - logger.message( - LegacyScalaOptions.yScriptRunnerWarning(scalacOpts.getOption(YScriptRunnerOption)) + scalacOpts.keys + .find(k => + k == ScalacOpt(s"-$YScriptRunnerOption") || k == ScalacOpt(s"--$YScriptRunnerOption") + ) + .map(_.value) + .foreach(k => + logger.message(LegacyScalaOptions.yScriptRunnerWarning(k, scalacOpts.getOption(k))) ) } } @@ -192,7 +197,7 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T], shared <- sharedOptions(options) scalacOptions = shared.scalacOptions updatedScalacOptions = scalacOptions.withScalacExtraOptions(shared.scalacExtra) - if updatedScalacOptions.exists(ScalacOptions.ScalacPrintOptions) + if updatedScalacOptions.map(_.noDashPrefixes).exists(ScalacOptions.ScalacPrintOptions) logger = shared.logger fixedBuildOptions = buildOptions.copy(scalaOptions = buildOptions.scalaOptions.copy(defaultScalaVersion = Some(ScalaCli.getDefaultScalaVersion)) diff --git a/modules/cli/src/main/scala/scala/cli/commands/default/LegacyScalaOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/default/LegacyScalaOptions.scala index 666f8723fb..5d1e3031db 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/default/LegacyScalaOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/default/LegacyScalaOptions.scala @@ -11,7 +11,6 @@ import scala.cli.commands.default.LegacyScalaOptions.* import scala.cli.commands.package0.Package import scala.cli.commands.shared.HelpGroup import scala.cli.commands.shared.HelpMessages.PowerString -import scala.cli.commands.shared.ScalacOptions.YScriptRunnerOption import scala.cli.commands.tags /** Options covering backwards compatibility with the old scala runner. @@ -168,7 +167,7 @@ object LegacyScalaOptions { implicit lazy val parser: Parser[LegacyScalaOptions] = Parser.derive implicit lazy val help: Help[LegacyScalaOptions] = Help.derive - def yScriptRunnerWarning(yScriptRunnerValue: Option[String]): String = { + def yScriptRunnerWarning(yScriptRunnerKey: String, yScriptRunnerValue: Option[String]): String = { val valueSpecificMsg = yScriptRunnerValue match { case Some(v @ "default") => s"scala.tools.nsc.DefaultScriptRunner (the $v script runner) is no longer available." @@ -185,7 +184,7 @@ object LegacyScalaOptions { s"Using $className as the script runner is no longer supported and will not be attempted." case _ => "" } - s"""Deprecated option '$YScriptRunnerOption' is ignored. + s"""Deprecated option '$yScriptRunnerKey' is ignored. |The script runner can no longer be picked as before. |$valueSpecificMsg""".stripMargin } diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala index 3254f3698c..f19d5f8f52 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala @@ -8,6 +8,7 @@ import caseapp.core.{Arg, Error} import com.github.plokhotnyuk.jsoniter_scala.core.* import com.github.plokhotnyuk.jsoniter_scala.macros.* +import scala.build.options.ScalacOpt.noDashPrefixes import scala.cli.commands.tags // format: off @@ -26,6 +27,13 @@ final case class ScalacOptions( // format: on object ScalacOptions { + extension (opt: String) { + private def hasValidScalacOptionDashes: Boolean = + opt.startsWith("-") && opt.length > 1 && ( + if opt.length > 2 then opt.charAt(2) != '-' + else opt.charAt(1) != '-' + ) + } private val scalacOptionsArg = Arg("scalacOption").copy( extraNames = Seq(Name("scala-opt"), Name("O"), Name("scala-option")), @@ -37,34 +45,32 @@ object ScalacOptions { origin = Some("ScalacOptions") ) // .withIsFlag(true) // The scalac options we handle accept no value after the -… argument - val YScriptRunnerOption = "-Yscriptrunner" - private val scalacOptionsPurePrefixes = - Set("-V", "-W", "-X", "-Y") - private val scalacOptionsPrefixes = - Set("-P") ++ scalacOptionsPurePrefixes + val YScriptRunnerOption = "Yscriptrunner" + private val scalacOptionsPurePrefixes = Set("V", "W", "X", "Y") + private val scalacOptionsPrefixes = Set("P") ++ scalacOptionsPurePrefixes private val scalacAliasedOptions = // these options don't require being passed after -O and accept an arg Set( - "-encoding", - "-release", - "-color", - "-g", - "-language", - "-opt", - "-target", - "-source", + "encoding", + "release", + "color", + "g", + "language", + "opt", + "target", + "source", YScriptRunnerOption ) private val scalacNoArgAliasedOptions = // these options don't require being passed after -O and don't accept an arg Set( - "-unchecked", - "-nowarn", - "-feature", - "-deprecation", - "-rewrite", - "-old-syntax", - "-new-syntax", - "-indent", - "-no-indent" + "unchecked", + "nowarn", + "feature", + "deprecation", + "rewrite", + "old-syntax", + "new-syntax", + "indent", + "no-indent" ) /** This includes all the scalac options which disregard inputs and print a help and/or context @@ -72,21 +78,21 @@ object ScalacOptions { */ val ScalacPrintOptions: Set[String] = scalacOptionsPurePrefixes ++ Set( - "-help", - "-opt:help", - "-Xshow-phases", - "-Xsource:help", - "-Xplugin-list", - "-Xmixin-force-forwarders:help", - "-Xlint:help", - "-Vphases" + "help", + "opt:help", + "Xshow-phases", + "Xsource:help", + "Xplugin-list", + "Xmixin-force-forwarders:help", + "Xlint:help", + "Vphases" ) /** This includes all the scalac options which are redirected to native Scala CLI options. */ - val ScalaCliRedirectedOptions = Set( - "-classpath", - "-cp", // redirected to --extra-jars - "-d" // redirected to --compilation-output + val ScalaCliRedirectedOptions: Set[String] = Set( + "classpath", + "cp", // redirected to --extra-jars + "d" // redirected to --compilation-output ) val ScalacDeprecatedOptions: Set[String] = Set( YScriptRunnerOption // old 'scala' runner specific, no longer supported @@ -109,15 +115,20 @@ object ScalacOptions { ): Either[(Error, List[String]), Option[(Option[List[String]], List[String])]] = args match { case h :: t - if scalacOptionsPrefixes.exists(h.startsWith) && - !ScalacDeprecatedOptions.contains(h) => + if h.hasValidScalacOptionDashes && + scalacOptionsPrefixes.exists(h.noDashPrefixes.startsWith) && + !ScalacDeprecatedOptions.contains(h.noDashPrefixes) => Right(Some((Some(acc.getOrElse(Nil) :+ h), t))) - case h :: t if scalacNoArgAliasedOptions.contains(h) => + case h :: t + if h.hasValidScalacOptionDashes && + scalacNoArgAliasedOptions.contains(h.noDashPrefixes) => Right(Some((Some(acc.getOrElse(Nil) :+ h), t))) case h :: t - if scalacAliasedOptions.exists(o => h.startsWith(o + ":")) && + if h.hasValidScalacOptionDashes && + scalacAliasedOptions.exists(o => h.noDashPrefixes.startsWith(o + ":")) && h.count(_ == ':') == 1 => Right(Some((Some(acc.getOrElse(Nil) :+ h), t))) - case h :: t if scalacAliasedOptions.contains(h) => + case h :: t + if h.hasValidScalacOptionDashes && scalacAliasedOptions.contains(h.noDashPrefixes) => // check if the next scalac arg is a different option or a param to the current option val maybeOptionArg = t.headOption.filter(!_.startsWith("-")) // if it's a param, it'll be treated as such and considered already parsed @@ -131,8 +142,8 @@ object ScalacOptions { } implicit lazy val parser: Parser[ScalacOptions] = { - val baseParser = scalacOptionsArgument :: NilParser - implicit val p = ArgFileOption.parser + val baseParser = scalacOptionsArgument :: NilParser + implicit val p: Parser[List[ArgFileOption]] = ArgFileOption.parser baseParser.addAll[List[ArgFileOption]].to[ScalacOptions] } @@ -143,7 +154,7 @@ object ScalacOptions { case class ArgFileOption(file: String) extends AnyVal object ArgFileOption { - val arg = Arg( + val arg: Arg = Arg( name = Name("args-file"), valueDescription = Some(ValueDescription("@arguments-file")), helpMessage = Some(HelpMessage("File with scalac options.")), diff --git a/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala b/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala index 51cb1bc8e1..7d513f3bfb 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala @@ -1,7 +1,7 @@ package scala.cli.commands.util import scala.build.Logger -import scala.build.options.ScalacOpt.filterScalacOptionKeys +import scala.build.options.ScalacOpt.{filterScalacOptionKeys, noDashPrefixes} import scala.build.options.{ScalacOpt, ShadowingSeq} import scala.cli.commands.bloop.BloopExit import scala.cli.commands.default.LegacyScalaOptions @@ -33,9 +33,13 @@ object ScalacOptionsUtil { extension (opts: ShadowingSeq[ScalacOpt]) { def filterNonRedirected: ShadowingSeq[ScalacOpt] = - opts.filterScalacOptionKeys(!ScalacOptions.ScalaCliRedirectedOptions.contains(_)) + opts.filterScalacOptionKeys(k => + !ScalacOptions.ScalaCliRedirectedOptions.contains(k.noDashPrefixes) + ) def filterNonDeprecated: ShadowingSeq[ScalacOpt] = - opts.filterScalacOptionKeys(!ScalacOptions.ScalacDeprecatedOptions.contains(_)) + opts.filterScalacOptionKeys(k => + !ScalacOptions.ScalacDeprecatedOptions.contains(k.noDashPrefixes) + ) def getOption(key: String): Option[String] = opts.get(ScalacOpt(key)).headOption.map(_.value) } diff --git a/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala index e7041d188f..bde7775454 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala @@ -40,15 +40,26 @@ trait CompileScalacCompatTestDefinitions { _: CompileTestDefinitions => for { mode <- modes if actualScalaVersion == Constants.scala3Next + dashPrefix <- Seq("-", "--") syntaxVariant <- Seq( - Seq(Seq("-color:never"), Seq("-language:noAutoTupling"), Seq("-language:strictEquality")), Seq( - Seq("-color", "never"), - Seq("-language", "noAutoTupling"), - Seq("-language", "strictEquality") + Seq(s"${dashPrefix}color:never"), + Seq(s"${dashPrefix}language:noAutoTupling"), + Seq(s"${dashPrefix}language:strictEquality") ), - Seq(Seq("-color:never"), Seq("\"-language:noAutoTupling,strictEquality\"")), - Seq(Seq("-color", "never"), Seq("-language", "\"noAutoTupling,strictEquality\"")) + Seq( + Seq(s"${dashPrefix}color", "never"), + Seq(s"${dashPrefix}language", "noAutoTupling"), + Seq(s"${dashPrefix}language", "strictEquality") + ), + Seq( + Seq(s"${dashPrefix}color:never"), + Seq(s"\"${dashPrefix}language:noAutoTupling,strictEquality\"") + ), + Seq( + Seq(s"${dashPrefix}color", "never"), + Seq(s"${dashPrefix}language", "\"noAutoTupling,strictEquality\"") + ) ) (cliOpts, directiveOpts) = { val (initialCliOpts, initialDirectiveOpts) = mode match { diff --git a/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala b/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala index a03d53f474..378dfe99b7 100644 --- a/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala +++ b/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala @@ -1,25 +1,33 @@ package scala.build.options +import scala.build.options.ScalacOpt.noDashPrefixes + final case class ScalacOpt(value: String) { /** @return raw key for the option (if valid) */ private[options] def key: Option[String] = - if value.startsWith("-") then Some(value.takeWhile(_ != ':')) + if value.startsWith("-") || value.startsWith("--") then Some(value.takeWhile(_ != ':')) else Some("@").filter(value.startsWith) /** @return raw key for the option (only if the key can be shadowed from the CLI) */ private[options] def shadowableKey: Option[String] = key match case Some(key) - if ScalacOpt.repeatingKeys.exists(rKey => rKey.startsWith(key + ":") || rKey == key) => None + if ScalacOpt.repeatingKeys + .exists(rKey => + rKey.startsWith(key.noDashPrefixes + ":") || rKey == key.noDashPrefixes + ) => None case otherwise => otherwise } object ScalacOpt { + extension (opt: String) { + def noDashPrefixes: String = opt.stripPrefix("--").stripPrefix("-") + } private val repeatingKeys = Set( - "-Xplugin", - "-P", // plugin options - "-language", - "-Wconf" + "Xplugin", + "P", // plugin options + "language", + "Wconf" ) implicit val hashedType: HashedType[ScalacOpt] = { @@ -32,12 +40,12 @@ object ScalacOpt { seq => groupCliOptions(seq.map(_.value)) ) - // Groups options (starting with `-` or `@`) with option arguments that follow + // Groups options (starting with `-`, `--` or `@`) with option arguments that follow def groupCliOptions(opts: Seq[String]): Seq[Int] = opts .zipWithIndex .collect { - case (opt, idx) if opt.startsWith("-") || opt.startsWith("@") => + case (opt, idx) if opt.startsWith("-") || opt.startsWith("--") || opt.startsWith("@") => idx } From 037314c8434fd2fd61dea2ea60c586de4f4ae939 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Wed, 30 Oct 2024 12:17:59 +0100 Subject: [PATCH 4/8] Disambiguate `--source` command line option between CLI and compiler, favouring `scalac` meaning --- .../cli/commands/package0/PackageOptions.scala | 2 -- .../cli/integration/BspTestDefinitions.scala | 2 +- .../cli/integration/PackageTestDefinitions.scala | 15 +++++---------- website/docs/reference/cli-options.md | 2 +- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/modules/cli/src/main/scala/scala/cli/commands/package0/PackageOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/package0/PackageOptions.scala index 917579ae11..205ec3b401 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/package0/PackageOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/package0/PackageOptions.scala @@ -51,8 +51,6 @@ final case class PackageOptions( @Name("sourcesJar") @Name("jarSources") @Name("sources") - @Name("source") - @Tag(tags.deprecated("source")) // alias to be removed in 1.6.x @Tag(tags.restricted) @Tag(tags.inShortHelp) src: Boolean = false, diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index 521e3bb5c1..eb316524cf 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -1712,7 +1712,7 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg "--power", "package", jarSources, - "--source", + "--src", "-o", sourceJarPath, extraOptions diff --git a/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala index fe54f6cd62..23c5c331f8 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala @@ -859,16 +859,11 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio test("source JAR") { val dest = os.rel / "sources.jar" simpleInputWithScalaAndSc.fromRoot { root => - val r = - os.proc(TestUtil.cli, "--power", "package", extraOptions, ".", "-o", dest, "--source").call( - cwd = root, - stdin = os.Inherit, - stdout = os.Inherit, - stderr = os.Pipe - ) - expect(r.err.trim().contains( - "The --source option alias has been deprecated and may be removed in a future version" - )) + os.proc(TestUtil.cli, "--power", "package", extraOptions, ".", "-o", dest, "--src").call( + cwd = root, + stdin = os.Inherit, + stdout = os.Inherit + ) expect(os.isFile(root / dest)) diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index b20e736750..0111b4f955 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -751,7 +751,7 @@ Generate a library JAR rather than an executable JAR ### `--src` -Aliases: `--jar-sources`, [deprecated] `--source`, `--sources`, `--sources-jar` +Aliases: `--jar-sources`, `--sources`, `--sources-jar` Generate a source JAR rather than an executable JAR From 59d2bb8af38e010f264cb17903bd0dfc4a87558f Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Thu, 31 Oct 2024 12:50:54 +0100 Subject: [PATCH 5/8] Ensure consecutive -Wunused:* flags are not ignored --- .../CompileScalacCompatTestDefinitions.scala | 130 +++++++++++------- .../scala/scala/build/options/ScalacOpt.scala | 3 +- 2 files changed, 84 insertions(+), 49 deletions(-) diff --git a/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala index bde7775454..872924ae58 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/CompileScalacCompatTestDefinitions.scala @@ -118,57 +118,91 @@ trait CompileScalacCompatTestDefinitions { _: CompileTestDefinitions => useDirective <- Seq(true, false) if !Properties.isWin optionsSource = if (useDirective) "using directive" else "command line" - } test(s"consecutive -Wconf:* flags are not ignored (passed via $optionsSource)") { - val sv = actualScalaVersion - val sourceFileName = "example.scala" - val warningConfOptions = Seq("-Wconf:cat=deprecation:e", "-Wconf:any:s") - val maybeDirectiveString = - if (useDirective) s"//> using options ${warningConfOptions.mkString(" ")}" else "" - TestInputs(os.rel / sourceFileName -> - s"""//> using scala $sv - |$maybeDirectiveString - |object WConfExample extends App { - | @deprecated("This method will be removed", "1.0.0") - | def oldMethod(): Unit = println("This is an old method.") - | oldMethod() - |} - |""".stripMargin).fromRoot { root => - val localBin = root / "local-bin" - os.proc( - TestUtil.cs, - "install", - "--install-dir", - localBin, - s"scalac:$sv" - ).call(cwd = root) - val cliRes = + sv = actualScalaVersion + } { + test(s"consecutive -Wconf:* flags are not ignored (passed via $optionsSource)") { + val sourceFileName = "example.scala" + val warningConfOptions = Seq("-Wconf:cat=deprecation:e", "-Wconf:any:s") + val maybeDirectiveString = + if (useDirective) s"//> using options ${warningConfOptions.mkString(" ")}" else "" + TestInputs(os.rel / sourceFileName -> + s"""//> using scala $sv + |$maybeDirectiveString + |object WConfExample extends App { + | @deprecated("This method will be removed", "1.0.0") + | def oldMethod(): Unit = println("This is an old method.") + | oldMethod() + |} + |""".stripMargin).fromRoot { root => + val localBin = root / "local-bin" os.proc( - TestUtil.cli, - "compile", - sourceFileName, - "--server=false", - if (useDirective) Nil else warningConfOptions - ) + TestUtil.cs, + "install", + "--install-dir", + localBin, + s"scalac:$sv" + ).call(cwd = root) + val cliRes = + os.proc( + TestUtil.cli, + "compile", + sourceFileName, + "--server=false", + if (useDirective) Nil else warningConfOptions + ) + .call(cwd = root, check = false, stderr = os.Pipe) + val scalacRes = os.proc(localBin / "scalac", warningConfOptions, sourceFileName) .call(cwd = root, check = false, stderr = os.Pipe) - val scalacRes = os.proc(localBin / "scalac", warningConfOptions, sourceFileName) - .call(cwd = root, check = false, stderr = os.Pipe) - expect(scalacRes.exitCode == cliRes.exitCode) - val scalacResErr = scalacRes.err.trim() - if (sv != Constants.scala3Lts) { - // TODO run this check for LTS when -Wconf gets fixed there - val cliResErr = - cliRes.err.trim().linesIterator.toList - // skip potentially irrelevant logs - .dropWhile(_.contains("Check")) - .mkString(System.lineSeparator()) - expect(cliResErr == scalacResErr) + expect(scalacRes.exitCode == cliRes.exitCode) + val scalacResErr = scalacRes.err.trim() + if (sv != Constants.scala3Lts) { + // TODO run this check for LTS when -Wconf gets fixed there + val cliResErr = + cliRes.err.trim().linesIterator.toList + // skip potentially irrelevant logs + .dropWhile(_.contains("Check")) + .mkString(System.lineSeparator()) + expect(cliResErr == scalacResErr) + } + else expect( + TestUtil.removeAnsiColors(cliRes.err.trim()) + .contains( + "method oldMethod in object WConfExample is deprecated since 1.0.0: This method will be removed" + ) + ) } - else expect( - TestUtil.removeAnsiColors(cliRes.err.trim()) - .contains( - "method oldMethod in object WConfExample is deprecated since 1.0.0: This method will be removed" - ) - ) } + + if (!sv.startsWith("2.12")) + test(s"consecutive -Wunused:* flags are not ignored (passed via $optionsSource)") { + val sourceFileName = "example.scala" + val unusedLintOptions = Seq("-Wunused:locals", "-Wunused:privates") + val maybeDirectiveString = + if (useDirective) s"//> using options ${unusedLintOptions.mkString(" ")}" else "" + TestInputs(os.rel / sourceFileName -> + s"""//> using scala $sv + |$maybeDirectiveString + |object WUnusedExample { + | private def unusedPrivate(): String = "stuff" + | def methodWithUnusedLocal() = { + | val smth = "hello" + | println("Hello") + | } + |} + |""".stripMargin).fromRoot { root => + val r = + os.proc( + TestUtil.cli, + "compile", + sourceFileName, + if (useDirective) Nil else unusedLintOptions + ) + .call(cwd = root, stderr = os.Pipe) + val err = r.err.trim() + val unusedKeyword = if (sv.startsWith("2")) "never used" else "unused" + expect(err.linesIterator.exists(l => l.contains(unusedKeyword) && l.contains("local"))) + expect(err.linesIterator.exists(l => l.contains(unusedKeyword) && l.contains("private"))) + } + } } } diff --git a/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala b/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala index 378dfe99b7..f8c52ba926 100644 --- a/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala +++ b/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala @@ -27,7 +27,8 @@ object ScalacOpt { "Xplugin", "P", // plugin options "language", - "Wconf" + "Wconf", + "Wunused" ) implicit val hashedType: HashedType[ScalacOpt] = { From 02931ae5a7fc38494d62c771f4b4040c0f827d23 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Thu, 31 Oct 2024 14:41:43 +0100 Subject: [PATCH 6/8] Ensure consecutive -Xmacro-settings:* flags are not ignored --- .../RunScalacCompatTestDefinitions.scala | 57 +++++++++++++++++++ .../scala/scala/build/options/ScalacOpt.scala | 1 + 2 files changed, 58 insertions(+) diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala index 3439e9db8c..c8c58f1836 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala @@ -549,4 +549,61 @@ trait RunScalacCompatTestDefinitions { expect(res.out.trim() == s"version $actualScalaVersion") } } + + for { + useDirective <- Seq(true, false) + if !Properties.isWin + optionsSource = if (useDirective) "using directive" else "command line" + if actualScalaVersion == Constants.scala3Next || actualScalaVersion == Constants.scala3NextRc + } + test(s"consecutive -Xmacro-settings:* flags are not ignored (passed via $optionsSource)") { + val sourceFileName = "example.scala" + val macroFileName = "macro.scala" + val macroSettings @ Seq(macroSetting1, macroSetting2, macroSetting3) = + Seq("one", "two", "three") + val macroSettingOptions = macroSettings.map(s => s"-Xmacro-settings:$s") + val maybeDirectiveString = + if (useDirective) s"//> using options ${macroSettingOptions.mkString(" ")}" else "" + TestInputs( + os.rel / macroFileName -> + """package x + |import scala.quoted.* + |object M: + | inline def settingsContains(inline x:String): Boolean = ${ + | settingsContainsImpl('x) + | } + | def settingsContainsImpl(x:Expr[String])(using Quotes): Expr[Boolean] = + | import quotes.reflect.* + | val v = x.valueOrAbort + | val r = CompilationInfo.XmacroSettings.contains(v) + | Expr(r) + |""".stripMargin, + os.rel / sourceFileName -> + s"""$maybeDirectiveString + |import x.M + |@main def main(): Unit = { + | val output = Seq( + | if M.settingsContains("$macroSetting1") then Seq("$macroSetting1") else Nil, + | if M.settingsContains("$macroSetting2") then Seq("$macroSetting2") else Nil, + | if M.settingsContains("$macroSetting3") then Seq("$macroSetting3") else Nil, + | if M.settingsContains("dummy") then Seq("dummy") else Nil, + | ) + | println(output.flatten.mkString(", ")) + |} + | + |""".stripMargin + ).fromRoot { root => + val r = os.proc( + TestUtil.cli, + "run", + ".", + "-O", + "-experimental", + if (useDirective) Nil else macroSettingOptions, + extraOptions + ) + .call(cwd = root, stderr = os.Pipe) + expect(r.out.trim() == macroSettings.mkString(", ")) + } + } } diff --git a/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala b/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala index f8c52ba926..b29c6ce6ff 100644 --- a/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala +++ b/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala @@ -24,6 +24,7 @@ object ScalacOpt { def noDashPrefixes: String = opt.stripPrefix("--").stripPrefix("-") } private val repeatingKeys = Set( + "Xmacro-settings", "Xplugin", "P", // plugin options "language", From a5db176ff8015c23a4e02480e9eb2d4f63c2fc38 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Thu, 31 Oct 2024 15:18:48 +0100 Subject: [PATCH 7/8] Add support for misc repeatable compiler options: - -coverage-exclude-classlikes - -coverage-exclude-files - -Wshadow - -Xlint - -Xplugin-disable - -Xplugin-require - -Yimports - -Yfrom-tasty-ignore-list --- .../cli/commands/shared/ScalacOptions.scala | 2 ++ .../scala/scala/build/options/ScalacOpt.scala | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala index f19d5f8f52..08ec966079 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala @@ -50,6 +50,8 @@ object ScalacOptions { private val scalacOptionsPrefixes = Set("P") ++ scalacOptionsPurePrefixes private val scalacAliasedOptions = // these options don't require being passed after -O and accept an arg Set( + "coverage-exclude-classlikes", + "coverage-exclude-files", "encoding", "release", "color", diff --git a/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala b/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala index b29c6ce6ff..ff7b0dfc5d 100644 --- a/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala +++ b/modules/options/src/main/scala/scala/build/options/ScalacOpt.scala @@ -24,12 +24,20 @@ object ScalacOpt { def noDashPrefixes: String = opt.stripPrefix("--").stripPrefix("-") } private val repeatingKeys = Set( - "Xmacro-settings", - "Xplugin", - "P", // plugin options + "coverage-exclude-classlikes", + "coverage-exclude-files", "language", + "P", // plugin options "Wconf", - "Wunused" + "Wunused", + "Wshadow", + "Xlint", + "Xmacro-settings", + "Xplugin", + "Xplugin-disable", + "Xplugin-require", + "Yimports", + "Yfrom-tasty-ignore-list" ) implicit val hashedType: HashedType[ScalacOpt] = { From 8e44678682be7655079f5232946bca7571575d08 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Thu, 31 Oct 2024 15:27:37 +0100 Subject: [PATCH 8/8] Support `-experimental`/`--experimental` and `-explain`/ `--explain` compiler flags without `-O` --- .../main/scala/scala/cli/commands/shared/ScalacOptions.scala | 2 ++ .../cli/integration/RunScalacCompatTestDefinitions.scala | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala index 08ec966079..e3bbd220a8 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala @@ -64,6 +64,8 @@ object ScalacOptions { ) private val scalacNoArgAliasedOptions = // these options don't require being passed after -O and don't accept an arg Set( + "experimental", + "explain", "unchecked", "nowarn", "feature", diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala index c8c58f1836..9ef2a8e7c0 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunScalacCompatTestDefinitions.scala @@ -597,10 +597,9 @@ trait RunScalacCompatTestDefinitions { TestUtil.cli, "run", ".", - "-O", - "-experimental", if (useDirective) Nil else macroSettingOptions, - extraOptions + extraOptions, + "-experimental" ) .call(cwd = root, stderr = os.Pipe) expect(r.out.trim() == macroSettings.mkString(", "))