Repository: pointfreeco/swift-case-paths (swift)

Issue:
EXC_BAD_ACCESS when extracting a `Result.success` from a failure, if the result is `Result<_, Error>`
This happens in 0.7.0, I haven't tried other releases.

I've encountered this crash in the wild, which I had to work around by not using case paths for any `Result` where the `Failure` generic type is `Error`. If you try to add this unit test to the test suite in this repo, you can reproduce it:

```swift
  func testExtractSuccessFromFailedResultWithErrorProtocolError() throws {
    struct MyError: Error {}
    let x = Result<String, Error>.failure(MyError()) 

    // since it's `Error`, execution traps at the following line with an EXC_BAD_ACCESS. 
    // If the  result type was over `MyError`, it would work correctly.
    XCTAssertNil((/Result.success).extract(from: x))
  }
```

There must be some swift runtime issue with Error protocol types vs concrete types that conform to `Error`, since it works if the type is `Result<String, MyError>` instead. I'm not too well versed in the swift runtime details to know how to fix this, except to work around it by not using case paths for these kinds of `Result` types for now.

If the value was a `.success` instead of a `.failure`, extraction does not crash, only when trying to extract a `.success` from this specific kind of Result if the value is a `.failure`. Extracting a `.failure` from this value also seems to work as expected.

Pull request:
Fixes #53.

Patch:
diff --git a/Sources/CasePaths/EnumReflection.swift b/Sources/CasePaths/EnumReflection.swift
index 70b59b5d..344cba40 100644
--- a/Sources/CasePaths/EnumReflection.swift
+++ b/Sources/CasePaths/EnumReflection.swift
@@ -282,6 +282,14 @@ extension Strategy {
       self.init(tag: tag, assumedAssociatedValueType: Value.self)
 
     } else if ExistentialMetadata(avType) != nil {
+      if avType == Error.self {
+        // For Objective-C interop, the Error existential is a pointer to an NSError-compatible
+        // (and thus AnyObject-compatible) object.
+        let strategy = Strategy<Enum, AnyObject>(nonExistentialTag: tag)
+        self = .existential { strategy.extract(from: $0, tag: tag) }
+        return
+      }
+
       // Convert protocol existentials to `Any` so that they can be cast (`as? Value`).
       let anyStrategy = Strategy<Enum, Any>(nonExistentialTag: tag)
       self = .existential { anyStrategy.extract(from: $0, tag: tag) }


Test patch:
diff --git a/Tests/CasePathsTests/CasePathsTests.swift b/Tests/CasePathsTests/CasePathsTests.swift
index 00f747ad..628baeca 100644
--- a/Tests/CasePathsTests/CasePathsTests.swift
+++ b/Tests/CasePathsTests/CasePathsTests.swift
@@ -896,6 +896,38 @@ final class CasePathsTests: XCTestCase {
     }
   }
 
+  func testExtractSuccessFromFailedResultWithErrorProtocolError() {
+    let path = /Result<String, Error>.success
+
+    func check(_ error: Error) {
+      let result = Result<String, Error>.failure(error)
+      XCTAssertNil(path.extract(from: result))
+    }
+
+    struct EmptyError: Error {
+    }
+
+    struct LittleError: Error {
+      var a = 42
+    }
+
+    struct BigError: Error {
+      var a = ""
+      var b = ""
+      var c = ""
+      var d = ""
+      var e = ""
+      var f = ""
+    }
+
+    for _ in 1...2 {
+      check(EmptyError())
+      check(LittleError())
+      check(BigError())
+      XCTAssertEqual(path.extract(from: .success("hello")), "hello")
+    }
+  }
+
   func testExtractionFailureOfOptional() {
     enum Action {
       case child1(Child1)