Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Sources/XMLCoder/Auxiliaries/Box/DateBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import Foundation

struct DateBox: Equatable {
struct DateBox: Equatable, Sendable {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify Sendable compliance with DateFormatter case.

While adding Sendable conformance to DateBox is good for concurrency safety, the Format.formatter(DateFormatter) case may be problematic since DateFormatter is not Sendable. This could lead to compiler warnings or errors in strict concurrency mode.

Let me verify if DateFormatter conforms to Sendable:


🏁 Script executed:

#!/bin/bash
# Check if DateFormatter conforms to Sendable in the codebase
ast-grep --pattern '$_: DateFormatter' | head -20
rg -A 5 -B 5 "DateFormatter.*Sendable\|Sendable.*DateFormatter"

Length of output: 117


Let’s inspect the Format enum in DateBox.swift to confirm its Sendable conformance and associated cases:


🏁 Script executed:

#!/bin/bash
rg -n "enum Format" -A 10 Sources/XMLCoder/Auxiliaries/Box/DateBox.swift

Length of output: 345


Fix the non-Sendable DateFormatter case in DateBox.Format.

The Format.formatter(DateFormatter) associated value makes Format (and thus DateBox) fail strict-Sendable checks, since DateFormatter isn’t Sendable. To resolve this, either:

  • Replace the DateFormatter–based case with a thread-safe alternative (e.g. a format identifier or closure that’s Sendable),
  • Or explicitly opt out of automatic checking by marking the enum as @unchecked Sendable.

For example, at the bottom of Sources/XMLCoder/Auxiliaries/Box/DateBox.swift:

     enum Format: Equatable {
         case secondsSince1970
         case millisecondsSince1970
         case iso8601
         case formatter(DateFormatter)
     }
+
+    // DateFormatter is not @Sendable; opt out of checking here
+    @unchecked
+    extension DateBox.Format: Sendable {}

• Location to update:

  • Sources/XMLCoder/Auxiliaries/Box/DateBox.swift (lines 12–17)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Sources/XMLCoder/Auxiliaries/Box/DateBox.swift around lines 11 to 17, the
Format enum inside DateBox uses a DateFormatter in one of its cases, which is
not Sendable and causes strict Sendable conformance to fail. To fix this, either
replace the DateFormatter case with a thread-safe, Sendable alternative like a
format identifier or closure, or mark the Format enum with @unchecked Sendable
to opt out of automatic Sendable checking. This change will ensure DateBox
conforms to Sendable without errors.

enum Format: Equatable {
case secondsSince1970
case millisecondsSince1970
Expand Down Expand Up @@ -44,7 +44,7 @@ struct DateBox: Equatable {

init?(iso8601 string: String) {
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
guard let unboxed = _iso8601Formatter.date(from: string) else {
guard let unboxed = ISO8601DateFormatter.xmlCoderFormatter().date(from: string) else {
return nil
}
self.init(unboxed, format: .iso8601)
Expand All @@ -70,7 +70,7 @@ struct DateBox: Equatable {
return milliseconds.description
case .iso8601:
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
return _iso8601Formatter.string(from: self.unboxed)
return ISO8601DateFormatter.xmlCoderFormatter().string(from: self.unboxed)
} else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}
Expand Down
18 changes: 7 additions & 11 deletions Sources/XMLCoder/Auxiliaries/ISO8601DateFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,10 @@

import Foundation

/// Shared ISO8601 Date Formatter
/// NOTE: This value is implicitly lazy and _must_ be lazy. We're compiled
/// against the latest SDK (w/ ISO8601DateFormatter), but linked against
/// whichever Foundation the user has. ISO8601DateFormatter might not exist, so
/// we better not hit this code path on an older OS.
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
var _iso8601Formatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = .withInternetDateTime
return formatter
}()
extension ISO8601DateFormatter {
static func xmlCoderFormatter() -> ISO8601DateFormatter {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = .withInternetDateTime
return formatter
}
}
2 changes: 1 addition & 1 deletion Sources/XMLCoder/Encoder/DynamicNodeEncoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</book>
```
*/
public protocol DynamicNodeEncoding: Encodable {
public protocol DynamicNodeEncoding: Encodable, Sendable {
static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding
}

Expand Down
16 changes: 11 additions & 5 deletions Sources/XMLCoder/Encoder/XMLEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ open class XMLEncoder {
// MARK: Options

/// The formatting of the output XML data.
public struct OutputFormatting: OptionSet {
public struct OutputFormatting: OptionSet, Sendable {
/// The format's default value.
public let rawValue: UInt

Expand All @@ -39,7 +39,7 @@ open class XMLEncoder {
}

/// A node's encoding type. Specifies how a node will be encoded.
public enum NodeEncoding {
public enum NodeEncoding : Sendable {
case attribute
case element
case both
Expand Down Expand Up @@ -233,8 +233,8 @@ open class XMLEncoder {
@available(*, deprecated, renamed: "NodeEncodingStrategy")
public typealias NodeEncodingStrategies = NodeEncodingStrategy

public typealias XMLNodeEncoderClosure = (CodingKey) -> NodeEncoding?
public typealias XMLEncodingClosure = (Encodable.Type, Encoder) -> XMLNodeEncoderClosure
public typealias XMLNodeEncoderClosure = @Sendable (CodingKey) -> NodeEncoding?
public typealias XMLEncodingClosure = @Sendable (Encodable.Type, Encoder) -> XMLNodeEncoderClosure

/// Set of strategies to use for encoding of nodes.
public enum NodeEncodingStrategy {
Expand Down Expand Up @@ -262,7 +262,13 @@ open class XMLEncoder {
guard let dynamicType = codableType as? DynamicNodeEncoding.Type else {
return { _ in nil }
}
return dynamicType.nodeEncoding(for:)
#if compiler(>=6.1)
return dynamicType.nodeEncoding(for:)
#else
return { (@Sendable key: CodingKey) in
dynamicType.nodeEncoding(for: key)
}
#endif
}
}

Expand Down
Loading