From 7b5ba2aca64d8e0c779e9450d5678925d22030db Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Sun, 21 Dec 2025 05:33:42 -0800 Subject: [PATCH 1/9] Fixed a future compilation issue by making `ProgressHandler` async --- Sources/CodableDatastore/Datastore/Datastore.swift | 12 ++++++------ Sources/CodableDatastore/Datastore/Progress.swift | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) 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 From e3094a1484be737aa0e6196c0066cfd271c42c33 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Sun, 21 Dec 2025 05:35:05 -0800 Subject: [PATCH 2/9] Fixed a Swift 6 compilation warning that conforming Never to Codable was not applied retroactively --- Sources/CodableDatastore/Indexes/Indexable.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/CodableDatastore/Indexes/Indexable.swift b/Sources/CodableDatastore/Indexes/Indexable.swift index 68af36c..c5853b6 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. /// From 98f072edaffbab9180395897d7e0cd38fd1402d7 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Sun, 21 Dec 2025 05:35:57 -0800 Subject: [PATCH 3/9] Fixed a Swift 6 compilation warning where `ReadOnly` and `ReadWrite` were not sendable --- Sources/CodableDatastore/Persistence/AccessMode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 {} From b28388fb3846691515b88bc4053e64059f41bbaf Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Sun, 21 Dec 2025 05:38:38 -0800 Subject: [PATCH 4/9] Fixed a Swift 6 compilation warning where `RangeReplaceableCollection` would warn when initialized with an `AsyncSequence` --- .../Persistence/Disk Persistence/Datastore/DatastorePage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From d5a2779fcf20cba68907f6cb23abb544fde906d7 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Sun, 21 Dec 2025 05:40:31 -0800 Subject: [PATCH 5/9] Fixed a Swift 6 compilation warning for duplicate Sendable annotations for date coding strategies --- .../Disk Persistence/ISO8601DateFormatter+Milliseconds.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift index dd85821..c9e25ba 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift @@ -43,7 +43,7 @@ extension JSONEncoder.DateEncodingStrategy { } } -#if compiler(>=6) +#if compiler(>=6) && compiler(<6.2) extension ISO8601DateFormatter: @unchecked @retroactive Sendable {} extension JSONDecoder.DateDecodingStrategy: @unchecked Sendable {} extension JSONEncoder.DateEncodingStrategy: @unchecked Sendable {} From e488b8c46723ebd96e5a7840a1ac8e6b5276271a Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Sun, 21 Dec 2025 05:47:56 -0800 Subject: [PATCH 6/9] Removed CI compiler option to treat C warnings as errors as it no longer seems to be supported --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a57c24d..602a5f2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v3 - 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 @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v3 - 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 From cf6a2a9846e9593bdf0fd6976ce55b1c927ec7c6 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Sun, 21 Dec 2025 05:55:59 -0800 Subject: [PATCH 7/9] Added Swift version printout to CI --- .github/workflows/api.yml | 3 +++ .github/workflows/test.yml | 6 ++++++ 2 files changed, 9 insertions(+) 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 602a5f2..e3a0c60 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,9 @@ 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 @@ -25,6 +28,9 @@ 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 From e0509d3a09d3641ff5ead64f939f4db515b41ea2 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Sun, 21 Dec 2025 05:57:40 -0800 Subject: [PATCH 8/9] Fixed Swift 6 issues when compiling on linux --- Sources/CodableDatastore/Indexes/Indexable.swift | 2 +- .../Persistence/Disk Persistence/DatedIdentifier.swift | 2 +- .../Persistence/Disk Persistence/JSONCoder.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/CodableDatastore/Indexes/Indexable.swift b/Sources/CodableDatastore/Indexes/Indexable.swift index c5853b6..ea7b881 100644 --- a/Sources/CodableDatastore/Indexes/Indexable.swift +++ b/Sources/CodableDatastore/Indexes/Indexable.swift @@ -112,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/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/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 From b8acb72a44c8e6c99f95446980eeecf5dd257f07 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Mon, 22 Dec 2025 04:10:43 -0800 Subject: [PATCH 9/9] Replaced `ISO8601DateFormatter` with `GlobalDateFormatter` when possible --- .../ISO8601DateFormatter+Milliseconds.swift | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/ISO8601DateFormatter+Milliseconds.swift index c9e25ba..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,7 +58,7 @@ 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) }