Skip to content
Open
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
31 changes: 15 additions & 16 deletions Sources/CasePaths/EnumReflection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,17 @@ extension AnyCasePath {
@available(macOS, deprecated: 9999, message: "Use a 'CasePathable' case key path, instead")
@available(tvOS, deprecated: 9999, message: "Use a 'CasePathable' case key path, instead")
@available(watchOS, deprecated: 9999, message: "Use a 'CasePathable' case key path, instead")
public init(unsafe embed: @escaping @Sendable (Value) -> Root) {
func open<Wrapped>(_: Wrapped.Type) -> @Sendable (Root) -> Value? {
optionalPromotedExtractHelp(unsafeBitCast(embed, to: (@Sendable (Value) -> Wrapped?).self))
as! @Sendable (Root) -> Value?
public init(unsafe embed: @escaping (Value) -> Root) {
func open<Wrapped>(_: Wrapped.Type) -> (Root) -> Value? {
optionalPromotedExtractHelp(unsafeBitCast(embed, to: ((Value) -> Wrapped?).self))
as! (Root) -> Value?
}
@UncheckedSendable var embed = embed
let extract =
((_Witness<Root>.self as? _AnyOptional.Type)?.wrappedType)
.map { _openExistential($0, do: open) }
?? extractHelp { [$embed] in $embed.wrappedValue($0) }
?? extractHelp(embed)
self.init(
embed: { [$embed] in $embed.wrappedValue($0) },
embed: embed,
extract: extract
)
}
Expand All @@ -39,11 +38,11 @@ extension AnyCasePath {
@available(tvOS, deprecated: 9999, message: "Use a 'CasePathable' case key path, instead")
@available(watchOS, deprecated: 9999, message: "Use a 'CasePathable' case key path, instead")
@_disfavoredOverload
public init(unsafe root: @autoclosure @escaping @Sendable () -> Root) where Value == Void {
func open<Wrapped>(_: Wrapped.Type) -> @Sendable (Root) -> Void? {
public init(unsafe root: @autoclosure @escaping () -> Root) where Value == Void {
func open<Wrapped>(_: Wrapped.Type) -> (Root) -> Void? {
optionalPromotedExtractVoidHelp(
unsafeBitCast(root, to: Wrapped?.self)
) as! @Sendable (Root) -> Void?
) as! (Root) -> Void?
}
let extract =
((_Witness<Root>.self as? _AnyOptional.Type)?.wrappedType)
Expand All @@ -61,8 +60,8 @@ private struct Cache: Sendable {
}

func extractHelp<Root, Value>(
_ embed: @escaping @Sendable (Value) -> Root
) -> @Sendable (Root) -> Value? {
_ embed: @escaping (Value) -> Root
) -> (Root) -> Value? {
guard
let metadata = EnumMetadata(Root.self),
metadata.typeDescriptor.fieldDescriptor != nil
Expand Down Expand Up @@ -106,8 +105,8 @@ func extractHelp<Root, Value>(
}

func optionalPromotedExtractHelp<Root, Value>(
_ embed: @escaping @Sendable (Value) -> Root?
) -> @Sendable (Root?) -> Value? {
_ embed: @escaping (Value) -> Root?
) -> (Root?) -> Value? {
guard Root.self != Value.self else { return { $0 as! Value? } }
guard
let metadata = EnumMetadata(Root.self),
Expand Down Expand Up @@ -138,7 +137,7 @@ func optionalPromotedExtractHelp<Root, Value>(
}
}

func extractVoidHelp<Root>(_ root: Root) -> @Sendable (Root) -> Void? {
func extractVoidHelp<Root>(_ root: Root) -> (Root) -> Void? {
guard
let metadata = EnumMetadata(Root.self),
metadata.typeDescriptor.fieldDescriptor != nil
Expand All @@ -151,7 +150,7 @@ func extractVoidHelp<Root>(_ root: Root) -> @Sendable (Root) -> Void? {
return { root in metadata.tag(of: root) == cachedTag ? () : nil }
}

func optionalPromotedExtractVoidHelp<Root>(_ root: Root?) -> @Sendable (Root?) -> Void? {
func optionalPromotedExtractVoidHelp<Root>(_ root: Root?) -> (Root?) -> Void? {
guard
let root = root,
let metadata = EnumMetadata(Root.self),
Expand Down
53 changes: 25 additions & 28 deletions Sources/CasePaths/Internal/Deprecations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,14 @@ extension Optional: _OptionalProtocol {
extension AnyCasePath {
@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead")
public init(_ embed: @escaping (Value) -> Root) {
@UncheckedSendable var embed = embed
self.init(unsafe: { [$embed] in $embed.wrappedValue($0) })
self.init(unsafe: embed)
}
}

extension AnyCasePath where Value == Void {
@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead")
@_disfavoredOverload
public init(_ root: @autoclosure @escaping @Sendable () -> Root) {
public init(_ root: @autoclosure @escaping () -> Root) {
self.init(unsafe: root())
}
}
Expand All @@ -116,10 +115,9 @@ extension AnyCasePath {
public prefix func / <Root, Value>(
embed: @escaping (Value) -> Root
) -> AnyCasePath<Root, Value> {
@UncheckedSendable var embed = embed
return AnyCasePath(
embed: { [$embed] in $embed.wrappedValue($0) },
extract: { [$embed] in extractHelp { $embed.wrappedValue($0) }($0) }
AnyCasePath(
embed: embed,
extract: { extractHelp(embed)($0) }
)
}

Expand All @@ -128,27 +126,26 @@ public prefix func / <Root, Value>(
public prefix func / <Root, Value>(
embed: @escaping (Value) -> Root?
) -> AnyCasePath<Root?, Value> {
@UncheckedSendable var embed = embed
return AnyCasePath(
embed: { [$embed] in $embed.wrappedValue($0) },
extract: optionalPromotedExtractHelp { [$embed] in $embed.wrappedValue($0) }
AnyCasePath(
embed: embed,
extract: optionalPromotedExtractHelp(embed)
)
}

@_documentation(visibility: internal)
@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead")
public prefix func / <Root>(
root: @autoclosure @escaping @Sendable () -> Root
root: @autoclosure @escaping () -> Root
) -> AnyCasePath<Root, Void> {
.init(embed: root, extract: extractVoidHelp(root()))
AnyCasePath(embed: root, extract: extractVoidHelp(root()))
}

@_documentation(visibility: internal)
@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead")
public prefix func / <Root>(
root: @autoclosure @escaping @Sendable () -> Root?
root: @autoclosure @escaping () -> Root?
) -> AnyCasePath<Root?, Void> {
.init(embed: root, extract: optionalPromotedExtractVoidHelp(root()))
AnyCasePath(embed: root, extract: optionalPromotedExtractVoidHelp(root()))
}

@_documentation(visibility: internal)
Expand Down Expand Up @@ -195,7 +192,7 @@ public prefix func / <Root, Value>(
*, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead"
)
public prefix func / <Root>(
root: @autoclosure @escaping @Sendable () -> Root
root: @autoclosure @escaping () -> Root
) -> (Root) -> Void? {
(/root).extract(from:)
}
Expand All @@ -206,7 +203,7 @@ public prefix func / <Root>(
*, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead"
)
public prefix func / <Root>(
root: @autoclosure @escaping @Sendable () -> Root
root: @autoclosure @escaping () -> Root
) -> (Root?) -> Void? {
(/root).extract(from:)
}
Expand Down Expand Up @@ -315,8 +312,8 @@ extension AnyCasePath where Root == Void {
/// - Parameter value: A constant value.
/// - Returns: A case path from `()` to `value`.
@available(*, deprecated)
public static func constant(_ value: @autoclosure @escaping @Sendable () -> Value) -> Self {
.init(
public static func constant(_ value: @autoclosure @escaping () -> Value) -> Self {
AnyCasePath(
embed: { _ in () },
extract: { .some(value()) }
)
Expand All @@ -328,8 +325,8 @@ extension AnyCasePath where Value == Never {
/// uninhabited `Never` type.
@available(*, deprecated)
public static var never: Self {
@Sendable func absurd<A>(_ never: Never) -> A {}
return .init(
func absurd<A>(_ never: Never) -> A {}
return AnyCasePath(
embed: absurd,
extract: { _ in nil }
)
Expand Down Expand Up @@ -370,7 +367,7 @@ extension AnyCasePath {
/// - Parameter embed: An enum case initializer.
/// - Returns: A case path that extracts associated values from enum cases.
@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead")
public static func `case`(_ embed: @escaping @Sendable (Value) -> Root) -> Self {
public static func `case`(_ embed: @escaping (Value) -> Root) -> Self {
self.init(
embed: embed,
extract: { CasePaths.extract(embed)($0) }
Expand All @@ -387,7 +384,7 @@ extension AnyCasePath where Value == Void {
/// - Parameter value: An enum case with no associated values.
/// - Returns: A case path that extracts `()` if the case matches, otherwise `nil`.
@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead")
public static func `case`(_ value: @autoclosure @escaping @Sendable () -> Root) -> Self {
public static func `case`(_ value: @autoclosure @escaping () -> Root) -> Self {
Self(
embed: value,
extract: extractVoidHelp(value())
Expand All @@ -413,7 +410,7 @@ extension AnyCasePath where Value == Void {
/// otherwise `nil`.
@available(*, deprecated, message: "Use a '@CasePathable' case property, instead")
public func extract<Root, Value>(
case embed: @escaping @Sendable (Value) -> Root,
case embed: @escaping (Value) -> Root,
from root: Root
) -> Value? {
CasePaths.extract(embed)(root)
Expand All @@ -437,7 +434,7 @@ public func extract<Root, Value>(
/// otherwise `nil`.
@available(*, deprecated, message: "Use a '@CasePathable' case property, instead")
public func extract<Root, Value>(
case embed: @escaping @Sendable (Value) -> Root?,
case embed: @escaping (Value) -> Root?,
from root: Root?
) -> Value? {
CasePaths.extract(embed)(root)
Expand All @@ -460,7 +457,7 @@ public func extract<Root, Value>(
/// - Parameter embed: An enum case initializer.
/// - Returns: A function that can attempt to extract associated values from an enum.
@available(*, deprecated, message: "Use a '@CasePathable' case property, instead")
public func extract<Root, Value>(_ embed: @escaping @Sendable (Value) -> Root) -> (Root) -> Value? {
public func extract<Root, Value>(_ embed: @escaping (Value) -> Root) -> (Root) -> Value? {
extractHelp(embed)
}

Expand All @@ -482,7 +479,7 @@ public func extract<Root, Value>(_ embed: @escaping @Sendable (Value) -> Root) -
/// - Returns: A function that can attempt to extract associated values from an enum.
@available(*, deprecated, message: "Use a '@CasePathable' case property, instead")
public func extract<Root, Value>(
_ embed: @escaping @Sendable (Value) -> Root?
) -> @Sendable (Root?) -> Value? {
_ embed: @escaping (Value) -> Root?
) -> (Root?) -> Value? {
optionalPromotedExtractHelp(embed)
}
9 changes: 8 additions & 1 deletion Sources/CasePaths/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@
/// \UserAction.Cases.settings // CasePath<UserAction, SettingsAction>
/// ```
@attached(extension, conformances: CasePathable, CasePathIterable)
@attached(member, names: named(AllCasePaths), named(allCasePaths), named(_$Element))
@attached(
member,
conformances: CasePathable,
CasePathIterable,
names: named(AllCasePaths),
named(allCasePaths),
named(_$Element)
)
public macro CasePathable() =
#externalMacro(
module: "CasePathsMacros", type: "CasePathableMacro"
Expand Down
23 changes: 5 additions & 18 deletions Sources/CasePathsCore/AnyCasePath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,23 @@ import Foundation
/// This type defines key path-like semantics for enum cases, and is used to derive ``CaseKeyPath``s
/// from types that conform to ``CasePathable``.
@dynamicMemberLookup
public struct AnyCasePath<Root, Value>: Sendable {
private let _embed: @Sendable (Value) -> Root
private let _extract: @Sendable (Root) -> Value?
public struct AnyCasePath<Root, Value> {
private let _embed: (Value) -> Root
private let _extract: (Root) -> Value?

/// Creates a type-erased case path from a pair of functions.
///
/// - Parameters:
/// - embed: A function that always succeeds in embedding a value in a root.
/// - extract: A function that can optionally fail in extracting a value from a root.
public init(
embed: @escaping @Sendable (Value) -> Root,
extract: @escaping @Sendable (Root) -> Value?
embed: @escaping (Value) -> Root,
extract: @escaping (Root) -> Value?
) {
self._embed = embed
self._extract = extract
}

public static func _$embed(
_ embed: @escaping (Value) -> Root,
extract: @escaping @Sendable (Root) -> Value?
) -> Self {
#if swift(>=5.10)
nonisolated(unsafe) let embed = embed
return Self(embed: { embed($0) }, extract: extract)
#else
@UncheckedSendable var embed = embed
return Self(embed: { [$embed] in $embed.wrappedValue($0) }, extract: extract)
#endif
}

/// Returns a root by embedding a value.
///
/// - Parameter value: A value to embed.
Expand Down
36 changes: 12 additions & 24 deletions Sources/CasePathsCore/CasePathable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,18 @@ public protocol CasePathable {
/// associated value types.
@_documentation(visibility: internal)
@dynamicMemberLookup
public struct Case<Value>: Sendable {
fileprivate let _embed: @Sendable (Value) -> Any
fileprivate let _extract: @Sendable (Any) -> Value?
public struct Case<Value> {
fileprivate let _embed: (Value) -> Any
fileprivate let _extract: (Any) -> Value?
}

extension Case {
public init<Root>(
embed: @escaping @Sendable (Value) -> Root,
extract: @escaping @Sendable (Root) -> Value?
embed: @escaping (Value) -> Root,
extract: @escaping (Root) -> Value?
) {
self._embed = embed
self._extract = { @Sendable in ($0 as? Root).flatMap(extract) }
self._extract = { ($0 as? Root).flatMap(extract) }
}

public init() {
Expand All @@ -72,14 +72,9 @@ extension Case {
dynamicMember keyPath: KeyPath<Value.AllCasePaths, AnyCasePath<Value, AppendedValue>>
) -> Case<AppendedValue>
where Value: CasePathable {
let keyPath = keyPath.unsafeSendable()
return Case<AppendedValue>(
embed: {
_embed(Value.allCasePaths[keyPath: keyPath].embed($0))
},
extract: {
_extract(from: $0).flatMap(Value.allCasePaths[keyPath: keyPath].extract)
}
Case<AppendedValue>(
embed: { _embed(Value.allCasePaths[keyPath: keyPath].embed($0)) },
extract: { _extract(from: $0).flatMap(Value.allCasePaths[keyPath: keyPath].extract) }
)
}

Expand Down Expand Up @@ -516,16 +511,9 @@ extension AnyCasePath where Value: CasePathable {
public subscript<AppendedValue>(
dynamicMember keyPath: KeyPath<Value.AllCasePaths, AnyCasePath<Value, AppendedValue>>
) -> AnyCasePath<Root, AppendedValue> {
let keyPath = keyPath.unsafeSendable()
return AnyCasePath<Root, AppendedValue>(
embed: {
embed(Value.allCasePaths[keyPath: keyPath].embed($0))
},
extract: {
extract(from: $0).flatMap(
Value.allCasePaths[keyPath: keyPath].extract(from:)
)
}
AnyCasePath<Root, AppendedValue>(
embed: { embed(Value.allCasePaths[keyPath: keyPath].embed($0)) },
extract: { extract(from: $0).flatMap(Value.allCasePaths[keyPath: keyPath].extract(from:)) }
)
}
}
22 changes: 0 additions & 22 deletions Sources/CasePathsCore/Internal/KeyPath+Sendable.swift

This file was deleted.

8 changes: 0 additions & 8 deletions Sources/CasePathsCore/Internal/UncheckedSendable.swift

This file was deleted.

Loading