Skip to content

Commit

Permalink
Add case path iteration (#159)
Browse files Browse the repository at this point in the history
* wip

* Generate subscript to look up case key path from a case

* Add case lookup via `Enum.allCasePaths[root]`

* wip

* fix`

* fix

* Add case path iteration

* Update ci.yml

* wip

* wip

* Work around Swift bug

* wip
  • Loading branch information
stephencelis authored Jun 6, 2024
1 parent 5ebea06 commit 59f98d2
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
config:
- debug
#- release
swift: ['5.9.1']
swift: ['5.10']
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
Expand Down
6 changes: 6 additions & 0 deletions Sources/CasePaths/Never+CasePathable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ extension Case {
return Case<Never>(embed: absurd, extract: { (_: Value) in nil })
}
}

extension Never.AllCasePaths: Sequence {
public func makeIterator() -> some IteratorProtocol<PartialCaseKeyPath<Never>> {
[].makeIterator()
}
}
11 changes: 11 additions & 0 deletions Sources/CasePaths/Optional+CasePathable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ extension Optional: CasePathable {
}
)
}

private let _allCasePaths: [PartialCaseKeyPath<Optional>] = [
\.none,
\.some,
]
}

public static var allCasePaths: AllCasePaths {
Expand All @@ -67,3 +72,9 @@ extension Case {
self[dynamicMember: keyPath].some
}
}

extension Optional.AllCasePaths: Sequence {
public func makeIterator() -> some IteratorProtocol<PartialCaseKeyPath<Optional>> {
_allCasePaths.makeIterator()
}
}
11 changes: 11 additions & 0 deletions Sources/CasePaths/Result+CasePathable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,20 @@ extension Result: CasePathable {
}
)
}

private let _allCasePaths: [PartialCaseKeyPath<Result>] = [
\.success,
\.failure,
]
}

public static var allCasePaths: AllCasePaths {
AllCasePaths()
}
}

extension Result.AllCasePaths: Sequence {
public func makeIterator() -> some IteratorProtocol<PartialCaseKeyPath<Result>> {
_allCasePaths.makeIterator()
}
}
38 changes: 25 additions & 13 deletions Sources/CasePathsMacros/CasePathableMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,35 +83,47 @@ extension CasePathableMacro: MemberMacro {

let rewriter = SelfRewriter(selfEquivalent: enumName)
let memberBlock = rewriter.rewrite(enumDecl.memberBlock).cast(MemberBlockSyntax.self)

let caseSubscript = generateCaseSubscript(from: memberBlock.members, enumName: enumName)

let rootSwitchCases = generateCases(from: memberBlock.members, enumName: enumName) {
"case .\($0.name): return \\.\($0.name)"
}
let rootSwitch: DeclSyntax = rootSwitchCases.isEmpty
? "\\.never"
: """
switch root {
\(raw: rootSwitchCases.map(\.description).joined(separator: "\n"))
}
"""
let casePaths = generateDeclSyntax(from: memberBlock.members, enumName: enumName)
let allCases = generateCases(from: memberBlock.members, enumName: enumName) {
"allCasePaths.append(\\.\($0.name))"
}

return [
"""
public struct AllCasePaths {
public struct AllCasePaths: Sequence {
public subscript(root: \(enumName)) -> PartialCaseKeyPath<\(enumName)> {
switch root {
\(raw: caseSubscript.map(\.description).joined(separator: "\n"))
}
\(rootSwitch)
}
\(raw: casePaths.map(\.description).joined(separator: "\n"))
public func makeIterator() -> IndexingIterator<[PartialCaseKeyPath<\(enumName)>]> {
\(raw: allCases.isEmpty ? "let" : "var") allCasePaths: [PartialCaseKeyPath<\(enumName)>] = []\
\(raw: allCases.map { "\n\($0.description)" }.joined())
return allCasePaths.makeIterator()
}
}
public static var allCasePaths: AllCasePaths { AllCasePaths() }
"""
]
}

static func generateCaseSubscript(
static func generateCases(
from elements: MemberBlockItemListSyntax,
enumName: TokenSyntax
enumName: TokenSyntax,
body: (EnumCaseElementSyntax) -> DeclSyntax
) -> [DeclSyntax] {
elements.flatMap {
if let decl = $0.decl.as(EnumCaseDeclSyntax.self) {
return decl.elements.map {
"case .\($0.name): return \\.\($0.name)" as DeclSyntax
}
return decl.elements.map(body)
}
if let ifConfigDecl = $0.decl.as(IfConfigDeclSyntax.self) {
let ifClauses = ifConfigDecl.clauses.flatMap { decl -> [DeclSyntax] in
Expand All @@ -120,7 +132,7 @@ extension CasePathableMacro: MemberMacro {
}
let title = "\(decl.poundKeyword.text) \(decl.condition?.description ?? "")"
return ["\(raw: title)"]
+ generateCaseSubscript(from: elements, enumName: enumName)
+ generateCases(from: elements, enumName: enumName, body: body)
}
return ifClauses + ["#endif"]
}
Expand Down
Loading

0 comments on commit 59f98d2

Please sign in to comment.