Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix macro compiler bug in release mode. #2827

Merged
merged 2 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "aedcf6f4cd486ccef5b312ccac85d4b3f6e58605",
"version" : "1.1.2"
"revision" : "6ea3b1b6a4957806d72030a32360d4bcb155a0d2",
"version" : "1.2.0"
}
},
{
Expand Down
29 changes: 26 additions & 3 deletions Sources/ComposableArchitectureMacros/ReducerMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ extension ReducerMacro: MemberMacro {
var reducerScopes: [String] = []
var storeCases: [String] = []
var storeScopes: [String] = []
var reducerTypeScopes: [String] = []

for enumCaseElement in enumCaseElements {
let element = enumCaseElement.element
Expand Down Expand Up @@ -285,6 +286,11 @@ extension ReducerMacro: MemberMacro {
return .\(name)(store.scope(state: \\.\(name), action: \\.\(name))!)
"""
)
reducerTypeScopes.append(
"""
Scope<Self.State, Self.Action, \(type.trimmed)>
"""
)
}
} else {
stateCaseDecls.append("case \(element.trimmedDescription)")
Expand Down Expand Up @@ -361,13 +367,30 @@ extension ReducerMacro: MemberMacro {
)
}
if !hasBody {
var staticVarBody = ""
if reducerTypeScopes.isEmpty {
staticVarBody = "EmptyReducer<Self.State, Self.Action>"
} else if reducerTypeScopes.count == 1 {
staticVarBody = reducerTypeScopes[0]
} else {
for _ in 1...(reducerTypeScopes.count - 1) {
staticVarBody.append("ReducerBuilder<Self.State, Self.Action>._Sequence<")
}
staticVarBody.append(reducerTypeScopes[0])
staticVarBody.append(", ")
for type in reducerTypeScopes.dropFirst() {
staticVarBody.append(type)
staticVarBody.append(">, ")
}
staticVarBody.removeLast(2)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is messy code, but it essentially generates a type like this:

ReducerBuilder<Self.State, Self.Action>._Sequence<
  ReducerBuilder<Self.State, Self.Action>._Sequence<
    ReducerBuilder<Self.State, Self.Action>._Sequence<
      Scope<Self.State, Self.Action, Feature1>, 
      Scope<Self.State, Self.Action, Feature2>
    >, 
    Scope<Self.State, Self.Action, Feature3>
  >, 
  Scope<Self.State, Self.Action, Feature4>
>

…from a list of Scope types.


decls.append(
"""
\(access)static var body: some ComposableArchitecture.Reducer<Self.State, Self.Action> {
ComposableArchitecture.CombineReducers {
@ComposableArchitecture.ReducerBuilder<Self.State, Self.Action>
\(access)static var body: \(raw: staticVarBody) {
\(raw: reducerScopes.joined(separator: "\n"))
}
}
"""
)
}
Expand Down
135 changes: 100 additions & 35 deletions Tests/ComposableArchitectureMacrosTests/ReducerMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,9 @@

}

static var body: some ComposableArchitecture.Reducer<Self.State, Self.Action> {
ComposableArchitecture.CombineReducers {
@ComposableArchitecture.ReducerBuilder<Self.State, Self.Action>
static var body: EmptyReducer<Self.State, Self.Action> {

}
}

enum CaseScope {
Expand All @@ -260,6 +259,7 @@
"""
@Reducer
enum Destination {
case activity(Activity)
case timeline(Timeline)
case tweet(Tweet)
case alert(AlertState<Alert>)
Expand All @@ -272,6 +272,7 @@
} expansion: {
#"""
enum Destination {
case activity(Activity)
case timeline(Timeline)
case tweet(Tweet)
@ReducerCaseEphemeral
Expand All @@ -286,37 +287,44 @@
@ObservableState
enum State: ComposableArchitecture.CaseReducerState {
typealias StateReducer = Destination
case activity(Activity.State)
case timeline(Timeline.State)
case tweet(Tweet.State)
case alert(AlertState<Alert>)
}

@CasePathable
enum Action {
case activity(Activity.Action)
case timeline(Timeline.Action)
case tweet(Tweet.Action)
case alert(AlertState<Alert>.Action)
}

static var body: some ComposableArchitecture.Reducer<Self.State, Self.Action> {
ComposableArchitecture.CombineReducers {
ComposableArchitecture.Scope(state: \Self.State.Cases.timeline, action: \Self.Action.Cases.timeline) {
Timeline()
}
ComposableArchitecture.Scope(state: \Self.State.Cases.tweet, action: \Self.Action.Cases.tweet) {
Tweet()
}
@ComposableArchitecture.ReducerBuilder<Self.State, Self.Action>
static var body: ReducerBuilder<Self.State, Self.Action>._Sequence<ReducerBuilder<Self.State, Self.Action>._Sequence<Scope<Self.State, Self.Action, Activity>, Scope<Self.State, Self.Action, Timeline>>, Scope<Self.State, Self.Action, Tweet>> {
ComposableArchitecture.Scope(state: \Self.State.Cases.activity, action: \Self.Action.Cases.activity) {
Activity()
}
ComposableArchitecture.Scope(state: \Self.State.Cases.timeline, action: \Self.Action.Cases.timeline) {
Timeline()
}
ComposableArchitecture.Scope(state: \Self.State.Cases.tweet, action: \Self.Action.Cases.tweet) {
Tweet()
}
}

enum CaseScope {
case activity(ComposableArchitecture.StoreOf<Activity>)
case timeline(ComposableArchitecture.StoreOf<Timeline>)
case tweet(ComposableArchitecture.StoreOf<Tweet>)
case alert(AlertState<Alert>)
}

static func scope(_ store: ComposableArchitecture.Store<Self.State, Self.Action>) -> CaseScope {
switch store.state {
case .activity:
return .activity(store.scope(state: \.activity, action: \.activity)!)
case .timeline:
return .timeline(store.scope(state: \.timeline, action: \.timeline)!)
case .tweet:
Expand All @@ -333,6 +341,67 @@
}
}

func testEnum_TwoCases() {
assertMacro {
"""
@Reducer
enum Destination {
case activity(Activity)
case timeline(Timeline)
}
"""
} expansion: {
#"""
enum Destination {
case activity(Activity)
case timeline(Timeline)

@CasePathable
@dynamicMemberLookup
@ObservableState
enum State: ComposableArchitecture.CaseReducerState {
typealias StateReducer = Destination
case activity(Activity.State)
case timeline(Timeline.State)
}

@CasePathable
enum Action {
case activity(Activity.Action)
case timeline(Timeline.Action)
}

@ComposableArchitecture.ReducerBuilder<Self.State, Self.Action>
static var body: ReducerBuilder<Self.State, Self.Action>._Sequence<Scope<Self.State, Self.Action, Activity>, Scope<Self.State, Self.Action, Timeline>> {
ComposableArchitecture.Scope(state: \Self.State.Cases.activity, action: \Self.Action.Cases.activity) {
Activity()
}
ComposableArchitecture.Scope(state: \Self.State.Cases.timeline, action: \Self.Action.Cases.timeline) {
Timeline()
}
}

enum CaseScope {
case activity(ComposableArchitecture.StoreOf<Activity>)
case timeline(ComposableArchitecture.StoreOf<Timeline>)
}

static func scope(_ store: ComposableArchitecture.Store<Self.State, Self.Action>) -> CaseScope {
switch store.state {
case .activity:
return .activity(store.scope(state: \.activity, action: \.activity)!)
case .timeline:
return .timeline(store.scope(state: \.timeline, action: \.timeline)!)
}
}
}

extension Destination: ComposableArchitecture.CaseReducer, ComposableArchitecture.Reducer {
}
"""#
}
}

func testEnum_CaseIgnored() {
assertMacro {
"""
Expand Down Expand Up @@ -364,11 +433,10 @@
case timeline(Timeline.Action)
}

static var body: some ComposableArchitecture.Reducer<Self.State, Self.Action> {
ComposableArchitecture.CombineReducers {
ComposableArchitecture.Scope(state: \Self.State.Cases.timeline, action: \Self.Action.Cases.timeline) {
Timeline()
}
@ComposableArchitecture.ReducerBuilder<Self.State, Self.Action>
static var body: Scope<Self.State, Self.Action, Timeline> {
ComposableArchitecture.Scope(state: \Self.State.Cases.timeline, action: \Self.Action.Cases.timeline) {
Timeline()
}
}

Expand Down Expand Up @@ -429,10 +497,9 @@
case dialog(ConfirmationDialogState<Dialog>.Action)
}

static var body: some ComposableArchitecture.Reducer<Self.State, Self.Action> {
ComposableArchitecture.CombineReducers {
@ComposableArchitecture.ReducerBuilder<Self.State, Self.Action>
static var body: EmptyReducer<Self.State, Self.Action> {

}
}

enum CaseScope {
Expand Down Expand Up @@ -493,17 +560,16 @@
case sheet(Counter.Action)
}

static var body: some ComposableArchitecture.Reducer<Self.State, Self.Action> {
ComposableArchitecture.CombineReducers {
ComposableArchitecture.Scope(state: \Self.State.Cases.drillDown, action: \Self.Action.Cases.drillDown) {
Counter()
}
ComposableArchitecture.Scope(state: \Self.State.Cases.popover, action: \Self.Action.Cases.popover) {
Counter()
}
ComposableArchitecture.Scope(state: \Self.State.Cases.sheet, action: \Self.Action.Cases.sheet) {
Counter()
}
@ComposableArchitecture.ReducerBuilder<Self.State, Self.Action>
static var body: ReducerBuilder<Self.State, Self.Action>._Sequence<ReducerBuilder<Self.State, Self.Action>._Sequence<Scope<Self.State, Self.Action, Counter>, Scope<Self.State, Self.Action, Counter>>, Scope<Self.State, Self.Action, Counter>> {
ComposableArchitecture.Scope(state: \Self.State.Cases.drillDown, action: \Self.Action.Cases.drillDown) {
Counter()
}
ComposableArchitecture.Scope(state: \Self.State.Cases.popover, action: \Self.Action.Cases.popover) {
Counter()
}
ComposableArchitecture.Scope(state: \Self.State.Cases.sheet, action: \Self.Action.Cases.sheet) {
Counter()
}
}

Expand Down Expand Up @@ -557,11 +623,10 @@
case feature(Nested.Feature.Action)
}

static var body: some ComposableArchitecture.Reducer<Self.State, Self.Action> {
ComposableArchitecture.CombineReducers {
ComposableArchitecture.Scope(state: \Self.State.Cases.feature, action: \Self.Action.Cases.feature) {
Nested.Feature()
}
@ComposableArchitecture.ReducerBuilder<Self.State, Self.Action>
static var body: Scope<Self.State, Self.Action, Nested.Feature> {
ComposableArchitecture.Scope(state: \Self.State.Cases.feature, action: \Self.Action.Cases.feature) {
Nested.Feature()
}
}

Expand Down
64 changes: 41 additions & 23 deletions Tests/ComposableArchitectureTests/MacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,28 +55,46 @@
}
}

// enum TestEnumReducer_Basics {
// @Reducer struct Feature {}
// @Reducer
// enum Destination {
// case feature(Feature)
// }
// }
enum TestEnumReducer_Basics {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests were failing on CI before the enum-reducers branch was merged, and in haste I just commented them out because I thought it was due to something else. Well, turns out it was catching the problem this PR fixes. So now we can bring them back.

@Reducer struct Feature {}
@Reducer
enum Destination1 {
case feature1(Feature)
}
@Reducer
enum Destination2 {
case feature1(Feature)
case feature2(Feature)
}
@Reducer
enum Destination3 {
case feature1(Feature)
case feature2(Feature)
case feature3(Feature)
}
@Reducer
enum Destination4 {
case feature1(Feature)
case feature2(Feature)
case feature3(Feature)
case feature4(Feature)
}
}

// enum TestEnumReducer_SynthesizedConformances {
// @Reducer
// struct Feature {
// }
// @Reducer(
// state: .codable, .decodable, .encodable, .equatable, .hashable, .sendable,
// action: .equatable, .hashable, .sendable
// )
// enum Destination {
// case feature(Feature)
// }
// func stateRequirements(_: some Codable & Equatable & Hashable & Sendable) {}
// func actionRequirements(_: some Equatable & Hashable & Sendable) {}
// func givenState(_ state: Destination.State) { stateRequirements(state) }
// func givenAction(_ action: Destination.Action) { actionRequirements(action) }
// }
enum TestEnumReducer_SynthesizedConformances {
@Reducer
struct Feature {
}
@Reducer(
state: .codable, .decodable, .encodable, .equatable, .hashable, .sendable,
action: .equatable, .hashable, .sendable
)
enum Destination {
case feature(Feature)
}
func stateRequirements(_: some Codable & Equatable & Hashable & Sendable) {}
func actionRequirements(_: some Equatable & Hashable & Sendable) {}
func givenState(_ state: Destination.State) { stateRequirements(state) }
func givenAction(_ action: Destination.Action) { actionRequirements(action) }
}
#endif