From 7b1d6ab315d1c9bc443dc51f4cbb30c771f927fe Mon Sep 17 00:00:00 2001 From: Syo Ikeda Date: Mon, 14 Nov 2016 21:49:45 +0900 Subject: [PATCH 1/6] Add type-erased `AnyError` for generic contexts In Swift 3, `Error` should be widely used instead of `NSError`. So the type should be useful to wrap arbitrary `Error` instances for uses in generic contexts. --- Result/Result.swift | 41 +++++++++++++++- Tests/ResultTests/ResultTests.swift | 74 +++++++++++------------------ 2 files changed, 68 insertions(+), 47 deletions(-) diff --git a/Result/Result.swift b/Result/Result.swift index a6416c1..493c7f0 100644 --- a/Result/Result.swift +++ b/Result/Result.swift @@ -108,10 +108,26 @@ public enum Result: ResultProtocol, CustomStringConvertib // MARK: - Derive result from failable closure +public func materialize(_ f: () throws -> T) -> Result { + return materialize(try f()) +} + +public func materialize(_ f: @autoclosure () throws -> T) -> Result { + do { + return .success(try f()) + } catch let error as AnyError { + return .failure(error) + } catch { + return .failure(AnyError(error)) + } +} + +@available(*, deprecated, message: "Use the overroad which returns `Result` instead") public func materialize(_ f: () throws -> T) -> Result { return materialize(try f()) } +@available(*, deprecated, message: "Use the overroad which returns `Result` instead") public func materialize(_ f: @autoclosure () throws -> T) -> Result { do { return .success(try f()) @@ -160,7 +176,7 @@ extension NSError: ErrorProtocolConvertible { } } -// MARK: - +// MARK: - Errors /// An “error” that is impossible to construct. /// @@ -169,6 +185,29 @@ extension NSError: ErrorProtocolConvertible { /// contains an `Int`eger and is guaranteed never to be a `failure`. public enum NoError: Swift.Error { } +/// A type-erased error which wraps an arbitrary error instance. This should be +/// useful for generic contexts. +public struct AnyError: Swift.Error, ErrorProtocolConvertible, Equatable { + /// The underlying error. + public let error: Swift.Error + + public init(_ error: Swift.Error) { + self.error = error + } + + public static func error(from error: Error) -> AnyError { + if let anyError = error as? AnyError { + return anyError + } + return AnyError(error) + } + + public static func ==(lhs: AnyError, rhs: AnyError) -> Bool { + return lhs.error._code == rhs.error._code + && lhs.error._domain == rhs.error._domain + } +} + // MARK: - migration support extension Result { @available(*, unavailable, renamed: "success") diff --git a/Tests/ResultTests/ResultTests.swift b/Tests/ResultTests/ResultTests.swift index e0285e7..31853cb 100644 --- a/Tests/ResultTests/ResultTests.swift +++ b/Tests/ResultTests/ResultTests.swift @@ -38,71 +38,54 @@ final class ResultTests: XCTestCase { // MARK: Try - Catch func testTryCatchProducesSuccesses() { - let result: Result = Result(try tryIsSuccess("success")) + let result: Result = Result(try tryIsSuccess("success")) XCTAssert(result == success) } func testTryCatchProducesFailures() { - #if os(Linux) - /// FIXME: skipped on Linux because of crash with swift-3.0-PREVIEW-4. - print("Test Case `\(#function)` skipped on Linux because of crash with swift-3.0-PREVIEW-4.") - #else - let result: Result = Result(try tryIsSuccess(nil)) - XCTAssert(result.error == error) - #endif + let result: Result = Result(try tryIsSuccess(nil)) + XCTAssert(result.error == error) } func testTryCatchWithFunctionProducesSuccesses() { let function = { try tryIsSuccess("success") } - let result: Result = Result(attempt: function) + let result: Result = Result(attempt: function) XCTAssert(result == success) } func testTryCatchWithFunctionCatchProducesFailures() { - #if os(Linux) - /// FIXME: skipped on Linux because of crash with swift-3.0-PREVIEW-4. - print("Test Case `\(#function)` skipped on Linux because of crash with swift-3.0-PREVIEW-4.") - #else - let function = { try tryIsSuccess(nil) } - - let result: Result = Result(attempt: function) - XCTAssert(result.error == error) - #endif + let function = { try tryIsSuccess(nil) } + + let result: Result = Result(attempt: function) + XCTAssert(result.error == error) } func testMaterializeProducesSuccesses() { - let result1 = materialize(try tryIsSuccess("success")) + let result1: Result = materialize(try tryIsSuccess("success")) XCTAssert(result1 == success) - let result2: Result = materialize { try tryIsSuccess("success") } + let result2: Result = materialize { try tryIsSuccess("success") } XCTAssert(result2 == success) } func testMaterializeProducesFailures() { - #if os(Linux) - /// FIXME: skipped on Linux because of crash with swift-3.0-PREVIEW-4. - print("Test Case `\(#function)` skipped on Linux because of crash with swift-3.0-PREVIEW-4.") - #else - let result1 = materialize(try tryIsSuccess(nil)) - XCTAssert(result1.error == error) - - let result2: Result = materialize { try tryIsSuccess(nil) } - XCTAssert(result2.error == error) - #endif + let result1: Result = materialize(try tryIsSuccess(nil)) + XCTAssert(result1.error == error) + + let result2: Result = materialize { try tryIsSuccess(nil) } + XCTAssert(result2.error == error) } // MARK: Recover func testRecoverProducesLeftForLeftSuccess() { - let left = Result.success("left") + let left = Result.success("left") XCTAssertEqual(left.recover("right"), "left") } func testRecoverProducesRightForLeftFailure() { - struct Error: Swift.Error {} - - let left = Result.failure(Error()) + let left = Result.failure(Error.a) XCTAssertEqual(left.recover("right"), "right") } @@ -169,13 +152,8 @@ final class ResultTests: XCTestCase { } func testTryMapProducesFailure() { - #if os(Linux) - /// FIXME: skipped on Linux because of crash with swift-3.0-PREVIEW-4. - print("Test Case `\(#function)` skipped on Linux because of crash with swift-3.0-PREVIEW-4.") - #else - let result = Result.success("fail").tryMap(tryIsSuccess) - XCTAssert(result == failure) - #endif + let result = Result.success("fail").tryMap(tryIsSuccess) + XCTAssert(result == failure) } // MARK: Operators @@ -202,11 +180,15 @@ final class ResultTests: XCTestCase { // MARK: - Fixtures -let success = Result.success("success") -let error = NSError(domain: "com.antitypical.Result", code: 1, userInfo: nil) -let error2 = NSError(domain: "com.antitypical.Result", code: 2, userInfo: nil) -let failure = Result.failure(error) -let failure2 = Result.failure(error2) +private enum Error: Swift.Error { + case a, b +} + +let success = Result.success("success") +let error = AnyError(Error.a) +let error2 = AnyError(Error.b) +let failure = Result.failure(error) +let failure2 = Result.failure(error2) // MARK: - Helpers From a7d0cdc3ded7579e592422e56eac8212486a840f Mon Sep 17 00:00:00 2001 From: Syo Ikeda Date: Mon, 14 Nov 2016 22:51:33 +0900 Subject: [PATCH 2/6] [AnyError] Add CustomStringConvertible conformance --- Result/Result.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Result/Result.swift b/Result/Result.swift index 493c7f0..7bc9fef 100644 --- a/Result/Result.swift +++ b/Result/Result.swift @@ -187,7 +187,7 @@ public enum NoError: Swift.Error { } /// A type-erased error which wraps an arbitrary error instance. This should be /// useful for generic contexts. -public struct AnyError: Swift.Error, ErrorProtocolConvertible, Equatable { +public struct AnyError: Swift.Error, ErrorProtocolConvertible, Equatable, CustomStringConvertible { /// The underlying error. public let error: Swift.Error @@ -206,6 +206,10 @@ public struct AnyError: Swift.Error, ErrorProtocolConvertible, Equatable { return lhs.error._code == rhs.error._code && lhs.error._domain == rhs.error._domain } + + public var description: String { + return String(describing: error) + } } // MARK: - migration support From fd89d0e2d0a6dc803027bad5bb175159e6f6330d Mon Sep 17 00:00:00 2001 From: Syo Ikeda Date: Tue, 15 Nov 2016 13:43:15 +0900 Subject: [PATCH 3/6] Fix typo --- Result/Result.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Result/Result.swift b/Result/Result.swift index 7bc9fef..e1468ba 100644 --- a/Result/Result.swift +++ b/Result/Result.swift @@ -122,12 +122,12 @@ public func materialize(_ f: @autoclosure () throws -> T) -> Result` instead") +@available(*, deprecated, message: "Use the overload which returns `Result` instead") public func materialize(_ f: () throws -> T) -> Result { return materialize(try f()) } -@available(*, deprecated, message: "Use the overroad which returns `Result` instead") +@available(*, deprecated, message: "Use the overload which returns `Result` instead") public func materialize(_ f: @autoclosure () throws -> T) -> Result { do { return .success(try f()) From 3cafe3ef9d795bb07a680ccc3874c3eeaefe9bd5 Mon Sep 17 00:00:00 2001 From: Syo Ikeda Date: Tue, 15 Nov 2016 14:15:55 +0900 Subject: [PATCH 4/6] [AnyError] Remove Equatable conformance --- Result/Result.swift | 7 +------ Tests/ResultTests/ResultTests.swift | 7 +++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Result/Result.swift b/Result/Result.swift index e1468ba..350fa04 100644 --- a/Result/Result.swift +++ b/Result/Result.swift @@ -187,7 +187,7 @@ public enum NoError: Swift.Error { } /// A type-erased error which wraps an arbitrary error instance. This should be /// useful for generic contexts. -public struct AnyError: Swift.Error, ErrorProtocolConvertible, Equatable, CustomStringConvertible { +public struct AnyError: Swift.Error, ErrorProtocolConvertible, CustomStringConvertible { /// The underlying error. public let error: Swift.Error @@ -202,11 +202,6 @@ public struct AnyError: Swift.Error, ErrorProtocolConvertible, Equatable, Custom return AnyError(error) } - public static func ==(lhs: AnyError, rhs: AnyError) -> Bool { - return lhs.error._code == rhs.error._code - && lhs.error._domain == rhs.error._domain - } - public var description: String { return String(describing: error) } diff --git a/Tests/ResultTests/ResultTests.swift b/Tests/ResultTests/ResultTests.swift index 31853cb..b5a7dbe 100644 --- a/Tests/ResultTests/ResultTests.swift +++ b/Tests/ResultTests/ResultTests.swift @@ -193,6 +193,13 @@ let failure2 = Result.failure(error2) // MARK: - Helpers +extension AnyError: Equatable { + public static func ==(lhs: AnyError, rhs: AnyError) -> Bool { + return lhs.error._code == rhs.error._code + && lhs.error._domain == rhs.error._domain + } +} + #if !os(Linux) func attempt(_ value: T, succeed: Bool, error: NSErrorPointer) -> T? { From 1a123289fc61a86e992c17140143b5c930559ff9 Mon Sep 17 00:00:00 2001 From: Syo Ikeda Date: Tue, 15 Nov 2016 14:22:04 +0900 Subject: [PATCH 5/6] [AnyError] initializer: Check if the given error is AnyError --- Result/Result.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Result/Result.swift b/Result/Result.swift index 350fa04..f5f604b 100644 --- a/Result/Result.swift +++ b/Result/Result.swift @@ -115,8 +115,6 @@ public func materialize(_ f: () throws -> T) -> Result { public func materialize(_ f: @autoclosure () throws -> T) -> Result { do { return .success(try f()) - } catch let error as AnyError { - return .failure(error) } catch { return .failure(AnyError(error)) } @@ -192,13 +190,14 @@ public struct AnyError: Swift.Error, ErrorProtocolConvertible, CustomStringConve public let error: Swift.Error public init(_ error: Swift.Error) { - self.error = error + if let anyError = error as? AnyError { + self = anyError + } else { + self.error = error + } } public static func error(from error: Error) -> AnyError { - if let anyError = error as? AnyError { - return anyError - } return AnyError(error) } From aac425da8f398a0875347c44e56a7606ecae2f26 Mon Sep 17 00:00:00 2001 From: Syo Ikeda Date: Tue, 29 Nov 2016 08:32:12 +0900 Subject: [PATCH 6/6] [AnyError] Separate protocol conformances from the type --- Result/Result.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Result/Result.swift b/Result/Result.swift index f5f604b..4d5e8a0 100644 --- a/Result/Result.swift +++ b/Result/Result.swift @@ -185,7 +185,7 @@ public enum NoError: Swift.Error { } /// A type-erased error which wraps an arbitrary error instance. This should be /// useful for generic contexts. -public struct AnyError: Swift.Error, ErrorProtocolConvertible, CustomStringConvertible { +public struct AnyError: Swift.Error { /// The underlying error. public let error: Swift.Error @@ -196,11 +196,15 @@ public struct AnyError: Swift.Error, ErrorProtocolConvertible, CustomStringConve self.error = error } } +} +extension AnyError: ErrorProtocolConvertible { public static func error(from error: Error) -> AnyError { return AnyError(error) } +} +extension AnyError: CustomStringConvertible { public var description: String { return String(describing: error) }