diff --git a/Sources/ComposableArchitectureMacros/ReducerMacro.swift b/Sources/ComposableArchitectureMacros/ReducerMacro.swift index 0e8390756bee..4f566a2793a4 100644 --- a/Sources/ComposableArchitectureMacros/ReducerMacro.swift +++ b/Sources/ComposableArchitectureMacros/ReducerMacro.swift @@ -468,7 +468,7 @@ private enum ReducerCase { let parameter = parameterClause.parameters.first, parameter.type.is(IdentifierTypeSyntax.self) || parameter.type.is(MemberTypeSyntax.self) { - let stateCase = attribute == .ephemeral ? element : element.suffixed("State") + let stateCase = attribute == .ephemeral ? element : element.suffixed("State").type return "case \(stateCase.trimmedDescription)" } else { return "case \(element.trimmedDescription)" @@ -496,7 +496,7 @@ private enum ReducerCase { let parameter = parameterClause.parameters.first, parameter.type.is(IdentifierTypeSyntax.self) || parameter.type.is(MemberTypeSyntax.self) { - return "case \(element.suffixed("Action").trimmedDescription)" + return "case \(element.suffixed("Action").type.trimmedDescription)" } else { return nil } @@ -545,11 +545,12 @@ private enum ReducerCase { { let name = element.name.text let type = parameter.type + let reducer = parameter.defaultValue?.value.trimmedDescription ?? "\(type.trimmed)()" return """ ComposableArchitecture.Scope(\ state: \\Self.State.Cases.\(name), action: \\Self.Action.Cases.\(name)\ ) { - \(type.trimmed)() + \(reducer) } """ } else { @@ -747,12 +748,21 @@ extension EnumCaseDeclSyntax { } extension EnumCaseElementSyntax { + fileprivate var type: Self { + var element = self + if var parameterClause = element.parameterClause { + parameterClause.parameters[parameterClause.parameters.startIndex].defaultValue = nil + element.parameterClause = parameterClause + } + return element + } + fileprivate func suffixed(_ suffix: TokenSyntax) -> Self { var element = self if var parameterClause = element.parameterClause, let type = parameterClause.parameters.first?.type { - let type = MemberTypeSyntax(baseType: type, name: suffix) + let type = MemberTypeSyntax(baseType: type.trimmed, name: suffix) parameterClause.parameters[parameterClause.parameters.startIndex].type = TypeSyntax(type) element.parameterClause = parameterClause } diff --git a/Tests/ComposableArchitectureMacrosTests/ReducerMacroTests.swift b/Tests/ComposableArchitectureMacrosTests/ReducerMacroTests.swift index 89b880a0a6e1..a84b1f6f379e 100644 --- a/Tests/ComposableArchitectureMacrosTests/ReducerMacroTests.swift +++ b/Tests/ComposableArchitectureMacrosTests/ReducerMacroTests.swift @@ -340,6 +340,67 @@ """# } } + + func testEnum_DefaultInitializer() { + assertMacro { + """ + @Reducer + enum Destination { + case timeline(Timeline) + case meeting(Meeting = Meeting(context: .sheet)) + } + """ + } expansion: { + #""" + enum Destination { + case timeline(Timeline) + case meeting(Meeting = Meeting(context: .sheet)) + + @CasePathable + @dynamicMemberLookup + @ObservableState + enum State: ComposableArchitecture.CaseReducerState { + typealias StateReducer = Destination + case timeline(Timeline.State) + case meeting(Meeting.State) + } + + @CasePathable + enum Action { + case timeline(Timeline.Action) + case meeting(Meeting.Action) + } + + @ComposableArchitecture.ReducerBuilder + static var body: ComposableArchitecture.ReducerBuilder._Sequence, ComposableArchitecture.Scope> { + ComposableArchitecture.Scope(state: \Self.State.Cases.timeline, action: \Self.Action.Cases.timeline) { + Timeline() + } + ComposableArchitecture.Scope(state: \Self.State.Cases.meeting, action: \Self.Action.Cases.meeting) { + Meeting(context: .sheet) + } + } + + enum CaseScope { + case timeline(ComposableArchitecture.StoreOf) + case meeting(ComposableArchitecture.StoreOf) + } + + static func scope(_ store: ComposableArchitecture.Store) -> CaseScope { + switch store.state { + case .timeline: + return .timeline(store.scope(state: \.timeline, action: \.timeline)!) + case .meeting: + return .meeting(store.scope(state: \.meeting, action: \.meeting)!) + } + } + } + + extension Destination: ComposableArchitecture.CaseReducer, ComposableArchitecture.Reducer { + } + """# + } + } func testEnum_Empty() { assertMacro { diff --git a/Tests/ComposableArchitectureTests/EnumReducerMacroTests.swift b/Tests/ComposableArchitectureTests/EnumReducerMacroTests.swift index 0fae5a9ced3e..69c34e6a3885 100644 --- a/Tests/ComposableArchitectureTests/EnumReducerMacroTests.swift +++ b/Tests/ComposableArchitectureTests/EnumReducerMacroTests.swift @@ -28,4 +28,15 @@ #endif } } + + private enum TestEnumReducer_DefaultInitializer { + @Reducer + struct Feature { + let context: String + } + @Reducer + enum Destination1 { + case feature1(Feature = Feature(context: "context")) + } + } #endif