Skip to content

Exit on main arg parse error #22947

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/MainProxies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import core.*
import Symbols.*, Types.*, Contexts.*, Decorators.*, util.Spans.*, Flags.*, Constants.*
import StdNames.{nme, tpnme}
import ast.Trees.*
import Names.Name
import Names.{Name, termName}
import Comments.Comment
import NameKinds.DefaultGetterName
import Annotations.Annotation
Expand Down Expand Up @@ -94,7 +94,13 @@ object MainProxies {
val handler = CaseDef(
Typed(errVar, TypeTree(defn.CLP_ParseError.typeRef)),
EmptyTree,
Apply(ref(defn.CLP_showError.termRef), errVar :: Nil))
Block(
List(Apply(ref(defn.CLP_showError.termRef), errVar :: Nil)),
Throw(errVar)
//Apply(Select(Select(Select(Ident(termName("java")), termName("lang")), termName("System")), termName("exit")),
// List(Literal(Constant(1))))
)
)
val body = Try(call, handler :: Nil, EmptyTree)
val mainArg = ValDef(nme.args, TypeTree(defn.ArrayType.appliedTo(defn.StringType)), EmptyTree)
.withFlags(Param)
Expand Down
27 changes: 16 additions & 11 deletions compiler/test/dotty/tools/utils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import scala.io.Source
import scala.jdk.StreamConverters._
import scala.reflect.ClassTag
import scala.util.Using.resource
import scala.util.chaining.given
import scala.util.control.{ControlThrowable, NonFatal}

import dotc.config.CommandLineParser
import dotc.util.chaining.*

object Dummy

Expand Down Expand Up @@ -76,7 +76,11 @@ object ToolName:
def named(s: String): ToolName = values.find(_.toString.equalsIgnoreCase(s)).getOrElse(throw IllegalArgumentException(s))

type ToolArgs = Map[ToolName, List[String]]
object ToolArgs:
def empty = Map.empty[ToolName, List[String]]
type PlatformFiles = Map[TestPlatform, List[String]]
object PlatformFiles:
def empty = Map.empty[TestPlatform, List[String]]

