From 2864f259821d8c199e9db85170305e4182df5e98 Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Thu, 26 Jun 2025 13:28:22 -0500 Subject: [PATCH 1/3] Handle Swift 6.2 sendability changes --- .../ArgumentParser/Parsable Types/ExpressibleByArgument.swift | 2 +- Sources/ArgumentParser/Parsable Types/ParsableArguments.swift | 2 +- .../CustomParsingEndToEndTests.swift | 4 +++- Tests/ArgumentParserUnitTests/HelpGenerationTests.swift | 2 ++ Tests/ArgumentParserUnitTests/UsageGenerationTests.swift | 1 + 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift b/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift index 33d81e19..51ed7cf6 100644 --- a/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift +++ b/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// /// A type that can be expressed as a command-line argument. -public protocol ExpressibleByArgument { +public protocol ExpressibleByArgument: SendableMetatype { /// Creates a new instance of this type from a command-line-specified /// argument. init?(argument: String) diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift index 7a476999..7f75a7fe 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift @@ -13,7 +13,7 @@ /// /// When you implement a `ParsableArguments` type, all properties must be declared with /// one of the four property wrappers provided by the `ArgumentParser` library. -public protocol ParsableArguments: Decodable { +public protocol ParsableArguments: Decodable, SendableMetatype { /// Creates an instance of this parsable type using the definitions /// given by each property's wrapper. init() diff --git a/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift index fe2e2a16..62d0d59d 100644 --- a/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift @@ -35,10 +35,11 @@ extension Array where Element == Name { // MARK: - private struct Foo: ParsableCommand { - enum Subgroup: Equatable { + enum Subgroup: Equatable, Sendable { case first(Int) case second(Int) + @Sendable static func makeFirst(_ str: String) throws -> Subgroup { guard let value = Int(str) else { throw ValidationError("Not a valid integer for 'first'") @@ -46,6 +47,7 @@ private struct Foo: ParsableCommand { return .first(value) } + @Sendable static func makeSecond(_ str: String) throws -> Subgroup { guard let value = Int(str) else { throw ValidationError("Not a valid integer for 'second'") diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index c414941b..19883493 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -148,6 +148,8 @@ extension HelpGenerationTests { enum OptionFlags: String, EnumerableFlag { case optional, required } enum Degree { case bachelor, graduate, doctorate + + @Sendable static func degreeTransform(_ string: String) throws -> Degree { switch string { case "bachelor": diff --git a/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift b/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift index 99d23f1e..d7cfae89 100644 --- a/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift @@ -132,6 +132,7 @@ extension UsageGenerationTests { enum Color { case red, blue + @Sendable static func transform(_ string: String) throws -> Color { switch string { case "red": From b8d89d2f3967efa4254a07a5ea599f216c0e229b Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Fri, 27 Jun 2025 14:32:07 -0500 Subject: [PATCH 2/3] Conditionalize the `SendableMetatype` conformances SendableMetatype is a marker protocol, so while it doesn't have runtime availability requirements, it's only available with >= 6.2 Swift compiler. --- .../ExpressibleByArgument.swift | 38 +++++++++++++++++++ .../Parsable Types/ParsableArguments.swift | 21 ++++++++++ 2 files changed, 59 insertions(+) diff --git a/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift b/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift index 51ed7cf6..00031b81 100644 --- a/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift +++ b/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift @@ -9,6 +9,7 @@ // //===----------------------------------------------------------------------===// +#if compiler(>=6.2) /// A type that can be expressed as a command-line argument. public protocol ExpressibleByArgument: SendableMetatype { /// Creates a new instance of this type from a command-line-specified @@ -44,6 +45,43 @@ public protocol ExpressibleByArgument: SendableMetatype { /// The default implementation of this property returns `.default`. static var defaultCompletionKind: CompletionKind { get } } +#else +/// A type that can be expressed as a command-line argument. +public protocol ExpressibleByArgument { + /// Creates a new instance of this type from a command-line-specified + /// argument. + init?(argument: String) + + /// The description of this instance to show as a default value in a + /// command-line tool's help screen. + var defaultValueDescription: String { get } + + /// An array of all possible strings that can convert to a value of this + /// type, for display in the help screen. + /// + /// The default implementation of this property returns an empty array. If the + /// conforming type is also `CaseIterable`, the default implementation returns + /// an array with a value for each case. + static var allValueStrings: [String] { get } + + /// A dictionary containing the descriptions for each possible value of this type, + /// for display in the help screen. + /// + /// The default implementation of this property returns an empty dictionary. If + /// the conforming type is also `CaseIterable`, the default implementation + /// returns a dictionary with a description for each value as its key-value pair. + /// Note that the conforming type must implement the + /// `defaultValueDescription` for each value - if the description and the + /// value are the same string, it's assumed that a description is not implemented. + static var allValueDescriptions: [String: String] { get } + + /// The completion kind to use for options or arguments of this type that + /// don't explicitly declare a completion kind. + /// + /// The default implementation of this property returns `.default`. + static var defaultCompletionKind: CompletionKind { get } +} +#endif extension ExpressibleByArgument { public var defaultValueDescription: String { diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift index 7f75a7fe..dc0a7b9c 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift @@ -9,6 +9,7 @@ // //===----------------------------------------------------------------------===// +#if compiler(>=6.2) /// A type that can be parsed from a program's command-line arguments. /// /// When you implement a `ParsableArguments` type, all properties must be declared with @@ -27,6 +28,26 @@ public protocol ParsableArguments: Decodable, SendableMetatype { /// The label to use for "Error: ..." messages from this type (experimental). static var _errorLabel: String { get } } +#else +/// A type that can be parsed from a program's command-line arguments. +/// +/// When you implement a `ParsableArguments` type, all properties must be declared with +/// one of the four property wrappers provided by the `ArgumentParser` library. +public protocol ParsableArguments: Decodable { + /// Creates an instance of this parsable type using the definitions + /// given by each property's wrapper. + init() + + /// Validates the properties of the instance after parsing. + /// + /// Implement this method to perform validation or other processing after + /// creating a new instance from command-line arguments. + mutating func validate() throws + + /// The label to use for "Error: ..." messages from this type (experimental). + static var _errorLabel: String { get } +} +#endif /// A type that provides the `ParsableCommand` interface to a `ParsableArguments` type. struct _WrappedParsableCommand: ParsableCommand { From 6e7317d6790db592384e883ed4eb187286410bd8 Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Fri, 27 Jun 2025 15:30:40 -0500 Subject: [PATCH 3/3] Simplify SendableMetatype conditionalizing Uses an underscored protocol to conditionally include conformance without repeating the entire declaration (thanks @rauhul). --- Sources/ArgumentParser/CMakeLists.txt | 1 + .../ExpressibleByArgument.swift | 40 +------------------ .../Parsable Types/ParsableArguments.swift | 23 +---------- .../Utilities/SwiftExtensions.swift | 17 ++++++++ 4 files changed, 20 insertions(+), 61 deletions(-) create mode 100644 Sources/ArgumentParser/Utilities/SwiftExtensions.swift diff --git a/Sources/ArgumentParser/CMakeLists.txt b/Sources/ArgumentParser/CMakeLists.txt index 25022c0f..ea7d83f9 100644 --- a/Sources/ArgumentParser/CMakeLists.txt +++ b/Sources/ArgumentParser/CMakeLists.txt @@ -46,6 +46,7 @@ add_library(ArgumentParser Utilities/Platform.swift Utilities/SequenceExtensions.swift Utilities/StringExtensions.swift + Utilities/SwiftExtensions.swift Utilities/Tree.swift Validators/CodingKeyValidator.swift diff --git a/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift b/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift index 00031b81..0f09b90a 100644 --- a/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift +++ b/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift @@ -9,9 +9,8 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=6.2) /// A type that can be expressed as a command-line argument. -public protocol ExpressibleByArgument: SendableMetatype { +public protocol ExpressibleByArgument: _SendableMetatype { /// Creates a new instance of this type from a command-line-specified /// argument. init?(argument: String) @@ -45,43 +44,6 @@ public protocol ExpressibleByArgument: SendableMetatype { /// The default implementation of this property returns `.default`. static var defaultCompletionKind: CompletionKind { get } } -#else -/// A type that can be expressed as a command-line argument. -public protocol ExpressibleByArgument { - /// Creates a new instance of this type from a command-line-specified - /// argument. - init?(argument: String) - - /// The description of this instance to show as a default value in a - /// command-line tool's help screen. - var defaultValueDescription: String { get } - - /// An array of all possible strings that can convert to a value of this - /// type, for display in the help screen. - /// - /// The default implementation of this property returns an empty array. If the - /// conforming type is also `CaseIterable`, the default implementation returns - /// an array with a value for each case. - static var allValueStrings: [String] { get } - - /// A dictionary containing the descriptions for each possible value of this type, - /// for display in the help screen. - /// - /// The default implementation of this property returns an empty dictionary. If - /// the conforming type is also `CaseIterable`, the default implementation - /// returns a dictionary with a description for each value as its key-value pair. - /// Note that the conforming type must implement the - /// `defaultValueDescription` for each value - if the description and the - /// value are the same string, it's assumed that a description is not implemented. - static var allValueDescriptions: [String: String] { get } - - /// The completion kind to use for options or arguments of this type that - /// don't explicitly declare a completion kind. - /// - /// The default implementation of this property returns `.default`. - static var defaultCompletionKind: CompletionKind { get } -} -#endif extension ExpressibleByArgument { public var defaultValueDescription: String { diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift index dc0a7b9c..1154d627 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift @@ -9,12 +9,11 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=6.2) /// A type that can be parsed from a program's command-line arguments. /// /// When you implement a `ParsableArguments` type, all properties must be declared with /// one of the four property wrappers provided by the `ArgumentParser` library. -public protocol ParsableArguments: Decodable, SendableMetatype { +public protocol ParsableArguments: Decodable, _SendableMetatype { /// Creates an instance of this parsable type using the definitions /// given by each property's wrapper. init() @@ -28,26 +27,6 @@ public protocol ParsableArguments: Decodable, SendableMetatype { /// The label to use for "Error: ..." messages from this type (experimental). static var _errorLabel: String { get } } -#else -/// A type that can be parsed from a program's command-line arguments. -/// -/// When you implement a `ParsableArguments` type, all properties must be declared with -/// one of the four property wrappers provided by the `ArgumentParser` library. -public protocol ParsableArguments: Decodable { - /// Creates an instance of this parsable type using the definitions - /// given by each property's wrapper. - init() - - /// Validates the properties of the instance after parsing. - /// - /// Implement this method to perform validation or other processing after - /// creating a new instance from command-line arguments. - mutating func validate() throws - - /// The label to use for "Error: ..." messages from this type (experimental). - static var _errorLabel: String { get } -} -#endif /// A type that provides the `ParsableCommand` interface to a `ParsableArguments` type. struct _WrappedParsableCommand: ParsableCommand { diff --git a/Sources/ArgumentParser/Utilities/SwiftExtensions.swift b/Sources/ArgumentParser/Utilities/SwiftExtensions.swift new file mode 100644 index 00000000..b09403bf --- /dev/null +++ b/Sources/ArgumentParser/Utilities/SwiftExtensions.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6.2) +/// Designates a type as having a sendable metatype. +public protocol _SendableMetatype: SendableMetatype {} +#else +public protocol _SendableMetatype {} +#endif