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

Issue:
@CasePathable triggers compiler bug with Never payloads
Starting in 1.4.0, enums with `Never` associated values, even as part of a generic type, trigger some sort of compiler bug (or misleading diagnostic) where it thinks switches on the enum may have unknown values. For example:

```swift
@CasePathable
enum Action {
    case alert(Never)
}
```

produces the warning `Switch covers known cases, but 'Action' may have additional unknown values` in its generated code. More specifically:

```swift
public struct AllCasePaths: Sequence {
    public subscript(root: Action) -> PartialCaseKeyPath<Action> {
        switch root { // Warning produced here.
        case .alert:
            return \.alert
        }
    }
}
```

This enum compiles cleanly if any other type is used as an associated value.

**Environment**
  - swift-case-paths: 1.4.0 && 1.4.1
  - Xcode: 15.4
  - Swift: 5.10

Edited to simplify example.

Pull request:
Fixes #161.

Patch:
diff --git a/Sources/CasePathsMacros/CasePathableMacro.swift b/Sources/CasePathsMacros/CasePathableMacro.swift
index f2ce714d..7a308119 100644
--- a/Sources/CasePathsMacros/CasePathableMacro.swift
+++ b/Sources/CasePathsMacros/CasePathableMacro.swift
@@ -83,17 +83,9 @@ extension CasePathableMacro: MemberMacro {
 
     let rewriter = SelfRewriter(selfEquivalent: enumName)
     let memberBlock = rewriter.rewrite(enumDecl.memberBlock).cast(MemberBlockSyntax.self)
-    let rootSwitchCases = generateCases(from: memberBlock.members, enumName: enumName) {
-      "case .\($0.name): return \\.\(raw: $0.name.text)"
+    let rootSubscriptCases = generateCases(from: memberBlock.members, enumName: enumName) {
+      "if root.is(\\.\(raw: $0.name.text)) { return \\.\(raw: $0.name.text) }"
     }
-    let rootSwitch: DeclSyntax =
-      rootSwitchCases.isEmpty
-      ? "\\.never"
-      : """
-      switch root {
-      \(raw: rootSwitchCases.map(\.description).joined(separator: "\n"))
-      }
-      """
     let casePaths = generateDeclSyntax(from: memberBlock.members, enumName: enumName)
     let allCases = generateCases(from: memberBlock.members, enumName: enumName) {
       "allCasePaths.append(\\.\($0.name))"
@@ -103,7 +95,7 @@ extension CasePathableMacro: MemberMacro {
       """
       public struct AllCasePaths: Sequence {
       public subscript(root: \(enumName)) -> PartialCaseKeyPath<\(enumName)> {
-      \(rootSwitch)
+      \(raw: rootSubscriptCases.map { "\($0.description)\n" }.joined())return \\.never
       }
       \(raw: casePaths.map(\.description).joined(separator: "\n"))
       public func makeIterator() -> IndexingIterator<[PartialCaseKeyPath<\(enumName)>]> {


Test patch:
diff --git a/Sources/CasePaths/XCTestSupport.swift b/Sources/CasePaths/XCTestSupport.swift
index 9c845aea..887b50ce 100644
--- a/Sources/CasePaths/XCTestSupport.swift
+++ b/Sources/CasePaths/XCTestSupport.swift
@@ -201,7 +201,8 @@ private func _XCTModify<Enum, Case>(
     XCTFail(
       """
       XCTModify failed: expected "\(typeName(Case.self))" value to be modified but it was unchanged.
-      """)
+      """
+    )
   }
 
   `enum` = casePath.embed(value)
diff --git a/Tests/CasePathsMacrosTests/CasePathableMacroTests.swift b/Tests/CasePathsMacrosTests/CasePathableMacroTests.swift
index 38a54b21..f839b6de 100644
--- a/Tests/CasePathsMacrosTests/CasePathableMacroTests.swift
+++ b/Tests/CasePathsMacrosTests/CasePathableMacroTests.swift
@@ -33,16 +33,19 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
-            case .baz:
+            }
+            if root.is(\.baz) {
               return \.baz
-            case .fizz:
+            }
+            if root.is(\.fizz) {
               return \.fizz
-            case .fizzier:
+            }
+            if root.is(\.fizzier) {
               return \.fizzier
             }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, Void> {
             CasePaths.AnyCasePath<Foo, Void>(
@@ -122,12 +125,13 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
-            case .baz:
+            }
+            if root.is(\.baz) {
               return \.baz
             }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, Int> {
             CasePaths.AnyCasePath<Foo, Int>(
@@ -181,10 +185,10 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
             }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, Int> {
             CasePaths.AnyCasePath<Foo, Int>(
@@ -223,10 +227,10 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
             }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, Int> {
             CasePaths.AnyCasePath<Foo, Int>(
@@ -265,10 +269,10 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
             }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, Int> {
             CasePaths.AnyCasePath<Foo, Int>(
@@ -344,7 +348,7 @@ final class CasePathableMacroTests: XCTestCase {
 
           public struct AllCasePaths: Sequence {
               public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-                  \.never
+                  return \.never
               }
 
               public func makeIterator() -> IndexingIterator<[PartialCaseKeyPath<Foo>]> {
@@ -367,7 +371,7 @@ final class CasePathableMacroTests: XCTestCase {
 
           public struct AllCasePaths: Sequence {
               public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-                  \.never
+                  return \.never
               }
 
               public func makeIterator() -> IndexingIterator<[PartialCaseKeyPath<Foo>]> {
@@ -395,10 +399,10 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
             }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, (Int, Bool)> {
             CasePaths.AnyCasePath<Foo, (Int, Bool)>(
@@ -440,10 +444,10 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
             }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, Bar<Foo>> {
             CasePaths.AnyCasePath<Foo, Bar<Foo>>(
@@ -485,10 +489,10 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
             }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, (int: Int, bool: Bool)> {
             CasePaths.AnyCasePath<Foo, (int: Int, bool: Bool)>(
@@ -564,32 +568,39 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
+            }
             #if os(macOS)
-            case .macCase:
+            if root.is(\.macCase) {
               return \.macCase
-            case .macSecond:
+            }
+            if root.is(\.macSecond) {
               return \.macSecond
+            }
             #elseif os(iOS)
-            case .iosCase:
+            if root.is(\.iosCase) {
               return \.iosCase
+            }
             #else
-            case .elseCase:
+            if root.is(\.elseCase) {
               return \.elseCase
-            case .elseLast:
+            }
+            if root.is(\.elseLast) {
               return \.elseLast
+            }
             #endif
             #if DEBUG
             #if INNER
-            case .twoLevelsDeep:
+            if root.is(\.twoLevelsDeep) {
               return \.twoLevelsDeep
-            case .twoLevels:
+            }
+            if root.is(\.twoLevels) {
               return \.twoLevels
+            }
             #endif
             #endif
-            }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, Void> {
             CasePaths.AnyCasePath<Foo, Void>(
@@ -744,10 +755,10 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
             }
+            return \.never
           }
           public var bar: CasePaths.AnyCasePath<Foo, Void> {
             CasePaths.AnyCasePath<Foo, Void>(
@@ -820,16 +831,19 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
-            case .baz:
+            }
+            if root.is(\.baz) {
               return \.baz
-            case .fizz:
+            }
+            if root.is(\.fizz) {
               return \.fizz
-            case .buzz:
+            }
+            if root.is(\.buzz) {
               return \.buzz
             }
+            return \.never
           }
           /// The bar case.
           public var bar: CasePaths.AnyCasePath<Foo, Void> {
@@ -934,10 +948,10 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
             }
+            return \.never
           }
             // baz
           // case foo
@@ -991,16 +1005,19 @@ final class CasePathableMacroTests: XCTestCase {
 
         public struct AllCasePaths: Sequence {
           public subscript(root: Foo) -> PartialCaseKeyPath<Foo> {
-            switch root {
-            case .bar:
+            if root.is(\.bar) {
               return \.bar
-            case .baz:
+            }
+            if root.is(\.baz) {
               return \.baz
-            case .fizz:
+            }
+            if root.is(\.fizz) {
               return \.fizz
-            case .fizzier/*Comment in case*/:
+            }
+            if root.is(\.fizzier) {
               return \.fizzier
             }
+            return \.never
           }
           // Comment above case
           public var bar: CasePaths.AnyCasePath<Foo, Void> {
diff --git a/Tests/CasePathsTests/MacroTests.swift b/Tests/CasePathsTests/MacroTests.swift
index 22702fa1..6700d21b 100644
--- a/Tests/CasePathsTests/MacroTests.swift
+++ b/Tests/CasePathsTests/MacroTests.swift
@@ -9,4 +9,9 @@
     case fizz(buzz: String)  // Comment on case
     case fizzier /*Comment in case*/(Int, buzzier: String)
   }
+
+  @CasePathable
+  enum Action {
+    case alert(Never)
+  }
 #endif