diff --git a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved index 776ea708829d..5ef87e21b1cf 100644 --- a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "3ebb70e4ef5203d6f8bebecc1bec69837803b880f6f397952a6f49aa1f6fe107", + "originHash" : "db98d6ddac14160dacbc01e142642e2f8a4a2d893db3651e8a137ad669ab86a4", "pins" : [ { "identity" : "combine-schedulers", @@ -64,24 +64,6 @@ "version" : "1.8.0" } }, - { - "identity" : "swift-docc-plugin", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-docc-plugin", - "state" : { - "revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", - "version" : "1.4.3" - } - }, - { - "identity" : "swift-docc-symbolkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-docc-symbolkit", - "state" : { - "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", - "version" : "1.0.0" - } - }, { "identity" : "swift-identified-collections", "kind" : "remoteSourceControl", @@ -91,15 +73,6 @@ "version" : "1.1.1" } }, - { - "identity" : "swift-macro-testing", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-macro-testing", - "state" : { - "revision" : "0b80a098d4805a21c412b65f01ffde7b01aab2fa", - "version" : "0.6.0" - } - }, { "identity" : "swift-navigation", "kind" : "remoteSourceControl", @@ -114,8 +87,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "21811d6230a625fa0f2e6ffa85be857075cc02c4", - "version" : "1.5.0" + "revision" : "671fa54b279fd73933b4a8b34782ebf6c8869145", + "version" : "1.5.1" } }, { @@ -123,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-sharing", "state" : { - "revision" : "2c840cf2ae0526ad6090e7796c4e13d9a2339f4a", - "version" : "2.3.3" + "revision" : "732871fabfc6b38fcdff5ad2f7336327dbf78e81", + "version" : "2.4.0" } }, { diff --git a/Sources/ComposableArchitectureMacros/ReducerMacro.swift b/Sources/ComposableArchitectureMacros/ReducerMacro.swift index 00e6a97e50a5..4ca39ff7f657 100644 --- a/Sources/ComposableArchitectureMacros/ReducerMacro.swift +++ b/Sources/ComposableArchitectureMacros/ReducerMacro.swift @@ -386,6 +386,9 @@ extension ReducerMacro: MemberMacro { \(access)static func scope(\ _ store: ComposableArchitecture.Store\ ) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { \(raw: storeScopes.joined(separator: "\n")) } @@ -627,7 +630,7 @@ private enum ReducerCase { { return """ case .\(name): - return .\(name)(store.scope(state: \\.\(name), action: \\.\(name))!) + return { .\(name)(store.scope(state: \\.\(name), action: \\.\(name))!) }() """ } else if let parameters = element.parameterClause?.parameters { let bindingNames = (0..) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { } @@ -321,15 +324,26 @@ case alert(AlertState) } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { case .activity: - return .activity(store.scope(state: \.activity, action: \.activity)!) + return { + .activity(store.scope(state: \.activity, action: \.activity)!) + }() case .timeline: - return .timeline(store.scope(state: \.timeline, action: \.timeline)!) + return { + .timeline(store.scope(state: \.timeline, action: \.timeline)!) + }() case .tweet: - return .tweet(store.scope(state: \.tweet, action: \.tweet)!) + return { + .tweet(store.scope(state: \.tweet, action: \.tweet)!) + }() case let .alert(v0): - return .alert(v0) + return { + .alert(v0) + }() } } } @@ -385,11 +399,18 @@ case meeting(ComposableArchitecture.StoreOf) } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { case .timeline: - return .timeline(store.scope(state: \.timeline, action: \.timeline)!) + return { + .timeline(store.scope(state: \.timeline, action: \.timeline)!) + }() case .meeting: - return .meeting(store.scope(state: \.meeting, action: \.meeting)!) + return { + .meeting(store.scope(state: \.meeting, action: \.meeting)!) + }() } } } @@ -433,6 +454,9 @@ } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { } @@ -483,6 +507,9 @@ } package static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { } @@ -533,6 +560,9 @@ } public static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { } @@ -581,9 +611,14 @@ case alert(AlertState) } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { case let .alert(v0): - return .alert(v0) + return { + .alert(v0) + }() } } } @@ -639,11 +674,18 @@ case timeline(ComposableArchitecture.StoreOf) } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { case .activity: - return .activity(store.scope(state: \.activity, action: \.activity)!) + return { + .activity(store.scope(state: \.activity, action: \.activity)!) + }() case .timeline: - return .timeline(store.scope(state: \.timeline, action: \.timeline)!) + return { + .timeline(store.scope(state: \.timeline, action: \.timeline)!) + }() } } } @@ -698,11 +740,18 @@ case meeting(Meeting) } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { case .timeline: - return .timeline(store.scope(state: \.timeline, action: \.timeline)!) + return { + .timeline(store.scope(state: \.timeline, action: \.timeline)!) + }() case let .meeting(v0): - return .meeting(v0) + return { + .meeting(v0) + }() } } } @@ -761,13 +810,22 @@ case meeting(Meeting, syncUp: SyncUp) } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { case let .alert(v0): - return .alert(v0) + return { + .alert(v0) + }() case let .dialog(v0): - return .dialog(v0) + return { + .dialog(v0) + }() case let .meeting(v0, v1): - return .meeting(v0, syncUp: v1) + return { + .meeting(v0, syncUp: v1) + }() } } } @@ -831,13 +889,22 @@ case sheet(ComposableArchitecture.StoreOf) } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { case .drillDown: - return .drillDown(store.scope(state: \.drillDown, action: \.drillDown)!) + return { + .drillDown(store.scope(state: \.drillDown, action: \.drillDown)!) + }() case .popover: - return .popover(store.scope(state: \.popover, action: \.popover)!) + return { + .popover(store.scope(state: \.popover, action: \.popover)!) + }() case .sheet: - return .sheet(store.scope(state: \.sheet, action: \.sheet)!) + return { + .sheet(store.scope(state: \.sheet, action: \.sheet)!) + }() } } } @@ -885,9 +952,14 @@ case feature(ComposableArchitecture.StoreOf) } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { case .feature: - return .feature(store.scope(state: \.feature, action: \.feature)!) + return { + .feature(store.scope(state: \.feature, action: \.feature)!) + }() } } } @@ -1247,30 +1319,49 @@ } static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + // NB: We are using immediately invoked closures in each case of this switch to work around a Swift + // bug that can cause large switch statements to blow the stack. + // https://forums.swift.org/t/struct-and-enum-accessors-take-a-large-amount-of-stack-space/63251/12 switch store.state { case .child: - return .child(store.scope(state: \.child, action: \.child)!) + return { + .child(store.scope(state: \.child, action: \.child)!) + }() #if os(macOS) case .mac: - return .mac(store.scope(state: \.mac, action: \.mac)!) + return { + .mac(store.scope(state: \.mac, action: \.mac)!) + }() case let .macAlert(v0): - return .macAlert(v0) + return { + .macAlert(v0) + }() #elseif os(iOS) case .phone: - return .phone(store.scope(state: \.phone, action: \.phone)!) + return { + .phone(store.scope(state: \.phone, action: \.phone)!) + }() #else case .other: - return .other(store.scope(state: \.other, action: \.other)!) + return { + .other(store.scope(state: \.other, action: \.other)!) + }() case .another: - return .another + return { + .another + }() #endif #if DEBUG #if INNER case .inner: - return .inner(store.scope(state: \.inner, action: \.inner)!) + return { + .inner(store.scope(state: \.inner, action: \.inner)!) + }() case let .innerDialog(v0): - return .innerDialog(v0) + return { + .innerDialog(v0) + }() #endif #endif diff --git a/Tests/ComposableArchitectureTests/Reducers/PresentationReducerTests.swift b/Tests/ComposableArchitectureTests/Reducers/PresentationReducerTests.swift index ffd03f5b8229..241a624d5e5b 100644 --- a/Tests/ComposableArchitectureTests/Reducers/PresentationReducerTests.swift +++ b/Tests/ComposableArchitectureTests/Reducers/PresentationReducerTests.swift @@ -1,4 +1,5 @@ import ComposableArchitecture +import SwiftUI import XCTest @available(*, deprecated, message: "TODO: Update to use case pathable syntax with Swift 5.9") @@ -2108,7 +2109,8 @@ final class PresentationReducerTests: BaseTCATestCase { ConfirmationDialogState { TextState("Hello!") } actions: { - }) + } + ) return .none case .destination(.presented(.dialog(.showAlert))): state.destination = .alert(AlertState { TextState("Hello!") }) @@ -2154,7 +2156,8 @@ final class PresentationReducerTests: BaseTCATestCase { ConfirmationDialogState { TextState("Hello!") } actions: { - }) + } + ) } await store.send(.destination(.dismiss)) { $0.destination = nil @@ -2617,7 +2620,7 @@ final class PresentationReducerTests: BaseTCATestCase { } } - #if !os(visionOS) + #if !os(visionOS) && compiler(<6.1) @Reducer struct TestEphemeralBindingDismissalFeature { @ObservableState @@ -2634,20 +2637,20 @@ final class PresentationReducerTests: BaseTCATestCase { .ifLet(\.$alert, action: /Action.alert) } } - // @MainActor - // func testEphemeralBindingDismissal() async { - // @Perception.Bindable var store = Store( - // initialState: TestEphemeralBindingDismissalFeature.State( - // alert: AlertState { TextState("Oops!") } - // ) - // ) { - // TestEphemeralBindingDismissalFeature() - // } - // - // XCTAssertNotNil(store.alert) - // $store.scope(state: \.alert, action: \.alert).wrappedValue = nil - // XCTAssertNil(store.alert) - // } + @MainActor + func testEphemeralBindingDismissal() async { + @Perception.Bindable var store = Store( + initialState: TestEphemeralBindingDismissalFeature.State( + alert: AlertState { TextState("Oops!") } + ) + ) { + TestEphemeralBindingDismissalFeature() + } + + XCTAssertNotNil(store.alert) + $store.scope(state: \.alert, action: \.alert).wrappedValue = nil + XCTAssertNil(store.alert) + } #endif } diff --git a/Tests/ComposableArchitectureTests/StoreTests.swift b/Tests/ComposableArchitectureTests/StoreTests.swift index 3887f20af7f0..387145fa5d6b 100644 --- a/Tests/ComposableArchitectureTests/StoreTests.swift +++ b/Tests/ComposableArchitectureTests/StoreTests.swift @@ -1,5 +1,6 @@ @preconcurrency import Combine @_spi(Internals) import ComposableArchitecture +import SwiftUI import XCTest #if canImport(Testing) @@ -524,7 +525,8 @@ final class StoreTests: BaseTCATestCase { [ .button, .child(2), - ]) + ] + ) } func testCascadingTaskCancellation() async { @@ -935,7 +937,8 @@ final class StoreTests: BaseTCATestCase { func testPresentationScope() async { let store = Store( initialState: Feature_testPresentationScope.State( - child: .init(child: .init())) + child: .init(child: .init()) + ) ) { Feature_testPresentationScope() } @@ -1103,27 +1106,27 @@ final class StoreTests: BaseTCATestCase { var body: some ReducerOf { EmptyReducer() } } - // #if !os(visionOS) - // @MainActor - // func testInvalidatedStoreScope() async throws { - // @Perception.Bindable var store = Store( - // initialState: InvalidatedStoreScopeParentFeature.State( - // child: InvalidatedStoreScopeChildFeature.State( - // grandchild: InvalidatedStoreScopeGrandchildFeature.State() - // ) - // ) - // ) { - // InvalidatedStoreScopeParentFeature() - // } - // store.send(.tap) - // - // @Perception.Bindable var childStore = store.scope(state: \.child, action: \.child)! - // let grandchildStoreBinding = $childStore.scope(state: \.grandchild, action: \.grandchild) - // - // store.send(.child(.dismiss)) - // grandchildStoreBinding.wrappedValue = nil - // } - // #endif + #if !os(visionOS) && compiler(<6.1) + @MainActor + func testInvalidatedStoreScope() async throws { + @Perception.Bindable var store = Store( + initialState: InvalidatedStoreScopeParentFeature.State( + child: InvalidatedStoreScopeChildFeature.State( + grandchild: InvalidatedStoreScopeGrandchildFeature.State() + ) + ) + ) { + InvalidatedStoreScopeParentFeature() + } + store.send(.tap) + + @Perception.Bindable var childStore = store.scope(state: \.child, action: \.child)! + let grandchildStoreBinding = $childStore.scope(state: \.grandchild, action: \.grandchild) + + store.send(.child(.dismiss)) + grandchildStoreBinding.wrappedValue = nil + } + #endif @MainActor func testSurroundingDependencies() {