Repository: pointfreeco/swift-case-paths
Pull request #29 (2021-05-26 22:25:09)

Eliminate `/` ambiguity via disfavored overloads
This changes `/` overloads to selectively use `@_disfavoredOverload`, which generally allows `/Foo.bar` to resolve to a case path without the ambiguity of key path-like "case path syntax" for extraction (like in `compactMap(/Result.success)`).


Patch:
diff --git a/Makefile b/Makefile
index 9cb1e99..1a8bf2c 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ test-linux:
 		--rm \
 		-v "$(PWD):$(PWD)" \
 		-w "$(PWD)" \
-		swift:5.1 \
+		swift:5.3 \
 		bash -c 'make test-swift'
 
 test-swift:
diff --git a/Sources/CasePaths/Operators.swift b/Sources/CasePaths/Operators.swift
index 03923e4..263f698 100644
--- a/Sources/CasePaths/Operators.swift
+++ b/Sources/CasePaths/Operators.swift
@@ -73,6 +73,7 @@ public prefix func / <Root>(
 ///   otherwise undefined.
 /// - Parameter case: An enum case initializer.
 /// - Returns: A function that can attempt to extract associated values from an enum.
+@_disfavoredOverload
 public prefix func / <Root, Value>(
   case: @escaping (Value) -> Root
 ) -> (Root) -> Value? {
@@ -85,6 +86,7 @@ public prefix func / <Root, Value>(
 ///   values. Its behavior is otherwise undefined.
 /// - Parameter root: A case with no an associated value.
 /// - Returns: A void case path.
+@_disfavoredOverload
 public prefix func / <Root>(
   root: Root
 ) -> (Root) -> Void? {


Test patch:
diff --git a/Tests/CasePathsTests/CasePathsTests.swift b/Tests/CasePathsTests/CasePathsTests.swift
index 5023ed6..ccf1d03 100644
--- a/Tests/CasePathsTests/CasePathsTests.swift
+++ b/Tests/CasePathsTests/CasePathsTests.swift
@@ -5,7 +5,8 @@ final class CasePathsTests: XCTestCase {
   func testEmbed() {
     enum Foo: Equatable { case bar(Int) }
 
-    XCTAssertEqual(.bar(42), (/Foo.bar).embed(42))
+    let fooBar = /Foo.bar
+    XCTAssertEqual(.bar(42), fooBar.embed(42))
     XCTAssertEqual(.bar(42), (/Foo.self).embed(Foo.bar(42)))
   }
 
@@ -13,60 +14,59 @@ final class CasePathsTests: XCTestCase {
     enum Foo: Equatable { case bar(Bar) }
     enum Bar: Equatable { case baz(Int) }
 
-    XCTAssertEqual(.bar(.baz(42)), (/Foo.bar .. Bar.baz).embed(42))
+    let fooBaz = /Foo.bar .. Bar.baz
+    XCTAssertEqual(.bar(.baz(42)), fooBaz.embed(42))
   }
 
   func testVoidCasePath() {
     enum Foo: Equatable { case bar }
 
-    XCTAssertEqual(.bar, (/Foo.bar).embed(()))
+    let fooBar = /Foo.bar
+    XCTAssertEqual(.bar, fooBar.embed(()))
   }
 
   func testCasePaths() {
+    let some = /String?.some
     XCTAssertEqual(
       .some("Hello"),
-      (/String?.some)
-        .extract(from: "Hello")
+      some.extract(from: "Hello")
     )
     XCTAssertNil(
-      (/String?.some)
-        .extract(from: .none)
+      some.extract(from: .none)
     )
 
+    let success = /Result<String, Error>.success
+    let failure = /Result<String, Error>.failure
     XCTAssertEqual(
       .some("Hello"),
-      (/Result<String, Error>.success)
-        .extract(from: .success("Hello"))
+      success.extract(from: .success("Hello"))
     )
     XCTAssertNil(
-      (/Result<String, Error>.failure)
-        .extract(from: .success("Hello"))
+      failure.extract(from: .success("Hello"))
     )
 
     struct MyError: Equatable, Error {}
-
+    let mySuccess = /Result<String, MyError>.success
+    let myFailure = /Result<String, MyError>.failure
     XCTAssertEqual(
       .some(MyError()),
-      (/Result<String, Error>.failure)
-        .extract(from: .failure(MyError()))
+      myFailure.extract(from: .failure(MyError()))
     )
     XCTAssertNil(
-      (/Result<String, Error>.success)
-        .extract(from: .failure(MyError()))
+      mySuccess.extract(from: .failure(MyError()))
     )
   }
 
   func testIdentity() {
+    let id = /Int.self
     XCTAssertEqual(
       .some(42),
-      (/Int.self)
-        .extract(from: 42)
+      id.extract(from: 42)
     )
 
     XCTAssertEqual(
       .some(42),
-      (/.self)
-        .extract(from: 42)
+      (/.self).extract(from: 42)
     )
   }
 
@@ -76,15 +76,23 @@ final class CasePathsTests: XCTestCase {
       case bar(none: Int)
     }
 
+    let fooBarSome = /Foo.bar(some:)
     XCTAssertEqual(
       .some(42),
-      (/Foo.bar(some:))
-        .extract(from: .bar(some: 42))
+      fooBarSome.extract(from: .bar(some: 42))
     )
     XCTAssertNil(
-      (/Foo.bar(some:))
-        .extract(from: .bar(none: 42))
+      fooBarSome.extract(from: .bar(none: 42))
     )
+
+//    let fooBarNone = /Foo.bar(none:)
+//      XCTAssertEqual(
+//        .some(42),
+//        fooBarNone.extract(from: .bar(none: 42))
+//      )
+//      XCTAssertNil(
+//        fooBarNone.extract(from: .bar(some: 42))
+//      )
   }
 
   func testMultiCases() {
@@ -92,9 +100,7 @@ final class CasePathsTests: XCTestCase {
       case bar(Int, String)
     }
 
-    guard
-      let fizzBuzz = (/Foo.bar)
-        .extract(from: .bar(42, "Blob"))
+    guard let fizzBuzz = (/Foo.bar).extract(from: .bar(42, "Blob"))
     else {
       XCTFail()
       return
@@ -108,9 +114,8 @@ final class CasePathsTests: XCTestCase {
       case bar(fizz: Int, buzz: String)
     }
 
-    guard
-      let fizzBuzz = CasePath<Foo, (fizz: Int, buzz: String)>.case(Foo.bar)
-        .extract(from: .bar(fizz: 42, buzz: "Blob"))
+    let fooBar: CasePath<Foo, (fizz: Int, buzz: String)> = /Foo.bar(fizz:buzz:)
+    guard let fizzBuzz = fooBar.extract(from: .bar(fizz: 42, buzz: "Blob"))
     else {
       XCTFail()
       return
@@ -124,9 +129,7 @@ final class CasePathsTests: XCTestCase {
       case bar(Int, buzz: String)
     }
 
-    guard
-      let fizzBuzz = (/Foo.bar)
-        .extract(from: .bar(42, buzz: "Blob"))
+    guard let fizzBuzz = (/Foo.bar).extract(from: .bar(42, buzz: "Blob"))
     else {
       XCTFail()
       return
@@ -143,10 +146,10 @@ final class CasePathsTests: XCTestCase {
       case baz
     }
 
+    let fooBar = /Foo.bar
     XCTAssertEqual(
       .baz,
-      (/Foo.bar)
-        .extract(from: .bar(.baz))
+      fooBar.extract(from: .bar(.baz))
     )
   }
 
@@ -159,15 +162,11 @@ final class CasePathsTests: XCTestCase {
       case baz(Never)
     }
 
-    XCTAssertNil(
-      (/Foo.bar)
-        .extract(from: Foo.foo)
-    )
+    let fooBar = /Foo.bar
+    XCTAssertNil(fooBar.extract(from: Foo.foo))
 
-    XCTAssertNil(
-      (/Foo.baz)
-        .extract(from: Foo.foo)
-    )
+    let fooBaz = /Foo.baz
+    XCTAssertNil(fooBaz.extract(from: Foo.foo))
   }
 
   func testEnumsWithoutAssociatedValues() {
@@ -176,23 +175,13 @@ final class CasePathsTests: XCTestCase {
       case baz
     }
 
-    XCTAssertNotNil(
-      (/Foo.bar)
-        .extract(from: .bar)
-    )
-    XCTAssertNil(
-      (/Foo.bar)
-        .extract(from: .baz)
-    )
+    let fooBar = /Foo.bar
+    XCTAssertNotNil(fooBar.extract(from: .bar))
+    XCTAssertNil(fooBar.extract(from: .baz))
 
-    XCTAssertNotNil(
-      (/Foo.baz)
-        .extract(from: .baz)
-    )
-    XCTAssertNil(
-      (/Foo.baz)
-        .extract(from: .bar)
-    )
+    let fooBaz = /Foo.baz
+    XCTAssertNotNil(fooBaz.extract(from: .baz))
+    XCTAssertNil(fooBaz.extract(from: .bar))
   }
 
   func testEnumsWithClosures() {
@@ -201,9 +190,8 @@ final class CasePathsTests: XCTestCase {
     }
 
     var didRun = false
-    guard
-      let bar = (/Foo.bar)
-        .extract(from: .bar { didRun = true })
+    let fooBar = /Foo.bar
+    guard let bar = fooBar.extract(from: .bar { didRun = true })
     else {
       XCTFail()
       return
@@ -259,13 +247,21 @@ final class CasePathsTests: XCTestCase {
         .compactMap(/Authentication.unauthenticated)
         .count
     )
+
+    enum Foo { case bar(Int, Int) }
+    XCTAssertEqual(
+      [3],
+      [Foo.bar(1, 2)].compactMap(/Foo.bar).map(+)
+    )
   }
 
   func testAppending() {
+    let success = /Result<Int?, Error>.success
+    let int = /Int?.some
+    let success2int = success .. int
     XCTAssertEqual(
       .some(42),
-      (/Result<Int?, Error>.success .. /Int?.some)
-        .extract(from: .success(.some(42)))
+      success2int.extract(from: .success(.some(42)))
     )
   }