Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/CasePathsCore/CasePathIterable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
///
/// The `@CasePathable` macro automatically generates a conformance to this protocol.
///
/// You can iterate over ``CasePathable/allCasePaths`` to get access to each individual case path:
/// You can collect ``CasePathable/allCasePaths`` into an array to get access to each case path:
///
/// ```swift
/// @CasePathable enum Field {
/// case title(String)
/// case body(String
/// case body(String)
/// case isLive
/// }
///
Expand Down
56 changes: 50 additions & 6 deletions Sources/CasePathsCore/CasePathReflectable.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/// A type that can reflect a case path from a given case.
/// A type that can reflect the case paths of an enum.
///
/// The `@CasePathable` macro automatically generates a conformance to this protocol on the enum's
/// ``CasePathable/AllCasePaths`` type.
///
/// You can look up an enum's case path by passing it to ``CasePathReflectable/subscript(_:)``:
/// ``CasePathable/AllCasePaths`` type, which powers ``CasePathable/case`` and iteration over an
/// enum's case key paths:
///
/// ```swift
/// @CasePathable
Expand All @@ -13,15 +12,60 @@
/// case isLive
/// }
///
/// Field.allCasePaths[.title("Hello, Blob!")] // \.title
/// Field.title("Hello, Blob!").case // \.title
/// Array(Field.allCasePaths) // [\.title, \.body, \.isLive]
/// ```
///
/// You should not need to interact with this protocol directly. Constrain generic code to
/// ``CasePathable`` instead.
public protocol CasePathReflectable<Root> {
/// The enum type that can be reflected.
associatedtype Root: CasePathable

static func _case(for root: Root) -> PartialCaseKeyPath<Root>

static var _allCaseKeyPaths: [PartialCaseKeyPath<Root>] { get }

/// Returns the case key path for a given root value.
///
/// - Parameter root: An root value.
/// - Parameter root: A root value.
/// - Returns: A case path to the root value.
@available(*, deprecated, message: "Use 'root.case' instead")
subscript(root: Root) -> PartialCaseKeyPath<Root> { get }
}

extension CasePathReflectable {
@available(*, deprecated, message: "Use 'root.case' instead")
public subscript(root: Root) -> PartialCaseKeyPath<Root> {
root.case
}
}

extension CasePathReflectable where Root.AllCasePaths == Self {
@available(*, deprecated, message: "Implement 'static _case(for:)' instead")
public static func _case(for root: Root) -> PartialCaseKeyPath<Root> {
Root.allCasePaths[root]
}
}

extension CasePathReflectable {
@available(*, deprecated, message: "Implement 'static _allCaseKeyPaths' instead")
public static var _allCaseKeyPaths: [PartialCaseKeyPath<Root>] {
[]
}
}

extension CasePathReflectable
where Self: Sequence, Element == PartialCaseKeyPath<Root>, Root.AllCasePaths == Self {
@available(*, deprecated, message: "Implement 'static _allCaseKeyPaths' instead")
public static var _allCaseKeyPaths: [PartialCaseKeyPath<Root>] {
Array(Root.allCasePaths)
}
}

