Skip to content

Commit 077dbad

Browse files
authored
Fix repeatable compiler options handling from the command line (#2666)
1 parent 511ff13 commit 077dbad

File tree

4 files changed

+122
-68
lines changed

4 files changed

+122
-68
lines changed

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

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

33
import scala.build.Logger
4+
import scala.build.options.ScalacOpt.filterScalacOptionKeys
45
import scala.build.options.{ScalacOpt, ShadowingSeq}
56
import scala.cli.commands.bloop.BloopExit
67
import scala.cli.commands.default.LegacyScalaOptions
@@ -31,8 +32,6 @@ object ScalacOptionsUtil {
3132
}
3233

3334
extension (opts: ShadowingSeq[ScalacOpt]) {
34-
def filterScalacOptionKeys(f: String => Boolean): ShadowingSeq[ScalacOpt] =
35-
opts.filterKeys(_.key.exists(f))
3635
def filterNonRedirected: ShadowingSeq[ScalacOpt] =
3736
opts.filterScalacOptionKeys(!ScalacOptions.ScalaCliRedirectedOptions.contains(_))
3837
def filterNonDeprecated: ShadowingSeq[ScalacOpt] =

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

+69-57
Original file line numberDiff line numberDiff line change
@@ -10,64 +10,76 @@ trait CompilerPluginTestDefinitions { _: CompileTestDefinitions =>
1010
CompilerPluginUtil.compilerPluginForScala3(pluginName, pluginErrorMsg)
1111
else CompilerPluginUtil.compilerPluginForScala2(pluginName, pluginErrorMsg)
1212

13-
test("build a custom compiler plugin and use it") {
14-
val pluginName = "divbyzero"
15-
val usePluginFile = "Main.scala"
16-
val outputJar = "div-by-zero.jar"
17-
val pluginErrorMsg = "definitely division by zero"
18-
compilerPluginInputs(pluginName, pluginErrorMsg)
19-
.add(os.rel / usePluginFile ->
20-
s"""//> using option -Xplugin:$outputJar
21-
|
22-
|object Test {
23-
| val five = 5
24-
| val amount = five / 0
25-
| def main(args: Array[String]): Unit = {
26-
| println(amount)
27-
| }
28-
|}
29-
|""".stripMargin)
30-
.fromRoot { root =>
31-
// build the compiler plugin
32-
os.proc(
33-
TestUtil.cli,
34-
"package",
35-
s"$pluginName.scala",
36-
"--power",
37-
"--with-compiler",
38-
"--library",
39-
"-o",
40-
outputJar,
41-
extraOptions
42-
).call(cwd = root)
43-
expect(os.isFile(root / outputJar))
13+
for {
14+
pluginViaDirective <- Seq(true, false)
15+
testLabel = if (pluginViaDirective) "use plugin via directive" else "use plugin via CLI option"
16+
}
17+
test(s"build a custom compiler plugin and use it ($testLabel)") {
18+
val pluginName = "divbyzero"
19+
val usePluginFile = "Main.scala"
20+
val outputJar = "div-by-zero.jar"
21+
val pluginErrorMsg = "definitely division by zero"
22+
compilerPluginInputs(pluginName, pluginErrorMsg)
23+
.add(os.rel / usePluginFile ->
24+
s"""${if (pluginViaDirective) s"//> using option -Xplugin:$outputJar" else ""}
25+
|
26+
|object Test {
27+
| val five = 5
28+
| val amount = five / 0
29+
| def main(args: Array[String]): Unit = {
30+
| println(amount)
31+
| }
32+
|}
33+
|""".stripMargin)
34+
.fromRoot { root =>
35+
// build the compiler plugin
36+
os.proc(
37+
TestUtil.cli,
38+
"package",
39+
s"$pluginName.scala",
40+
"--power",
41+
"--with-compiler",
42+
"--library",
43+
"-o",
44+
outputJar,
45+
extraOptions
46+
).call(cwd = root)
47+
expect(os.isFile(root / outputJar))
4448

45-
// verify the plugin is loaded
46-
val pluginListResult = os.proc(
47-
TestUtil.cli,
48-
"compile",
49-
s"-Xplugin:$outputJar",
50-
"-Xplugin-list",
51-
extraOptions
52-
).call(cwd = root, mergeErrIntoOut = true)
53-
expect(pluginListResult.out.text().contains(pluginName))
49+
// verify the plugin is loaded
50+
val pluginListResult = os.proc(
51+
TestUtil.cli,
52+
"compile",
53+
s"-Xplugin:$outputJar",
54+
"-Xplugin-list",
55+
extraOptions
56+
).call(cwd = root, mergeErrIntoOut = true)
57+
expect(pluginListResult.out.text().contains(pluginName))
5458

55-
// verify the compiler plugin phase is being added correctly
56-
os.proc(
57-
TestUtil.cli,
58-
"compile",
59-
s"-Xplugin:$outputJar",
60-
"-Xshow-phases",
61-
extraOptions
62-
).call(cwd = root, mergeErrIntoOut = true)
63-
expect(pluginListResult.out.text().contains(pluginName))
59+
// verify the compiler plugin phase is being added correctly
60+
os.proc(
61+
TestUtil.cli,
62+
"compile",
63+
s"-Xplugin:$outputJar",
64+
"-Xshow-phases",
65+
extraOptions
66+
).call(cwd = root, mergeErrIntoOut = true)
67+
expect(pluginListResult.out.text().contains(pluginName))
6468

65-
// verify the compiler plugin is working
66-
// TODO: this shouldn't require running with --server=false
67-
val res = os.proc(TestUtil.cli, "compile", usePluginFile, "--server=false", extraOptions)
68-
.call(cwd = root, mergeErrIntoOut = true, check = false)
69-
expect(res.exitCode == 1)
70-
expect(res.out.text().contains(pluginErrorMsg))
71-
}
72-
}
69+
val pluginOptions = if (pluginViaDirective) Nil else Seq(s"-Xplugin:$outputJar")
70+
// verify the compiler plugin is working
71+
// TODO: this shouldn't require running with --server=false
72+
val res = os.proc(
73+
TestUtil.cli,
74+
"compile",
75+
pluginOptions,
76+
usePluginFile,
77+
"--server=false",
78+
extraOptions
79+
)
80+
.call(cwd = root, mergeErrIntoOut = true, check = false)
81+
expect(res.exitCode == 1)
82+
expect(res.out.text().contains(pluginErrorMsg))
83+
}
84+
}
7385
}

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

+37
Original file line numberDiff line numberDiff line change
@@ -458,4 +458,41 @@ class SipScalaTests extends ScalaCliSuite {
458458
)
459459
}
460460
}
461+
462+
test(s"code using scala-continuations should compile for Scala 2.12.2") {
463+
val sourceFileName = "example.scala"
464+
TestInputs(os.rel / sourceFileName ->
465+
"""import scala.util.continuations._
466+
|
467+
|object ContinuationsExample extends App {
468+
| def generator(init: Int): Int @cps[Unit] = {
469+
| shift { k: (Int => Unit) =>
470+
| for (i <- init to 10) k(i)
471+
| }
472+
| 0 // We never reach this point, but it enables the function to compile.
473+
| }
474+
|
475+
| reset {
476+
| val result = generator(1)
477+
| println(result)
478+
| }
479+
|}
480+
|""".stripMargin).fromRoot { root =>
481+
val continuationsVersion = "1.0.3"
482+
val res = os.proc(
483+
TestUtil.cli,
484+
"compile",
485+
sourceFileName,
486+
"--compiler-plugin",
487+
s"org.scala-lang.plugins:::scala-continuations-plugin:$continuationsVersion",
488+
"--dependency",
489+
s"org.scala-lang.plugins::scala-continuations-library:$continuationsVersion",
490+
"-P:continuations:enable",
491+
"-S",
492+
"2.12.2"
493+
)
494+
.call(cwd = root)
495+
expect(res.exitCode == 0)
496+
}
497+
}
461498
}

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

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package scala.build.options
22

