[SE-0413] Adopt typed throws in Result

Make `init(catching:)` and `get()` use typed throws. The former infers
the `Failure` type from the closure provided (once full type inference
is in place) and the latter only throws errors of the `Failure` type.
This commit is contained in:
Doug Gregor
2024-01-13 06:33:27 -08:00
parent d19f082665
commit 7d74b3ba5c
4 changed files with 68 additions and 9 deletions

View File

@@ -161,8 +161,9 @@ public enum Result<Success, Failure: Error> {
///
/// - Returns: The success value, if the instance represents a success.
/// - Throws: The failure value, if the instance represents a failure.
@_alwaysEmitIntoClient
@inlinable
public func get() throws -> Success {
public func get() throws(Failure) -> Success {
switch self {
case let .success(success):
return success
@@ -172,13 +173,42 @@ public enum Result<Success, Failure: Error> {
}
}
extension Result where Failure == Swift.Error {
extension Result {
/// Creates a new result by evaluating a throwing closure, capturing the
/// returned value as a success, or any thrown error as a failure.
///
/// - Parameter body: A throwing closure to evaluate.
@_transparent
public init(catching body: () throws -> Success) {
/// - Parameter body: A potentially throwing closure to evaluate.
@_alwaysEmitIntoClient
@inlinable
public init(catching body: () throws(Failure) -> Success) {
do {
self = .success(try body())
} catch {
self = .failure(error)
}
}
}
extension Result {
/// ABI: Historical get() throws
@_silgen_name("$ss6ResultO3getxyKF")
@usableFromInline
func __abi_get() throws -> Success {
switch self {
case let .success(success):
return success
case let .failure(failure):
throw failure
}
}
}
extension Result where Failure == Swift.Error {
/// ABI: Historical init(catching:)
@_silgen_name("$ss6ResultOss5Error_pRs_rlE8catchingAByxsAC_pGxyKXE_tcfCa")
@usableFromInline
init(__abi_catching body: () throws(Failure) -> Success) {
do {
self = .success(try body())
} catch {

View File

@@ -26,6 +26,8 @@ Func Unicode.UTF32.Parser.parseScalar(from:) has generic signature change from <
Func Unicode.UTF32.decode(_:) has generic signature change from <I where I : Swift.IteratorProtocol, I.Element == Swift.Unicode.UTF32.CodeUnit> to <I where I : Swift.IteratorProtocol, I.Element == Swift.UInt32>
Func Unicode.UTF8.decode(_:) has generic signature change from <I where I : Swift.IteratorProtocol, I.Element == Swift.Unicode.UTF8.CodeUnit> to <I where I : Swift.IteratorProtocol, I.Element == Swift.UInt8>
Constructor Mirror.init(_:children:displayStyle:ancestorRepresentation:) has generic signature change from <Subject, C where C : Swift.Collection, C.Element == Swift.Mirror.Child> to <Subject, C where C : Swift.Collection, C.Element == (label: Swift.String?, value: Any)>
// Generalizations due to typed throws.
Func AnyBidirectionalCollection.map(_:) has generic signature change from <Element, T> to <Element, T, E where E : Swift.Error>
Func AnyBidirectionalCollection.map(_:) is now without @rethrows
Func AnyCollection.map(_:) has generic signature change from <Element, T> to <Element, T, E where E : Swift.Error>
@@ -38,5 +40,6 @@ Func Collection.map(_:) has generic signature change from <Self, T where Self :
Func Collection.map(_:) is now without @rethrows
Func Sequence.map(_:) has generic signature change from <Self, T where Self : Swift.Sequence> to <Self, T, E where Self : Swift.Sequence, E : Swift.Error>
Func Sequence.map(_:) is now without @rethrows
Constructor Result.init(catching:) has generic signature change from <Success, Failure where Failure == any Swift.Error> to <Success, Failure where Failure : Swift.Error>
Protocol SIMDScalar has generic signature change from <Self == Self.SIMD16Storage.Scalar, Self.SIMD16Storage : Swift.SIMDStorage, Self.SIMD2Storage : Swift.SIMDStorage, Self.SIMD32Storage : Swift.SIMDStorage, Self.SIMD4Storage : Swift.SIMDStorage, Self.SIMD64Storage : Swift.SIMDStorage, Self.SIMD8Storage : Swift.SIMDStorage, Self.SIMDMaskScalar : Swift.FixedWidthInteger, Self.SIMDMaskScalar : Swift.SIMDScalar, Self.SIMDMaskScalar : Swift.SignedInteger, Self.SIMD16Storage.Scalar == Self.SIMD2Storage.Scalar, Self.SIMD2Storage.Scalar == Self.SIMD32Storage.Scalar, Self.SIMD32Storage.Scalar == Self.SIMD4Storage.Scalar, Self.SIMD4Storage.Scalar == Self.SIMD64Storage.Scalar, Self.SIMD64Storage.Scalar == Self.SIMD8Storage.Scalar> to <Self == Self.SIMD16Storage.Scalar, Self.SIMD16Storage : Swift.SIMDStorage, Self.SIMD2Storage : Swift.SIMDStorage, Self.SIMD32Storage : Swift.SIMDStorage, Self.SIMD4Storage : Swift.SIMDStorage, Self.SIMD64Storage : Swift.SIMDStorage, Self.SIMD8Storage : Swift.SIMDStorage, Self.SIMDMaskScalar : Swift.FixedWidthInteger, Self.SIMDMaskScalar : Swift.SIMDScalar, Self.SIMDMaskScalar : Swift.SignedInteger, Self.SIMDMaskScalar == Self.SIMDMaskScalar.SIMDMaskScalar, Self.SIMD16Storage.Scalar == Self.SIMD2Storage.Scalar, Self.SIMD2Storage.Scalar == Self.SIMD32Storage.Scalar, Self.SIMD32Storage.Scalar == Self.SIMD4Storage.Scalar, Self.SIMD4Storage.Scalar == Self.SIMD64Storage.Scalar, Self.SIMD64Storage.Scalar == Self.SIMD8Storage.Scalar>

View File

@@ -75,6 +75,8 @@ Constructor _SmallString.init(taggedCocoa:) has return type change from Swift._S
Enum Never has added a conformance to an existing protocol Decodable
Enum Never has added a conformance to an existing protocol Encodable
Enum Never has added a conformance to an existing protocol Identifiable
// These functions haven't actually changed ABI, but are using @_silgen_name tricks to maintain the old ABI while moving to typed throws.
Func AnyBidirectionalCollection.map(_:) has been renamed to Func __rethrows_map(_:)
Func AnyBidirectionalCollection.map(_:) has mangled name changing from 'Swift.AnyBidirectionalCollection.map<A>((A) throws -> A1) throws -> Swift.Array<A1>' to 'Swift.AnyBidirectionalCollection.__rethrows_map<A>((A) throws -> A1) throws -> Swift.Array<A1>'
Func AnyBidirectionalCollection.map(_:) is now without @rethrows
@@ -87,14 +89,16 @@ Func AnyRandomAccessCollection.map(_:) is now without @rethrows
Func AnySequence.map(_:) has been renamed to Func __rethrows_map(_:)
Func AnySequence.map(_:) has mangled name changing from 'Swift.AnySequence.map<A>((A) throws -> A1) throws -> Swift.Array<A1>' to 'Swift.AnySequence.__rethrows_map<A>((A) throws -> A1) throws -> Swift.Array<A1>'
Func AnySequence.map(_:) is now without @rethrows
// These are using
Func Sequence.map(_:) has been renamed to Func __rethrows_map(_:)
Func Sequence.map(_:) has mangled name changing from '(extension in Swift):Swift.Sequence.map<A>((A.Element) throws -> A1) throws -> Swift.Array<A1>' to '(extension in Swift):Swift.Sequence.__rethrows_map<A>((A.Element) throws -> A1) throws -> Swift.Array<A1>'
Func Sequence.map(_:) is now without @rethrows
Func Collection.map(_:) has been renamed to Func __rethrows_map(_:)
Func Collection.map(_:) has mangled name changing from '(extension in Swift):Swift.Collection.map<A>((A.Element) throws -> A1) throws -> Swift.Array<A1>' to '(extension in Swift):Swift.Collection.__rethrows_map<A>((A.Element) throws -> A1) throws -> Swift.Array<A1>'
Func Collection.map(_:) is now without @rethrows
Constructor Result.init(__abi_catching:) is a new API without @available attribute
Constructor Result.init(catching:) has been removed
Func Result.get() has been renamed to Func __abi_get()
Func Result.get() has mangled name changing from 'Swift.Result.get() throws -> A' to 'Swift.Result.__abi_get() throws -> A'
// These haven't actually been removed; they are simply marked unavailable.
// This seems to be a false positive in the ABI checker. This is not an ABI

View File

@@ -67,10 +67,16 @@ ResultTests.test("Throwing Initialization and Unwrapping") {
func throwing() throws -> String {
throw Err.err
}
func throwingTyped() throws(Err) -> String {
throw .err
}
func knownNotThrowing() -> String { return string }
let result1 = Result { try throwing() }
let result2 = Result { try notThrowing() }
expectEqual(result1.failure as? Err, Err.err)
expectEqual(result2.success, string)
@@ -98,6 +104,22 @@ ResultTests.test("Throwing Initialization and Unwrapping") {
} catch {
expectUnreachable()
}
// Test strongly typed error via closure.
// FIXME: Type inference should eliminate the need for the throws(Err)
// annotations below.
let result4 = Result { () throws(Err) in try throwingTyped() }
let _: Result<String, Err> = result4 // check the type
expectEqual(result4.failure, .err)
do throws(Err) {
_ = try result4.get()
} catch let error {
expectEqual(error, .err)
}
let result5 = Result { knownNotThrowing() }
let _: Result<String, Never> = result5 // check the type
_ = result5.get() // no need for 'try'
}
ResultTests.test("Functional Transforms") {