diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d93c580b..7d6b91d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,27 +16,11 @@ concurrency: jobs: macos-14: name: macOS 14 (Xcode ${{ matrix.xcode }}) - runs-on: macOS-13 + runs-on: macOS-14 strategy: matrix: xcode: - - '15.2' - steps: - - uses: actions/checkout@v4 - - name: Select Xcode ${{ matrix.xcode }} - run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app - - name: Print Swift version - run: swift --version - - name: Run tests (platforms) - run: make test-platforms - - macos-13: - name: macOS 13 (Xcode ${{ matrix.xcode }}) - runs-on: macOS-13 - strategy: - matrix: - xcode: - - '14.3.1' + - '15.4' steps: - uses: actions/checkout@v4 - name: Select Xcode ${{ matrix.xcode }} diff --git a/Package.swift b/Package.swift index e5c40e13..f41f022f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version: 5.9 import PackageDescription @@ -24,6 +24,9 @@ let package = Package( name: "CustomDump", dependencies: [ .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency") ] ), .testTarget( diff --git a/Package@swift-5.9.swift b/Package@swift-6.0.swift similarity index 85% rename from Package@swift-5.9.swift rename to Package@swift-6.0.swift index 8866f19e..018d21c0 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-6.0.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version: 6.0 import PackageDescription @@ -24,9 +24,6 @@ let package = Package( name: "CustomDump", dependencies: [ .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") - ], - swiftSettings: [ - .enableExperimentalFeature("StrictConcurrency") ] ), .testTarget( diff --git a/Sources/CustomDump/Conformances/KeyPath.swift b/Sources/CustomDump/Conformances/KeyPath.swift index 90a8f25a..3b5b5578 100644 --- a/Sources/CustomDump/Conformances/KeyPath.swift +++ b/Sources/CustomDump/Conformances/KeyPath.swift @@ -2,12 +2,9 @@ import Foundation extension AnyKeyPath: CustomDumpStringConvertible { public var customDumpDescription: String { - // NB: We gate this to 5.9+ due to this crasher: https://github.com/apple/swift/issues/64865 - #if swift(>=5.9) - if #available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) { - return self.debugDescription - } - #endif + if #available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) { + return self.debugDescription + } return """ \(typeName(Self.self))<\ \(typeName(Self.rootType, genericsAbbreviated: false)), \ diff --git a/Sources/CustomDump/Conformances/Swift.swift b/Sources/CustomDump/Conformances/Swift.swift index 1e512621..7e37f27b 100644 --- a/Sources/CustomDump/Conformances/Swift.swift +++ b/Sources/CustomDump/Conformances/Swift.swift @@ -6,7 +6,7 @@ extension Character: CustomDumpRepresentable { } } -#if (swift(>=5.7) && !targetEnvironment(macCatalyst) && (os(iOS) || os(tvOS) || os(watchOS))) || (swift(>=5.7.1) && os(macOS)) +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) @available(macOS 13, iOS 16, watchOS 9, tvOS 16, *) extension Duration: CustomDumpStringConvertible { public var customDumpDescription: String { diff --git a/Sources/CustomDump/Conformances/UserNotifications.swift b/Sources/CustomDump/Conformances/UserNotifications.swift index a3bf94ed..a984c6e5 100644 --- a/Sources/CustomDump/Conformances/UserNotifications.swift +++ b/Sources/CustomDump/Conformances/UserNotifications.swift @@ -29,7 +29,7 @@ switch self.rawValue { case .alert: return "UNAuthorizationOptions.alert" - #if (os(iOS) || os(watchOS)) && (swift(<5.9) || !canImport(CompositorServices)) + #if os(iOS) || os(watchOS) case .announcement: return "UNAuthorizationOptions.announcement" #endif @@ -56,7 +56,7 @@ var allCases: [UNAuthorizationOptions] = [ .alert ] - #if (os(iOS) || os(watchOS)) && (swift(<5.9) || !canImport(CompositorServices)) + #if os(iOS) || os(watchOS) allCases.append(.announcement) #endif allCases.append(contentsOf: [ @@ -132,11 +132,9 @@ struct Option: CustomDumpStringConvertible { var rawValue: UNNotificationPresentationOptions var customDumpDescription: String { - #if swift(<5.9) || !canImport(CompositorServices) - if self.rawValue == .alert { - return "UNNotificationPresentationOptions.alert" - } - #endif + if self.rawValue == .alert { + return "UNNotificationPresentationOptions.alert" + } if self.rawValue == .badge { return "UNNotificationPresentationOptions.badge" } @@ -155,8 +153,10 @@ var options = self var children: [Option] = [] - var allCases: [UNNotificationPresentationOptions] = [] - appendBannerList(&allCases) + var allCases: [UNNotificationPresentationOptions] = [.alert, .badge] + if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) { + allCases.append(contentsOf: [.banner, .list]) + } allCases.append(.sound) for option in allCases { if options.contains(option) { @@ -174,19 +174,6 @@ displayStyle: .set ) } - - // NB: Workaround for Xcode 13.2's new, experimental build system. - // - // defaults write com.apple.dt.XCBuild EnableSwiftBuildSystemIntegration 1 - private func appendBannerList(_ allCases: inout [UNNotificationPresentationOptions]) { - #if swift(<5.9) || !canImport(CompositorServices) - allCases.append(.alert) - #endif - allCases.append(.badge) - if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) { - allCases.append(contentsOf: [.banner, .list]) - } - } } @available(iOS 10, macOS 10.14, tvOS 10, watchOS 3, *) diff --git a/Sources/CustomDump/Diff.swift b/Sources/CustomDump/Diff.swift index f357b777..ab375020 100644 --- a/Sources/CustomDump/Diff.swift +++ b/Sources/CustomDump/Diff.swift @@ -207,7 +207,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String to: &out ) - if let areInIncreasingOrder = areInIncreasingOrder { + if let areInIncreasingOrder { lhsChildren.sort(by: areInIncreasingOrder) rhsChildren.sort(by: areInIncreasingOrder) } @@ -692,8 +692,8 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String ) default: - if let lhs = stringFromStringProtocol(lhs), - let rhs = stringFromStringProtocol(rhs), + if let lhs = String(stringProtocol: lhs), + let rhs = String(stringProtocol: rhs), lhs.contains(where: \.isNewline) || rhs.contains(where: \.isNewline) { let lhsMirror = Mirror( diff --git a/Sources/CustomDump/Dump.swift b/Sources/CustomDump/Dump.swift index 510b3946..e1e7d923 100644 --- a/Sources/CustomDump/Dump.swift +++ b/Sources/CustomDump/Dump.swift @@ -411,7 +411,7 @@ func _customDump( ) default: - if let value = stringFromStringProtocol(value) { + if let value = String(stringProtocol: value) { if value.contains(where: \.isNewline) { if maxDepth <= 0 { out.write("\"…\"") diff --git a/Sources/CustomDump/Internal/Box.swift b/Sources/CustomDump/Internal/Box.swift deleted file mode 100644 index 9e2ab542..00000000 --- a/Sources/CustomDump/Internal/Box.swift +++ /dev/null @@ -1,79 +0,0 @@ -enum Box {} - -// MARK: - Equatable - -protocol AnyEquatable { - static func isEqual(_ lhs: Any, _ rhs: Any) -> Bool -} - -extension Box: AnyEquatable where T: Equatable { - static func isEqual(_ lhs: Any, _ rhs: Any) -> Bool { - lhs as? T == rhs as? T - } -} - -func isMirrorEqual(_ lhs: Any, _ rhs: Any) -> Bool { - func open(_: LHS.Type) -> Bool? { - (Box.self as? AnyEquatable.Type)?.isEqual(lhs, rhs) - } - if let isEqual = _openExistential(type(of: lhs), do: open) { return isEqual } - let lhsMirror = Mirror(customDumpReflecting: lhs) - let rhsMirror = Mirror(customDumpReflecting: rhs) - guard - lhsMirror.subjectType == rhsMirror.subjectType, - lhsMirror.children.count == rhsMirror.children.count - else { return false } - guard !lhsMirror.children.isEmpty, !rhsMirror.children.isEmpty - else { - return String(describing: lhs) == String(describing: rhs) - } - for (lhsChild, rhsChild) in zip(lhsMirror.children, rhsMirror.children) { - guard - lhsChild.label == rhsChild.label, - isMirrorEqual(lhsChild.value, rhsChild.value) - else { return false } - } - return true -} - -// MARK: - Identifiable - -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) -protocol AnyIdentifiable { - static func isIdentityEqual(_ lhs: Any, _ rhs: Any) -> Bool -} - -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) -extension Box: AnyIdentifiable where T: Identifiable { - static func isIdentityEqual(_ lhs: Any, _ rhs: Any) -> Bool { - guard let lhs = lhs as? T, let rhs = rhs as? T else { return false } - return lhs.id == rhs.id - } -} - -func isIdentityEqual(_ lhs: Any, _ rhs: Any) -> Bool { - guard #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) else { return false } - func open(_: LHS.Type) -> Bool? { - (Box.self as? AnyIdentifiable.Type)?.isIdentityEqual(lhs, rhs) - } - return _openExistential(type(of: lhs), do: open) ?? false -} - -// MARK: - StringProtocol - -protocol AnyStringProtocol { - static func string(from value: Any) -> String? -} - -extension Box: AnyStringProtocol where T: StringProtocol { - static func string(from value: Any) -> String? { - (value as? T).map { String($0) } - } -} - -func stringFromStringProtocol(_ value: Any) -> String? { - func open(_: STR.Type) -> String? { - (Box.self as? AnyStringProtocol.Type)?.string(from: value) - } - return _openExistential(type(of: value), do: open) -} diff --git a/Sources/CustomDump/Internal/Identifiable.swift b/Sources/CustomDump/Internal/Identifiable.swift new file mode 100644 index 00000000..3abeb9a2 --- /dev/null +++ b/Sources/CustomDump/Internal/Identifiable.swift @@ -0,0 +1,8 @@ +func isIdentityEqual(_ lhs: Any, _ rhs: Any) -> Bool { + guard let lhs = lhs as? any Identifiable else { return false } + func open(_ lhs: LHS) -> Bool { + guard let rhs = rhs as? LHS else { return false } + return lhs.id == rhs.id + } + return open(lhs) +} diff --git a/Sources/CustomDump/Internal/Mirror.swift b/Sources/CustomDump/Internal/Mirror.swift index f9a592d6..8929fe66 100644 --- a/Sources/CustomDump/Internal/Mirror.swift +++ b/Sources/CustomDump/Internal/Mirror.swift @@ -25,3 +25,30 @@ extension Mirror { } } } + +func isMirrorEqual(_ lhs: Any, _ rhs: Any) -> Bool { + guard let lhs = lhs as? any Equatable else { + let lhsMirror = Mirror(customDumpReflecting: lhs) + let rhsMirror = Mirror(customDumpReflecting: rhs) + guard + lhsMirror.subjectType == rhsMirror.subjectType, + lhsMirror.children.count == rhsMirror.children.count + else { return false } + guard !lhsMirror.children.isEmpty, !rhsMirror.children.isEmpty + else { + return String(describing: lhs) == String(describing: rhs) + } + for (lhsChild, rhsChild) in zip(lhsMirror.children, rhsMirror.children) { + guard + lhsChild.label == rhsChild.label, + isMirrorEqual(lhsChild.value, rhsChild.value) + else { return false } + } + return true + } + func open(_ lhs: T) -> Bool { + guard let rhs = rhs as? T else { return false } + return lhs == rhs + } + return open(lhs) +} diff --git a/Sources/CustomDump/Internal/String.swift b/Sources/CustomDump/Internal/String.swift index b655e9c8..e6434c1b 100644 --- a/Sources/CustomDump/Internal/String.swift +++ b/Sources/CustomDump/Internal/String.swift @@ -1,6 +1,11 @@ import Foundation extension String { + init?(stringProtocol value: Any) { + guard let value = value as? any StringProtocol else { return nil } + self.init(value) + } + func indenting(by count: Int) -> String { self.indenting(with: String(repeating: " ", count: count)) } diff --git a/Tests/CustomDumpTests/DumpTests.swift b/Tests/CustomDumpTests/DumpTests.swift index 7f1762d5..b42f0f42 100644 --- a/Tests/CustomDumpTests/DumpTests.swift +++ b/Tests/CustomDumpTests/DumpTests.swift @@ -708,7 +708,7 @@ final class DumpTests: XCTestCase { } func testKeyPath() { - // NB: While this should run on >=5.9, it currently crashes CI on Xcode 15.2 + // NB: While this should run on >=5.9, it currently crashes CI on Xcode 15 #if swift(>=5.10) && (os(iOS) || os(macOS) || os(tvOS) || os(watchOS)) var dump = "" if #available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) { @@ -1184,7 +1184,7 @@ final class DumpTests: XCTestCase { ) } - #if (swift(>=5.7) && !targetEnvironment(macCatalyst) && (os(iOS) || os(tvOS) || os(watchOS))) || (swift(>=5.7.1) && os(macOS)) + #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) func testDuration() { guard #available(macOS 13, iOS 16, watchOS 9, tvOS 16, *) else { return }