Skip to content

Commit d547504

Browse files
authored
Merge pull request #39 from owenv/allowed_opts
Diagnose unsupported options and add descriptions for option-parsing errors
2 parents a8d3820 + 6560a6b commit d547504

File tree

5 files changed

+50
-14
lines changed

5 files changed

+50
-14
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,11 @@ public struct Driver {
191191
public init(
192192
args: [String],
193193
env: [String: String] = ProcessEnv.vars,
194-
diagnosticsHandler: @escaping DiagnosticsEngine.DiagnosticsHandler = Driver.stderrDiagnosticsHandler
194+
diagnosticsEngine: DiagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler])
195195
) throws {
196196
self.env = env
197197

198-
self.diagnosticEngine = DiagnosticsEngine(handlers: [diagnosticsHandler])
198+
self.diagnosticEngine = diagnosticsEngine
199199

200200
if case .subcommand = try Self.invocationRunMode(forArgs: args).mode {
201201
throw Error.subcommandPassedToDriver
@@ -205,7 +205,7 @@ public struct Driver {
205205

206206
self.driverKind = try Self.determineDriverKind(args: &args)
207207
self.optionTable = OptionTable()
208-
self.parsedOptions = try optionTable.parse(Array(args), forInteractiveMode: self.driverKind == .interactive)
208+
self.parsedOptions = try optionTable.parse(Array(args), for: self.driverKind)
209209

210210
let explicitTarget = (self.parsedOptions.getLastArgument(.target)?.asSingle)
211211
.map {

Sources/SwiftDriver/Options/OptionParsing.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,34 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12-
public enum OptionParseError : Error, Equatable {
12+
import TSCBasic
13+
14+
public enum OptionParseError : Error, Equatable, DiagnosticData {
1315
case unknownOption(index: Int, argument: String)
1416
case missingArgument(index: Int, argument: String)
17+
case unsupportedOption(index: Int, argument: String, option: Option, currentDriverKind: DriverKind)
18+
19+
public var description: String {
20+
switch self {
21+
case let .unknownOption(index: _, argument: arg):
22+
return "unknown argument: '\(arg)'"
23+
case let .missingArgument(index: _, argument: arg):
24+
return "missing argument value for '\(arg)'"
25+
case let .unsupportedOption(index: _, argument: arg, option: option, currentDriverKind: driverKind):
26+
// TODO: This logic to choose the recommended kind is copied from the C++
27+
// driver and could be improved.
28+
let recommendedDriverKind: DriverKind = option.attributes.contains(.noBatch) ? .interactive : .batch
29+
return "option '\(arg)' is not supported by '\(driverKind.usage)'; did you mean to use '\(recommendedDriverKind.usage)'?"
30+
}
31+
}
1532
}
1633

1734
extension OptionTable {
1835
/// Parse the given command-line arguments into a set of options.
1936
///
2037
/// Throws an error if the command line contains any errors.
2138
public func parse(_ arguments: [String],
22-
forInteractiveMode isInteractiveMode: Bool = false) throws -> ParsedOptions {
39+
for driverKind: DriverKind) throws -> ParsedOptions {
2340
var trie = PrefixTrie<String.UTF8View, Option>()
2441
for opt in options {
2542
trie[opt.spelling.utf8] = opt
@@ -42,7 +59,7 @@ extension OptionTable {
4259
parsedOptions.addInput(argument)
4360

4461
// In interactive mode, synthesize a "--" argument for all args after the first input.
45-
if isInteractiveMode && index < arguments.endIndex {
62+
if driverKind == .interactive && index < arguments.endIndex {
4663
parsedOptions.addOption(.DASHDASH, argument: .multiple(Array(arguments[index...])))
4764
break
4865
}
@@ -59,6 +76,13 @@ extension OptionTable {
5976
index: index - 1, argument: argument)
6077
}
6178

79+
// Make sure this option is supported by the current driver kind.
80+
guard option.isAccepted(by: driverKind) else {
81+
throw OptionParseError.unsupportedOption(
82+
index: index - 1, argument: argument, option: option,
83+
currentDriverKind: driverKind)
84+
}
85+
6286
// Translate the argument
6387
switch option.kind {
6488
case .input:

Sources/swift-driver/main.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import TSCBasic
1616
import TSCUtility
1717

1818
var intHandler: InterruptHandler?
19+
let diagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler])
1920

2021
do {
2122
let processSet = ProcessSet()
@@ -39,7 +40,7 @@ do {
3940
try exec(path: subcommandPath?.pathString ?? "", args: Array(arguments.dropFirst()))
4041
}
4142

42-
var driver = try Driver(args: arguments)
43+
var driver = try Driver(args: arguments, diagnosticsEngine: diagnosticsEngine)
4344
let resolver = try ArgsResolver()
4445
try driver.run(resolver: resolver, processSet: processSet)
4546

@@ -48,6 +49,8 @@ do {
4849
}
4950
} catch Diagnostics.fatalError {
5051
exit(EXIT_FAILURE)
52+
} catch let diagnosticData as DiagnosticData {
53+
diagnosticsEngine.emit(.error(diagnosticData))
5154
} catch {
5255
print("error: \(error)")
5356
exit(EXIT_FAILURE)

Tests/SwiftDriverTests/Helpers/AssertDiagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fileprivate func assertDriverDiagnostics(
2323
let matcher = DiagnosticVerifier()
2424
defer { matcher.verify(file: file, line: line) }
2525

26-
var driver = try Driver(args: args, env: env, diagnosticsHandler: matcher.emit(_:))
26+
var driver = try Driver(args: args, env: env, diagnosticsEngine: DiagnosticsEngine(handlers: [matcher.emit(_:)]))
2727
try body(&driver, matcher)
2828
}
2929

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,38 @@ final class SwiftDriverTests: XCTestCase {
2121
let results = try options.parse([
2222
"input1", "-color-diagnostics", "-Ifoo", "-I", "bar spaces",
2323
"-I=wibble", "input2", "-module-name", "main",
24-
"-sanitize=a,b,c", "--", "-foo", "-bar"])
24+
"-sanitize=a,b,c", "--", "-foo", "-bar"], for: .batch)
2525
XCTAssertEqual(results.description,
2626
"input1 -color-diagnostics -I foo -I 'bar spaces' -I=wibble input2 -module-name main -sanitize=a,b,c -- -foo -bar")
2727
}
2828

2929
func testParseErrors() {
3030
let options = OptionTable()
3131

32-
XCTAssertThrowsError(try options.parse(["-unrecognized"])) { error in
32+
XCTAssertThrowsError(try options.parse(["-unrecognized"], for: .batch)) { error in
3333
XCTAssertEqual(error as? OptionParseError, .unknownOption(index: 0, argument: "-unrecognized"))
3434
}
3535

36-
XCTAssertThrowsError(try options.parse(["-I"])) { error in
36+
XCTAssertThrowsError(try options.parse(["-I"], for: .batch)) { error in
3737
XCTAssertEqual(error as? OptionParseError, .missingArgument(index: 0, argument: "-I"))
3838
}
3939

40-
XCTAssertThrowsError(try options.parse(["-color-diagnostics", "-I"])) { error in
40+
XCTAssertThrowsError(try options.parse(["-color-diagnostics", "-I"], for: .batch)) { error in
4141
XCTAssertEqual(error as? OptionParseError, .missingArgument(index: 1, argument: "-I"))
4242
}
4343

44-
XCTAssertThrowsError(try options.parse(["-module-name"])) { error in
44+
XCTAssertThrowsError(try options.parse(["-module-name"], for: .batch)) { error in
4545
XCTAssertEqual(error as? OptionParseError, .missingArgument(index: 0, argument: "-module-name"))
4646
}
47+
48+
XCTAssertThrowsError(try options.parse(["-o"], for: .interactive)) { error in
49+
XCTAssertEqual(error as? OptionParseError, .unsupportedOption(index: 0, argument: "-o", option: .o, currentDriverKind: .interactive))
50+
}
51+
52+
XCTAssertThrowsError(try options.parse(["-repl"], for: .batch)) { error in
53+
XCTAssertEqual(error as? OptionParseError, .unsupportedOption(index: 0, argument: "-repl", option: .repl, currentDriverKind: .batch))
54+
}
55+
4756
}
4857

4958
func testInvocationRunModes() throws {
@@ -293,7 +302,7 @@ final class SwiftDriverTests: XCTestCase {
293302
XCTAssertEqual(driver.moduleName, "main")
294303
}
295304

296-
try assertNoDriverDiagnostics(args: "swiftc", "-repl") { driver in
305+
try assertNoDriverDiagnostics(args: "swift", "-repl") { driver in
297306
XCTAssertNil(driver.moduleOutput)
298307
XCTAssertEqual(driver.moduleName, "REPL")
299308
}

0 commit comments

Comments
 (0)