diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e0b8adb..c75167da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,7 @@ jobs: strategy: matrix: xcode: - - '14.3.1' - - '15.2' + - '15.4' steps: - uses: actions/checkout@v4 - name: Select Xcode ${{ matrix.xcode }} @@ -35,7 +34,7 @@ jobs: strategy: matrix: swift: - - '5.9' + - '5.10' name: Ubuntu (Swift ${{ matrix.swift }}) runs-on: ubuntu-latest container: swift:${{ matrix.swift }} diff --git a/Package.swift b/Package.swift index 53e06258..3b70c7c3 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,6 @@ -// swift-tools-version: 5.6 +// swift-tools-version: 5.9 +import CompilerPluginSupport import PackageDescription let package = Package( @@ -17,19 +18,36 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0") + .package(url: "https://github.com/apple/swift-syntax", "509.0.0"..<"601.0.0"), + .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.0"), + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"), ], targets: [ .target( name: "CasePaths", dependencies: [ - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") + "CasePathsMacros", + .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), ] ), .testTarget( name: "CasePathsTests", dependencies: ["CasePaths"] ), + .macro( + name: "CasePathsMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + ] + ), + .testTarget( + name: "CasePathsMacrosTests", + dependencies: [ + "CasePathsMacros", + .product(name: "MacroTesting", package: "swift-macro-testing"), + ] + ), ] ) @@ -39,3 +57,15 @@ let package = Package( .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") ) #endif + +for target in package.targets { + target.swiftSettings = target.swiftSettings ?? [] + target.swiftSettings?.append(contentsOf: [ + .enableExperimentalFeature("StrictConcurrency") + ]) + // target.swiftSettings?.append( + // .unsafeFlags([ + // "-enable-library-evolution", + // ]) + // ) +} diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift deleted file mode 100644 index 3b70c7c3..00000000 --- a/Package@swift-5.9.swift +++ /dev/null @@ -1,71 +0,0 @@ -// swift-tools-version: 5.9 - -import CompilerPluginSupport -import PackageDescription - -let package = Package( - name: "swift-case-paths", - platforms: [ - .iOS(.v13), - .macOS(.v10_15), - .tvOS(.v13), - .watchOS(.v6), - ], - products: [ - .library( - name: "CasePaths", - targets: ["CasePaths"] - ) - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-syntax", "509.0.0"..<"601.0.0"), - .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.0"), - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"), - ], - targets: [ - .target( - name: "CasePaths", - dependencies: [ - "CasePathsMacros", - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), - ] - ), - .testTarget( - name: "CasePathsTests", - dependencies: ["CasePaths"] - ), - .macro( - name: "CasePathsMacros", - dependencies: [ - .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), - .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), - ] - ), - .testTarget( - name: "CasePathsMacrosTests", - dependencies: [ - "CasePathsMacros", - .product(name: "MacroTesting", package: "swift-macro-testing"), - ] - ), - ] -) - -#if !os(Windows) - // Add the documentation compiler plugin if possible - package.dependencies.append( - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") - ) -#endif - -for target in package.targets { - target.swiftSettings = target.swiftSettings ?? [] - target.swiftSettings?.append(contentsOf: [ - .enableExperimentalFeature("StrictConcurrency") - ]) - // target.swiftSettings?.append( - // .unsafeFlags([ - // "-enable-library-evolution", - // ]) - // ) -} diff --git a/Sources/CasePaths/AnyCasePath.swift b/Sources/CasePaths/AnyCasePath.swift index 11cc1861..445ca412 100644 --- a/Sources/CasePaths/AnyCasePath.swift +++ b/Sources/CasePaths/AnyCasePath.swift @@ -63,80 +63,44 @@ extension AnyCasePath: CustomDebugStringConvertible { #endif extension AnyCasePath { - #if swift(>=5.9) - @available( - iOS, deprecated: 9999, - message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead." - ) - @available( - macOS, deprecated: 9999, - message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead." - ) - @available( - tvOS, deprecated: 9999, - message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead." - ) - @available( - watchOS, deprecated: 9999, - message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead." - ) - public func modify( - _ root: inout Root, - _ body: (inout Value) throws -> Result - ) throws -> Result { - guard var value = self.extract(from: root) else { throw ExtractionFailed() } - let result = try body(&value) - root = self.embed(value) - return result - } - #else - /// Attempts to modify a value in a root. - /// - /// - Parameters: - /// - root: A root to modify if the case path matches. - /// - body: A closure that can mutate the case's associated value. If the closure throws, the - /// root will be left unmodified. - /// - Returns: The return value, if any, of the body closure. - public func modify( - _ root: inout Root, - _ body: (inout Value) throws -> Result - ) throws -> Result { - guard var value = self.extract(from: root) else { throw ExtractionFailed() } - let result = try body(&value) - root = self.embed(value) - return result - } - #endif + @available( + iOS, deprecated: 9999, + message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead." + ) + @available( + macOS, deprecated: 9999, + message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead." + ) + @available( + tvOS, deprecated: 9999, + message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead." + ) + @available( + watchOS, deprecated: 9999, + message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead." + ) + public func modify( + _ root: inout Root, + _ body: (inout Value) throws -> Result + ) throws -> Result { + guard var value = self.extract(from: root) else { throw ExtractionFailed() } + let result = try body(&value) + root = self.embed(value) + return result + } - #if swift(>=5.9) - @available(iOS, deprecated: 9999, message: "Chain case key paths together, instead.") - @available(macOS, deprecated: 9999, message: "Chain case key paths together, instead.") - @available(tvOS, deprecated: 9999, message: "Chain case key paths together, instead.") - @available(watchOS, deprecated: 9999, message: "Chain case key paths together, instead.") - public func appending( - path: AnyCasePath - ) -> AnyCasePath { - AnyCasePath( - embed: { self.embed(path.embed($0)) }, - extract: { self.extract(from: $0).flatMap(path.extract) } - ) - } - #else - /// Returns a new case path created by appending the given case path to this one. - /// - /// Use this method to extend this case path to the value type of another case path. - /// - /// - Parameter path: The case path to append. - /// - Returns: A case path from the root of this case path to the value type of `path`. - public func appending( - path: AnyCasePath - ) -> AnyCasePath { - AnyCasePath( - embed: { self.embed(path.embed($0)) }, - extract: { self.extract(from: $0).flatMap(path.extract) } - ) - } - #endif + @available(iOS, deprecated: 9999, message: "Chain case key paths together, instead.") + @available(macOS, deprecated: 9999, message: "Chain case key paths together, instead.") + @available(tvOS, deprecated: 9999, message: "Chain case key paths together, instead.") + @available(watchOS, deprecated: 9999, message: "Chain case key paths together, instead.") + public func appending( + path: AnyCasePath + ) -> AnyCasePath { + AnyCasePath( + embed: { self.embed(path.embed($0)) }, + extract: { self.extract(from: $0).flatMap(path.extract) } + ) + } } struct ExtractionFailed: Error {} diff --git a/Sources/CasePaths/CasePathable.swift b/Sources/CasePaths/CasePathable.swift index e2ef3784..bd827cab 100644 --- a/Sources/CasePaths/CasePathable.swift +++ b/Sources/CasePaths/CasePathable.swift @@ -43,22 +43,14 @@ public protocol CasePathable { static var allCasePaths: AllCasePaths { get } } -#if swift(>=5.9) - /// A type that is used to distinguish case key paths from key paths by wrapping the enum and - /// associated value types. - @_documentation(visibility:internal) - @dynamicMemberLookup - public struct Case: Sendable { - fileprivate let _embed: @Sendable (Value) -> Any - fileprivate let _extract: @Sendable (Any) -> Value? - } -#else - @dynamicMemberLookup - public struct Case { - fileprivate let _embed: @Sendable (Value) -> Any - fileprivate let _extract: @Sendable (Any) -> Value? - } -#endif +/// A type that is used to distinguish case key paths from key paths by wrapping the enum and +/// associated value types. +@_documentation(visibility:internal) +@dynamicMemberLookup +public struct Case: Sendable { + fileprivate let _embed: @Sendable (Value) -> Any + fileprivate let _extract: @Sendable (Any) -> Value? +} extension Case { public init( diff --git a/Sources/CasePaths/EnumReflection.swift b/Sources/CasePaths/EnumReflection.swift index 9fda4804..886eb25b 100644 --- a/Sources/CasePaths/EnumReflection.swift +++ b/Sources/CasePaths/EnumReflection.swift @@ -222,12 +222,7 @@ private struct MetadataKind: Equatable { } @_spi(Reflection) public func tag(of value: Enum) -> UInt32 { - #if swift(<5.8) - // NB: Workaround for https://github.com/apple/swift/issues/61708 - guard self.typeDescriptor.emptyCaseCount + self.typeDescriptor.payloadCaseCount > 1 - else { return 0 } - #endif - return withUnsafePointer(to: value) { + withUnsafePointer(to: value) { self.valueWitnessTable.getEnumTag($0, self.ptr) } } diff --git a/Sources/CasePaths/Internal/Deprecations.swift b/Sources/CasePaths/Internal/Deprecations.swift index c2c8ca2f..e9dd0019 100644 --- a/Sources/CasePaths/Internal/Deprecations.swift +++ b/Sources/CasePaths/Internal/Deprecations.swift @@ -7,35 +7,20 @@ // Deprecated after 1.4.2: extension AnyCasePath where Root == Value { - #if swift(>=5.9) - @available(*, deprecated, message: "Use the '\\.self' case key path, instead") - public static var `self`: Self { - .init( - embed: { $0 }, - extract: { .some($0) } - ) - } - #else - public static var `self`: Self { - .init( - embed: { $0 }, - extract: { .some($0) } - ) - } - #endif + @available(*, deprecated, message: "Use the '\\.self' case key path, instead") + public static var `self`: Self { + .init( + embed: { $0 }, + extract: { .some($0) } + ) + } } extension AnyCasePath where Root: _OptionalProtocol, Value == Root.Wrapped { - #if swift(>=5.9) - @available(*, deprecated, message: "Use the '\\Optional.Cases.some' case key path, instead") - public static var some: Self { - .init(embed: { Root($0) }, extract: { $0.optional }) - } - #else - public static var some: Self { - .init(embed: { Root($0) }, extract: { $0.optional }) - } - #endif + @available(*, deprecated, message: "Use the '\\Optional.Cases.some' case key path, instead") + public static var some: Self { + .init(embed: { Root($0) }, extract: { $0.optional }) + } } public protocol _OptionalProtocol { @@ -49,247 +34,137 @@ extension Optional: _OptionalProtocol { } extension AnyCasePath { - #if swift(>=5.9) - @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) }) - } - #else - public init(_ embed: @escaping (Value) -> Root) { - @UncheckedSendable var embed = embed - self.init(unsafe: { [$embed] in $embed.wrappedValue($0) }) - } - #endif + @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) }) + } } extension AnyCasePath where Value == Void { - #if swift(>=5.9) - @available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") - @_disfavoredOverload - public init(_ root: @autoclosure @escaping @Sendable () -> Root) { - self.init(unsafe: root()) - } - #else - @_disfavoredOverload - public init(_ root: @autoclosure @escaping @Sendable () -> Root) { - self.init(unsafe: root()) - } - #endif + @available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") + @_disfavoredOverload + public init(_ root: @autoclosure @escaping @Sendable () -> Root) { + self.init(unsafe: root()) + } } extension AnyCasePath where Root == Value { - #if swift(>=5.9) - @available(*, deprecated, message: "Use the '\\.self' case key path, instead") - public init(_ type: Root.Type) { - self = .self - } - #else - public init(_ type: Root.Type) { - self = .self - } - #endif + @available(*, deprecated, message: "Use the '\\.self' case key path, instead") + public init(_ type: Root.Type) { + self = .self + } } prefix operator / extension AnyCasePath { - #if swift(>=5.9) - @_documentation(visibility:internal) - @available(*, deprecated, message: "Use 'CasePathable.is' with a case key path, instead") - public static func ~= (pattern: AnyCasePath, value: Root) -> Bool { - pattern.extract(from: value) != nil - } - #else - public static func ~= (pattern: AnyCasePath, value: Root) -> Bool { - pattern.extract(from: value) != nil - } - #endif -} - -#if swift(>=5.9) @_documentation(visibility:internal) - @available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") - public prefix func / ( - embed: @escaping (Value) -> Root - ) -> AnyCasePath { - @UncheckedSendable var embed = embed - return AnyCasePath( - embed: { [$embed] in $embed.wrappedValue($0) }, - extract: { [$embed] in extractHelp { $embed.wrappedValue($0) }($0) } - ) - } -#else - public prefix func / ( - embed: @escaping (Value) -> Root - ) -> AnyCasePath { - .init(embed: { embed($0) }, extract: { extractHelp { embed($0) }($0) }) + @available(*, deprecated, message: "Use 'CasePathable.is' with a case key path, instead") + public static func ~= (pattern: AnyCasePath, value: Root) -> Bool { + pattern.extract(from: value) != nil } -#endif +} -#if swift(>=5.9) - @_documentation(visibility:internal) - @available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") - public prefix func / ( - embed: @escaping (Value) -> Root? - ) -> AnyCasePath { - @UncheckedSendable var embed = embed - return AnyCasePath( - embed: { [$embed] in $embed.wrappedValue($0) }, - extract: optionalPromotedExtractHelp { [$embed] in $embed.wrappedValue($0) } - ) - } -#else - public prefix func / ( - embed: @escaping (Value) -> Root? - ) -> AnyCasePath { - .init(embed: { embed($0) }, extract: optionalPromotedExtractHelp { embed($0) }) - } -#endif +@_documentation(visibility:internal) +@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") +public prefix func / ( + embed: @escaping (Value) -> Root +) -> AnyCasePath { + @UncheckedSendable var embed = embed + return AnyCasePath( + embed: { [$embed] in $embed.wrappedValue($0) }, + extract: { [$embed] in extractHelp { $embed.wrappedValue($0) }($0) } + ) +} -#if swift(>=5.9) - @_documentation(visibility:internal) - @available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") - public prefix func / ( - root: @autoclosure @escaping @Sendable () -> Root - ) -> AnyCasePath { - .init(embed: root, extract: extractVoidHelp(root())) - } -#else - public prefix func / ( - root: @autoclosure @escaping @Sendable () -> Root - ) -> AnyCasePath { - .init(embed: root, extract: extractVoidHelp(root())) - } -#endif +@_documentation(visibility:internal) +@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") +public prefix func / ( + embed: @escaping (Value) -> Root? +) -> AnyCasePath { + @UncheckedSendable var embed = embed + return AnyCasePath( + embed: { [$embed] in $embed.wrappedValue($0) }, + extract: optionalPromotedExtractHelp { [$embed] in $embed.wrappedValue($0) } + ) +} -#if swift(>=5.9) - @_documentation(visibility:internal) - @available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") - public prefix func / ( - root: @autoclosure @escaping @Sendable () -> Root? - ) -> AnyCasePath { - .init(embed: root, extract: optionalPromotedExtractVoidHelp(root())) - } -#else - public prefix func / ( - root: @autoclosure @escaping @Sendable () -> Root? - ) -> AnyCasePath { - .init(embed: root, extract: optionalPromotedExtractVoidHelp(root())) - } -#endif +@_documentation(visibility:internal) +@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") +public prefix func / ( + root: @autoclosure @escaping @Sendable () -> Root +) -> AnyCasePath { + .init(embed: root, extract: extractVoidHelp(root())) +} -#if swift(>=5.9) - @_documentation(visibility:internal) - @available(*, deprecated, message: "Use the '\\.self' case key path, instead") - public prefix func / ( - type: Root.Type - ) -> AnyCasePath { - .self - } -#else - public prefix func / ( - type: Root.Type - ) -> AnyCasePath { - .self - } -#endif +@_documentation(visibility:internal) +@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") +public prefix func / ( + root: @autoclosure @escaping @Sendable () -> Root? +) -> AnyCasePath { + .init(embed: root, extract: optionalPromotedExtractVoidHelp(root())) +} -#if swift(>=5.9) - @_documentation(visibility:internal) - @available(*, deprecated, message: "Use a case key path (like '\\.self' or '\\.some'), instead") - public prefix func / ( - path: AnyCasePath - ) -> AnyCasePath { - path - } -#else - public prefix func / ( - path: AnyCasePath - ) -> AnyCasePath { - path - } -#endif +@_documentation(visibility:internal) +@available(*, deprecated, message: "Use the '\\.self' case key path, instead") +public prefix func / ( + type: Root.Type +) -> AnyCasePath { + .self +} -#if swift(>=5.9) - @_disfavoredOverload - @_documentation(visibility:internal) - @available( - *, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead" - ) - public prefix func / ( - embed: @escaping (Value) -> Root - ) -> (Root) -> Value? { - (/embed).extract(from:) - } -#else - @_disfavoredOverload - public prefix func / ( - embed: @escaping (Value) -> Root - ) -> (Root) -> Value? { - (/embed).extract(from:) - } -#endif +@_documentation(visibility:internal) +@available(*, deprecated, message: "Use a case key path (like '\\.self' or '\\.some'), instead") +public prefix func / ( + path: AnyCasePath +) -> AnyCasePath { + path +} -#if swift(>=5.9) - @_disfavoredOverload - @_documentation(visibility:internal) - @available( - *, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead" - ) - public prefix func / ( - embed: @escaping (Value) -> Root? - ) -> (Root?) -> Value? { - (/embed).extract(from:) - } -#else - @_disfavoredOverload - public prefix func / ( - embed: @escaping (Value) -> Root? - ) -> (Root?) -> Value? { - (/embed).extract(from:) - } -#endif +@_disfavoredOverload +@_documentation(visibility:internal) +@available( + *, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead" +) +public prefix func / ( + embed: @escaping (Value) -> Root +) -> (Root) -> Value? { + (/embed).extract(from:) +} -#if swift(>=5.9) - @_disfavoredOverload - @_documentation(visibility:internal) - @available( - *, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead" - ) - public prefix func / ( - root: @autoclosure @escaping @Sendable () -> Root - ) -> (Root) -> Void? { - (/root).extract(from:) - } -#else - @_disfavoredOverload - public prefix func / ( - root: @autoclosure @escaping @Sendable () -> Root - ) -> (Root) -> Void? { - (/root).extract(from:) - } -#endif +@_disfavoredOverload +@_documentation(visibility:internal) +@available( + *, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead" +) +public prefix func / ( + embed: @escaping (Value) -> Root? +) -> (Root?) -> Value? { + (/embed).extract(from:) +} -#if swift(>=5.9) - @_disfavoredOverload - @_documentation(visibility:internal) - @available( - *, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead" - ) - public prefix func / ( - root: @autoclosure @escaping @Sendable () -> Root - ) -> (Root?) -> Void? { - (/root).extract(from:) - } -#else - @_disfavoredOverload - public prefix func / ( - root: @autoclosure @escaping @Sendable () -> Root - ) -> (Root?) -> Void? { - (/root).extract(from:) - } -#endif +@_disfavoredOverload +@_documentation(visibility:internal) +@available( + *, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead" +) +public prefix func / ( + root: @autoclosure @escaping @Sendable () -> Root +) -> (Root) -> Void? { + (/root).extract(from:) +} + +@_disfavoredOverload +@_documentation(visibility:internal) +@available( + *, deprecated, message: "Use a 'CasePathable' case property via dynamic member lookup, instead" +) +public prefix func / ( + root: @autoclosure @escaping @Sendable () -> Root +) -> (Root?) -> Void? { + (/root).extract(from:) +} precedencegroup CasePathCompositionPrecedence { associativity: left @@ -298,152 +173,79 @@ precedencegroup CasePathCompositionPrecedence { infix operator .. : CasePathCompositionPrecedence extension AnyCasePath { - #if swift(>=5.9) - @_documentation(visibility:internal) - @available(*, deprecated, message: "Append 'CasePathable' case key paths, instead") - public static func .. ( - lhs: AnyCasePath, - rhs: AnyCasePath - ) -> AnyCasePath { - lhs.appending(path: rhs) - } - #else - public static func .. ( - lhs: AnyCasePath, - rhs: AnyCasePath - ) -> AnyCasePath { - lhs.appending(path: rhs) - } - #endif - - #if swift(>=5.9) - @_documentation(visibility:internal) - @available(*, deprecated, message: "Append 'CasePathable' case key paths, instead") - public static func .. ( - lhs: AnyCasePath, - rhs: @escaping (AppendedValue) -> Value - ) -> AnyCasePath { - lhs.appending(path: /rhs) - } - #else - public static func .. ( - lhs: AnyCasePath, - rhs: @escaping (AppendedValue) -> Value - ) -> AnyCasePath { - lhs.appending(path: /rhs) - } - #endif -} - -#if swift(>=5.9) @_documentation(visibility:internal) - @available(*, deprecated, message: "Chain 'CasePathable' case properties, instead") - public func .. ( - lhs: @escaping (Root) -> Value?, - rhs: @escaping (AppendedValue) -> Value - ) -> (Root) -> AppendedValue? { - return { root in lhs(root).flatMap((/rhs).extract(from:)) } + @available(*, deprecated, message: "Append 'CasePathable' case key paths, instead") + public static func .. ( + lhs: AnyCasePath, + rhs: AnyCasePath + ) -> AnyCasePath { + lhs.appending(path: rhs) } -#else - public func .. ( - lhs: @escaping (Root) -> Value?, + + @_documentation(visibility:internal) + @available(*, deprecated, message: "Append 'CasePathable' case key paths, instead") + public static func .. ( + lhs: AnyCasePath, rhs: @escaping (AppendedValue) -> Value - ) -> (Root) -> AppendedValue? { - return { root in lhs(root).flatMap((/rhs).extract(from:)) } + ) -> AnyCasePath { + lhs.appending(path: /rhs) } -#endif +} -#if swift(>=5.9) - @available( - *, deprecated, message: "Use XCTest's 'XCTUnwrap' with a 'CasePathable' case property, instead" - ) - public func XCTUnwrap( - _ enum: @autoclosure () throws -> Enum, - case extract: (Enum) -> Case?, - _ message: @autoclosure () -> String = "", - file: StaticString = #file, - line: UInt = #line - ) throws -> Case { - let `enum` = try `enum`() - guard let value = extract(`enum`) - else { - #if canImport(ObjectiveC) - _ = XCTCurrentTestCase?.perform(Selector(("setContinueAfterFailure:")), with: false) - #endif - let message = message() - XCTFail( - """ - XCTUnwrap failed: expected to extract value of type "\(typeName(Case.self))" from \ - "\(typeName(Enum.self))"\ - \(message.isEmpty ? "" : " - " + message) … - - Actual: - \(String(describing: `enum`)) - """, - file: file, - line: line - ) - throw UnwrappingCase() - } - return value - } -#else - public func XCTUnwrap( - _ enum: @autoclosure () throws -> Enum, - case extract: (Enum) -> Case?, - _ message: @autoclosure () -> String = "", - file: StaticString = #file, - line: UInt = #line - ) throws -> Case { - let `enum` = try `enum`() - guard let value = extract(`enum`) - else { - #if canImport(ObjectiveC) - _ = XCTCurrentTestCase?.perform(Selector(("setContinueAfterFailure:")), with: false) - #endif - let message = message() - XCTFail( - """ - XCTUnwrap failed: expected to extract value of type "\(typeName(Case.self))" from \ - "\(typeName(Enum.self))"\ - \(message.isEmpty ? "" : " - " + message) … - - Actual: - \(String(describing: `enum`)) - """, - file: file, - line: line - ) - throw UnwrappingCase() - } - return value - } -#endif +@_documentation(visibility:internal) +@available(*, deprecated, message: "Chain 'CasePathable' case properties, instead") +public func .. ( + lhs: @escaping (Root) -> Value?, + rhs: @escaping (AppendedValue) -> Value +) -> (Root) -> AppendedValue? { + return { root in lhs(root).flatMap((/rhs).extract(from:)) } +} -#if swift(>=5.9) - @available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") - public func XCTModify( - _ enum: inout Enum, - case casePath: AnyCasePath, - _ message: @autoclosure () -> String = "", - _ body: (inout Case) throws -> Void, - file: StaticString = #file, - line: UInt = #line - ) { - _XCTModify(&`enum`, case: casePath, message(), body, file: file, line: line) - } -#else - public func XCTModify( - _ enum: inout Enum, - case casePath: AnyCasePath, - _ message: @autoclosure () -> String = "", - _ body: (inout Case) throws -> Void, - file: StaticString = #file, - line: UInt = #line - ) { - _XCTModify(&`enum`, case: casePath, message(), body, file: file, line: line) +@available( + *, deprecated, message: "Use XCTest's 'XCTUnwrap' with a 'CasePathable' case property, instead" +) +public func XCTUnwrap( + _ enum: @autoclosure () throws -> Enum, + case extract: (Enum) -> Case?, + _ message: @autoclosure () -> String = "", + file: StaticString = #file, + line: UInt = #line +) throws -> Case { + let `enum` = try `enum`() + guard let value = extract(`enum`) + else { + #if canImport(ObjectiveC) + _ = XCTCurrentTestCase?.perform(Selector(("setContinueAfterFailure:")), with: false) + #endif + let message = message() + XCTFail( + """ + XCTUnwrap failed: expected to extract value of type "\(typeName(Case.self))" from \ + "\(typeName(Enum.self))"\ + \(message.isEmpty ? "" : " - " + message) … + + Actual: + \(String(describing: `enum`)) + """, + file: file, + line: line + ) + throw UnwrappingCase() } -#endif + return value +} + +@available(*, deprecated, message: "Use a 'CasePathable' case key path, instead") +public func XCTModify( + _ enum: inout Enum, + case casePath: AnyCasePath, + _ message: @autoclosure () -> String = "", + _ body: (inout Case) throws -> Void, + file: StaticString = #file, + line: UInt = #line +) { + _XCTModify(&`enum`, case: casePath, message(), body, file: file, line: line) +} // Deprecated after 1.0.0: diff --git a/Sources/CasePaths/Internal/OpenExistential.swift b/Sources/CasePaths/Internal/OpenExistential.swift index 8aba3808..c8daeb71 100644 --- a/Sources/CasePaths/Internal/OpenExistential.swift +++ b/Sources/CasePaths/Internal/OpenExistential.swift @@ -1,42 +1,9 @@ -#if swift(>=5.7) - // MARK: swift(>=5.7) - // MARK: Equatable +func _isEqual(_ lhs: Any, _ rhs: Any) -> Bool? { + (lhs as? any Equatable)?.isEqual(other: rhs) +} - func _isEqual(_ lhs: Any, _ rhs: Any) -> Bool? { - (lhs as? any Equatable)?.isEqual(other: rhs) +extension Equatable { + fileprivate func isEqual(other: Any) -> Bool { + self == other as? Self } - - extension Equatable { - fileprivate func isEqual(other: Any) -> Bool { - self == other as? Self - } - } -#else - // MARK: - - // MARK: swift(<5.7) - - private enum Witness {} - - // MARK: Equatable - - func _isEqual(_ lhs: Any, _ rhs: Any) -> Bool? { - func open(_: T.Type) -> Bool? { - (Witness.self as? AnyEquatable.Type)?.isEqual(lhs, rhs) - } - return _openExistential(type(of: lhs), do: open) - } - - private protocol AnyEquatable { - static func isEqual(_ lhs: Any, _ rhs: Any) -> Bool - } - - extension Witness: AnyEquatable where T: Equatable { - fileprivate static func isEqual(_ lhs: Any, _ rhs: Any) -> Bool { - guard - let lhs = lhs as? T, - let rhs = rhs as? T - else { return false } - return lhs == rhs - } - } -#endif +} diff --git a/Sources/CasePaths/Macros.swift b/Sources/CasePaths/Macros.swift index 0e603e3e..448c8f47 100644 --- a/Sources/CasePaths/Macros.swift +++ b/Sources/CasePaths/Macros.swift @@ -1,35 +1,33 @@ -#if swift(>=5.9) - /// Defines and implements conformance of the CasePathable protocol. - /// - /// This macro conforms the type to the ``CasePathable`` protocol, and adds ``CaseKeyPath`` - /// support for all its cases. - /// - /// For example, the following code applies the `CasePathable` macro to the type `UserAction`: - /// - /// ```swift - /// @CasePathable - /// enum UserAction { - /// case home(HomeAction) - /// case settings(SettingsAction) - /// } - /// ``` - /// - /// This macro application extends the type with the ability to derive a case key paths from each - /// of its cases using a familiar key path expression: - /// - /// ```swift - /// // Case key paths can be inferred using the same name as the case: - /// _: CaseKeyPath = \.home - /// _: CaseKeyPath = \.settings - /// - /// // Or they can be fully qualified under the type's `Cases`: - /// \UserAction.Cases.home // CasePath - /// \UserAction.Cases.settings // CasePath - /// ``` - @attached(extension, conformances: CasePathable) - @attached(member, names: named(AllCasePaths), named(allCasePaths)) - public macro CasePathable() = - #externalMacro( - module: "CasePathsMacros", type: "CasePathableMacro" - ) -#endif +/// Defines and implements conformance of the CasePathable protocol. +/// +/// This macro conforms the type to the ``CasePathable`` protocol, and adds ``CaseKeyPath`` +/// support for all its cases. +/// +/// For example, the following code applies the `CasePathable` macro to the type `UserAction`: +/// +/// ```swift +/// @CasePathable +/// enum UserAction { +/// case home(HomeAction) +/// case settings(SettingsAction) +/// } +/// ``` +/// +/// This macro application extends the type with the ability to derive a case key paths from each +/// of its cases using a familiar key path expression: +/// +/// ```swift +/// // Case key paths can be inferred using the same name as the case: +/// _: CaseKeyPath = \.home +/// _: CaseKeyPath = \.settings +/// +/// // Or they can be fully qualified under the type's `Cases`: +/// \UserAction.Cases.home // CasePath +/// \UserAction.Cases.settings // CasePath +/// ``` +@attached(extension, conformances: CasePathable) +@attached(member, names: named(AllCasePaths), named(allCasePaths)) +public macro CasePathable() = + #externalMacro( + module: "CasePathsMacros", type: "CasePathableMacro" + ) diff --git a/Tests/CasePathsTests/CasePathsTests.swift b/Tests/CasePathsTests/CasePathsTests.swift index b4b8bb59..03574781 100644 --- a/Tests/CasePathsTests/CasePathsTests.swift +++ b/Tests/CasePathsTests/CasePathsTests.swift @@ -7,33 +7,23 @@ final class CasePathsTests: XCTestCase { XCTAssertNil(Int?.none[case: \.some]) XCTAssertNil(Int?.some(42)[case: \.none]) XCTAssertNotNil(Int?.none[case: \.none]) - #if swift(>=5.9) - XCTAssertEqual((\Int?.Cases.some)(42), 42) - XCTAssertEqual((\Int?.Cases.none)(), nil) - #else - let somePath: CaseKeyPath = \.some - let nonePath: CaseKeyPath = \.none - XCTAssertEqual(somePath(42), 42) - XCTAssertEqual(nonePath(), nil) - #endif - #if swift(>=5.9) - XCTAssertEqual(Fizz.buzz(.fizzBuzz(.int(42)))[case: \.buzz.fizzBuzz.int], 42) - let buzzPath1: CaseKeyPath = \Fizz.Cases.buzz - let buzzPath2: CaseKeyPath = \Fizz.Cases.buzz - XCTAssertEqual(buzzPath1, \.buzz) - XCTAssertEqual(buzzPath2, \.buzz) - let buzzPath3 = \Fizz.Cases.buzz - XCTAssertEqual(buzzPath1, buzzPath3) - XCTAssertNotEqual(buzzPath2, buzzPath3) - XCTAssertEqual(ifLet(state: \Fizz.buzz, action: \Fizz.Cases.buzz), 42) - XCTAssertEqual(ifLet(state: \Fizz.buzz, action: \Foo.Cases.bar), nil) - let fizzBuzzPath1: CaseKeyPath = \Fizz.Cases.buzz.fizzBuzz.int - let fizzBuzzPath2: CaseKeyPath = \Fizz.Cases.buzz.fizzBuzz.int - let fizzBuzzPath3 = \Fizz.Cases.buzz.fizzBuzz.int - XCTAssertNotEqual(fizzBuzzPath1, fizzBuzzPath3) - XCTAssertEqual(fizzBuzzPath2, fizzBuzzPath3) - #endif - + XCTAssertEqual((\Int?.Cases.some)(42), 42) + XCTAssertEqual((\Int?.Cases.none)(), nil) + XCTAssertEqual(Fizz.buzz(.fizzBuzz(.int(42)))[case: \.buzz.fizzBuzz.int], 42) + let buzzPath1: CaseKeyPath = \Fizz.Cases.buzz + let buzzPath2: CaseKeyPath = \Fizz.Cases.buzz + XCTAssertEqual(buzzPath1, \.buzz) + XCTAssertEqual(buzzPath2, \.buzz) + let buzzPath3 = \Fizz.Cases.buzz + XCTAssertEqual(buzzPath1, buzzPath3) + XCTAssertNotEqual(buzzPath2, buzzPath3) + XCTAssertEqual(ifLet(state: \Fizz.buzz, action: \Fizz.Cases.buzz), 42) + XCTAssertEqual(ifLet(state: \Fizz.buzz, action: \Foo.Cases.bar), nil) + let fizzBuzzPath1: CaseKeyPath = \Fizz.Cases.buzz.fizzBuzz.int + let fizzBuzzPath2: CaseKeyPath = \Fizz.Cases.buzz.fizzBuzz.int + let fizzBuzzPath3 = \Fizz.Cases.buzz.fizzBuzz.int + XCTAssertNotEqual(fizzBuzzPath1, fizzBuzzPath3) + XCTAssertEqual(fizzBuzzPath2, fizzBuzzPath3) XCTAssertEqual(Optional.allCasePaths[Int?.some(42)], \.some) XCTAssertNotEqual(Optional.allCasePaths[Int?.some(42)], \.none) XCTAssertEqual(Optional.allCasePaths[Int?.none], \.none) @@ -46,16 +36,8 @@ final class CasePathsTests: XCTestCase { XCTAssertNil(Result.failure(SomeError())[case: \.success]) XCTAssertNil(Result.success(42)[case: \.failure]) XCTAssertNotNil(Result.failure(SomeError())[case: \.failure]) - #if swift(>=5.9) - XCTAssertEqual((\Result.Cases.success)(42), .success(42)) - XCTAssertEqual((\Result.Cases.failure)(SomeError()), .failure(SomeError())) - #else - let successPath: CaseKeyPath, Int> = \.success - let failurePath: CaseKeyPath, SomeError> = \.failure - XCTAssertEqual(successPath(42), .success(42)) - XCTAssertEqual(failurePath(SomeError()), .failure(SomeError())) - #endif - + XCTAssertEqual((\Result.Cases.success)(42), .success(42)) + XCTAssertEqual((\Result.Cases.failure)(SomeError()), .failure(SomeError())) XCTAssertEqual(Result.allCasePaths[Result.success(42)], \.success) XCTAssertNotEqual(Result.allCasePaths[Result.success(42)], \.failure) XCTAssertEqual(Result.allCasePaths[Result.failure(SomeError())], \.failure) @@ -75,187 +57,183 @@ final class CasePathsTests: XCTestCase { XCTAssertEqual(loadable, .isLoaded) } - #if swift(>=5.9) - func testCaseKeyPaths() { - var foo: Foo = .bar(.int(1)) + func testCaseKeyPaths() { + var foo: Foo = .bar(.int(1)) - XCTAssertEqual(foo.bar, .int(1)) - // NB: Due to a Swift bug, this is only possible to do outside the library: - // XCTAssertEqual(foo.bar?.int, 1) + XCTAssertEqual(foo.bar, .int(1)) + // NB: Due to a Swift bug, this is only possible to do outside the library: + // XCTAssertEqual(foo.bar?.int, 1) - XCTAssertEqual(foo[keyPath: \.bar], .int(1)) - XCTAssertEqual(foo[keyPath: \.bar?.int], 1) + XCTAssertEqual(foo[keyPath: \.bar], .int(1)) + XCTAssertEqual(foo[keyPath: \.bar?.int], 1) - XCTAssertEqual(foo[case: \.bar], .int(1)) - XCTAssertEqual(foo[case: \.bar.int], 1) + XCTAssertEqual(foo[case: \.bar], .int(1)) + XCTAssertEqual(foo[case: \.bar.int], 1) - foo[case: \.bar] = .int(42) + foo[case: \.bar] = .int(42) - XCTAssertEqual(foo, .bar(.int(42))) + XCTAssertEqual(foo, .bar(.int(42))) - foo[case: \.baz] = .string("Forty-two") + foo[case: \.baz] = .string("Forty-two") - XCTAssertEqual(foo, .bar(.int(42))) + XCTAssertEqual(foo, .bar(.int(42))) - foo[case: \.bar.int] = 1792 + foo[case: \.bar.int] = 1792 - XCTAssertEqual(foo, .bar(.int(1792))) + XCTAssertEqual(foo, .bar(.int(1792))) - foo[case: \.baz.string] = "Seventeen hundred and ninety-two" + foo[case: \.baz.string] = "Seventeen hundred and ninety-two" - XCTAssertEqual(foo, .bar(.int(1792))) + XCTAssertEqual(foo, .bar(.int(1792))) - foo[case: \.bar] = .int(42) + foo[case: \.bar] = .int(42) - XCTAssertEqual((\Foo.Cases.self)(.bar(.int(1))), .bar(.int(1))) - XCTAssertEqual((\Foo.Cases.bar)(.int(1)), .bar(.int(1))) - XCTAssertEqual((\Foo.Cases.bar.int)(1), .bar(.int(1))) - XCTAssertEqual((\Foo.Cases.fizzBuzz)(), .fizzBuzz) + XCTAssertEqual((\Foo.Cases.self)(.bar(.int(1))), .bar(.int(1))) + XCTAssertEqual((\Foo.Cases.bar)(.int(1)), .bar(.int(1))) + XCTAssertEqual((\Foo.Cases.bar.int)(1), .bar(.int(1))) + XCTAssertEqual((\Foo.Cases.fizzBuzz)(), .fizzBuzz) - XCTAssertEqual(Foo.allCasePaths[.bar(.int(1))], \.bar) - XCTAssertEqual(Foo.allCasePaths[.baz(.string(""))], \.baz) - XCTAssertEqual(Foo.allCasePaths[.fizzBuzz], \.fizzBuzz) - XCTAssertEqual(Foo.allCasePaths[.foo(nil)], \.foo) + XCTAssertEqual(Foo.allCasePaths[.bar(.int(1))], \.bar) + XCTAssertEqual(Foo.allCasePaths[.baz(.string(""))], \.baz) + XCTAssertEqual(Foo.allCasePaths[.fizzBuzz], \.fizzBuzz) + XCTAssertEqual(Foo.allCasePaths[.foo(nil)], \.foo) - XCTAssertEqual( - Array(Foo.allCasePaths), - [ - \.bar, - \.baz, - \.fizzBuzz, - \.blob, - \.foo, - ] - ) - } + XCTAssertEqual( + Array(Foo.allCasePaths), + [ + \.bar, + \.baz, + \.fizzBuzz, + \.blob, + \.foo, + ] + ) + } - func testCasePathableModify() { - var foo = Foo.bar(.int(21)) - foo.modify(\.bar.int) { $0 *= 2 } - XCTAssertEqual(foo, .bar(.int(42))) - } + func testCasePathableModify() { + var foo = Foo.bar(.int(21)) + foo.modify(\.bar.int) { $0 *= 2 } + XCTAssertEqual(foo, .bar(.int(42))) + } - #if DEBUG && !os(Linux) && !os(Windows) - func testCasePathableModify_Failure() { - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - var foo = Foo.bar(.int(21)) - XCTExpectFailure { - foo.modify(\.baz.string) { $0.append("!") } - } - XCTAssertEqual(foo, .bar(.int(21))) + #if DEBUG && !os(Linux) && !os(Windows) + func testCasePathableModify_Failure() { + guard ProcessInfo.processInfo.environment["CI"] == nil else { return } + var foo = Foo.bar(.int(21)) + XCTExpectFailure { + foo.modify(\.baz.string) { $0.append("!") } } - #endif - - func testAppend() { - let fooToBar = \Foo.Cases.bar - let barToInt = \Bar.Cases.int - let fooToInt = fooToBar.appending(path: barToInt) - - XCTAssertEqual(Foo.bar(.int(42))[case: fooToInt], 42) - XCTAssertEqual(Foo.baz(.string("Hello"))[case: fooToInt], nil) - XCTAssertEqual(Foo.bar(.int(123)), fooToInt(123)) + XCTAssertEqual(foo, .bar(.int(21))) } + #endif - func testMatch() { - switch Foo.bar(.int(42)) { - case \.bar.int: - break - default: - XCTFail() - } + func testAppend() { + let fooToBar = \Foo.Cases.bar + let barToInt = \Bar.Cases.int + let fooToInt = fooToBar.appending(path: barToInt) - switch Foo.bar(.int(42)) { - case \.bar: - break - default: - XCTFail() - } + XCTAssertEqual(Foo.bar(.int(42))[case: fooToInt], 42) + XCTAssertEqual(Foo.baz(.string("Hello"))[case: fooToInt], nil) + XCTAssertEqual(Foo.bar(.int(123)), fooToInt(123)) + } - XCTAssertTrue(Foo.bar(.int(42)).is(\.bar)) - XCTAssertTrue(Foo.bar(.int(42)).is(\.bar.int)) - XCTAssertFalse(Foo.bar(.int(42)).is(\.baz)) - XCTAssertFalse(Foo.bar(.int(42)).is(\.baz.string)) - XCTAssertFalse(Foo.bar(.int(42)).is(\.blob)) - XCTAssertFalse(Foo.bar(.int(42)).is(\.fizzBuzz)) - XCTAssertTrue(Foo.foo(nil).is(\.foo)) - XCTAssertTrue(Foo.foo(nil).is(\.foo.none)) - XCTAssertTrue(Foo.foo("").is(\.foo)) - XCTAssertFalse(Foo.foo(nil).is(\.bar)) + func testMatch() { + switch Foo.bar(.int(42)) { + case \.bar.int: + break + default: + XCTFail() } - func testPartialCaseKeyPath() { - let partialPath = \Foo.Cases.bar as PartialCaseKeyPath - XCTAssertEqual(.bar(.int(42)), partialPath(Bar.int(42))) - XCTAssertNil(partialPath(42)) - - XCTAssertEqual(.int(42), Foo.bar(.int(42))[case: partialPath] as? Bar) - XCTAssertNil(Foo.baz(.string("Hello"))[case: partialPath]) + switch Foo.bar(.int(42)) { + case \.bar: + break + default: + XCTFail() } - func testExistentials() { - let caseA: PartialCaseKeyPath = \.a - let caseB: PartialCaseKeyPath = \.b + XCTAssertTrue(Foo.bar(.int(42)).is(\.bar)) + XCTAssertTrue(Foo.bar(.int(42)).is(\.bar.int)) + XCTAssertFalse(Foo.bar(.int(42)).is(\.baz)) + XCTAssertFalse(Foo.bar(.int(42)).is(\.baz.string)) + XCTAssertFalse(Foo.bar(.int(42)).is(\.blob)) + XCTAssertFalse(Foo.bar(.int(42)).is(\.fizzBuzz)) + XCTAssertTrue(Foo.foo(nil).is(\.foo)) + XCTAssertTrue(Foo.foo(nil).is(\.foo.none)) + XCTAssertTrue(Foo.foo("").is(\.foo)) + XCTAssertFalse(Foo.foo(nil).is(\.bar)) + } - let a = A.a("Hello") - guard let valueA = a[case: caseA] else { return XCTFail() } - guard let b = caseB(valueA) else { return XCTFail() } - XCTAssertEqual(b, .b("Hello")) - } + func testPartialCaseKeyPath() { + let partialPath = \Foo.Cases.bar as PartialCaseKeyPath + XCTAssertEqual(.bar(.int(42)), partialPath(Bar.int(42))) + XCTAssertNil(partialPath(42)) - func testExistentials_Optional() { - let foo: PartialCaseKeyPath = \.foo - XCTAssertNotNil(foo(String?.none as Any)) - XCTAssertNotNil(foo(String?.some("Blob") as Any)) - XCTAssertNotNil(foo("Blob")) - } + XCTAssertEqual(.int(42), Foo.bar(.int(42))[case: partialPath] as? Bar) + XCTAssertNil(Foo.baz(.string("Hello"))[case: partialPath]) + } - func testIs_Optional() { - XCTAssertTrue(Optional(Foo.fizzBuzz).is(\.fizzBuzz)) - XCTAssertFalse(Optional(Foo.fizzBuzz).is(\.bar)) - XCTAssertFalse(Optional(Foo.fizzBuzz).is(\.baz)) - XCTAssertFalse(Optional(Foo.fizzBuzz).is(\.blob)) - XCTAssertFalse(Optional(Foo.fizzBuzz).is(\.foo)) - } - #endif -} + func testExistentials() { + let caseA: PartialCaseKeyPath = \.a + let caseB: PartialCaseKeyPath = \.b -#if swift(>=5.9) - @CasePathable - enum A: Equatable { - case a(String) + let a = A.a("Hello") + guard let valueA = a[case: caseA] else { return XCTFail() } + guard let b = caseB(valueA) else { return XCTFail() } + XCTAssertEqual(b, .b("Hello")) } - @CasePathable - enum B: Equatable { - case b(String) + func testExistentials_Optional() { + let foo: PartialCaseKeyPath = \.foo + XCTAssertNotNil(foo(String?.none as Any)) + XCTAssertNotNil(foo(String?.some("Blob") as Any)) + XCTAssertNotNil(foo("Blob")) } - @CasePathable @dynamicMemberLookup enum Foo: Equatable { - case bar(Bar) - case baz(Baz) - case fizzBuzz - case blob(Blob) - case foo(String?) - } - @CasePathable @dynamicMemberLookup enum Bar: Equatable { - case int(Int) - } - @CasePathable @dynamicMemberLookup enum Baz: Equatable { - case string(String) - } - @CasePathable enum Blob: Equatable { - } - @CasePathable @dynamicMemberLookup enum Fizz: Equatable { - case buzz(Buzz?) - } - @CasePathable @dynamicMemberLookup enum Buzz: Equatable { - case fizzBuzz(FizzBuzz?) - } - @CasePathable @dynamicMemberLookup enum FizzBuzz: Equatable { - case int(Int) + func testIs_Optional() { + XCTAssertTrue(Optional(Foo.fizzBuzz).is(\.fizzBuzz)) + XCTAssertFalse(Optional(Foo.fizzBuzz).is(\.bar)) + XCTAssertFalse(Optional(Foo.fizzBuzz).is(\.baz)) + XCTAssertFalse(Optional(Foo.fizzBuzz).is(\.blob)) + XCTAssertFalse(Optional(Foo.fizzBuzz).is(\.foo)) } +} + +@CasePathable +enum A: Equatable { + case a(String) +} + +@CasePathable +enum B: Equatable { + case b(String) +} + +@CasePathable @dynamicMemberLookup enum Foo: Equatable { + case bar(Bar) + case baz(Baz) + case fizzBuzz + case blob(Blob) + case foo(String?) +} +@CasePathable @dynamicMemberLookup enum Bar: Equatable { + case int(Int) +} +@CasePathable @dynamicMemberLookup enum Baz: Equatable { + case string(String) +} +@CasePathable enum Blob: Equatable { +} +@CasePathable @dynamicMemberLookup enum Fizz: Equatable { + case buzz(Buzz?) +} +@CasePathable @dynamicMemberLookup enum Buzz: Equatable { + case fizzBuzz(FizzBuzz?) +} +@CasePathable @dynamicMemberLookup enum FizzBuzz: Equatable { + case int(Int) +} - func ifLet(state: KeyPath, action: CaseKeyPath) -> Int? { 42 } - @_disfavoredOverload - func ifLet(state: KeyPath, action: CaseKeyPath) -> Int? { nil } -#endif +func ifLet(state: KeyPath, action: CaseKeyPath) -> Int? { 42 } +@_disfavoredOverload +func ifLet(state: KeyPath, action: CaseKeyPath) -> Int? { nil } diff --git a/Tests/CasePathsTests/MacroTests.swift b/Tests/CasePathsTests/MacroTests.swift index 60e882e6..d9f670b6 100644 --- a/Tests/CasePathsTests/MacroTests.swift +++ b/Tests/CasePathsTests/MacroTests.swift @@ -1,18 +1,16 @@ -#if swift(>=5.9) - import CasePaths +import CasePaths - @CasePathable - private enum Comments { - // Comment above case - case bar - /*Comment before case*/ case baz(Int) - case fizz(buzz: String) // Comment on case - case fizzier /*Comment in case*/(Int, buzzier: String) - case fizziest - } +@CasePathable +private enum Comments { + // Comment above case + case bar + /*Comment before case*/ case baz(Int) + case fizz(buzz: String) // Comment on case + case fizzier /*Comment in case*/(Int, buzzier: String) + case fizziest +} - @CasePathable - enum Action { - case alert(Never) - } -#endif +@CasePathable +enum Action { + case alert(Never) +} diff --git a/Tests/CasePathsTests/ReflectionTests.swift b/Tests/CasePathsTests/ReflectionTests.swift index b21f7912..1aefbc39 100644 --- a/Tests/CasePathsTests/ReflectionTests.swift +++ b/Tests/CasePathsTests/ReflectionTests.swift @@ -2,81 +2,79 @@ import XCTest final class ReflectionTests: XCTestCase { - #if swift(>=5.7) - func testProject() throws { - struct MyIdentifiable: Identifiable { - let id = 42 - } - let success = Result.success(MyIdentifiable()) - let anyIdentifiable = try XCTUnwrap(EnumMetadata.project(success) as? any Identifiable) - func id(of identifiable: some Identifiable) -> AnyHashable { - identifiable.id - } - XCTAssertEqual(42, id(of: anyIdentifiable)) + func testProject() throws { + struct MyIdentifiable: Identifiable { + let id = 42 } - - func testProject_Existential() throws { - struct MyIdentifiable: Identifiable { - let id = 42 - } - let success = Result.success(MyIdentifiable()) - let anyIdentifiable = try XCTUnwrap(EnumMetadata.project(success) as? any Identifiable) - func id(of identifiable: some Identifiable) -> AnyHashable { - identifiable.id - } - XCTAssertEqual(42, id(of: anyIdentifiable)) + let success = Result.success(MyIdentifiable()) + let anyIdentifiable = try XCTUnwrap(EnumMetadata.project(success) as? any Identifiable) + func id(of identifiable: some Identifiable) -> AnyHashable { + identifiable.id } + XCTAssertEqual(42, id(of: anyIdentifiable)) + } - func testProject_Indirect() throws { - struct MyIdentifiable: Identifiable { - let id = 42 - } - indirect enum Enum { - case indirectCase(MyIdentifiable) - } - let indirect = Enum.indirectCase(MyIdentifiable()) - let anyIdentifiable = try XCTUnwrap(EnumMetadata.project(indirect) as? any Identifiable) - func id(of identifiable: some Identifiable) -> AnyHashable { - identifiable.id - } - XCTAssertEqual(42, id(of: anyIdentifiable)) + func testProject_Existential() throws { + struct MyIdentifiable: Identifiable { + let id = 42 + } + let success = Result.success(MyIdentifiable()) + let anyIdentifiable = try XCTUnwrap(EnumMetadata.project(success) as? any Identifiable) + func id(of identifiable: some Identifiable) -> AnyHashable { + identifiable.id } + XCTAssertEqual(42, id(of: anyIdentifiable)) + } - func testProject_NoPayload() throws { - enum Enum { - case noPayload - } - let value = EnumMetadata.project(Enum.noPayload) - try XCTUnwrap(EnumMetadata.project(value) as? Void) + func testProject_Indirect() throws { + struct MyIdentifiable: Identifiable { + let id = 42 + } + indirect enum Enum { + case indirectCase(MyIdentifiable) + } + let indirect = Enum.indirectCase(MyIdentifiable()) + let anyIdentifiable = try XCTUnwrap(EnumMetadata.project(indirect) as? any Identifiable) + func id(of identifiable: some Identifiable) -> AnyHashable { + identifiable.id } + XCTAssertEqual(42, id(of: anyIdentifiable)) + } - func testLabel() throws { - enum Enum: Equatable { - case label(id: Int) - case multiLabel(id: Int, name: String) - } + func testProject_NoPayload() throws { + enum Enum { + case noPayload + } + let value = EnumMetadata.project(Enum.noPayload) + try XCTUnwrap(EnumMetadata.project(value) as? Void) + } - XCTAssertEqual(EnumMetadata.project(Enum.label(id: 42)) as? Int, 42) - let pair = try XCTUnwrap( - EnumMetadata.project(Enum.multiLabel(id: 42, name: "Blob")) as? (Int, String) - ) - XCTAssert(pair == (42, "Blob")) + func testLabel() throws { + enum Enum: Equatable { + case label(id: Int) + case multiLabel(id: Int, name: String) } - func testCompound() throws { - let object = Object() - enum Enum: Equatable { - indirect case indirect(Int, Object?, Int, Object?) - case direct(Int, Object?, Int, Object?) - } + XCTAssertEqual(EnumMetadata.project(Enum.label(id: 42)) as? Int, 42) + let pair = try XCTUnwrap( + EnumMetadata.project(Enum.multiLabel(id: 42, name: "Blob")) as? (Int, String) + ) + XCTAssert(pair == (42, "Blob")) + } - let indirect = try XCTUnwrap( - EnumMetadata.project(Enum.indirect(42, nil, 43, object)) - as? (Int, Object?, Int, Object?) - ) - XCTAssert(indirect == (42, nil, 43, object)) + func testCompound() throws { + let object = Object() + enum Enum: Equatable { + indirect case indirect(Int, Object?, Int, Object?) + case direct(Int, Object?, Int, Object?) } - #endif + + let indirect = try XCTUnwrap( + EnumMetadata.project(Enum.indirect(42, nil, 43, object)) + as? (Int, Object?, Int, Object?) + ) + XCTAssert(indirect == (42, nil, 43, object)) + } } private class Object: Equatable { diff --git a/Tests/CasePathsTests/XCTModifyTests.swift b/Tests/CasePathsTests/XCTModifyTests.swift index a46ed134..55e51eaa 100644 --- a/Tests/CasePathsTests/XCTModifyTests.swift +++ b/Tests/CasePathsTests/XCTModifyTests.swift @@ -1,4 +1,4 @@ -#if swift(>=5.9) && DEBUG && (os(iOS) || os(macOS) || os(tvOS) || os(watchOS)) +#if DEBUG && (os(iOS) || os(macOS) || os(tvOS) || os(watchOS)) @_spi(Internals) import CasePaths import XCTest