Skip to content

Commit f1af337

Browse files
authored
Swift 6 updates (#3379)
* Swift 6 updates - Soft-deprecate `_SynthesizedConformance` now that Xcode 16 has fixed this bug. - Update docs accordingly. - Document Xcode 16 macro gotcha around custom build configuration names. * wip
1 parent 8013f1a commit f1af337

File tree

35 files changed

+112
-101
lines changed

35 files changed

+112
-101
lines changed

.github/workflows/ci.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ jobs:
8282
deriveddata-examples-${{ hashFiles('**/Sources/**/*.swift', '**/Tests/**/*.swift', '**/Examples/**/*.swift') }}
8383
restore-keys: |
8484
deriveddata-examples-
85-
- name: Select Xcode 15.4
86-
run: sudo xcode-select -s /Applications/Xcode_15.4.app
87-
- name: Set IgnoreFileSystemDeviceInodeChanges flag
85+
- name: Select Xcode 16
86+
run: sudo xcode-select -s /Applications/Xcode_16_beta_6.app
87+
- name: Set IgnoreFileSystemDeviceInodeChanges flag
8888
run: defaults write com.apple.dt.XCBuild IgnoreFileSystemDeviceInodeChanges -bool YES
89-
- name: Update mtime for incremental builds
89+
- name: Update mtime for incremental builds
9090
uses: chetan/git-restore-mtime-action@v2
9191
- name: CaseStudies (SwiftUI)
9292
run: make SCHEME="CaseStudies (SwiftUI)" test-example

Examples/CaseStudies/SwiftUICaseStudies/04-Navigation-Multiple-Destinations.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ private let readMe = """
88

99
@Reducer
1010
struct MultipleDestinations {
11-
@Reducer(state: .equatable)
11+
@Reducer
1212
enum Destination {
1313
case drillDown(Counter)
1414
case popover(Counter)
@@ -46,6 +46,7 @@ struct MultipleDestinations {
4646
.ifLet(\.$destination, action: \.destination)
4747
}
4848
}
49+
extension MultipleDestinations.Destination.State: Equatable {}
4950

5051
struct MultipleDestinationsView: View {
5152
@Bindable var store: StoreOf<MultipleDestinations>

Examples/CaseStudies/SwiftUICaseStudies/04-NavigationStack.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ private let readMe = """
77

88
@Reducer
99
struct NavigationDemo {
10-
@Reducer(state: .equatable)
10+
@Reducer
1111
enum Path {
1212
case screenA(ScreenA)
1313
case screenB(ScreenB)
@@ -65,6 +65,7 @@ struct NavigationDemo {
6565
.forEach(\.path, action: \.path)
6666
}
6767
}
68+
extension NavigationDemo.Path.State: Equatable {}
6869

6970
struct NavigationDemoView: View {
7071
@Bindable var store: StoreOf<NavigationDemo>

Examples/Integration/Integration/Legacy/LegacyPresentationTestCase.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ private enum PresentationTestCase {
99
var message = ""
1010
@PresentationState var destination: Destination.State?
1111
}
12-
enum Action: Equatable, Sendable {
12+
enum Action: Sendable {
1313
case alertButtonTapped
1414
case customAlertButtonTapped
1515
case destination(PresentationAction<Destination.Action>)
@@ -21,7 +21,7 @@ private enum PresentationTestCase {
2121
case sheetButtonTapped
2222
}
2323

24-
@Reducer(state: .equatable, action: .equatable)
24+
@Reducer
2525
enum Destination {
2626
case alert(AlertState<AlertAction>)
2727
case customAlert
@@ -312,8 +312,8 @@ private enum PresentationTestCase {
312312
}
313313
}
314314
}
315-
316315
}
316+
extension PresentationTestCase.Feature.Destination.State: Equatable {}
317317

318318
struct PresentationTestCaseView: View {
319319
private let store: StoreOf<PresentationTestCase.Feature>

Examples/Integration/Integration/iOS 17/ObservableEnumTestCase.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ struct ObservableEnumView: View {
6666

6767
@Reducer
6868
struct Feature {
69-
@Reducer(state: .equatable)
69+
@Reducer
7070
enum Destination {
7171
case feature1(ObservableBasicsView.Feature)
7272
case feature2(ObservableBasicsView.Feature)
@@ -111,6 +111,7 @@ struct ObservableEnumView: View {
111111
}
112112
}
113113
}
114+
extension ObservableEnumView.Feature.Destination.State: Equatable {}
114115

115116
#Preview {
116117
Logger.shared.isEnabled = true

Examples/Integration/Integration/iOS 17/ObservablePresentationTestCase.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ struct ObservablePresentationView: View {
9494

9595
@Reducer
9696
struct Feature {
97-
@Reducer(state: .equatable)
97+
@Reducer
9898
enum Destination {
9999
case fullScreenCover(ObservableBasicsView.Feature)
100100
case popover(ObservableBasicsView.Feature)
@@ -146,3 +146,4 @@ struct ObservablePresentationView: View {
146146
}
147147
}
148148
}
149+
extension ObservablePresentationView.Feature.Destination.State: Equatable {}

Examples/SyncUps/SyncUps/AppFeature.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftUI
33

44
@Reducer
55
struct AppFeature {
6-
@Reducer(state: .equatable)
6+
@Reducer
77
enum Path {
88
case detail(SyncUpDetail)
99
case meeting(Meeting, syncUp: SyncUp)
@@ -47,6 +47,7 @@ struct AppFeature {
4747
.forEach(\.path, action: \.path)
4848
}
4949
}
50+
extension AppFeature.Path.State: Equatable {}
5051

5152
struct AppView: View {
5253
@Bindable var store: StoreOf<AppFeature>

Examples/SyncUps/SyncUps/SyncUpDetail.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftUI
33

44
@Reducer
55
struct SyncUpDetail {
6-
@Reducer(state: .equatable)
6+
@Reducer
77
enum Destination {
88
case alert(AlertState<Alert>)
99
case edit(SyncUpForm)
@@ -109,6 +109,7 @@ struct SyncUpDetail {
109109
.ifLet(\.$destination, action: \.destination)
110110
}
111111
}
112+
extension SyncUpDetail.Destination.State: Equatable {}
112113

113114
struct SyncUpDetailView: View {
114115
@Bindable var store: StoreOf<SyncUpDetail>

Examples/SyncUps/SyncUps/SyncUpsList.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftUI
33

44
@Reducer
55
struct SyncUpsList {
6-
@Reducer(state: .equatable)
6+
@Reducer
77
enum Destination {
88
case add(SyncUpForm)
99
case alert(AlertState<Alert>)
@@ -73,6 +73,7 @@ struct SyncUpsList {
7373
.ifLet(\.$destination, action: \.destination)
7474
}
7575
}
76+
extension SyncUpsList.Destination.State: Equatable {}
7677

7778
struct SyncUpsListView: View {
7879
@Bindable var store: StoreOf<SyncUpsList>

Examples/TicTacToe/tic-tac-toe/Sources/AppCore/AppCore.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ComposableArchitecture
22
import LoginCore
33
import NewGameCore
44

5-
@Reducer(state: .equatable)
5+
@Reducer
66
public enum TicTacToe {
77
case login(Login)
88
case newGame(NewGame)
@@ -37,3 +37,4 @@ public enum TicTacToe {
3737
}
3838
}
3939
}
40+
extension TicTacToe.State: Equatable {}

Makefile

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
CONFIG = debug
22
PLATFORM = iOS
3-
PLATFORM_IOS = iOS Simulator,id=$(call udid_for,iOS 17.5,iPhone \d\+ Pro [^M])
3+
PLATFORM_IOS = iOS Simulator,id=$(call udid_for,iOS,iPhone \d\+ Pro [^M])
44
PLATFORM_MACOS = macOS
55
PLATFORM_MAC_CATALYST = macOS,variant=Mac Catalyst
6-
PLATFORM_TVOS = tvOS Simulator,id=$(call udid_for,tvOS 17.5,TV)
7-
PLATFORM_VISIONOS = visionOS Simulator,id=$(call udid_for,visionOS 1.2,Vision)
8-
PLATFORM_WATCHOS = watchOS Simulator,id=$(call udid_for,watchOS 10.5,Watch)
6+
PLATFORM_TVOS = tvOS Simulator,id=$(call udid_for,tvOS,TV)
7+
PLATFORM_VISIONOS = visionOS Simulator,id=$(call udid_for,visionOS,Vision)
8+
PLATFORM_WATCHOS = watchOS Simulator,id=$(call udid_for,watchOS,Watch)
99

1010
TEST_RUNNER_CI = $(CI)
1111

Sources/ComposableArchitecture/Documentation.docc/Extensions/Deprecations/ReducerDeprecations.md

+4
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ instead.
1818
- ``Reducer/forEach(_:action:element:fileID:filePath:line:column:)-1oguc``
1919
- ``Reducer/forEach(_:action:destination:fileID:filePath:line:column:)-74erx``
2020
- ``Reducer/onChange(of:removeDuplicates:_:)``
21+
22+
### Enum reducers
23+
24+
- ``_SynthesizedConformance``

Sources/ComposableArchitecture/Documentation.docc/Extensions/Reducer.md

+31-49
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ more concise and more powerful.
1616
* [Nested enum reducers](#Nested-enum-reducers)
1717
* [Gotchas](#Gotchas)
1818
* [Autocomplete](#Autocomplete)
19-
* [Circular reference errors](#Circular-reference-errors)
2019
* [#Preview and enum reducers](#Preview-and-enum-reducers)
2120
* [CI build failures](#CI-build-failures)
2221

@@ -390,38 +389,29 @@ is a way to get access to the state in the store without any observation taking
390389
#### Synthesizing protocol conformances on State and Action
391390

392391
Since the `State` and `Action` types are generated automatically for you when using `@Reducer` on an
393-
enum, it's not possible to directly synthesize conformances of `Equatable`, `Hashable`,
394-
_etc._, on those types. And further, due to a bug in the Swift compiler you cannot currently do
395-
this:
392+
enum, you must extend these types yourself to synthesize conformances of `Equatable`, `Hashable`,
393+
_etc._:
396394

397395
```swift
398396
@Reducer
399397
enum Destination {
400398
// ...
401399
}
402-
extension Destination.State: Equatable {} //
400+
extension Destination.State: Equatable {}
403401
```
404402

405-
See <doc:Reducer#Circular-reference-errors> below for more info on this error.
406-
407-
So, to work around this compiler bug the `@Reducer` macro takes two
408-
``ComposableArchitecture/_SynthesizedConformance`` arguments that allow you to describe which
409-
protocols you want to attach to the `State` or `Action` types:
410-
411-
```swift
412-
@Reducer(state: .equatable, .sendable, action: .sendable)
413-
enum Destination {
414-
// ...
415-
}
416-
```
417-
418-
You can provide any combination of
419-
``ComposableArchitecture/_SynthesizedConformance/codable``,
420-
``ComposableArchitecture/_SynthesizedConformance/decodable``,
421-
``ComposableArchitecture/_SynthesizedConformance/encodable``,
422-
``ComposableArchitecture/_SynthesizedConformance/equatable``,
423-
``ComposableArchitecture/_SynthesizedConformance/hashable``, or
424-
``ComposableArchitecture/_SynthesizedConformance/sendable``.
403+
> Note: In Swift <6 the above extension causes a compiler error due to a bug in Swift.
404+
>
405+
> To work around this compiler bug, the library provides a version of the `@Reducer` macro that
406+
> takes two ``ComposableArchitecture/_SynthesizedConformance`` arguments, which allow you to
407+
> describe the protocols you want to attach to the `State` or `Action` types:
408+
>
409+
> ```swift
410+
> @Reducer(state: .equatable, .sendable, action: .sendable)
411+
> enum Destination {
412+
> // ...
413+
> }
414+
> ```
425415
426416
#### Nested enum reducers
427417
@@ -464,30 +454,6 @@ providing additional type hints to the compiler:
464454
+ Reduce<State, Action> { state, action in
465455
```
466456

467-
#### Circular reference errors
468-
469-
There is currently a bug in the Swift compiler and macros that prevents you from extending types
470-
that are inside other types with macros applied in the same file. For example, if you wanted to
471-
extend a reducer's `State` with some extra functionality:
472-
473-
```swift
474-
@Reducer
475-
struct Feature {
476-
@ObservableState
477-
struct State { /* ... */ }
478-
// ...
479-
}
480-
481-
extension Feature.State { // 🛑 Circular reference
482-
// ...
483-
}
484-
```
485-
486-
This unfortunately does not work. It is a
487-
[known issue](https://github.com/apple/swift/issues/66450), and the only workaround is to either
488-
move the extension to a separate file, or move the code from the extension to be directly inside the
489-
`State` type.
490-
491457
#### #Preview and enum reducers
492458

493459
The `#Preview` macro is not capable of seeing the expansion of any macros since it is a macro
@@ -551,6 +517,22 @@ struct Feature_Previews: PreviewProvider {
551517
}
552518
```
553519

520+
#### Error: External macro implementation … could not be found
521+
522+
When integrating with the Composable Architecture, one may encounter the following error:
523+
524+
> Error: External macro implementation type 'ComposableArchitectureMacros.ReducerMacro' could not be
525+
> found for macro 'Reducer()'
526+
527+
This error can show up when the macro has not yet been enabled, which is a separate error that
528+
should be visible from Xcode's Issue navigator.
529+
530+
Sometimes, however, this error will still emit due to an Xcode bug in which a custom build
531+
configuration name is being used in the project. In general, using a build configuration other than
532+
"Debug" or "Release" can trigger upstream build issues with Swift packages, and we recommend only
533+
using the default "Debug" and "Release" build configuration names to avoid the above issue and
534+
others.
535+
554536
#### CI build failures
555537

556538
When testing your code on an external CI server you may run into errors such as the following:

Sources/ComposableArchitecture/Documentation.docc/Extensions/ReducerMacro.md

-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@
99
- ``ReducerCaseIgnored()``
1010
- ``CaseReducer``
1111
- ``CaseReducerState``
12-
- ``_SynthesizedConformance``

Sources/ComposableArchitecture/Documentation.docc/Tutorials/BuildingSyncUps/06-SyncUpDetail/EditingAndDeletingSyncUp-03-code-0001.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import SwiftUI
33

44
@Reducer
55
struct SyncUpDetail {
6-
@Reducer(state: .equatable)
6+
@Reducer
77
enum Destination {
88
}
99
// ...
1010
}
11+
extension SyncUpDetail.Destination.State: Equatable {}
1112

1213
struct SyncUpDetailView: View {
1314
// ...

Sources/ComposableArchitecture/Documentation.docc/Tutorials/BuildingSyncUps/06-SyncUpDetail/EditingAndDeletingSyncUp-03-code-0002.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftUI
33

44
@Reducer
55
struct SyncUpDetail {
6-
@Reducer(state: .equatable)
6+
@Reducer
77
enum Destination {
88
case alert(AlertState<Alert>)
99
case edit(SyncUpForm)
@@ -14,6 +14,7 @@ struct SyncUpDetail {
1414
}
1515
// ...
1616
}
17+
extension SyncUpDetail.Destination.State: Equatable {}
1718

1819
struct SyncUpDetailView: View {
1920
// ...

Sources/ComposableArchitecture/Documentation.docc/Tutorials/BuildingSyncUps/06-SyncUpDetail/EditingAndDeletingSyncUp-03-code-0003-previous.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftUI
33

44
@Reducer
55
struct SyncUpDetail {
6-
@Reducer(state: .equatable)
6+
@Reducer
77
enum Destination {
88
case alert(AlertState<Alert>)
99
case edit(SyncUpForm)
@@ -78,6 +78,7 @@ struct SyncUpDetail {
7878
.ifLet(\.$alert, action: \.alert)
7979
}
8080
}
81+
extension SyncUpDetail.Destination.State: Equatable {}
8182

8283
extension AlertState where Action == SyncUpDetail.Action.Alert {
8384
static let deleteSyncUp = Self {

Sources/ComposableArchitecture/Documentation.docc/Tutorials/BuildingSyncUps/06-SyncUpDetail/EditingAndDeletingSyncUp-03-code-0003.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftUI
33

44
@Reducer
55
struct SyncUpDetail {
6-
@Reducer(state: .equatable)
6+
@Reducer
77
enum Destination {
88
case alert(AlertState<Alert>)
99
case edit(SyncUpForm)
@@ -79,6 +79,7 @@ struct SyncUpDetail {
7979
.ifLet(\.$alert, action: \.alert)
8080
}
8181
}
82+
extension SyncUpDetail.Destination.State: Equatable {}
8283

8384
extension AlertState where Action == SyncUpDetail.Action.Alert {
8485
static let deleteSyncUp = Self {

0 commit comments

Comments
 (0)