Skip to content

Commit 656fa9c

Browse files
Support #if branching in enum reducers/state (#2800)
* Support `#if` branching in enum reducers/state This adds support for `#if` branching across enum cases in observable state and reducer enums. * wip * add tests --------- Co-authored-by: Brandon Williams <[email protected]>
1 parent 591f1a4 commit 656fa9c

File tree

7 files changed

+751
-197
lines changed

7 files changed

+751
-197
lines changed

Sources/ComposableArchitectureMacros/ObservableStateMacro.swift

Lines changed: 104 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,6 @@ extension ObservableStateMacro: MemberMacro {
248248

249249
var declarations = [DeclSyntax]()
250250

251-
let access = declaration.modifiers.first {
252-
$0.name.tokenKind == .keyword(.public) || $0.name.tokenKind == .keyword(.package)
253-
}
254251
declaration.addIfNeeded(
255252
ObservableStateMacro.registrarVariable(observableType), to: &declarations)
256253
declaration.addIfNeeded(ObservableStateMacro.idVariable(), to: &declarations)
@@ -260,6 +257,106 @@ extension ObservableStateMacro: MemberMacro {
260257
}
261258
}
262259

260+
extension Array where Element == ObservableStateCase {
261+
init(members: MemberBlockItemListSyntax) {
262+
var tag = 0
263+
self.init(members: members, tag: &tag)
264+
}
265+
266+
init(members: MemberBlockItemListSyntax, tag: inout Int) {
267+
self = members.flatMap { member -> [ObservableStateCase] in
268+
if let enumCaseDecl = member.decl.as(EnumCaseDeclSyntax.self) {
269+
return enumCaseDecl.elements.map {
270+
defer { tag += 1 }
271+
return ObservableStateCase.element($0, tag: tag)
272+
}
273+
}
274+
if let ifConfigDecl = member.decl.as(IfConfigDeclSyntax.self) {
275+
let configs = ifConfigDecl.clauses.flatMap { decl -> [ObservableStateCase.IfConfig] in
276+
guard let elements = decl.elements?.as(MemberBlockItemListSyntax.self)
277+
else { return [] }
278+
return [
279+
ObservableStateCase.IfConfig(
280+
poundKeyword: decl.poundKeyword,
281+
condition: decl.condition,
282+
cases: Array(members: elements, tag: &tag)
283+
)
284+
]
285+
}
286+
return [.ifConfig(configs)]
287+
}
288+
return []
289+
}
290+
}
291+
}
292+
293+
enum ObservableStateCase {
294+
case element(EnumCaseElementSyntax, tag: Int)
295+
indirect case ifConfig([IfConfig])
296+
297+
struct IfConfig {
298+
let poundKeyword: TokenSyntax
299+
let condition: ExprSyntax?
300+
let cases: [ObservableStateCase]
301+
}
302+
303+
var getCase: String {
304+
switch self {
305+
case let .element(element, tag):
306+
if let parameters = element.parameterClause?.parameters, parameters.count == 1 {
307+
return """
308+
case let .\(element.name.text)(state):
309+
return ._$id(for: state)._$tag(\(tag))
310+
"""
311+
} else {
312+
return """
313+
case .\(element.name.text):
314+
return ._$inert._$tag(\(tag))
315+
"""
316+
}
317+
case let .ifConfig(configs):
318+
return configs
319+
.map {
320+
"""
321+
\($0.poundKeyword.text) \($0.condition?.trimmedDescription ?? "")
322+
\($0.cases.map(\.getCase).joined(separator: "\n"))
323+
"""
324+
}
325+
.joined(separator: "\n") + "#endif\n"
326+
}
327+
}
328+
329+
var willModifyCase: String {
330+
switch self {
331+
case let .element(element, _):
332+
if let parameters = element.parameterClause?.parameters,
333+
parameters.count == 1,
334+
let parameter = parameters.first
335+
{
336+
return """
337+
case var .\(element.name.text)(state):
338+
\(ObservableStateMacro.moduleName)._$willModify(&state)
339+
self = .\(element.name.text)(\(parameter.firstName.map { "\($0): " } ?? "")state)
340+
"""
341+
} else {
342+
return """
343+
case .\(element.name.text):
344+
break
345+
"""
346+
}
347+
case let .ifConfig(configs):
348+
return configs
349+
.map {
350+
"""
351+
\($0.poundKeyword.text) \($0.condition?.trimmedDescription ?? "")
352+
\($0.cases.map(\.willModifyCase).joined(separator: "\n"))
353+
"""
354+
}
355+
.joined(separator: "\n") + "#endif\n"
356+
}
357+
}
358+
}
359+
263360
extension ObservableStateMacro {
264361
public static func enumExpansion<
265362
Declaration: DeclGroupSyntax,
@@ -269,43 +366,12 @@ extension ObservableStateMacro {
269366
providingMembersOf declaration: Declaration,
270367
in context: Context
271368
) throws -> [DeclSyntax] {
272-
let enumCaseDecls = declaration.memberBlock.members
273-
.flatMap { $0.decl.as(EnumCaseDeclSyntax.self)?.elements ?? [] }
369+
let cases = [ObservableStateCase](members: declaration.memberBlock.members)
274370
var getCases: [String] = []
275371
var willModifyCases: [String] = []
276-
for (tag, enumCaseDecl) in enumCaseDecls.enumerated() {
277-
// TODO: Support multiple parameters of observable state?
278-
if let parameters = enumCaseDecl.parameterClause?.parameters,
279-
parameters.count == 1,
280-
let parameter = parameters.first
281-
{
282-
getCases.append(
283-
"""
284-
case let .\(enumCaseDecl.name.text)(state):
285-
return ._$id(for: state)._$tag(\(tag))
286-
"""
287-
)
288-
willModifyCases.append(
289-
"""
290-
case var .\(enumCaseDecl.name.text)(state):
291-
\(moduleName)._$willModify(&state)
292-
self = .\(enumCaseDecl.name.text)(\(parameter.firstName.map { "\($0): " } ?? "")state)
293-
"""
294-
)
295-
} else {
296-
getCases.append(
297-
"""
298-
case .\(enumCaseDecl.name.text):
299-
return ._$inert._$tag(\(tag))
300-
"""
301-
)
302-
willModifyCases.append(
303-
"""
304-
case .\(enumCaseDecl.name.text):
305-
break
306-
"""
307-
)
308-
}
372+
for enumCase in cases {
373+
getCases.append(enumCase.getCase)
374+
willModifyCases.append(enumCase.willModifyCase)
309375
}
310376

311377
return [

0 commit comments

Comments
 (0)