Skip to content

Commit e0e2942

Browse files
committed
Stop trapping on validation errors
With backtrace support built into command-line Swift, calling `fatalError` for configuration/validation errors isn't very effective, since the error messages get hidden by the backtrace. This switches to simply printing the validation message to stderr and exiting with a failing error code, which possibly should have always been the behavior.
1 parent 964da9f commit e0e2942

File tree

8 files changed

+33
-36
lines changed

8 files changed

+33
-36
lines changed

Sources/ArgumentParser/Parsable Properties/Argument.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public struct Argument<Value>:
8282
case .value(let v):
8383
return v
8484
case .definition:
85-
fatalError(directlyInitializedError)
85+
configurationFailure(directlyInitializedError)
8686
}
8787
}
8888
set {

Sources/ArgumentParser/Parsable Properties/Flag.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public struct Flag<Value>: Decodable, ParsedWrapper {
107107
case .value(let v):
108108
return v
109109
case .definition:
110-
fatalError(directlyInitializedError)
110+
configurationFailure(directlyInitializedError)
111111
}
112112
}
113113
set {

Sources/ArgumentParser/Parsable Properties/Option.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public struct Option<Value>: Decodable, ParsedWrapper {
8787
case .value(let v):
8888
return v
8989
case .definition:
90-
fatalError(directlyInitializedError)
90+
configurationFailure(directlyInitializedError)
9191
}
9292
}
9393
set {

Sources/ArgumentParser/Parsable Properties/OptionGroup.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public struct OptionGroup<Value: ParsableArguments>: Decodable, ParsedWrapper {
101101
case .value(let v):
102102
return v
103103
case .definition:
104-
fatalError(directlyInitializedError)
104+
configurationFailure(directlyInitializedError)
105105
}
106106
}
107107
set {

Sources/ArgumentParser/Parsable Types/ParsableArguments.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ extension ArgumentSet {
290290
do {
291291
try type._validate(parent: parent)
292292
} catch {
293-
assertionFailure("\(error)")
293+
configurationFailure("\(error)")
294294
}
295295
#endif
296296

@@ -321,11 +321,23 @@ extension ArgumentSet {
321321
}
322322
}
323323

324+
/// Prints the given message to standard error and exits with a failure code.
325+
///
326+
/// - Parameter message: The message to print to standard error. `message`
327+
/// should be pre-wrapped, if desired.
328+
func configurationFailure(_ message: String) -> Never {
329+
var errorOut = Platform.standardError
330+
print("\n")
331+
print(String(repeating: "-", count: 70))
332+
print(message, to: &errorOut)
333+
print(String(repeating: "-", count: 70))
334+
print("\n")
335+
Platform.exit(Platform.exitCodeFailure)
336+
}
337+
324338
/// The fatal error message to display when someone accesses a
325339
/// `ParsableArguments` type after initializing it directly.
326340
internal let directlyInitializedError = """
327-
328-
--------------------------------------------------------------------
329341
Can't read a value from a parsable argument definition.
330342
331343
This error indicates that a property declared with an `@Argument`,
@@ -335,6 +347,4 @@ internal let directlyInitializedError = """
335347
To get a valid value, either call one of the static parsing methods
336348
(`parse`, `parseAsRoot`, or `main`) or define an initializer that
337349
initializes _every_ property of your parsable type.
338-
--------------------------------------------------------------------
339-
340350
"""

Sources/ArgumentParser/Parsable Types/ParsableCommand.swift

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -215,17 +215,13 @@ extension ParsableCommand {
215215
guard sub.configuration.subcommands.isEmpty else { continue }
216216
guard sub is AsyncParsableCommand.Type else { continue }
217217

218-
fatalError(
218+
configurationFailure(
219219
"""
220-
221-
--------------------------------------------------------------------
222220
Asynchronous subcommand of a synchronous root.
223221
224222
The asynchronous command `\(sub)` is declared as a subcommand of the synchronous root command `\(root)`.
225223
226224
With this configuration, your asynchronous `run()` method will not be called. To fix this issue, change `\(root)`'s `ParsableCommand` conformance to `AsyncParsableCommand`.
227-
--------------------------------------------------------------------
228-
229225
""".wrapped(to: 70))
230226
}
231227
}
@@ -256,25 +252,19 @@ extension ParsableCommand {
256252
func failAsyncHierarchy(
257253
rootCommand: ParsableCommand.Type, subCommand: ParsableCommand.Type
258254
) -> Never {
259-
fatalError(
255+
configurationFailure(
260256
"""
261-
262-
--------------------------------------------------------------------
263257
Asynchronous subcommand of a synchronous root.
264258
265259
The asynchronous command `\(subCommand)` is declared as a subcommand of the synchronous root command `\(rootCommand)`.
266260
267261
With this configuration, your asynchronous `run()` method will not be called. To fix this issue, change `\(rootCommand)`'s `ParsableCommand` conformance to `AsyncParsableCommand`.
268-
--------------------------------------------------------------------
269-
270262
""".wrapped(to: 70))
271263
}
272264

273265
func failAsyncPlatform(rootCommand: ParsableCommand.Type) -> Never {
274-
fatalError(
266+
configurationFailure(
275267
"""
276-
277-
--------------------------------------------------------------------
278268
Asynchronous root command needs availability annotation.
279269
280270
The asynchronous root command `\(rootCommand)` needs an availability annotation in order to be executed asynchronously. To fix this issue, add the following availability attribute to your `\(rootCommand)` declaration or set the minimum platform in your "Package.swift" file.
@@ -283,7 +273,5 @@ func failAsyncPlatform(rootCommand: ParsableCommand.Type) -> Never {
283273
+ """
284274
285275
@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
286-
--------------------------------------------------------------------
287-
288276
""")
289277
}

Sources/ArgumentParser/Parsing/CommandParser.swift

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,18 @@ struct CommandParser {
4848
} catch Tree<ParsableCommand.Type>.InitializationError.recursiveSubcommand(
4949
let command)
5050
{
51-
fatalError(
52-
"The ParsableCommand \"\(command)\" can't have itself as its own subcommand."
53-
)
54-
} catch Tree<ParsableCommand.Type>.InitializationError.aliasMatchingCommand(
55-
let command)
51+
configurationFailure(
52+
"""
53+
The command \"\(command)\" can't have itself as its own subcommand.
54+
""".wrapped(to: 70))
55+
} catch Tree<ParsableCommand.Type>
56+
.InitializationError.aliasMatchingCommand(let command)
5657
{
57-
fatalError(
58-
"The ParsableCommand \"\(command)\" can't have an alias with the same name as the command itself."
59-
)
58+
configurationFailure(
59+
"""
60+
The command \"\(command)\" can't have an alias with the same name \
61+
as the command itself.
62+
""".wrapped(to: 70))
6063
} catch {
6164
fatalError("Unexpected error: \(error).")
6265
}

Sources/ArgumentParser/Validators/ParsableArgumentsValidation.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,9 @@ struct ParsableArgumentsValidationError: Error, CustomStringConvertible {
5454
.hangingIndentingEachLine(by: 2)
5555
}
5656
return """
57-
58-
--------------------------------------------------------------------
5957
Validation failed for `\(parsableArgumentsType)`:
6058
6159
\(errorDescriptions.joined(separator: "\n"))
62-
--------------------------------------------------------------------
63-
6460
"""
6561
}
6662
}

0 commit comments

Comments
 (0)