Adopt Swift 6 language mode with strict concurrency checking#832
Open
thuyetngocluong wants to merge 4 commits into
Open
Adopt Swift 6 language mode with strict concurrency checking#832thuyetngocluong wants to merge 4 commits into
thuyetngocluong wants to merge 4 commits into
Conversation
Migrate the package to swift-tools-version 6.0 with swiftLanguageModes [.v6] so it builds cleanly (0 errors, 0 warnings) under Swift 6 strict data-race safety, preserving the public API and runtime behavior. - Replace the stored mutable globals SwiftDate.defaultRegion and DateFormats.autoFormats with computed properties backed by an internal OSUnfairLocked box (OSAllocatedUnfairLock on iOS 16/macOS 13+, C os_unfair_lock on older systems), keeping the synchronous get/set API. - Add Sendable conformances: Region, ISOFormatter.Options, ISOParser.Options, DateToStringStyles, StringToDateStyles. - Remove dead code that violated strict concurrency: the unused @atomic property wrapper and Bundle.appModule/BundleFinder (orphaned since the RelativeFormatter removal), plus the Package.swift resource entries pointing at directories deleted in that same cleanup (hard error under tools 6.0). - Handle the Calendar.Component cases introduced by newer SDKs (.dayOfYear, .isLeapMonth, .isRepeatedDay) and the new Calendar.Identifier cases in exhaustive switches, with availability guards; difference(in:) now returns a real value for .dayOfYear on iOS 18/macOS 15+. Mark the Calendar.Identifier CustomStringConvertible conformance @retroactive. - Add TestConcurrencySafety stress tests (concurrent defaultRegion and autoFormats access); suite is clean under ThreadSanitizer. - Fix two tests broken by newer Foundation behavior (dateComponents zero-fills and attaches calendar/timeZone; JSONEncoder key order is randomized per process - use .sortedKeys). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates SwiftDate to Swift 6 language mode with strict concurrency checking, replacing previously mutable global/shared state with synchronized access while keeping the public API surface largely source-compatible. It also updates several types for Sendable compliance, adjusts logic for newer Foundation/SDK enum cases, and updates tests to be deterministic and to validate concurrency safety.
Changes:
- Adopt Swift 6 tools/language mode in SwiftPM and CocoaPods, and remove stale SwiftPM resource entries.
- Make
SwiftDate.defaultRegionandDateFormats.autoFormatsconcurrency-safe via an unfair-lock-backed storage wrapper. - Add/adjust
Sendableconformances and update code/tests for newer Foundation behaviors and newer SDK enum cases.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/SwiftDateTests/TestRegion.swift | Makes JSON encoding deterministic by using .sortedKeys and updates expected JSON string order. |
| Tests/SwiftDateTests/TestDateInRegion+Components.swift | Updates a component comparison to avoid brittle whole-DateComponents equality with newer Foundation. |
| Tests/SwiftDateTests/TestConcurrencySafety.swift | Adds stress tests for concurrent reads/writes of defaultRegion and autoFormats. |
| SwiftDate.podspec | Bumps CocoaPods swift_versions to 6.0. |
| Sources/SwiftDate/SwiftDate.swift | Replaces stored mutable defaultRegion with synchronized computed access; keeps autoFormats passthrough. |
| Sources/SwiftDate/Supports/Commons.swift | Introduces OSUnfairLocked and uses it to synchronize DateFormats.autoFormats; updates Calendar component mapping for new cases. |
| Sources/SwiftDate/Supports/Calendars.swift | Adds @retroactive and expands Calendar.Identifier.description for new identifiers. |
| Sources/SwiftDate/Formatters/ISOParser.swift | Marks ISOParser.Options as Sendable. |
| Sources/SwiftDate/Formatters/ISOFormatter.swift | Marks ISOFormatter.Options as Sendable. |
| Sources/SwiftDate/Formatters/Formatter+Protocols.swift | Marks style enums as Sendable. |
| Sources/SwiftDate/DateInRegion/Region.swift | Marks Region as Sendable. |
| Sources/SwiftDate/Date/Date+Compare.swift | Handles newer Calendar.Component cases in component extraction. |
| Package.swift | Moves to swift-tools 6.0, removes deleted resources, and sets swiftLanguageModes: [.v6]. |
Comments suppressed due to low confidence (1)
Sources/SwiftDate/Supports/Calendars.swift:79
- New
Calendar.Identifiercases were added todescription, butinit(_ rawValue:)doesn’t map these strings back to the corresponding identifiers. This breaks round-tripping/decoding for the newly supported identifiers (e.g. "bangla" will fall intodefaultand silently become the default calendar).
case .vikram: return "vikram"
@unknown default:
fatalError("Unsupported calendar \(self)")
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Comment on lines
+277
to
+282
| case .isLeapMonth: | ||
| if #available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) { | ||
| return NSCalendar.Unit.isLeapMonth | ||
| } else { | ||
| return [] | ||
| } |
Comment on lines
+283
to
+288
| case .isRepeatedDay: | ||
| if #available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) { | ||
| return NSCalendar.Unit.isRepeatedDay | ||
| } else { | ||
| return [] | ||
| } |
The Calendar.Identifier cases added by the iOS 26 SDK and the Calendar.Component/NSCalendar.Unit additions have different SDK floors: Calendar.Component.isLeapMonth ships since the iOS 17 SDK, .dayOfYear since iOS 18, while .isRepeatedDay, NSCalendar.Unit.isLeapMonth/ .isRepeatedDay and the 11 regional calendar identifiers exist only in the iOS 26 SDK. Wrap each usage in the matching #if compiler(...) so older toolchains neither fail to compile the missing symbols nor emit exhaustiveness warnings for cases their SDK does have. Minimum supported toolchain is Xcode 16 (Swift 6.0), imposed by swift-tools-version 6.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment on lines
13
to
+23
| import Foundation | ||
| import os | ||
|
|
||
| // MARK: - Lock Protected Value Support | ||
|
|
||
| /// An unfair-lock protected mutable box used to expose the library-wide | ||
| /// configuration (`SwiftDate.defaultRegion`, `DateFormats.autoFormats`) as | ||
| /// concurrency-safe shared state under Swift 6 strict data-race safety. | ||
| /// On iOS 16/macOS 13 and later it uses Apple's `OSAllocatedUnfairLock`; | ||
| /// on older systems it falls back to the C `os_unfair_lock` API. | ||
| internal final class OSUnfairLocked<Value: Sendable>: @unchecked Sendable { |
Comment on lines
73
to
75
| } | ||
|
|
||
| // MARK: - DateFormatter |
Comment on lines
+35
to
+38
| let region = SwiftDate.defaultRegion | ||
| XCTAssertFalse(region.timeZone.identifier.isEmpty) | ||
| _ = DateInRegion(Date(), region: region).toISO() | ||
| _ = Region() // fills every attribute from a single defaultRegion snapshot |
Comment on lines
+77
to
81
| #endif | ||
| @unknown default: | ||
| fatalError("Unsupported calendar \(self)") | ||
| fatalError("Unsupported calendar identifier") | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR migrates SwiftDate to Swift 6 language mode with strict concurrency (data-race safety) enabled, while keeping the public API source-compatible and the runtime behavior unchanged. The library now builds with 0 errors and 0 warnings in Swift 6 mode, and the full test suite passes — including under ThreadSanitizer.
What changed
Package manifest
swift-tools-version: 6.0+swiftLanguageModes: [.v6](platforms stay at macOS 10.15 / iOS 13 / watchOS 6 / tvOS 13).resources:entries pointing atFormatters/RelativeFormatter/langsandResources— those directories were deleted together with the RelativeFormatter removal, and under tools 6.0 the missing paths are a hard build error (they were silently warned about under 5.5).SwiftDate.podspec:swift_versionsbumped to6.0.Concurrency-safe shared state
SwiftDate.defaultRegionandDateFormats.autoFormatswere stored mutable globals — a hard error under Swift 6. They are now computed properties backed by an internalOSUnfairLocked<Value: Sendable>box:OSAllocatedUnfairLockon iOS 16 / macOS 13 and later, and falls back to the Cos_unfair_lockAPI (heap-allocated, so the lock has a stable address) on older systems.get/setAPI is unchanged, so every existing call site — including the manyregion: Region = SwiftDate.defaultRegiondefault arguments — keeps compiling as-is.Sendable conformances
Region(its only stored property is aCalendar),ISOFormatter.Options,ISOParser.Options,DateToStringStyles,StringToDateStyles. TheISOFormatter.Optionsconformance is what resolves the 13 "static property is not concurrency-safe" errors on its option-set constants.Newer SDK enum cases
Recent SDKs added
Calendar.Component.dayOfYear(iOS 18/macOS 15) and.isLeapMonth/.isRepeatedDay(iOS 26/macOS 26), plus 11 newCalendar.Identifiercases — all of which made existing exhaustive switches warn (@unknown defaultdoes not cover cases that are known to the SDK you compile against):Calendar.Component.nsCalendarUnitmaps the three new cases to theirNSCalendar.Unitcounterparts behind availability guards (empty set on older OSes, where those values cannot even be constructed).Date.difference(in:)/differences(in:from:)now return a real value for.dayOfYearon iOS 18/macOS 15+;.isLeapMonth/.isRepeatedDayreturnnil(they are Boolean metadata — no integer difference exists).Calendar.Identifier.descriptioncovers the 11 new identifiers, and the conformance is marked@retroactive.Dead code removal
The unused
@Atomicproperty wrapper andBundle.appModule/BundleFinder(orphaned since the RelativeFormatter removal, and itself a Swift 6 violation as a stored mutable static).Tests
TestConcurrencySafetystress tests hammerdefaultRegion/autoFormatswithDispatchQueue.concurrentPerformwhile other threads parse and format through them.master): newer Foundation zero-fills and attachescalendar/timeZonetodateComponents(from:to:)results, breaking whole-valueDateComponentsequality against1.hours; andJSONEncoderkey order is randomized per process, breaking the raw-JSON string comparison (now uses.sortedKeys).Verification
swift buildin Swift 6 mode: 0 errors, 0 warnings (Swift 6.3.3 toolchain)swift test: 71/71 passing, repeatedlyswift test --sanitize=thread: clean, including the new concurrency stress testsNotes for reviewers
@retroactive).OSUnfairLockedmakes eachget/setindividually atomic; compound read-modify-write patterns likeSwiftDate.autoFormats.append(...)are not atomic as a whole (this is documented on the property, and matches the previous unsynchronized behavior).🤖 Generated with Claude Code