Repository: pointfreeco/swift-case-paths
Pull request #212 (2025-01-09 20:04:06)

Add `CasePathsCore` module
The `@CasePathable` macro requires SwiftSyntax, which is quite heavy to rely on and build, but not every library or app depending on case paths needs to build or load the macro, so let's extract the core functionality of case paths to a "core" module that can be built and linked against in isolation.

stephencelis: To build combined documentation:

```
swift package generate-documentation --enable-experimental-combined-documentation --target CasePaths --target CasePathsCore
```

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5b46817..6b5f05c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ jobs:
       matrix:
         xcode:
           - '15.4'
-          - '16.0'
+          - '16.2'
     steps:
       - uses: actions/checkout@v4
       - name: Select Xcode ${{ matrix.xcode }}
@@ -38,7 +38,7 @@ jobs:
       matrix:
         xcode:
           - '15.4'
-          - '16.0'
+          - '16.2'
     steps:
       - uses: actions/checkout@v4
       - name: Select Xcode ${{ matrix.xcode }}
@@ -64,29 +64,22 @@ jobs:
         run: swift test -c release --parallel
 
   wasm:
-    name: SwiftWasm
+    name: Wasm
     runs-on: ubuntu-latest
     env:
       OMIT_MACRO_TESTS: 1
-    strategy:
-      matrix:
-        include:
-          - toolchain: swift-DEVELOPMENT-SNAPSHOT-2024-09-18-a
-            swift-sdk: swift-wasm-DEVELOPMENT-SNAPSHOT-2024-09-20-a
-            checksum: ac7318b8beee4870162292715654499127833b847af6b3d94c32e574579ea189
     steps:
       - uses: actions/checkout@v4
       - uses: bytecodealliance/actions/wasmtime/setup@v1
       - name: Install Swift and Swift SDK for WebAssembly
         run: |
           PREFIX=/opt/swift
-          SWIFT_TOOLCHAIN_TAG="${{ matrix.toolchain }}"
-          SWIFT_SDK_TAG="${{ matrix.swift-sdk }}"
           set -ex
-          curl -f -o /tmp/swift.tar.gz "https://download.swift.org/development/ubuntu2204/$SWIFT_TOOLCHAIN_TAG/$SWIFT_TOOLCHAIN_TAG-ubuntu22.04.tar.gz"
+          curl -f -o /tmp/swift.tar.gz "https://download.swift.org/swift-6.0.2-release/ubuntu2204/swift-6.0.2-RELEASE/swift-6.0.2-RELEASE-ubuntu22.04.tar.gz"
           sudo mkdir -p $PREFIX; sudo tar -xzf /tmp/swift.tar.gz -C $PREFIX --strip-component 1
-          $PREFIX/usr/bin/swift sdk install "https://github.com/swiftwasm/swift/releases/download/$SWIFT_SDK_TAG/$SWIFT_SDK_TAG-wasm32-unknown-wasi.artifactbundle.zip" --checksum ${{ matrix.checksum }}
+          $PREFIX/usr/bin/swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-6.0.2-RELEASE/swift-wasm-6.0.2-RELEASE-wasm32-unknown-wasi.artifactbundle.zip --checksum 6ffedb055cb9956395d9f435d03d53ebe9f6a8d45106b979d1b7f53358e1dcb4
           echo "$PREFIX/usr/bin" >> $GITHUB_PATH
+
       - name: Build tests
         run: swift build --swift-sdk wasm32-unknown-wasi --build-tests -Xlinker -z -Xlinker stack-size=$((1024 * 1024))
       - name: Run tests
@@ -130,4 +123,4 @@ jobs:
     env:
       OMIT_MACRO_TESTS: 1
     steps:
-      - uses: johankool/swift-android-test-action@v1
\ No newline at end of file
+      - uses: johankool/swift-android-test-action@v1
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index 6733a8f..1906563 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -12,18 +12,16 @@ concurrency:
 jobs:
   swift_format:
     name: swift-format
-    runs-on: macos-14
+    runs-on: macos-15
+    permissions:
+      contents: write
     steps:
       - uses: actions/checkout@v4
-      - name: Xcode Select
-        run: sudo xcode-select -s /Applications/Xcode_16.0.app
-      - name: Tap
-        run: brew install swift-format
+      - name: Select Xcode 16.2
+        run: sudo xcode-select -s /Applications/Xcode_16.2.app
       - name: Format
         run: make format
-      - uses: stefanzweifel/git-auto-commit-action@v4
+      - uses: stefanzweifel/git-auto-commit-action@v5
         with:
           commit_message: Run swift-format
           branch: 'main'
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.spi.yml b/.spi.yml
index a39bfdd..4c79d3c 100644
--- a/.spi.yml
+++ b/.spi.yml
@@ -1,4 +1,4 @@
 version: 1
 builder:
   configs:
-  - documentation_targets: [CasePaths]
+  - documentation_targets: [CasePathsCore, CasePaths]
diff --git a/Package.resolved b/Package.resolved
index cf331d8..436d678 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -1,5 +1,5 @@
 {
-  "originHash" : "e4bb2d04e477a6c143e71b4967a73419b0f830aeecd56f64be2b7060b746a574",
+  "originHash" : "75a28c180eaa852896a1e043cbbf340d2f28af3ec620072b1a1b7d9a6041d14e",
   "pins" : [
     {
       "identity" : "swift-docc-plugin",
diff --git a/Package.swift b/Package.swift
index c768a4f..29636c5 100644
--- a/Package.swift
+++ b/Package.swift
@@ -16,25 +16,31 @@ let package = Package(
     .library(
       name: "CasePaths",
       targets: ["CasePaths"]
-    )
+    ),
+    .library(
+      name: "CasePathsCore",
+      targets: ["CasePathsCore"]
+    ),
   ],
   dependencies: [
-    .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"601.0.0-prerelease"),
+    .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"601.0.0"),
     .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.2.2"),
   ],
   targets: [
     .target(
       name: "CasePaths",
       dependencies: [
+        "CasePathsCore",
         "CasePathsMacros",
+      ]
+    ),
+    .target(
+      name: "CasePathsCore",
+      dependencies: [
         .product(name: "IssueReporting", package: "xctest-dynamic-overlay"),
         .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
       ]
     ),
-    .testTarget(
-      name: "CasePathsTests",
-      dependencies: ["CasePaths"]
-    ),
     .macro(
       name: "CasePathsMacros",
       dependencies: [
@@ -42,6 +48,10 @@ let package = Package(
         .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
       ]
     ),
+    .testTarget(
+      name: "CasePathsTests",
+      dependencies: ["CasePaths"]
+    ),
   ]
 )
 
diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift
index f3cc0a2..f89ab63 100644
--- a/Package@swift-6.0.swift
+++ b/Package@swift-6.0.swift
@@ -16,25 +16,31 @@ let package = Package(
     .library(
       name: "CasePaths",
       targets: ["CasePaths"]
-    )
+    ),
+    .library(
+      name: "CasePathsCore",
+      targets: ["CasePathsCore"]
+    ),
   ],
   dependencies: [
-    .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"601.0.0-prerelease"),
+    .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"601.0.0"),
     .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.2.2"),
   ],
   targets: [
     .target(
       name: "CasePaths",
       dependencies: [
+        "CasePathsCore",
         "CasePathsMacros",
+      ]
+    ),
+    .target(
+      name: "CasePathsCore",
+      dependencies: [
         .product(name: "IssueReporting", package: "xctest-dynamic-overlay"),
         .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
       ]
     ),
-    .testTarget(
-      name: "CasePathsTests",
-      dependencies: ["CasePaths"]
-    ),
     .macro(
       name: "CasePathsMacros",
       dependencies: [
@@ -42,6 +48,10 @@ let package = Package(
         .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
       ]
     ),
+    .testTarget(
+      name: "CasePathsTests",
+      dependencies: ["CasePaths"]
+    ),
   ],
   swiftLanguageModes: [.v6]
 )
diff --git a/Sources/CasePaths/Documentation.docc/AnyCasePathDeprecations.md b/Sources/CasePaths/Documentation.docc/AnyCasePathDeprecations.md
deleted file mode 100644
index bd3a7a3..0000000
--- a/Sources/CasePaths/Documentation.docc/AnyCasePathDeprecations.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Deprecated interfaces
-
-Review unsupported CasePaths APIs and their replacements.
-
-## Overview
-
-Avoid using deprecated APIs in your app. See the replacement that you should use instead.
-
-## Topics
-
-### Creating paths
-
-- ``AnyCasePath/init(unsafe:)-5fryq``
-- ``AnyCasePath/init(unsafe:)-93j7i``
-- ``AnyCasePath/init(_:)-5t7e8``
-- ``AnyCasePath/init(_:)-65qkv``
-- ``AnyCasePath/init(_:)-2cuas``
-- ``AnyCasePath/case(_:)-1k3jw``
-- ``AnyCasePath/case(_:)-g885``
-- ``AnyCasePath/constant(_:)``
-- ``AnyCasePath/self``
-- ``AnyCasePath/some``
-- ``AnyCasePath/never``
-- ``AnyCasePath/description-type.property``
-- ``AnyCasePath/rawValue``
-
-### Modifying values
-
-- ``AnyCasePath/modify(_:_:)``
-
-### Appending paths
-
-- ``AnyCasePath/appending(path:)``
-- ``AnyCasePath/modify(_:_:)``
-
-### Type alias
-
-- ``CasePath``
diff --git a/Sources/CasePaths/Documentation.docc/CasePathableMacro.md b/Sources/CasePaths/Documentation.docc/CasePathableMacro.md
index 97c4d2a..01f1460 100644
--- a/Sources/CasePaths/Documentation.docc/CasePathableMacro.md
+++ b/Sources/CasePaths/Documentation.docc/CasePathableMacro.md
@@ -1,7 +1,6 @@
 # ``CasePaths/CasePathable()``
 