/** Take a prefix of each file, extract tool args, parse, and combine.
* Arg parsing respects quotation marks. Result is a map from ToolName to the combined tokens.
Expand All @@ -90,21 +94,23 @@ def toolArgsFor(files: List[JPath], charset: Charset = UTF_8): ToolArgs =
* If the ToolName is Target, then also accumulate the file name associated with the given platform.
*/
def platformAndToolArgsFor(files: List[JPath], charset: Charset = UTF_8): (PlatformFiles, ToolArgs) =
files.foldLeft(Map.empty[TestPlatform, List[String]] -> Map.empty[ToolName, List[String]]) { (res, path) =>
files.foldLeft((PlatformFiles.empty, ToolArgs.empty)) { (res, path) =>
val toolargs = toolArgsParse(resource(Files.lines(path, charset))(_.limit(10).toScala(List)), Some(path.toString))
toolargs.foldLeft(res) {
case ((plat, acc), (tool, args)) =>
case ((plats, tools), (tool, args)) =>
val name = ToolName.named(tool)
val tokens = CommandLineParser.tokenize(args)

val plat1 = if name eq ToolName.Target then
val plats1 = if name eq ToolName.Target then
val testPlatform = TestPlatform.named(tokens.head)
val fileName = path.toString
plat.updatedWith(testPlatform)(_.map(fileName :: _).orElse(Some(fileName :: Nil)))
plats.updatedWith(testPlatform)(_.map(fileName :: _).orElse(Some(fileName :: Nil)))
else
plat
plats

plat1 -> acc.updatedWith(name)(v0 => v0.map(_ ++ tokens).orElse(Some(tokens)))
val tools1 = tools.updatedWith(name)(v0 => v0.map(_ ++ tokens).orElse(Some(tokens)))

(plats1, tools1)
}
}

Expand All @@ -131,19 +137,18 @@ private val directiveUnknown = raw"//> using (.*)".r.unanchored
// If args string ends in close comment, stop at the `*` `/`.
// Returns all the matches by the regex.
def toolArgsParse(lines: List[String], filename: Option[String]): List[(String,String)] =
lines.flatMap {
lines.flatMap:
case toolArg("scalac", _) => sys.error(s"`// scalac: args` not supported. Please use `//> using options args`${filename.fold("")(f => s" in file $f")}")
case toolArg("javac", _) => sys.error(s"`// javac: args` not supported. Please use `//> using javacOpt args`${filename.fold("")(f => s" in file $f")}")
case toolArg(name, args) => List((name, args))
case _ => Nil
} ++
lines.flatMap {
++
lines.flatMap:
case directiveOptionsArg(args) => List(("scalac", args))
case directiveJavacOptions(args) => List(("javac", args))
case directiveTargetOptions(platform) => List(("target", platform))
case directiveUnknown(rest) => sys.error(s"Unknown directive: `//> using ${CommandLineParser.tokenize(rest).headOption.getOrElse("''")}`${filename.fold("")(f => s" in file $f")}")
case _ => Nil
}

import org.junit.Test
import org.junit.Assert._
Expand Down
21 changes: 13 additions & 8 deletions compiler/test/dotty/tools/vulpix/ChildJVMMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ public class ChildJVMMain {
static final String MessageStart = "##THIS IS THE START FOR ME, HELLO##";
static final String MessageEnd = "##THIS IS THE END FOR ME, GOODBYE##";

private static void runMain(String dir) throws Exception {
private static void runMain(String line) throws Exception {
Method meth = null;
Object[] args = new Object[]{ new String[]{ } };
String[] args = new String[]{ };
try {
String[] tokens = line.split("\\s+", 2);
String dir = tokens[0];
if (tokens.length > 1) {
args = tokens[1].split("\\s+");
}
String jcp = System.getProperty("java.class.path");
String sep = File.pathSeparator;
System.setProperty("java.class.path", jcp == null ? dir : dir + sep + jcp);
Expand All @@ -37,15 +42,15 @@ private static void runMain(String dir) throws Exception {
}
System.out.println(MessageStart);

meth.invoke(null, args);
meth.invoke(null, new Object[] { args });
}

public static void main(String[] args) throws Exception {
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));

while (true) {
runMain(stdin.readLine());
System.out.println(MessageEnd);
}
while (true) {
runMain(stdin.readLine());
System.out.println(MessageEnd);
}
}
}
17 changes: 9 additions & 8 deletions compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ trait RunnerOrchestration {

/** Running a `Test` class's main method from the specified `classpath` */
def runMain(classPath: String, toolArgs: ToolArgs)(implicit summaryReport: SummaryReporting): Status =
monitor.runMain(classPath)
monitor.runMain(classPath, toolArgs.getOrElse(ToolName.Java, Nil))

/** Each method of Debuggee can be called only once, in the order of definition.*/
trait Debuggee:
Expand Down Expand Up @@ -86,8 +86,8 @@ trait RunnerOrchestration {
*/
private class RunnerMonitor {

def runMain(classPath: String)(implicit summaryReport: SummaryReporting): Status =
withRunner(_.runMain(classPath))
def runMain(classPath: String, args: List[String])(implicit summaryReport: SummaryReporting): Status =
withRunner(_.runMain(classPath, args))

def debugMain(classPath: String)(f: Debuggee => Unit)(implicit summaryReport: SummaryReporting): Unit =
withRunner(_.debugMain(classPath)(f))
Expand Down Expand Up @@ -132,17 +132,17 @@ trait RunnerOrchestration {
process = null

/** Blocks less than `maxDuration` while running `Test.main` from `dir` */
def runMain(classPath: String): Status =
def runMain(classPath: String, args: List[String]): Status =
assert(process ne null, "Runner was killed and then reused without setting a new process")
awaitStatusOrRespawn(startMain(classPath))
awaitStatusOrRespawn(startMain(classPath, args))

def debugMain(classPath: String)(f: Debuggee => Unit): Unit =
assert(process ne null, "Runner was killed and then reused without setting a new process")

val debuggee = new Debuggee:
private var mainFuture: Future[Status] = null
def readJdiPort(): Int = process.getJdiPort()
def launch(): Unit = mainFuture = startMain(classPath)
def launch(): Unit = mainFuture = startMain(classPath, Nil)
def exit(): Status =
awaitStatusOrRespawn(mainFuture)

Expand All @@ -153,9 +153,10 @@ trait RunnerOrchestration {
throw e
end debugMain

private def startMain(classPath: String): Future[Status] =
private def startMain(classPath: String, args: List[String]): Future[Status] =
val line = if args.isEmpty then classPath else s"$classPath ${args.mkString(" ")}"
// pass classpath to running process
process.printLine(classPath)
process.printLine(line)

// Create a future reading the object:
Future:
Expand Down
17 changes: 8 additions & 9 deletions library/src/scala/util/CommandLineParser.scala
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
package scala.util

import scala.util.control.ControlThrowable

/** A utility object to support command line parsing for @main methods */
object CommandLineParser {

/** An exception raised for an illegal command line
* @param idx The index of the argument that's faulty (starting from 0)
* @param msg The error message
*/
class ParseError(val idx: Int, val msg: String) extends Exception
* @param idx The index of the argument that's faulty (starting from 0)
* @param msg The error message
*/
class ParseError(val idx: Int, val msg: String) extends Exception(msg, null, false, false)

/** Parse command line argument `s`, which has index `n`, as a value of type `T`
* @throws ParseError if argument cannot be converted to type `T`.
*/
def parseString[T](str: String, n: Int)(using fs: FromString[T]): T = {
def parseString[T](str: String, n: Int)(using fs: FromString[T]): T =
try fs.fromString(str)
catch {
case ex: IllegalArgumentException => throw ParseError(n, ex.toString)
}
}
catch case ex: IllegalArgumentException => throw ParseError(n, ex.toString)

/** Parse `n`'th argument in `args` (counting from 0) as a value of type `T`
* @throws ParseError if argument does not exist or cannot be converted to type `T`.
Expand Down
1 change: 1 addition & 0 deletions tests/printing/infix-operations.check
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ package <empty> {
{
case error @ _:scala.util.CommandLineParser.ParseError =>
scala.util.CommandLineParser.showError(error)
throw error
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion tests/run/Pouring.check
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Illegal command line: more arguments expected
Moves: Vector(Empty(0), Empty(1), Fill(0), Fill(1), Pour(0,1), Pour(1,0))
Solution: Some(Fill(1) Pour(1,0) Empty(0) Pour(1,0) Fill(1) Pour(1,0) --> Vector(4, 6))
2 changes: 2 additions & 0 deletions tests/run/Pouring.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// java: 6 4 7

type Glass = Int
type Levels = Vector[Int]

Expand Down
Loading