Skip to content

Commit

Permalink
Swift 6 updates (#3379)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
stephencelis committed Sep 12, 2024
1 parent 8013f1a commit f1af337
Show file tree
Hide file tree
Showing 35 changed files with 112 additions and 101 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ jobs:
deriveddata-examples-${{ hashFiles('**/Sources/**/*.swift', '**/Tests/**/*.swift', '**/Examples/**/*.swift') }}
restore-keys: |
deriveddata-examples-
- name: Select Xcode 15.4
run: sudo xcode-select -s /Applications/Xcode_15.4.app
- name: Set IgnoreFileSystemDeviceInodeChanges flag
- name: Select Xcode 16
run: sudo xcode-select -s /Applications/Xcode_16_beta_6.app
- name: Set IgnoreFileSystemDeviceInodeChanges flag
run: defaults write com.apple.dt.XCBuild IgnoreFileSystemDeviceInodeChanges -bool YES
- name: Update mtime for incremental builds
- name: Update mtime for incremental builds
uses: chetan/git-restore-mtime-action@v2
- name: CaseStudies (SwiftUI)
run: make SCHEME="CaseStudies (SwiftUI)" test-example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ private let readMe = """

@Reducer
struct MultipleDestinations {
@Reducer(state: .equatable)
@Reducer
enum Destination {
case drillDown(Counter)
case popover(Counter)
Expand Down Expand Up @@ -46,6 +46,7 @@ struct MultipleDestinations {
.ifLet(\.$destination, action: \.destination)
}
}
extension MultipleDestinations.Destination.State: Equatable {}

struct MultipleDestinationsView: View {
@Bindable var store: StoreOf<MultipleDestinations>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ private let readMe = """

@Reducer
struct NavigationDemo {
@Reducer(state: .equatable)
@Reducer
enum Path {
case screenA(ScreenA)
case screenB(ScreenB)
Expand Down Expand Up @@ -65,6 +65,7 @@ struct NavigationDemo {
.forEach(\.path, action: \.path)
}
}
extension NavigationDemo.Path.State: Equatable {}

struct NavigationDemoView: View {
@Bindable var store: StoreOf<NavigationDemo>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ private enum PresentationTestCase {
var message = ""
@PresentationState var destination: Destination.State?
}
enum Action: Equatable, Sendable {
enum Action: Sendable {
case alertButtonTapped
case customAlertButtonTapped
case destination(PresentationAction<Destination.Action>)
Expand All @@ -21,7 +21,7 @@ private enum PresentationTestCase {
case sheetButtonTapped
}

@Reducer(state: .equatable, action: .equatable)
@Reducer
enum Destination {
case alert(AlertState<AlertAction>)
case customAlert
Expand Down Expand Up @@ -312,8 +312,8 @@ private enum PresentationTestCase {
}
}
}

}
extension PresentationTestCase.Feature.Destination.State: Equatable {}

struct PresentationTestCaseView: View {
private let store: StoreOf<PresentationTestCase.Feature>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ struct ObservableEnumView: View {

@Reducer
struct Feature {
@Reducer(state: .equatable)
@Reducer
enum Destination {
case feature1(ObservableBasicsView.Feature)
case feature2(ObservableBasicsView.Feature)
Expand Down Expand Up @@ -111,6 +111,7 @@ struct ObservableEnumView: View {
}
}
}
extension ObservableEnumView.Feature.Destination.State: Equatable {}

#Preview {
Logger.shared.isEnabled = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ struct ObservablePresentationView: View {

@Reducer
struct Feature {
@Reducer(state: .equatable)
@Reducer
enum Destination {
case fullScreenCover(ObservableBasicsView.Feature)
case popover(ObservableBasicsView.Feature)
Expand Down Expand Up @@ -146,3 +146,4 @@ struct ObservablePresentationView: View {
}
}
}
extension ObservablePresentationView.Feature.Destination.State: Equatable {}
3 changes: 2 additions & 1 deletion Examples/SyncUps/SyncUps/AppFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI

@Reducer
struct AppFeature {
@Reducer(state: .equatable)
@Reducer
enum Path {
case detail(SyncUpDetail)
case meeting(Meeting, syncUp: SyncUp)
Expand Down Expand Up @@ -47,6 +47,7 @@ struct AppFeature {
.forEach(\.path, action: \.path)
}
}
extension AppFeature.Path.State: Equatable {}

struct AppView: View {
@Bindable var store: StoreOf<AppFeature>
Expand Down
3 changes: 2 additions & 1 deletion Examples/SyncUps/SyncUps/SyncUpDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI

@Reducer
struct SyncUpDetail {
@Reducer(state: .equatable)
@Reducer
enum Destination {
case alert(AlertState<Alert>)
case edit(SyncUpForm)
Expand Down Expand Up @@ -109,6 +109,7 @@ struct SyncUpDetail {
.ifLet(\.$destination, action: \.destination)
}
}
extension SyncUpDetail.Destination.State: Equatable {}

struct SyncUpDetailView: View {
@Bindable var store: StoreOf<SyncUpDetail>
Expand Down
3 changes: 2 additions & 1 deletion Examples/SyncUps/SyncUps/SyncUpsList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI

@Reducer
struct SyncUpsList {
@Reducer(state: .equatable)
@Reducer
enum Destination {
case add(SyncUpForm)
case alert(AlertState<Alert>)
Expand Down Expand Up @@ -73,6 +73,7 @@ struct SyncUpsList {
.ifLet(\.$destination, action: \.destination)
}
}
extension SyncUpsList.Destination.State: Equatable {}

struct SyncUpsListView: View {
@Bindable var store: StoreOf<SyncUpsList>
Expand Down
3 changes: 2 additions & 1 deletion Examples/TicTacToe/tic-tac-toe/Sources/AppCore/AppCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ComposableArchitecture
import LoginCore
import NewGameCore

@Reducer(state: .equatable)
@Reducer
public enum TicTacToe {
case login(Login)
case newGame(NewGame)
Expand Down Expand Up @@ -37,3 +37,4 @@ public enum TicTacToe {
}
}
}
extension TicTacToe.State: Equatable {}
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
CONFIG = debug
PLATFORM = iOS
PLATFORM_IOS = iOS Simulator,id=$(call udid_for,iOS 17.5,iPhone \d\+ Pro [^M])
PLATFORM_IOS = iOS Simulator,id=$(call udid_for,iOS,iPhone \d\+ Pro [^M])
PLATFORM_MACOS = macOS
PLATFORM_MAC_CATALYST = macOS,variant=Mac Catalyst
PLATFORM_TVOS = tvOS Simulator,id=$(call udid_for,tvOS 17.5,TV)
PLATFORM_VISIONOS = visionOS Simulator,id=$(call udid_for,visionOS 1.2,Vision)
PLATFORM_WATCHOS = watchOS Simulator,id=$(call udid_for,watchOS 10.5,Watch)
PLATFORM_TVOS = tvOS Simulator,id=$(call udid_for,tvOS,TV)
PLATFORM_VISIONOS = visionOS Simulator,id=$(call udid_for,visionOS,Vision)
PLATFORM_WATCHOS = watchOS Simulator,id=$(call udid_for,watchOS,Watch)

TEST_RUNNER_CI = $(CI)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ instead.
- ``Reducer/forEach(_:action:element:fileID:filePath:line:column:)-1oguc``
- ``Reducer/forEach(_:action:destination:fileID:filePath:line:column:)-74erx``
- ``Reducer/onChange(of:removeDuplicates:_:)``

### Enum reducers

- ``_SynthesizedConformance``
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ more concise and more powerful.
* [Nested enum reducers](#Nested-enum-reducers)
* [Gotchas](#Gotchas)
* [Autocomplete](#Autocomplete)
* [Circular reference errors](#Circular-reference-errors)
* [#Preview and enum reducers](#Preview-and-enum-reducers)
* [CI build failures](#CI-build-failures)

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

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

```swift
@Reducer
enum Destination {
// ...
}
extension Destination.State: Equatable {} //
extension Destination.State: Equatable {}
```

See <doc:Reducer#Circular-reference-errors> below for more info on this error.

So, to work around this compiler bug the `@Reducer` macro takes two
``ComposableArchitecture/_SynthesizedConformance`` arguments that allow you to describe which
protocols you want to attach to the `State` or `Action` types:

```swift
@Reducer(state: .equatable, .sendable, action: .sendable)
enum Destination {
// ...
}
```

You can provide any combination of
``ComposableArchitecture/_SynthesizedConformance/codable``,
``ComposableArchitecture/_SynthesizedConformance/decodable``,
``ComposableArchitecture/_SynthesizedConformance/encodable``,
``ComposableArchitecture/_SynthesizedConformance/equatable``,
``ComposableArchitecture/_SynthesizedConformance/hashable``, or
``ComposableArchitecture/_SynthesizedConformance/sendable``.
> Note: In Swift <6 the above extension causes a compiler error due to a bug in Swift.
>
> To work around this compiler bug, the library provides a version of the `@Reducer` macro that
> takes two ``ComposableArchitecture/_SynthesizedConformance`` arguments, which allow you to
> describe the protocols you want to attach to the `State` or `Action` types:
>
> ```swift
> @Reducer(state: .equatable, .sendable, action: .sendable)
> enum Destination {
> // ...
> }
> ```
#### Nested enum reducers
Expand Down Expand Up @@ -464,30 +454,6 @@ providing additional type hints to the compiler:
+ Reduce<State, Action> { state, action in
```

#### Circular reference errors

There is currently a bug in the Swift compiler and macros that prevents you from extending types
that are inside other types with macros applied in the same file. For example, if you wanted to
extend a reducer's `State` with some extra functionality:

```swift
@Reducer
struct Feature {
@ObservableState
struct State { /* ... */ }
// ...
}

extension Feature.State { // 🛑 Circular reference
// ...
}
```

This unfortunately does not work. It is a
[known issue](https://github.com/apple/swift/issues/66450), and the only workaround is to either
move the extension to a separate file, or move the code from the extension to be directly inside the
`State` type.

#### #Preview and enum reducers

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

#### Error: External macro implementation … could not be found

When integrating with the Composable Architecture, one may encounter the following error:

> Error: External macro implementation type 'ComposableArchitectureMacros.ReducerMacro' could not be
> found for macro 'Reducer()'
This error can show up when the macro has not yet been enabled, which is a separate error that
should be visible from Xcode's Issue navigator.

Sometimes, however, this error will still emit due to an Xcode bug in which a custom build
configuration name is being used in the project. In general, using a build configuration other than
"Debug" or "Release" can trigger upstream build issues with Swift packages, and we recommend only
using the default "Debug" and "Release" build configuration names to avoid the above issue and
others.

#### CI build failures

When testing your code on an external CI server you may run into errors such as the following:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@
- ``ReducerCaseIgnored()``
- ``CaseReducer``
- ``CaseReducerState``
- ``_SynthesizedConformance``
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import SwiftUI

@Reducer
struct SyncUpDetail {
@Reducer(state: .equatable)
@Reducer
enum Destination {
}
// ...
}
extension SyncUpDetail.Destination.State: Equatable {}

struct SyncUpDetailView: View {
// ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI

@Reducer
struct SyncUpDetail {
@Reducer(state: .equatable)
@Reducer
enum Destination {
case alert(AlertState<Alert>)
case edit(SyncUpForm)
Expand All @@ -14,6 +14,7 @@ struct SyncUpDetail {
}
// ...
}
extension SyncUpDetail.Destination.State: Equatable {}

struct SyncUpDetailView: View {
// ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI

@Reducer
struct SyncUpDetail {
@Reducer(state: .equatable)
@Reducer
enum Destination {
case alert(AlertState<Alert>)
case edit(SyncUpForm)
Expand Down Expand Up @@ -78,6 +78,7 @@ struct SyncUpDetail {
.ifLet(\.$alert, action: \.alert)
}
}
extension SyncUpDetail.Destination.State: Equatable {}

extension AlertState where Action == SyncUpDetail.Action.Alert {
static let deleteSyncUp = Self {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI

@Reducer
struct SyncUpDetail {
@Reducer(state: .equatable)
@Reducer
enum Destination {
case alert(AlertState<Alert>)
case edit(SyncUpForm)
Expand Down Expand Up @@ -79,6 +79,7 @@ struct SyncUpDetail {
.ifLet(\.$alert, action: \.alert)
}
}
extension SyncUpDetail.Destination.State: Equatable {}

extension AlertState where Action == SyncUpDetail.Action.Alert {
static let deleteSyncUp = Self {
Expand Down
Loading

0 comments on commit f1af337

Please sign in to comment.