-## Topics
+## Further reading
 
-### CasePathable conformance
-
-- ``CasePathable``
+See the [`CasePathsCore`](../casepathscore) module for the `CasePathable` protocol and other core
+library types.
diff --git a/Sources/CasePaths/Documentation.docc/CasePaths.md b/Sources/CasePaths/Documentation.docc/CasePaths.md
index bc22501..fc84a40 100644
--- a/Sources/CasePaths/Documentation.docc/CasePaths.md
+++ b/Sources/CasePaths/Documentation.docc/CasePaths.md
@@ -4,31 +4,17 @@ Case paths bring the power and ergonomics of key paths to enums.
 
 ## Overview
 
-<!-- TODO: Copy over README -->
+This module exports the core functionality of the Case Paths library, as well as the `@CasePathable`
+macro.
 
-## Topics
-
-### Case paths
+To read the core documentation, see the [`CasePathsCore`](../casepathscore) module.
 
-- ``CaseKeyPath``
+## Topics
 
 ### Creating case paths
 
 - ``CasePathable()``
 
-### Swift support
-
-- ``Swift/Optional``
-- ``Swift/Result``
-
-### XCTest support
-
-- ``XCTModify(_:case:_:_:file:line:)-94qma``
-
-### Guides
-
-- <doc:Swift59>
-
 ### Deprecated interfaces
 
 - <doc:Deprecations>
diff --git a/Sources/CasePaths/Documentation.docc/Deprecations.md b/Sources/CasePaths/Documentation.docc/Deprecations.md
index 9739bfe..739fa81 100644
--- a/Sources/CasePaths/Documentation.docc/Deprecations.md
+++ b/Sources/CasePaths/Documentation.docc/Deprecations.md
@@ -14,3 +14,17 @@ Avoid using deprecated APIs in your app. See the replacement that you should use
 - ``extract(case:from:)-ylkc``
 - ``extract(_:)-34fjh``
 - ``extract(_:)-3twft``
+
+### Creating paths
+
+- ``CasePathsCore/AnyCasePath``
+
+### Type aliases
+
+- ``CasePath``
+
+### Test helpers
+
+- ``XCTModify(_:case:_:_:file:line:)-14526``
+- ``XCTModify(_:case:_:_:file:line:)-1pef3``
+- ``XCTModify(_:_:_:file:line:)``
diff --git a/Sources/CasePaths/Documentation.docc/XCTModify.md b/Sources/CasePaths/Documentation.docc/XCTModify.md
index 33e66e4..68b1c54 100644
--- a/Sources/CasePaths/Documentation.docc/XCTModify.md
+++ b/Sources/CasePaths/Documentation.docc/XCTModify.md
@@ -1,4 +1,4 @@
-# ``CasePaths/XCTModify(_:case:_:_:file:line:)-94qma``
+# ``CasePaths/XCTModify(_:case:_:_:file:line:)-14526``
 
 ## Topics
 
diff --git a/Sources/CasePaths/Documentation.docc/XCTModifyDeprecations.md b/Sources/CasePaths/Documentation.docc/XCTModifyDeprecations.md
index c8a554e..de8a8f1 100644
--- a/Sources/CasePaths/Documentation.docc/XCTModifyDeprecations.md
+++ b/Sources/CasePaths/Documentation.docc/XCTModifyDeprecations.md
@@ -10,7 +10,7 @@ Avoid using deprecated APIs in your app. See the replacement that you should use
 
 ### XCTModify
 
-- ``XCTModify(_:case:_:_:file:line:)-1lm9s``
+- ``XCTModify(_:case:_:_:file:line:)-1pef3``
 
 ### XCTUnwrap
 