extension CasePathReflectable where Self: Sequence, Element == PartialCaseKeyPath<Root> {
@available(*, deprecated, message: "Iterate over 'Array(Enum.allCasePaths)' instead")
public func makeIterator() -> IndexingIterator<[PartialCaseKeyPath<Root>]> {
Self._allCaseKeyPaths.makeIterator()
}
}
34 changes: 32 additions & 2 deletions Sources/CasePathsCore/CasePathable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ import IssueReporting
/// }
///
/// public static var allCasePaths: AllCasePaths { AllCasePaths() }
///
/// public var `case`: PartialCaseKeyPath<Self> {
/// switch self {
/// case .success: return \.success
/// case .failure: return \.failure
/// }
/// }
///
/// public static var _allCaseKeyPaths: [PartialCaseKeyPath<Self>] {
/// [\.success, \.failure]
/// }
/// }
/// ```
public protocol CasePathable {
Expand All @@ -40,6 +51,22 @@ public protocol CasePathable {

/// A collection of all case paths of this type.
static var allCasePaths: AllCasePaths { get }

/// A case key path to this enum's case.
///
/// ```swift
/// @CasePathable
/// enum Field {
/// case title(String)
/// case body(String)
/// case isLive
/// }
///
/// Field.title("Hello, Blob!").case // \.title
/// ```
var `case`: PartialCaseKeyPath<Self> { get }

static var _allCaseKeyPaths: [PartialCaseKeyPath<Self>] { get }
}

/// A type that is used to distinguish case key paths from key paths by wrapping the enum and
Expand Down Expand Up @@ -494,9 +521,12 @@ extension CasePathable {
}

extension CasePathable where AllCasePaths: CasePathReflectable<Self> {
/// A case key path to this enum's case.
public var `case`: PartialCaseKeyPath<Self> {
Self.allCasePaths[self]
AllCasePaths._case(for: self)
}

public static var _allCaseKeyPaths: [PartialCaseKeyPath<Self>] {
AllCasePaths._allCaseKeyPaths
}
}

Expand Down
11 changes: 8 additions & 3 deletions Sources/CasePathsCore/Never+CasePathable.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
extension Never: CasePathable, CasePathIterable {
public struct AllCasePaths: CasePathReflectable, Sendable {
public subscript(root: Never) -> PartialCaseKeyPath<Never> {
public static func _case(for root: Never) -> PartialCaseKeyPath<Never> {
\.never
}

public static var _allCaseKeyPaths: [PartialCaseKeyPath<Never>] {
[]
}
}

public static var allCasePaths: AllCasePaths {
Expand Down Expand Up @@ -30,7 +34,8 @@ extension Case {
}

extension Never.AllCasePaths: Sequence {
public func makeIterator() -> some IteratorProtocol<PartialCaseKeyPath<Never>> {
[].makeIterator()
@available(*, deprecated, message: "Iterate over 'Array(Never.allCasePaths)' instead")
public func makeIterator() -> IndexingIterator<[PartialCaseKeyPath<Never>]> {
Self._allCaseKeyPaths.makeIterator()
}
}
11 changes: 8 additions & 3 deletions Sources/CasePathsCore/Optional+CasePathable.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
extension Optional: CasePathable, CasePathIterable {
@dynamicMemberLookup
public struct AllCasePaths: CasePathReflectable, Sendable {
public subscript(root: Optional) -> PartialCaseKeyPath<Optional> {
public static func _case(for root: Optional) -> PartialCaseKeyPath<Optional> {
switch root {
case .none: return \.none
case .some: return \.some
}
}

public static var _allCaseKeyPaths: [PartialCaseKeyPath<Optional>] {
[\.none, \.some]
}

/// A case path to the absence of a value.
public var none: AnyCasePath<Optional, Void> {
AnyCasePath(
Expand Down Expand Up @@ -69,8 +73,9 @@ extension Case {
}

extension Optional.AllCasePaths: Sequence {
public func makeIterator() -> some IteratorProtocol<PartialCaseKeyPath<Optional>> {
[\.none, \.some].makeIterator()
@available(*, deprecated, message: "Iterate over 'Array(Optional.allCasePaths)' instead")
public func makeIterator() -> IndexingIterator<[PartialCaseKeyPath<Optional>]> {
Self._allCaseKeyPaths.makeIterator()
}
}

Expand Down
11 changes: 8 additions & 3 deletions Sources/CasePathsCore/Result+CasePathable.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
extension Result: CasePathable, CasePathIterable {
public struct AllCasePaths: CasePathReflectable, Sendable {
public subscript(root: Result) -> PartialCaseKeyPath<Result> {
public static func _case(for root: Result) -> PartialCaseKeyPath<Result> {
switch root {
case .success: return \.success
case .failure: return \.failure
}
}

public static var _allCaseKeyPaths: [PartialCaseKeyPath<Result>] {
[\.success, \.failure]
}

/// A success case path, for embedding or extracting a `Success` value.
public var success: AnyCasePath<Result, Success> {
AnyCasePath(
Expand Down Expand Up @@ -36,7 +40,8 @@ extension Result: CasePathable, CasePathIterable {
}

extension Result.AllCasePaths: Sequence {
public func makeIterator() -> some IteratorProtocol<PartialCaseKeyPath<Result>> {
[\.success, \.failure].makeIterator()
@available(*, deprecated, message: "Iterate over 'Array(Result.allCasePaths)' instead")
public func makeIterator() -> IndexingIterator<[PartialCaseKeyPath<Result>]> {
Self._allCaseKeyPaths.makeIterator()
}
}
8 changes: 4 additions & 4 deletions Sources/CasePathsMacrosSupport/CasePathableMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ extension CasePathableMacro: MemberMacro {
let selfRewriter = SelfRewriter(selfEquivalent: enumName)
let memberBlock = selfRewriter.rewrite(enumDecl.memberBlock).cast(MemberBlockSyntax.self)
let rootSubscriptCases = generateCases(from: memberBlock.members, enumName: enumName) {
"if root.is(\\.\($0.name.text)) { return \\.\($0.name.text) }"
"if case .\($0.name.text) = root { return \\.\($0.name.text) }"
}
let elementRewriter = ElementRewriter()
let casePaths = generateDeclSyntax(
Expand All @@ -157,15 +157,15 @@ extension CasePathableMacro: MemberMacro {
var decls: [DeclSyntax] = [
"""
public struct AllCasePaths: CasePaths.CasePathReflectable, Swift.Sendable, Swift.Sequence {
public subscript(root: \(enumName)) -> CasePaths.PartialCaseKeyPath<\(enumName)> {
public static func _case(for root: \(enumName)) -> CasePaths.PartialCaseKeyPath<\(enumName)> {
\(raw: rootSubscriptCases.map { "\($0.description)\n" }.joined())\(raw: subscriptReturn)
}
\(raw: casePaths.map(\.description).joined(separator: "\n"))
public func makeIterator() -> Swift.IndexingIterator<[CasePaths.PartialCaseKeyPath<\(enumName)>]> {
public static var _allCaseKeyPaths: [CasePaths.PartialCaseKeyPath<\(enumName)>] {
\(raw: allCases.isEmpty ? "let" : "var") allCasePaths: \
[CasePaths.PartialCaseKeyPath<\(enumName)>] = []\
\(raw: allCases.map { "\n\($0.description)" }.joined())
return allCasePaths.makeIterator()
return allCasePaths
}
}
""",
Expand Down
Loading