diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index 2655cb8..189f232 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -18,6 +18,9 @@ jobs: - name: Mark Workspace As Safe # https://github.com/actions/checkout/issues/766 run: git config --global --add safe.directory ${GITHUB_WORKSPACE} + - name: Swift Version + run: | + swift --version - name: Diagnose API Breaking Changes run: | swift package diagnose-api-breaking-changes origin/main --products CodableDatastore diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a57c24d..e3a0c60 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,9 +12,12 @@ jobs: steps: - name: Checkout Source uses: actions/checkout@v3 + - name: Swift Version + run: | + swift --version - name: Build run: | - swift build --build-tests --configuration release -Xswiftc -enable-testing -Xswiftc -warnings-as-errors -Xcc -Werror + swift build --build-tests --configuration release -Xswiftc -enable-testing -Xswiftc -warnings-as-errors - name: Run Tests run: | swift test --skip-build --configuration release @@ -25,9 +28,12 @@ jobs: steps: - name: Checkout Source uses: actions/checkout@v3 + - name: Swift Version + run: | + swift --version - name: Build run: | - swift build --build-tests --configuration debug -Xswiftc -enable-testing -Xswiftc -warnings-as-errors -Xcc -Werror + swift build --build-tests --configuration debug -Xswiftc -enable-testing -Xswiftc -warnings-as-errors - name: Run Tests run: | swift test --skip-build --configuration debug diff --git a/Sources/CodableDatastore/Datastore/Datastore.swift b/Sources/CodableDatastore/Datastore/Datastore.swift index 6645143..aa40082 100644 --- a/Sources/CodableDatastore/Datastore/Datastore.swift +++ b/Sources/CodableDatastore/Datastore/Datastore.swift @@ -194,7 +194,7 @@ extension Datastore { /// Notify progress handlers we are evaluating for possible migrations. for handler in warmupProgressHandlers { - handler(.evaluating) + await handler(.evaluating) } /// Grab an up-to-date descriptor and check the indexes against it @@ -272,7 +272,7 @@ extension Datastore { /// Notify progress handlers we are starting an entry. for handler in warmupProgressHandlers { - handler(.working(current: index, total: persistedDescriptor.size)) + await handler(.working(current: index, total: persistedDescriptor.size)) } let instanceData = try await encoder(instance) @@ -354,7 +354,7 @@ extension Datastore { let completeProgress = Progress.complete(total: persistedDescriptor.size) for handler in warmupProgressHandlers { - handler(completeProgress) + await handler(completeProgress) } warmupProgressHandlers.removeAll() @@ -401,7 +401,7 @@ extension Datastore where AccessMode == ReadWrite { else { return } let warmUpProgress = try await self.warmupIfNeeded { progress in - progressHandler?(progress.adding(current: 0, total: descriptor.size)) + await progressHandler?(progress.adding(current: 0, total: descriptor.size)) } /// Make sure we still need to do the work, as the warm up may have made changes anyways due to incompatible types. @@ -419,12 +419,12 @@ extension Datastore where AccessMode == ReadWrite { /// Make sure the stored version is smaller than the one we require, otherwise stop early. version.rawValue < minimumVersion.rawValue else { - progressHandler?(warmUpProgress.adding(current: descriptor.size, total: descriptor.size)) + await progressHandler?(warmUpProgress.adding(current: descriptor.size, total: descriptor.size)) return } try await self.migrate(index: index) { migrateProgress in - progressHandler?(warmUpProgress.adding(migrateProgress)) + await progressHandler?(warmUpProgress.adding(migrateProgress)) } } } diff --git a/Sources/CodableDatastore/Datastore/Progress.swift b/Sources/CodableDatastore/Datastore/Progress.swift index 6823c4e..a01d1e5 100644 --- a/Sources/CodableDatastore/Datastore/Progress.swift +++ b/Sources/CodableDatastore/Datastore/Progress.swift @@ -8,7 +8,7 @@ import Foundation -public typealias ProgressHandler = @Sendable (_ progress: Progress) -> Void +public typealias ProgressHandler = @Sendable (_ progress: Progress) async -> Void public enum Progress: Sendable { case evaluating diff --git a/Sources/CodableDatastore/Indexes/Indexable.swift b/Sources/CodableDatastore/Indexes/Indexable.swift index 68af36c..ea7b881 100644 --- a/Sources/CodableDatastore/Indexes/Indexable.swift +++ b/Sources/CodableDatastore/Indexes/Indexable.swift @@ -24,6 +24,17 @@ public struct AnyIndexable { #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) /// Matching implementation from https://github.com/apple/swift/pull/64899/files +#if compiler(>=6) +extension Never: @retroactive Codable { + public init(from decoder: any Decoder) throws { + let context = DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unable to decode an instance of Never.") + throw DecodingError.typeMismatch(Never.self, context) + } + public func encode(to encoder: any Encoder) throws {} +} +#else extension Never: Codable { public init(from decoder: any Decoder) throws { let context = DecodingError.Context( @@ -34,6 +45,7 @@ extension Never: Codable { public func encode(to encoder: any Encoder) throws {} } #endif +#endif /// A marker protocol for types that can be used as a ranged index. /// @@ -100,7 +112,7 @@ extension Optional: RangedIndexable where Wrapped: RangedIndexable {} // MARK: - Foundation Conformances extension Date: RangedIndexable {} -#if canImport(Darwin) +#if canImport(Darwin) || compiler(>=6.2) extension Decimal: RangedIndexable {} #else extension Decimal: RangedIndexable, @unchecked Sendable {} diff --git a/Sources/CodableDatastore/Persistence/AccessMode.swift b/Sources/CodableDatastore/Persistence/AccessMode.swift index 44ede4f..18a8814 100644 --- a/Sources/CodableDatastore/Persistence/AccessMode.swift +++ b/Sources/CodableDatastore/Persistence/AccessMode.swift @@ -7,7 +7,7 @@ // /// An AccessMode marker type. -public protocol _AccessMode {} +public protocol _AccessMode: Sendable {} /// A marker type that indicates read-only access. public enum ReadOnly: _AccessMode {} diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastorePage.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastorePage.swift index 30884dc..9e26972 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastorePage.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastorePage.swift @@ -216,7 +216,7 @@ actor MultiplexedAsyncSequence: AsyncSequence wh } } -extension RangeReplaceableCollection { +extension RangeReplaceableCollection where Self: Sendable { init(_ sequence: S) async throws where S.Element == Element { self = try await sequence.reduce(into: Self.init()) { @Sendable partialResult, element in partialResult.append(element) diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/DatedIdentifier.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/DatedIdentifier.swift index 58e2712..4abfbd4 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/DatedIdentifier.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/DatedIdentifier.swift @@ -120,6 +120,6 @@ private extension DateFormatter { }() } -#if !canImport(Darwin) +#if !canImport(Darwin) && compiler(<6.2) extension DateFormatter: @unchecked Sendable {} #endif diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift index dd85821..be7c483 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift @@ -8,8 +8,29 @@ import Foundation -extension ISO8601DateFormatter { - static let withMilliseconds: ISO8601DateFormatter = { +private struct GlobalDateFormatter: Sendable { + @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) + static let cachedFormatter = Date.ISO8601FormatStyle(includingFractionalSeconds: true) + + static let parse: @Sendable (_ value: String) -> Date? = { + if #available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) { + return { try? cachedFormatter.parse($0) } + } else { + return { ISO8601DateFormatter.withMilliseconds.date(from: $0) } + } + }() + + static let format: @Sendable (_ value: Date) -> String = { + if #available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) { + return { cachedFormatter.format($0) } + } else { + return { ISO8601DateFormatter.withMilliseconds.string(from: $0) } + } + }() +} + +private extension ISO8601DateFormatter { + nonisolated(unsafe) static let withMilliseconds: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.timeZone = TimeZone(secondsFromGMT: 0) formatter.formatOptions = [ @@ -27,7 +48,7 @@ extension JSONDecoder.DateDecodingStrategy { static let iso8601WithMilliseconds: Self = custom { decoder in let container = try decoder.singleValueContainer() let string = try container.decode(String.self) - guard let date = ISO8601DateFormatter.withMilliseconds.date(from: string) else { + guard let date = GlobalDateFormatter.parse(string) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)") } return date @@ -37,13 +58,13 @@ extension JSONDecoder.DateDecodingStrategy { extension JSONEncoder.DateEncodingStrategy { static let iso8601WithMilliseconds: Self = custom { date, encoder in - let string = ISO8601DateFormatter.withMilliseconds.string(from: date) + let string = GlobalDateFormatter.format(date) var container = encoder.singleValueContainer() try container.encode(string) } } -#if compiler(>=6) +#if compiler(>=6) && compiler(<6.2) extension ISO8601DateFormatter: @unchecked @retroactive Sendable {} extension JSONDecoder.DateDecodingStrategy: @unchecked Sendable {} extension JSONEncoder.DateEncodingStrategy: @unchecked Sendable {} diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/JSONCoder.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/JSONCoder.swift index df81289..5f31b4c 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/JSONCoder.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/JSONCoder.swift @@ -29,7 +29,7 @@ extension JSONDecoder { }() } -#if !canImport(Darwin) +#if !canImport(Darwin) && compiler(<6.2) extension JSONEncoder: @unchecked Sendable {} extension JSONDecoder: @unchecked Sendable {} #endif