diff --git a/Sources/CasePaths/EnumReflection.swift b/Sources/CasePaths/EnumReflection.swift
index f316608..38db412 100644
--- a/Sources/CasePaths/EnumReflection.swift
+++ b/Sources/CasePaths/EnumReflection.swift
@@ -7,8 +7,7 @@ extension AnyCasePath {
   /// given an enum embed function.
   ///
   /// > Important: This operation is provided for backwards compatibility. Avoid introducing it to
-  /// > your code and instead favor using types that conform to ``CasePathable`` and
-  /// > ``CaseKeyPath``.
+  /// > your code and instead favor using types that employ the ``CasePathable()`` macro.
   ///
   /// - Parameter embed: An embed function.
   @available(iOS, deprecated: 9999, message: "Use a 'CasePathable' case key path, instead")
@@ -34,8 +33,7 @@ extension AnyCasePath {
   /// Returns a void case path for a case with no associated value.
   ///
   /// > Important: This operation is provided for backwards compatibility. Avoid introducing it to
-  /// > your code and instead favor using types that conform to ``CasePathable`` and
-  /// > ``CaseKeyPath``.
+  /// > your code and instead favor using types that employ the ``CasePathable()`` macro.
   @available(iOS, deprecated: 9999, message: "Use a 'CasePathable' case key path, instead")
   @available(macOS, deprecated: 9999, message: "Use a 'CasePathable' case key path, instead")
   @available(tvOS, deprecated: 9999, message: "Use a 'CasePathable' case key path, instead")
diff --git a/Sources/CasePaths/Internal/Deprecations.swift b/Sources/CasePaths/Internal/Deprecations.swift
index 76be8cd..16e839f 100644
--- a/Sources/CasePaths/Internal/Deprecations.swift
+++ b/Sources/CasePaths/Internal/Deprecations.swift
@@ -4,6 +4,49 @@
   import ObjectiveC
 #endif
 
+extension AnyCasePath {
+  @available(
+    iOS, deprecated: 9999,
+    message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
+  )
+  @available(
+    macOS, deprecated: 9999,
+    message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
+  )
+  @available(
+    tvOS, deprecated: 9999,
+    message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
+  )
+  @available(
+    watchOS, deprecated: 9999,
+    message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
+  )
+  public func modify<Result>(
+    _ root: inout Root,
+    _ body: (inout Value) throws -> Result
+  ) throws -> Result {
+    guard var value = self.extract(from: root) else { throw ExtractionFailed() }
+    let result = try body(&value)
+    root = self.embed(value)
+    return result
+  }
+
+  @available(iOS, deprecated: 9999, message: "Chain case key paths together, instead.")
+  @available(macOS, deprecated: 9999, message: "Chain case key paths together, instead.")
+  @available(tvOS, deprecated: 9999, message: "Chain case key paths together, instead.")
+  @available(watchOS, deprecated: 9999, message: "Chain case key paths together, instead.")
+  public func appending<AppendedValue>(
+    path: AnyCasePath<Value, AppendedValue>
+  ) -> AnyCasePath<Root, AppendedValue> {
+    AnyCasePath<Root, AppendedValue>(
+      embed: { self.embed(path.embed($0)) },
+      extract: { self.extract(from: $0).flatMap(path.extract) }
+    )
+  }
+}
+
+struct ExtractionFailed: Error {}
+
 // Deprecated after 1.4.2:
 
 extension AnyCasePath where Root == Value {
@@ -23,12 +66,14 @@ extension AnyCasePath where Root: _OptionalProtocol, Value == Root.Wrapped {
   }
 }
 
+@_documentation(visibility: private)
 public protocol _OptionalProtocol {
   associatedtype Wrapped
   var optional: Wrapped? { get }
   init(_ some: Wrapped)
 }
 
+@_documentation(visibility: private)
 extension Optional: _OptionalProtocol {
   public var optional: Wrapped? { self }
 }
@@ -252,8 +297,8 @@ public func XCTModify<Enum, Case>(
 /// A type-erased case path that supports embedding a value in a root and attempting to extract a
 /// root's embedded value.
 ///
-/// This type has been renamed to ``AnyCasePath`` and is primarily employed by the
-/// ``CasePathable()`` macro to derive ``CaseKeyPath``s from an enum's cases.
+/// This type has been renamed to `AnyCasePath` and is primarily employed by the ``CasePathable()``
+/// macro to derive `CaseKeyPath`s from an enum's cases.
 @available(*, deprecated, renamed: "AnyCasePath")
 public typealias CasePath = AnyCasePath
 
diff --git a/Sources/CasePaths/Internal/Exports.swift b/Sources/CasePaths/Internal/Exports.swift
new file mode 100644
index 0000000..69697a7
--- /dev/null
+++ b/Sources/CasePaths/Internal/Exports.swift
@@ -0,0 +1,1 @@
+@_exported import CasePathsCore
diff --git a/Sources/CasePaths/Internal/OpenExistential.swift b/Sources/CasePaths/Internal/OpenExistential.swift
deleted file mode 100644
index c8daeb7..0000000
--- a/Sources/CasePaths/Internal/OpenExistential.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-func _isEqual(_ lhs: Any, _ rhs: Any) -> Bool? {
-  (lhs as? any Equatable)?.isEqual(other: rhs)
-}
-
-extension Equatable {
-  fileprivate func isEqual(other: Any) -> Bool {
-    self == other as? Self
-  }
-}
diff --git a/Sources/CasePaths/Internal/UncheckedSendable.swift b/Sources/CasePaths/Internal/UncheckedSendable.swift
deleted file mode 100644
index a3109c7..0000000
--- a/Sources/CasePaths/Internal/UncheckedSendable.swift
+++ /dev/null
@@ -1,8 +0,0 @@
-@propertyWrapper
-struct UncheckedSendable<Value>: @unchecked Sendable {
-  var wrappedValue: Value
-  init(wrappedValue value: Value) {
-    self.wrappedValue = value
-  }
-  var projectedValue: Self { self }
-}
diff --git a/Sources/CasePaths/Macros.swift b/Sources/CasePaths/Macros.swift
index 23ae2b1..aa4d424 100644
--- a/Sources/CasePaths/Macros.swift
+++ b/Sources/CasePaths/Macros.swift
@@ -1,7 +1,7 @@
 /// Defines and implements conformance of the CasePathable protocol.
 ///
-/// This macro conforms the type to the ``CasePathable`` protocol, and adds ``CaseKeyPath``
-/// support for all its cases.
+/// This macro conforms the type to the `CasePathable` protocol, and adds `CaseKeyPath` support for
+/// all its cases.
 ///
 /// For example, the following code applies the `CasePathable` macro to the type `UserAction`:
 ///
diff --git a/Sources/CasePaths/AnyCasePath.swift b/Sources/CasePathsCore/AnyCasePath.swift
similarity index 60%
rename from Sources/CasePaths/AnyCasePath.swift
rename to Sources/CasePathsCore/AnyCasePath.swift
index 41fafe4..30ea95d 100644
--- a/Sources/CasePaths/AnyCasePath.swift
+++ b/Sources/CasePathsCore/AnyCasePath.swift
@@ -70,46 +70,3 @@ extension AnyCasePath: CustomDebugStringConvertible {
     "AnyCasePath<\(typeName(Root.self)), \(typeName(Value.self))>"
   }
 }
-
-extension AnyCasePath {
-  @available(
-    iOS, deprecated: 9999,
-    message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
-  )
-  @available(
-    macOS, deprecated: 9999,
-    message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
-  )
-  @available(
-    tvOS, deprecated: 9999,
-    message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
-  )
-  @available(
-    watchOS, deprecated: 9999,
-    message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
-  )
-  public func modify<Result>(
-    _ root: inout Root,
-    _ body: (inout Value) throws -> Result
-  ) throws -> Result {
-    guard var value = self.extract(from: root) else { throw ExtractionFailed() }
-    let result = try body(&value)
-    root = self.embed(value)
-    return result
-  }
-
-  @available(iOS, deprecated: 9999, message: "Chain case key paths together, instead.")
-  @available(macOS, deprecated: 9999, message: "Chain case key paths together, instead.")
-  @available(tvOS, deprecated: 9999, message: "Chain case key paths together, instead.")
-  @available(watchOS, deprecated: 9999, message: "Chain case key paths together, instead.")
-  public func appending<AppendedValue>(
-    path: AnyCasePath<Value, AppendedValue>
-  ) -> AnyCasePath<Root, AppendedValue> {
-    AnyCasePath<Root, AppendedValue>(
-      embed: { self.embed(path.embed($0)) },
-      extract: { self.extract(from: $0).flatMap(path.extract) }
-    )
-  }
-}
-
-struct ExtractionFailed: Error {}
diff --git a/Sources/CasePaths/CasePathIterable.swift b/Sources/CasePathsCore/CasePathIterable.swift
similarity index 84%
rename from Sources/CasePaths/CasePathIterable.swift
rename to Sources/CasePathsCore/CasePathIterable.swift
index c49ed45..d96618c 100644
--- a/Sources/CasePaths/CasePathIterable.swift
+++ b/Sources/CasePathsCore/CasePathIterable.swift
@@ -1,6 +1,6 @@
 /// A type that provides a collection of all of its case paths.
 ///
-/// The ``CasePathable()`` macro automatically generates a conformance to this protocol.
+/// The `@CasePathable` macro automatically generates a conformance to this protocol.
 ///
 /// You can iterate over ``CasePathable/allCasePaths`` to get access to each individual case path:
 ///
diff --git a/Sources/CasePaths/CasePathReflectable.swift b/Sources/CasePathsCore/CasePathReflectable.swift
similarity index 71%
rename from Sources/CasePaths/CasePathReflectable.swift
rename to Sources/CasePathsCore/CasePathReflectable.swift
index 2e7cbb2..d2b3e4b 100644
--- a/Sources/CasePaths/CasePathReflectable.swift
+++ b/Sources/CasePathsCore/CasePathReflectable.swift
@@ -1,9 +1,9 @@
 /// A type that can reflect a case path from a given case.
 ///
-/// The ``CasePathable()`` macro automatically generates a conformance to this protocol on the
-/// enum's ``CasePathable/AllCasePaths`` type.
+/// The `@CasePathable` macro automatically generates a conformance to this protocol on the enum's
+/// ``CasePathable/AllCasePaths`` type.
 ///
-/// You can look up an enum's case path by passing it to ``subscript(root:)``:
+/// You can look up an enum's case path by passing it to ``CasePathReflectable/subscript(_:)``:
 ///
 /// ```swift
 /// @CasePathable
diff --git a/Sources/CasePaths/CasePathable.swift b/Sources/CasePathsCore/CasePathable.swift
similarity index 97%
rename from Sources/CasePaths/CasePathable.swift
rename to Sources/CasePathsCore/CasePathable.swift
index bfad75f..ee916a2 100644
--- a/Sources/CasePaths/CasePathable.swift
+++ b/Sources/CasePathsCore/CasePathable.swift
@@ -2,8 +2,7 @@ import IssueReporting
 
 /// A type that provides a collection of all of its case paths.
 ///
-/// Use the ``CasePathable()`` macro to automatically add case paths, and this conformance, to an
-/// enum.
+/// Use the `@CasePathable` macro to automatically add case paths, and this conformance, to an enum.
 ///
 /// It is also possible, though less common, to manually conform a type to `CasePathable`. For
 /// example, the `Result` type is extended to be case-pathable with the following extension:
@@ -105,12 +104,12 @@ extension Case: _AnyCase {
 
 /// A key path to the associated value of an enum case.
 ///
-/// The most common way to make an instance of this type is by applying the ``CasePathable()`` macro
+/// The most common way to make an instance of this type is by applying the `@CasePathable` macro
 /// to an enum and using a key path expression like `\SomeEnum.Cases.someCase`, or simply
 /// `\.someCase` where the type can be inferred.
 ///
 /// To extract an associated value from an enum using a case key path, pass the key path to the
-/// ``CasePathable/subscript(case:)-6cdhl``. For example:
+/// ``CasePathable/subscript(case:)-3yqx3``. For example:
 ///
 /// ```swift
 /// @CasePathable
@@ -129,7 +128,7 @@ extension Case: _AnyCase {
 /// // anotherValue is nil
 /// ```
 ///
-/// To replace an associated value, assign it through ``CasePathable/subscript(case:)-8yr2s``. If
+/// To replace an associated value, assign it through ``CasePathable/subscript(case:)-2t4f8``. If
 /// the given path does not match the given enum case, the replacement will fail. For
 /// example:
 ///
@@ -321,7 +320,7 @@ extension CasePathable {
   /// e[case: \.anotherCase]  // nil
   /// ```
   ///
-  /// See ``CasePathable/subscript(case:)-8yr2s`` for replacing an associated value in a root
+  /// See ``CasePathable/subscript(case:)-2t4f8`` for replacing an associated value in a root
   /// enum, and see ``Swift/KeyPath/callAsFunction(_:)`` for embedding an associated value in a
   /// brand new root enum.
   public subscript<Value>(case keyPath: CaseKeyPath<Self, Value>) -> Value? {
@@ -354,7 +353,7 @@ extension CasePathable {
   /// // e is still SomeEnum.someCase(24)
   /// ```
   ///
-  /// See ``CasePathable/subscript(case:)-6cdhl`` for extracting an associated value from a root
+  /// See ``CasePathable/subscript(case:)-3yqx3`` for extracting an associated value from a root
   /// enum, and see ``Swift/KeyPath/callAsFunction(_:)`` for embedding an associated value in a
   /// brand new root enum.
   @_disfavoredOverload
diff --git a/Sources/CasePathsCore/Documentation.docc/CasePathsCore.md b/Sources/CasePathsCore/Documentation.docc/CasePathsCore.md
new file mode 100644
index 0000000..0d5f073
--- /dev/null
+++ b/Sources/CasePathsCore/Documentation.docc/CasePathsCore.md
@@ -0,0 +1,28 @@
+# ``CasePathsCore``
+
+Case paths bring the power and ergonomics of key paths to enums.
+
+## Overview
+
+This module contains the core functionality of the Case Paths library, minus the `@CasePathable`
+macro, and is automatically imported when you `import CasePaths`
+
+See the [`CasePaths`](../casepaths) module for information about the `@CasePathable` macro and
+other non-core functionality.
+
+## Topics
+
+### Creating case paths
+
+- ``CasePathable``
+- ``CaseKeyPath``
+
+### Swift support
+
+- ``Swift/Optional``
+- ``Swift/Result``
+- ``Swift/Never``
+
+### Migration guides
+
+- <doc:MigrationGuides>
diff --git a/Sources/CasePaths/Documentation.docc/AnyCasePath.md b/Sources/CasePathsCore/Documentation.docc/Extensions/AnyCasePath.md
similarity index 64%
rename from Sources/CasePaths/Documentation.docc/AnyCasePath.md
rename to Sources/CasePathsCore/Documentation.docc/Extensions/AnyCasePath.md
index 84654ea..22de6d0 100644
--- a/Sources/CasePaths/Documentation.docc/AnyCasePath.md
+++ b/Sources/CasePathsCore/Documentation.docc/Extensions/AnyCasePath.md
@@ -1,11 +1,11 @@
-# ``CasePaths/AnyCasePath``
+# ``CasePathsCore/AnyCasePath``
 
 ## Topics
 
 ### Creating paths
 
 - ``init(embed:extract:)``
-- ``init(_:)-65qkv``
+- ``init(_:)``
 - ``init()``
 
 ### Extracting and embedding values
@@ -16,7 +16,3 @@
 ### Appending paths
 
 - ``subscript(dynamicMember:)``
-
-### Deprecated interfaces
-
-- <doc:AnyCasePathDeprecations>
diff --git a/Sources/CasePaths/Documentation.docc/CaseKeyPath.md b/Sources/CasePathsCore/Documentation.docc/Extensions/CaseKeyPath.md
similarity index 85%
rename from Sources/CasePaths/Documentation.docc/CaseKeyPath.md
rename to Sources/CasePathsCore/Documentation.docc/Extensions/CaseKeyPath.md
index 0384fde..3638ff1 100644
--- a/Sources/CasePaths/Documentation.docc/CaseKeyPath.md
+++ b/Sources/CasePathsCore/Documentation.docc/Extensions/CaseKeyPath.md
@@ -14,4 +14,4 @@
 ### Partial case paths
 
 - ``PartialCaseKeyPath``
-- ``CasePathable/subscript(case:)-8av06``
+- ``CasePathable/subscript(case:)-73fim``
diff --git a/Sources/CasePaths/Documentation.docc/CasePathable.md b/Sources/CasePathsCore/Documentation.docc/Extensions/CasePathable.md
similarity index 68%
rename from Sources/CasePaths/Documentation.docc/CasePathable.md
rename to Sources/CasePathsCore/Documentation.docc/Extensions/CasePathable.md
index 10ef6c7..cecb804 100644
--- a/Sources/CasePaths/Documentation.docc/CasePathable.md
+++ b/Sources/CasePathsCore/Documentation.docc/Extensions/CasePathable.md
@@ -1,4 +1,4 @@
-# ``CasePaths/CasePathable``
+# ``CasePathsCore/CasePathable``
 
 ## Topics
 
@@ -8,15 +8,15 @@
 
 ### Extracting, replacing, and modifying values
 
-- ``subscript(case:)-6cdhl``
-- ``subscript(case:)-8yr2s``
+- ``subscript(case:)-3yqx3``
+- ``subscript(case:)-2t4f8``
 - ``modify(_:yield:fileID:filePath:line:column:)``
 
 ### Case properties
 
 - ``is(_:)``
-- ``subscript(dynamicMember:)-7ik0u``
-- ``subscript(dynamicMember:)-7sz4x``
+- ``subscript(dynamicMember:)-emck``
+- ``subscript(dynamicMember:)-dm5y``
 
 ### Iteration and reflection
 
diff --git a/Sources/CasePathsCore/Documentation.docc/MigrationGuides.md b/Sources/CasePathsCore/Documentation.docc/MigrationGuides.md
new file mode 100644
index 0000000..79368ee
--- /dev/null
+++ b/Sources/CasePathsCore/Documentation.docc/MigrationGuides.md
@@ -0,0 +1,17 @@
+# Migration guides
+
+Learn how to upgrade your application to the newest version of Case Paths.
+
+## Overview
+
+Case Paths is under constant development, and we are always looking for ways to simplify the
+library, and make it more powerful. As such, we often need to deprecate certain APIs in favor of
+newer ones. We recommend people update their code as quickly as possible to the newest APIs, and
+these guides contain tips to do so.
+
+> Important: Before following any particular migration guide be sure you have followed all the 
+> preceding migration guides.
+
+## Topics
+
+- <doc:MigratingTo1.1>
diff --git a/Sources/CasePaths/Documentation.docc/Swift59.md b/Sources/CasePathsCore/Documentation.docc/MigrationGuides/MigratingTo1.1.md
similarity index 82%
rename from Sources/CasePaths/Documentation.docc/Swift59.md
rename to Sources/CasePathsCore/Documentation.docc/MigrationGuides/MigratingTo1.1.md
index aca57da..08e71e4 100644
--- a/Sources/CasePaths/Documentation.docc/Swift59.md
+++ b/Sources/CasePathsCore/Documentation.docc/MigrationGuides/MigratingTo1.1.md
@@ -1,6 +1,6 @@
-# Upgrading to CasePaths 1.1 and Swift 5.9
+# Migrating to 1.1
 
-Learn how to migrate existing case path code to utilize the new ``CasePathable()`` macro and
+Learn how to migrate existing case path code to utilize the new `@CasePathable` macro and
 ``CaseKeyPath``s.
 
 ## Overview
@@ -16,7 +16,7 @@ In past versions of the library, the primary way to derive a case path was via t
 
 It kind of looks like a key path with the `\` tilting the wrong way, but is actually an invocation
 of a `/` prefix operator with an `Enum.case` initializer. Given just this initializer, the function
-uses runtime reflection to produce a ``CasePath`` value.
+uses runtime reflection to produce a `CasePath` value.
 
 So given an enum:
 
@@ -37,7 +37,7 @@ runtime, it now offers a much better solution that is free of reflection-based c
 
 Deriving case paths is now a two-step process that is still mostly free of boilerplate:
 
-1. You attach the ``CasePathable()`` macro to your enum
+1. You attach the `@CasePathable` macro to your enum:
 
 ```swift
 @CasePathable
@@ -58,9 +58,8 @@ that can extract, modify, and embed the associated value of an enum case.
 ### Passing case key paths to APIs that take case paths
 
 While libraries that use case paths should be updated to take ``CaseKeyPath``s directly, and should
-deprecate APIs that take ``CasePath``s (now ``AnyCasePath``s), you can continue to use these
-existing APIs by converting case key paths to type-erased case paths via
-``AnyCasePath/init(_:)-65qkv``:
+deprecate APIs that take `CasePath`s (now ``AnyCasePath``s), you can continue to use these existing
+APIs by converting case key paths to type-erased case paths via ``AnyCasePath/init(_:)``:
 
 ```swift
 // Before:
@@ -79,7 +78,7 @@ action: \.home
 
 ### Working with case key paths
 
-If you maintain APIs that take ``CasePath`` (now ``AnyCasePath``) values, you should introduce new
+If you maintain APIs that take `CasePath` (now ``AnyCasePath``) values, you should introduce new
 APIs that take ``CaseKeyPath``s instead. ``CaseKeyPath``s have all the functionality of
 ``AnyCasePath``s (and more), but you work with them more like regular key paths:
 
@@ -104,7 +103,7 @@ casePath(value)
 ```
 
 Case key paths can also replace an enum's existing associated value via
-``CasePathable/subscript(case:)-8yr2s``:
+``CasePathable/subscript(case:)-2t4f8``:
 
 ```swift
 root[case: casePath] = value
diff --git a/Sources/CasePaths/Internal/KeyPath+Sendable.swift b/Sources/CasePathsCore/Internal/KeyPath+Sendable.swift
similarity index 100%
rename from Sources/CasePaths/Internal/KeyPath+Sendable.swift
rename to Sources/CasePathsCore/Internal/KeyPath+Sendable.swift
diff --git a/Sources/CasePaths/Internal/TypeName.swift b/Sources/CasePathsCore/Internal/TypeName.swift
similarity index 98%
rename from Sources/CasePaths/Internal/TypeName.swift
rename to Sources/CasePathsCore/Internal/TypeName.swift
index 4e68c2d..0b5aa79 100644
--- a/Sources/CasePaths/Internal/TypeName.swift
+++ b/Sources/CasePathsCore/Internal/TypeName.swift
@@ -1,5 +1,5 @@
 // NB: This is adapted from Custom Dump and should ideally be kept in sync.
-func typeName(
+package func typeName(
   _ type: Any.Type,
   qualified: Bool = true,
   genericsAbbreviated: Bool = false  // NB: This defaults to `true` in Custom Dump
diff --git a/Sources/CasePathsCore/Internal/UncheckedSendable.swift b/Sources/CasePathsCore/Internal/UncheckedSendable.swift
new file mode 100644
index 0000000..9793ddc
--- /dev/null
+++ b/Sources/CasePathsCore/Internal/UncheckedSendable.swift
@@ -0,0 +1,8 @@
+@propertyWrapper
+package struct UncheckedSendable<Value>: @unchecked Sendable {
+  package var wrappedValue: Value
+  package init(wrappedValue value: Value) {
+    self.wrappedValue = value
+  }
+  package var projectedValue: Self { self }
+}


Test patch:
diff --git a/Sources/CasePaths/XCTestSupport.swift b/Sources/CasePaths/XCTestSupport.swift
index 9bbe03a..046845b 100644
--- a/Sources/CasePaths/XCTestSupport.swift
+++ b/Sources/CasePaths/XCTestSupport.swift
@@ -10,7 +10,7 @@ public func XCTModify<Wrapped>(
   file: StaticString = #filePath,
   line: UInt = #line
 ) {
-  XCTModify(&optional, case: \.some, message(), body, file: file, line: line)
+  XCTModify(&optional, case: .some, message(), body, file: file, line: line)
 }
 
 /// Asserts that an enum value matches a particular case and modifies the associated value in place.
@@ -81,3 +81,13 @@ func _XCTModify<Enum, Case>(
 }
 
 struct UnwrappingCase: Error {}
+
+func _isEqual(_ lhs: Any, _ rhs: Any) -> Bool? {
+  (lhs as? any Equatable)?.isEqual(other: rhs)
+}
+
+extension Equatable {
+  fileprivate func isEqual(other: Any) -> Bool {
+    self == other as? Self
+  }
+}