From 4bdc47969e7414d0e762e4c18937a14f9ed833a6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 27 Aug 2026 11:06:54 -0700 Subject: [PATCH 1/6] 2.0 support --- .github/workflows/ci.yml | 14 ++ Package.resolved | 10 +- Package.swift | 19 +- Sources/SwiftNavigation/CaseBindable.swift | 13 +- .../SwiftNavigation/Traits/CasePaths.swift | 34 ++-- .../CaseBindableMacro.swift | 5 + .../SwiftUINavigation/Traits/CasePaths.swift | 10 +- .../CaseBindableMacroTests.swift | 162 +++++++++++++----- 8 files changed, 193 insertions(+), 74 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04a2bed22..eae8f474f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,3 +37,17 @@ jobs: run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES - name: Run tests run: make test-${{ matrix.variation }} + + case-paths-1: + runs-on: macos-15 + env: + ENABLE_ALL_TRAITS: 1 + SWIFT_NAVIGATION_CASE_PATHS_1: 1 + steps: + - uses: actions/checkout@v5 + - name: Select Xcode 16.4 + run: sudo xcode-select -s /Applications/Xcode_16.4.app + - name: Skip macro validation + run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES + - name: Run tests + run: make test-macos diff --git a/Package.resolved b/Package.resolved index 7ae7ea869..599c65ae8 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "fdc10853c1416eddaab5f368180b2890aa34ef46ff72e4ae1e58d0d63371abe5", + "originHash" : "d2c9346bbc5720afbd7008e6a106d3e0e262716111a3feba70f28f415770a39a", "pins" : [ { "identity" : "swift-case-paths", "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "1197e80bc7e4b177051b6869ef93d8ac3ad677da", - "version" : "1.8.0" + "branch" : "protocol-case-paths", + "revision" : "7de09e716e60436f88c37a173e59e7d640d7bc6f" } }, { @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-macro-testing", "state" : { - "revision" : "2e494c632d510715c96a694ff25e2a8d4ac3f64b", - "version" : "0.6.5" + "revision" : "0b6bd11ea72e07eec539f2f000efad0b363a90cc", + "version" : "0.7.0" } }, { diff --git a/Package.swift b/Package.swift index ef166b0a4..5b084db40 100644 --- a/Package.swift +++ b/Package.swift @@ -58,9 +58,19 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.8.0"), + // .package( + // url: "https://github.com/pointfreeco/swift-case-paths", + // "1.10.0"..<(ProcessInfo.processInfo.environment["SWIFT_NAVIGATION_CASE_PATHS_1"] == nil + // ? "3.0.0" : "2.0.0") + // ), + .package( + url: "https://github.com/pointfreeco/swift-case-paths", + branch: ProcessInfo.processInfo.environment["SWIFT_NAVIGATION_CASE_PATHS_1"] == nil + ? "protocol-case-paths" + : "case-path-protocol-2" + ), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), - .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.0"), + .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.7.0"), .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.8.0"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.4.1"), @@ -142,6 +152,11 @@ let package = Package( name: "SwiftNavigationMacrosTests", dependencies: [ "SwiftNavigationMacros", + .product( + name: "CasePaths", + package: "swift-case-paths", + condition: .when(traits: ["CasePaths"]) + ), .product(name: "MacroTesting", package: "swift-macro-testing"), ] ), diff --git a/Sources/SwiftNavigation/CaseBindable.swift b/Sources/SwiftNavigation/CaseBindable.swift index a32a83320..e8f0624db 100644 --- a/Sources/SwiftNavigation/CaseBindable.swift +++ b/Sources/SwiftNavigation/CaseBindable.swift @@ -25,11 +25,16 @@ /// Toggle("Is on back order?", isOn: $isOnBackOrder) /// } /// ``` - @attached(extension, conformances: CasePathable, CasePathIterable, CaseBindable) + #if canImport(CasePaths2) + @attached(extension, conformances: CasePathable, CaseBindable) + #else + @attached(extension, conformances: CasePathable, CasePathIterable, CaseBindable) + #endif @attached( member, names: named(AllCasePaths), named(allCasePaths), + named(caseName), named(_$Element), named(UIBindingEnumeration), named(BindingEnumeration), @@ -123,7 +128,7 @@ extension UIBinding where Value: CasePathable { public func _$case( - _ keyPath: KeyPath> + _ keyPath: KeyPath> ) -> UIBinding { self[dynamicMember: keyPath]! } @@ -209,7 +214,7 @@ extension SwiftUI.Binding where Value: CasePathable { public func _$case( - _ keyPath: KeyPath> + _ keyPath: KeyPath> ) -> SwiftUI.Binding { SwiftUI.Binding(_unwrapping: self[_case: keyPath])! } @@ -224,7 +229,7 @@ extension CasePathable { fileprivate subscript( - _case keyPath: KeyPath> + _case keyPath: KeyPath> ) -> Member? { get { Self.allCasePaths[keyPath: keyPath].extract(from: self) } set { diff --git a/Sources/SwiftNavigation/Traits/CasePaths.swift b/Sources/SwiftNavigation/Traits/CasePaths.swift index 5970c7394..31afa1f05 100644 --- a/Sources/SwiftNavigation/Traits/CasePaths.swift +++ b/Sources/SwiftNavigation/Traits/CasePaths.swift @@ -8,7 +8,7 @@ /// - Returns: A new binding. @_disfavoredOverload public subscript( - dynamicMember keyPath: KeyPath> + dynamicMember keyPath: KeyPath> ) -> UIBinding? where Value: CasePathable { func open(_ location: some _UIBinding) -> UIBinding { @@ -25,7 +25,7 @@ /// - Parameter keyPath: A case key path to a specific associated value. /// - Returns: A new binding. public subscript( - dynamicMember keyPath: KeyPath> + dynamicMember keyPath: KeyPath> ) -> UIBinding where Value == V? { func open(_ location: some _UIBinding) -> UIBinding { @@ -42,26 +42,24 @@ /// - Parameter keyPath: A case key path to a case with no associated value. /// - Returns: A new binding. public subscript( - dynamicMember keyPath: KeyPath> + dynamicMember keyPath: KeyPath> ) -> UIBinding where Value == V? { UIBinding(self[dynamicMember: keyPath]) } } - private final class _UIBindingEnumToOptionalCase: _UIBinding - where Base.Value: CasePathable { + private final class _UIBindingEnumToOptionalCase: _UIBinding + where Base.Value: CasePathable, Path.Root == Base.Value { let base: Base - let keyPath: _SendableKeyPath> - let casePath: AnyCasePath - init( - base: Base, keyPath: _SendableKeyPath> - ) { + let keyPath: _SendableKeyPath + nonisolated(unsafe) let casePath: Path + init(base: Base, keyPath: _SendableKeyPath) { self.base = base self.keyPath = keyPath self.casePath = Base.Value.allCasePaths[keyPath: keyPath] } - var wrappedValue: Case? { + var wrappedValue: Path.Value? { get { casePath.extract(from: base.wrappedValue) } @@ -81,17 +79,17 @@ } private final class _UIBindingOptionalEnumToCase< - Base: _UIBinding, Enum: CasePathable, Case - >: _UIBinding { + Base: _UIBinding, Path: CasePath + >: _UIBinding where Path.Root: CasePathable { let base: Base - let keyPath: _SendableKeyPath> - let casePath: AnyCasePath - init(base: Base, keyPath: _SendableKeyPath>) { + let keyPath: _SendableKeyPath + nonisolated(unsafe) let casePath: Path + init(base: Base, keyPath: _SendableKeyPath) { self.base = base self.keyPath = keyPath - self.casePath = Enum.allCasePaths[keyPath: keyPath] + self.casePath = Path.Root.allCasePaths[keyPath: keyPath] } - var wrappedValue: Case? { + var wrappedValue: Path.Value? { get { base.wrappedValue.flatMap(casePath.extract(from:)) } diff --git a/Sources/SwiftNavigationMacros/CaseBindableMacro.swift b/Sources/SwiftNavigationMacros/CaseBindableMacro.swift index 80b8c15bb..60b935a09 100644 --- a/Sources/SwiftNavigationMacros/CaseBindableMacro.swift +++ b/Sources/SwiftNavigationMacros/CaseBindableMacro.swift @@ -33,6 +33,11 @@ enumDecl.inheritanceClause?.inheritedTypes.contains { ["CaseBindable", "\(moduleName).CaseBindable"].contains($0.type.trimmedDescription) } ?? false + || extensions.contains { + $0.inheritanceClause?.inheritedTypes.contains { + $0.type.trimmedDescription.hasSuffix("CaseBindable") + } ?? false + } if !conformsToCaseBindable { let caseBindableExtension: DeclSyntax = """ \(declaration.attributes.availability)extension \(type.trimmed): \ diff --git a/Sources/SwiftUINavigation/Traits/CasePaths.swift b/Sources/SwiftUINavigation/Traits/CasePaths.swift index ff7237369..4c7df5092 100644 --- a/Sources/SwiftUINavigation/Traits/CasePaths.swift +++ b/Sources/SwiftUINavigation/Traits/CasePaths.swift @@ -11,7 +11,7 @@ /// - Parameter keyPath: A case key path to a specific associated value. /// - Returns: A new binding. public subscript( - dynamicMember keyPath: KeyPath> + dynamicMember keyPath: KeyPath> ) -> Binding? where Value: CasePathable { Binding(unwrapping: self[keyPath]) @@ -24,7 +24,7 @@ /// - Parameter keyPath: A case key path to a specific associated value. /// - Returns: A new binding. public subscript( - dynamicMember keyPath: KeyPath> + dynamicMember keyPath: KeyPath> ) -> Binding where Value == Enum? { self[keyPath] @@ -39,7 +39,7 @@ /// - Parameter keyPath: A case key path to a specific associated value. /// - Returns: A new binding. public subscript( - dynamicMember keyPath: KeyPath> + dynamicMember keyPath: KeyPath> ) -> Binding where Value == Enum? { Binding(self[keyPath]) @@ -48,7 +48,7 @@ extension CasePathable { fileprivate subscript( - keyPath: KeyPath> + keyPath: KeyPath> ) -> Member? { get { Self.allCasePaths[keyPath: keyPath].extract(from: self) @@ -62,7 +62,7 @@ extension Optional where Wrapped: CasePathable { fileprivate subscript( - keyPath: KeyPath> + keyPath: KeyPath> ) -> Member? { get { self.flatMap(Wrapped.allCasePaths[keyPath: keyPath].extract(from:)) diff --git a/Tests/SwiftNavigationMacrosTests/CaseBindableMacroTests.swift b/Tests/SwiftNavigationMacrosTests/CaseBindableMacroTests.swift index be41c20d9..448c65ec7 100644 --- a/Tests/SwiftNavigationMacrosTests/CaseBindableMacroTests.swift +++ b/Tests/SwiftNavigationMacrosTests/CaseBindableMacroTests.swift @@ -1,15 +1,26 @@ -#if CasePaths && canImport(MacroTesting) +// NB: These snapshots pin the CasePaths 2.x expansion. The CasePaths 1.x configuration +// delegates to that version's 'CasePathableMacro', whose output these snapshots do not +// describe. +#if CasePaths && canImport(MacroTesting) && canImport(CasePaths2) import CasePathsMacrosSupport import MacroTesting import SnapshotTesting import SwiftNavigationMacros + import SwiftSyntaxBuilder + import SwiftSyntaxMacroExpansion import Testing @Suite( .macros( [ - CaseBindableMacro.self, - CasePathableMacro.self, + "CaseBindable": MacroSpec( + type: CaseBindableMacro.self, + conformances: ["CasePathable", "CaseBindable"] + ), + "CasePathable": MacroSpec( + type: CasePathableMacro.self, + conformances: ["CasePathable"] + ), ], record: .failed ) @@ -34,56 +45,84 @@ case onSale(price: Int, discount: Int) case discontinued - public struct AllCasePaths: CasePaths.CasePathReflectable, Swift.Sendable, Swift.Sequence { + public nonisolated struct AllCasePaths: CasePaths.CasePathReflectable, Swift.Hashable, Swift.Sendable { + public func embed(_ value: Status) -> Status { + value + } + public func extract(from root: Status) -> Status? { + root + } public subscript(root: Status) -> CasePaths.PartialCaseKeyPath { - if root.is(\.inStock) { + if case .inStock = root { return \.inStock } - if root.is(\.outOfStock) { + if case .outOfStock = root { return \.outOfStock } - if root.is(\.onSale) { + if case .onSale = root { return \.onSale } - if root.is(\.discontinued) { + if case .discontinued = root { return \.discontinued } return \.never } - public var inStock: CasePaths.AnyCasePath { - ._$embed(Status.inStock) { - guard case let .inStock(v0) = $0 else { + public struct _$inStock: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { + public func embed(_ value: Int) -> Status { + Status.inStock(quantity: value) + } + public func extract(from root: Status) -> Int? { + guard case let .inStock(v0) = root else { return nil } return v0 } } - public var outOfStock: CasePaths.AnyCasePath { - ._$embed(Status.outOfStock) { - guard case let .outOfStock(v0) = $0 else { + public var inStock: _$inStock { + _$inStock() + } + public struct _$outOfStock: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { + public func embed(_ value: Bool) -> Status { + Status.outOfStock(isOnBackOrder: value) + } + public func extract(from root: Status) -> Bool? { + guard case let .outOfStock(v0) = root else { return nil } return v0 } } - public var onSale: CasePaths.AnyCasePath { - ._$embed(Status.onSale) { - guard case let .onSale(v0, v1) = $0 else { + public var outOfStock: _$outOfStock { + _$outOfStock() + } + public struct _$onSale: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { + public func embed(_ value: (price: Int, discount: Int)) -> Status { + Status.onSale(price: value.0, discount: value.1) + } + public func extract(from root: Status) -> (price: Int, discount: Int)? { + guard case let .onSale(v0, v1) = root else { return nil } return (v0, v1) } } - public var discontinued: CasePaths.AnyCasePath { - ._$embed({ - Status.discontinued - }) { - guard case .discontinued = $0 else { + public var onSale: _$onSale { + _$onSale() + } + public struct _$discontinued: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { + public func embed(_ value: Void) -> Status { + Status.discontinued + } + public func extract(from root: Status) -> Void? { + guard case .discontinued = root else { return nil } return () } } + public var discontinued: _$discontinued { + _$discontinued() + } public func makeIterator() -> Swift.IndexingIterator<[CasePaths.PartialCaseKeyPath]> { var allCasePaths: [CasePaths.PartialCaseKeyPath] = [] allCasePaths.append(\.inStock) @@ -94,10 +133,28 @@ } } - public static var allCasePaths: AllCasePaths { + public nonisolated static var allCasePaths: AllCasePaths { AllCasePaths() } + public nonisolated static func caseName( + for keyPath: CasePaths.PartialCaseKeyPath + ) -> Swift.String? { + if keyPath == \.inStock { + return "inStock" + } + if keyPath == \.outOfStock { + return "outOfStock" + } + if keyPath == \.onSale { + return "onSale" + } + if keyPath == \.discontinued { + return "discontinued" + } + return nil + } + public enum UIBindingEnumeration { case inStock(SwiftNavigation.UIBinding) case outOfStock(SwiftNavigation.UIBinding) @@ -144,10 +201,7 @@ #endif } - extension Status: CasePaths.CasePathable, CasePaths.CasePathIterable { - } - - extension Status: SwiftNavigation.CaseBindable { + extension Status: nonisolated CasePathable, nonisolated CaseBindable { } """# } @@ -169,34 +223,50 @@ case inStock(quantity: Int) case discontinued - public struct AllCasePaths: CasePaths.CasePathReflectable, Swift.Sendable, Swift.Sequence { + public nonisolated struct AllCasePaths: CasePaths.CasePathReflectable, Swift.Hashable, Swift.Sendable { + public func embed(_ value: Status) -> Status { + value + } + public func extract(from root: Status) -> Status? { + root + } public subscript(root: Status) -> CasePaths.PartialCaseKeyPath { - if root.is(\.inStock) { + if case .inStock = root { return \.inStock } - if root.is(\.discontinued) { + if case .discontinued = root { return \.discontinued } return \.never } - public var inStock: CasePaths.AnyCasePath { - ._$embed(Status.inStock) { - guard case let .inStock(v0) = $0 else { + public struct _$inStock: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { + public func embed(_ value: Int) -> Status { + Status.inStock(quantity: value) + } + public func extract(from root: Status) -> Int? { + guard case let .inStock(v0) = root else { return nil } return v0 } } - public var discontinued: CasePaths.AnyCasePath { - ._$embed({ - Status.discontinued - }) { - guard case .discontinued = $0 else { + public var inStock: _$inStock { + _$inStock() + } + public struct _$discontinued: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { + public func embed(_ value: Void) -> Status { + Status.discontinued + } + public func extract(from root: Status) -> Void? { + guard case .discontinued = root else { return nil } return () } } + public var discontinued: _$discontinued { + _$discontinued() + } public func makeIterator() -> Swift.IndexingIterator<[CasePaths.PartialCaseKeyPath]> { var allCasePaths: [CasePaths.PartialCaseKeyPath] = [] allCasePaths.append(\.inStock) @@ -205,10 +275,22 @@ } } - public static var allCasePaths: AllCasePaths { + public nonisolated static var allCasePaths: AllCasePaths { AllCasePaths() } + public nonisolated static func caseName( + for keyPath: CasePaths.PartialCaseKeyPath + ) -> Swift.String? { + if keyPath == \.inStock { + return "inStock" + } + if keyPath == \.discontinued { + return "discontinued" + } + return nil + } + public enum UIBindingEnumeration { case inStock(SwiftNavigation.UIBinding) case discontinued @@ -243,7 +325,7 @@ #endif } - extension Status: CasePaths.CasePathable, CasePaths.CasePathIterable { + extension Status: nonisolated CasePathable { } extension Status: SwiftNavigation.CaseBindable { From d613317569f648b582048f5da3c8b6e98d9dca2b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 27 Aug 2026 11:10:55 -0700 Subject: [PATCH 2/6] Adopt `CasePath` protocol This upcoming protocol allows for better compatibility between CasePaths 1.0 and the upcoming 2.0. --- .github/workflows/ci.yml | 14 -- Package.resolved | 6 +- Package.swift | 18 +-- Sources/SwiftNavigation/CaseBindable.swift | 6 +- .../CaseBindableMacroTests.swift | 151 +++++------------- 5 files changed, 43 insertions(+), 152 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eae8f474f..04a2bed22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,17 +37,3 @@ jobs: run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES - name: Run tests run: make test-${{ matrix.variation }} - - case-paths-1: - runs-on: macos-15 - env: - ENABLE_ALL_TRAITS: 1 - SWIFT_NAVIGATION_CASE_PATHS_1: 1 - steps: - - uses: actions/checkout@v5 - - name: Select Xcode 16.4 - run: sudo xcode-select -s /Applications/Xcode_16.4.app - - name: Skip macro validation - run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES - - name: Run tests - run: make test-macos diff --git a/Package.resolved b/Package.resolved index 599c65ae8..c45f4995c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "d2c9346bbc5720afbd7008e6a106d3e0e262716111a3feba70f28f415770a39a", + "originHash" : "cd9e2b02667c613489f804270bf8a5c3f36c56cb2524904530c69cf68f2a3138", "pins" : [ { "identity" : "swift-case-paths", "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "branch" : "protocol-case-paths", - "revision" : "7de09e716e60436f88c37a173e59e7d640d7bc6f" + "branch" : "macro-spec", + "revision" : "cdfbedad1014028667110642217fc731495be0ad" } }, { diff --git a/Package.swift b/Package.swift index 5b084db40..524d5c511 100644 --- a/Package.swift +++ b/Package.swift @@ -58,17 +58,8 @@ let package = Package( ), ], dependencies: [ - // .package( - // url: "https://github.com/pointfreeco/swift-case-paths", - // "1.10.0"..<(ProcessInfo.processInfo.environment["SWIFT_NAVIGATION_CASE_PATHS_1"] == nil - // ? "3.0.0" : "2.0.0") - // ), - .package( - url: "https://github.com/pointfreeco/swift-case-paths", - branch: ProcessInfo.processInfo.environment["SWIFT_NAVIGATION_CASE_PATHS_1"] == nil - ? "protocol-case-paths" - : "case-path-protocol-2" - ), + // .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.10.0"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", branch: "macro-spec"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.7.0"), .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), @@ -152,11 +143,6 @@ let package = Package( name: "SwiftNavigationMacrosTests", dependencies: [ "SwiftNavigationMacros", - .product( - name: "CasePaths", - package: "swift-case-paths", - condition: .when(traits: ["CasePaths"]) - ), .product(name: "MacroTesting", package: "swift-macro-testing"), ] ), diff --git a/Sources/SwiftNavigation/CaseBindable.swift b/Sources/SwiftNavigation/CaseBindable.swift index e8f0624db..831bcbcc3 100644 --- a/Sources/SwiftNavigation/CaseBindable.swift +++ b/Sources/SwiftNavigation/CaseBindable.swift @@ -25,11 +25,7 @@ /// Toggle("Is on back order?", isOn: $isOnBackOrder) /// } /// ``` - #if canImport(CasePaths2) - @attached(extension, conformances: CasePathable, CaseBindable) - #else - @attached(extension, conformances: CasePathable, CasePathIterable, CaseBindable) - #endif + @attached(extension, conformances: CasePathable, CasePathIterable, CaseBindable) @attached( member, names: named(AllCasePaths), diff --git a/Tests/SwiftNavigationMacrosTests/CaseBindableMacroTests.swift b/Tests/SwiftNavigationMacrosTests/CaseBindableMacroTests.swift index 448c65ec7..9b8b873df 100644 --- a/Tests/SwiftNavigationMacrosTests/CaseBindableMacroTests.swift +++ b/Tests/SwiftNavigationMacrosTests/CaseBindableMacroTests.swift @@ -1,7 +1,4 @@ -// NB: These snapshots pin the CasePaths 2.x expansion. The CasePaths 1.x configuration -// delegates to that version's 'CasePathableMacro', whose output these snapshots do not -// describe. -#if CasePaths && canImport(MacroTesting) && canImport(CasePaths2) +#if CasePaths && canImport(MacroTesting) import CasePathsMacrosSupport import MacroTesting import SnapshotTesting @@ -15,11 +12,11 @@ [ "CaseBindable": MacroSpec( type: CaseBindableMacro.self, - conformances: ["CasePathable", "CaseBindable"] + conformances: ["CasePathable", "CasePathIterable", "CaseBindable"] ), "CasePathable": MacroSpec( type: CasePathableMacro.self, - conformances: ["CasePathable"] + conformances: ["CasePathable", "CasePathIterable"] ), ], record: .failed @@ -45,84 +42,56 @@ case onSale(price: Int, discount: Int) case discontinued - public nonisolated struct AllCasePaths: CasePaths.CasePathReflectable, Swift.Hashable, Swift.Sendable { - public func embed(_ value: Status) -> Status { - value - } - public func extract(from root: Status) -> Status? { - root - } + public struct AllCasePaths: CasePaths.CasePathReflectable, Swift.Sendable, Swift.Sequence { public subscript(root: Status) -> CasePaths.PartialCaseKeyPath { - if case .inStock = root { + if root.is(\.inStock) { return \.inStock } - if case .outOfStock = root { + if root.is(\.outOfStock) { return \.outOfStock } - if case .onSale = root { + if root.is(\.onSale) { return \.onSale } - if case .discontinued = root { + if root.is(\.discontinued) { return \.discontinued } return \.never } - public struct _$inStock: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { - public func embed(_ value: Int) -> Status { - Status.inStock(quantity: value) - } - public func extract(from root: Status) -> Int? { - guard case let .inStock(v0) = root else { + public var inStock: CasePaths.AnyCasePath { + ._$embed(Status.inStock) { + guard case let .inStock(v0) = $0 else { return nil } return v0 } } - public var inStock: _$inStock { - _$inStock() - } - public struct _$outOfStock: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { - public func embed(_ value: Bool) -> Status { - Status.outOfStock(isOnBackOrder: value) - } - public func extract(from root: Status) -> Bool? { - guard case let .outOfStock(v0) = root else { + public var outOfStock: CasePaths.AnyCasePath { + ._$embed(Status.outOfStock) { + guard case let .outOfStock(v0) = $0 else { return nil } return v0 } } - public var outOfStock: _$outOfStock { - _$outOfStock() - } - public struct _$onSale: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { - public func embed(_ value: (price: Int, discount: Int)) -> Status { - Status.onSale(price: value.0, discount: value.1) - } - public func extract(from root: Status) -> (price: Int, discount: Int)? { - guard case let .onSale(v0, v1) = root else { + public var onSale: CasePaths.AnyCasePath { + ._$embed(Status.onSale) { + guard case let .onSale(v0, v1) = $0 else { return nil } return (v0, v1) } } - public var onSale: _$onSale { - _$onSale() - } - public struct _$discontinued: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { - public func embed(_ value: Void) -> Status { - Status.discontinued - } - public func extract(from root: Status) -> Void? { - guard case .discontinued = root else { + public var discontinued: CasePaths.AnyCasePath { + ._$embed({ + Status.discontinued + }) { + guard case .discontinued = $0 else { return nil } return () } } - public var discontinued: _$discontinued { - _$discontinued() - } public func makeIterator() -> Swift.IndexingIterator<[CasePaths.PartialCaseKeyPath]> { var allCasePaths: [CasePaths.PartialCaseKeyPath] = [] allCasePaths.append(\.inStock) @@ -133,28 +102,10 @@ } } - public nonisolated static var allCasePaths: AllCasePaths { + public static var allCasePaths: AllCasePaths { AllCasePaths() } - public nonisolated static func caseName( - for keyPath: CasePaths.PartialCaseKeyPath - ) -> Swift.String? { - if keyPath == \.inStock { - return "inStock" - } - if keyPath == \.outOfStock { - return "outOfStock" - } - if keyPath == \.onSale { - return "onSale" - } - if keyPath == \.discontinued { - return "discontinued" - } - return nil - } - public enum UIBindingEnumeration { case inStock(SwiftNavigation.UIBinding) case outOfStock(SwiftNavigation.UIBinding) @@ -201,7 +152,7 @@ #endif } - extension Status: nonisolated CasePathable, nonisolated CaseBindable { + extension Status: CasePathable, CasePathIterable, CaseBindable { } """# } @@ -223,50 +174,34 @@ case inStock(quantity: Int) case discontinued - public nonisolated struct AllCasePaths: CasePaths.CasePathReflectable, Swift.Hashable, Swift.Sendable { - public func embed(_ value: Status) -> Status { - value - } - public func extract(from root: Status) -> Status? { - root - } + public struct AllCasePaths: CasePaths.CasePathReflectable, Swift.Sendable, Swift.Sequence { public subscript(root: Status) -> CasePaths.PartialCaseKeyPath { - if case .inStock = root { + if root.is(\.inStock) { return \.inStock } - if case .discontinued = root { + if root.is(\.discontinued) { return \.discontinued } return \.never } - public struct _$inStock: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { - public func embed(_ value: Int) -> Status { - Status.inStock(quantity: value) - } - public func extract(from root: Status) -> Int? { - guard case let .inStock(v0) = root else { + public var inStock: CasePaths.AnyCasePath { + ._$embed(Status.inStock) { + guard case let .inStock(v0) = $0 else { return nil } return v0 } } - public var inStock: _$inStock { - _$inStock() - } - public struct _$discontinued: CasePaths.CasePath, Swift.Hashable, Swift.Sendable { - public func embed(_ value: Void) -> Status { - Status.discontinued - } - public func extract(from root: Status) -> Void? { - guard case .discontinued = root else { + public var discontinued: CasePaths.AnyCasePath { + ._$embed({ + Status.discontinued + }) { + guard case .discontinued = $0 else { return nil } return () } } - public var discontinued: _$discontinued { - _$discontinued() - } public func makeIterator() -> Swift.IndexingIterator<[CasePaths.PartialCaseKeyPath]> { var allCasePaths: [CasePaths.PartialCaseKeyPath] = [] allCasePaths.append(\.inStock) @@ -275,22 +210,10 @@ } } - public nonisolated static var allCasePaths: AllCasePaths { + public static var allCasePaths: AllCasePaths { AllCasePaths() } - public nonisolated static func caseName( - for keyPath: CasePaths.PartialCaseKeyPath - ) -> Swift.String? { - if keyPath == \.inStock { - return "inStock" - } - if keyPath == \.discontinued { - return "discontinued" - } - return nil - } - public enum UIBindingEnumeration { case inStock(SwiftNavigation.UIBinding) case discontinued @@ -325,7 +248,7 @@ #endif } - extension Status: nonisolated CasePathable { + extension Status: CasePathable, CasePathIterable { } extension Status: SwiftNavigation.CaseBindable { From 55681898b0aa807a3c221f24475697539911f5d5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 27 Aug 2026 13:34:01 -0700 Subject: [PATCH 3/6] wip --- .../xcshareddata/swiftpm/Package.resolved | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6a95c0f11..44fdc514d 100644 --- a/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "6ae399cd9f48f7a1439d7ea7b8ba4803830f408f9917ef1138f51c4f48f58fa2", + "originHash" : "448790588933bc343ade865ddca768d59811e3cf1d2efc744edfa96985995e49", "pins" : [ { "identity" : "combine-schedulers", @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "1197e80bc7e4b177051b6869ef93d8ac3ad677da", - "version" : "1.8.0" + "branch" : "macro-spec", + "revision" : "cdfbedad1014028667110642217fc731495be0ad" } }, { @@ -96,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-macro-testing", "state" : { - "revision" : "2e494c632d510715c96a694ff25e2a8d4ac3f64b", - "version" : "0.6.5" + "revision" : "0b6bd11ea72e07eec539f2f000efad0b363a90cc", + "version" : "0.7.0" } }, { From a60b6e1657fb21524a9a77ca5d7d45001aba8ff9 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 27 Aug 2026 16:01:49 -0700 Subject: [PATCH 4/6] wip --- Package.swift | 3 ++- .../xcshareddata/swiftpm/Package.resolved | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 524d5c511..3a4d9335c 100644 --- a/Package.swift +++ b/Package.swift @@ -63,7 +63,8 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.7.0"), .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), - .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.8.0"), + // .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.8.0"), + .package(url: "https://github.com/pointfreeco/swift-sharing", branch: "case-path-protocol"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.4.1"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"605.0.0"), diff --git a/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved index 44fdc514d..f2e90923b 100644 --- a/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "448790588933bc343ade865ddca768d59811e3cf1d2efc744edfa96985995e49", + "originHash" : "88984c8eede9cde02665ef3243a9b25c4db6540653a1e9a5f849428e085617e7", "pins" : [ { "identity" : "combine-schedulers", @@ -109,6 +109,15 @@ "version" : "2.0.10" } }, + { + "identity" : "swift-sharing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-sharing", + "state" : { + "branch" : "case-path-protocol", + "revision" : "dcfe2c0839691730e427ad78350182a7afa9669e" + } + }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", From 9e1b0be95d0103164518f5044ae1cb99391213bf Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 31 Aug 2026 10:59:27 -0700 Subject: [PATCH 5/6] wip --- Package.resolved | 14 +++++++------- Package.swift | 6 ++---- Package@swift-6.0.swift | 6 ++---- Package@swift-6.1.swift | 5 ++--- Package@swift-6.2.swift | 6 ++---- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/Package.resolved b/Package.resolved index b3f554195..be678cf5a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "9b13aa544c68a64054188f520a69b71d39c6c8d3caf579612f1c6d54064de617", + "originHash" : "ec9e997bed87527379922584201be46e70c2efdf78de999abb9cb1425fcee9a0", "pins" : [ { "identity" : "swift-case-paths", "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "branch" : "macro-spec", - "revision" : "cdfbedad1014028667110642217fc731495be0ad" + "revision" : "cb4cba6a8a46a86db0d37dbe09935ead6b24c91a", + "version" : "1.10.0" } }, { @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "ada3fea49b5f4279a71daad27987cb179ddba757", - "version" : "1.7.2" + "revision" : "a8cd6c976f335ed361dcecddb0dc39ebda51bc3e", + "version" : "1.6.1" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "de219a1cf34e958134e75a9ebb134cf09bf52fc6", - "version" : "2.0.11" + "revision" : "25ac73741c3436605d61eceb5207e896973918e7", + "version" : "2.0.10" } }, { diff --git a/Package.swift b/Package.swift index 5d9c5ea4e..73bdffa3a 100644 --- a/Package.swift +++ b/Package.swift @@ -58,14 +58,12 @@ let package = Package( ), ], dependencies: [ - // .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.10.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", branch: "macro-spec"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.10.0"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), .package(url: "https://github.com/pointfreeco/swift-issue-reporting", from: "2.1.0"), .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.7.0"), - // .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.8.0"), - .package(url: "https://github.com/pointfreeco/swift-sharing", branch: "case-path-protocol"), + .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.10.1"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"605.0.0"), ], diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 3118169cf..e8bc9c1cb 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -31,13 +31,11 @@ let package = Package( ), ], dependencies: [ - // .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.8.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", branch: "macro-spec"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.10.0"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.0"), .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), - // .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.8.0"), - .package(url: "https://github.com/pointfreeco/swift-sharing", branch: "case-path-protocol"), + .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.10.1"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.13.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"605.0.0"), diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index 6e598b8ec..a92a9db75 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -58,12 +58,11 @@ let package = Package( ), ], dependencies: [ - // .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.10.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", branch: "macro-spec"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.10.0"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.0"), .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), - // .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.8.0"), + .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.10.1"), .package(url: "https://github.com/pointfreeco/swift-sharing", branch: "case-path-protocol"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.13.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0"), diff --git a/Package@swift-6.2.swift b/Package@swift-6.2.swift index 96ca2d734..e604d590c 100644 --- a/Package@swift-6.2.swift +++ b/Package@swift-6.2.swift @@ -58,13 +58,11 @@ let package = Package( ), ], dependencies: [ - // .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.10.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", branch: "macro-spec"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.10.0"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.0"), .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), - // .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.8.0"), - .package(url: "https://github.com/pointfreeco/swift-sharing", branch: "case-path-protocol"), + .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.10.1"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.13.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"605.0.0"), From ffcf06e89ea40437c5ec3c77e2de710fc3163d91 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 31 Aug 2026 11:18:55 -0700 Subject: [PATCH 6/6] wip --- Package@swift-6.1.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index a92a9db75..6c58c81eb 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -63,7 +63,6 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.0"), .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.10.1"), - .package(url: "https://github.com/pointfreeco/swift-sharing", branch: "case-path-protocol"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.13.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"605.0.0"),