33
final case class ScalacOpt(value: String) {
4-
def key: Option[String] =
5-
if (value.startsWith("-"))
6-
Some(value.takeWhile(_ != ':'))
7-
.filterNot(key => ScalacOpt.repeatingKeys.exists(_.startsWith(key)))
8-
else if (value.startsWith("@"))
9-
Some("@")
10-
else
11-
None
4+
5+
/** @return raw key for the option (if valid) */
6+
private[options] def key: Option[String] =
7+
if value.startsWith("-") then Some(value.takeWhile(_ != ':'))
8+
else Some("@").filter(value.startsWith)
9+
10+
/** @return raw key for the option (only if the key can be shadowed from the CLI) */
11+
private[options] def shadowableKey: Option[String] =
12+
key.filterNot(key => ScalacOpt.repeatingKeys.exists(_.startsWith(key)))
1213
}
1314

1415
object ScalacOpt {
@@ -22,7 +23,7 @@ object ScalacOpt {
2223
}
2324
implicit val keyOf: ShadowingSeq.KeyOf[ScalacOpt] =
2425
ShadowingSeq.KeyOf(
25-
_.key,
26+
_.shadowableKey,
2627
seq => groupCliOptions(seq.map(_.value))
2728
)
2829

@@ -34,4 +35,9 @@ object ScalacOpt {
3435
case (opt, idx) if opt.startsWith("-") || opt.startsWith("@") =>
3536
idx
3637
}
38+
39+
extension (opts: ShadowingSeq[ScalacOpt]) {
40+
def filterScalacOptionKeys(f: String => Boolean): ShadowingSeq[ScalacOpt] =
41+
opts.filterKeys(_.key.exists(f))
42+
}
3743
}

0 commit comments

Comments
 (0)