Skip to content

Commit 6b0dc95

Browse files
authored
Merge pull request #11 from swhitty/struct-sendable
Struct sendable
2 parents 45154f9 + 2f9f5bd commit 6b0dc95

File tree

6 files changed

+57
-45
lines changed

6 files changed

+57
-45
lines changed

Sources/KeyValueDecoder.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import CoreFoundation
3333
import Foundation
3434

3535
/// Top level encoder that converts `[String: Any]`, `[Any]` or `Any` into `Codable` types.
36-
public final class KeyValueDecoder {
36+
public struct KeyValueDecoder: Sendable {
3737

3838
/// Contextual user-provided information for use during encoding.
3939
public var userInfo: [CodingUserInfoKey: any Sendable]
@@ -94,7 +94,7 @@ extension KeyValueDecoder: TopLevelDecoder {
9494
extension KeyValueDecoder {
9595

9696
static func makePlistCompatible() -> KeyValueDecoder {
97-
let decoder = KeyValueDecoder()
97+
var decoder = KeyValueDecoder()
9898
decoder.nilDecodingStrategy = .stringNull
9999
return decoder
100100
}

Sources/KeyValueEncoder.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import Foundation
3333

3434
/// Top level encoder that converts `Codable` instances into loosely typed `[String: Any]`, `[Any]` or `Any`.
35-
public final class KeyValueEncoder {
35+
public struct KeyValueEncoder: Sendable {
3636

3737
/// Contextual user-provided information for use during encoding.
3838
public var userInfo: [CodingUserInfoKey: any Sendable]
@@ -85,7 +85,7 @@ extension KeyValueEncoder: TopLevelEncoder {
8585
extension KeyValueEncoder {
8686

8787
static func makePlistCompatible() -> KeyValueEncoder {
88-
let encoder = KeyValueEncoder()
88+
var encoder = KeyValueEncoder()
8989
encoder.nilEncodingStrategy = .stringNull
9090
return encoder
9191
}

Tests/KeyValueDecoderTests.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ struct KeyValueDecoderTests {
146146

147147
@Test
148148
func decodesRounded_Ints() throws {
149-
let decoder = KeyValueDecoder()
149+
var decoder = KeyValueDecoder()
150150
decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero)
151151

152152
#expect(
@@ -235,7 +235,7 @@ struct KeyValueDecoderTests {
235235

236236
@Test
237237
func decodesRounded_UInts() throws {
238-
let decoder = KeyValueDecoder()
238+
var decoder = KeyValueDecoder()
239239
decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero)
240240

241241
#expect(
@@ -422,7 +422,7 @@ struct KeyValueDecoderTests {
422422

423423
@Test
424424
func decodes_Null() throws {
425-
let decoder = KeyValueDecoder()
425+
var decoder = KeyValueDecoder()
426426
decoder.nilDecodingStrategy = .default
427427

428428
#expect(throws: (any Error).self) {
@@ -461,7 +461,7 @@ struct KeyValueDecoderTests {
461461

462462
@Test
463463
func decodes_UnkeyedOptionals() throws {
464-
let decoder = KeyValueDecoder()
464+
var decoder = KeyValueDecoder()
465465

466466
decoder.nilDecodingStrategy = .removed
467467
#expect(
@@ -704,7 +704,7 @@ struct KeyValueDecoderTests {
704704

705705
@Test
706706
func decodes_UnkeyedNil() throws {
707-
let decoder = KeyValueDecoder()
707+
var decoder = KeyValueDecoder()
708708
decoder.nilDecodingStrategy = .default
709709

710710
#expect(
@@ -917,7 +917,7 @@ struct KeyValueDecoderTests {
917917
UInt8(from: Double.nan, using: .clamping(roundingRule: nil)) == nil
918918
)
919919

920-
let decoder = KeyValueDecoder()
920+
var decoder = KeyValueDecoder()
921921
decoder.intDecodingStrategy = .clamping(roundingRule: .toNearestOrAwayFromZero)
922922
#expect(
923923
try decoder.decode([Int8].self, from: [10, 20.5, 1000, -Double.infinity]) == [
@@ -1004,7 +1004,7 @@ private extension KeyValueDecoder {
10041004
return try closure(&container)
10051005
}
10061006

1007-
let decoder = KeyValueDecoder()
1007+
var decoder = KeyValueDecoder()
10081008
decoder.userInfo[.decoder] = proxy as any DecodingProxy
10091009
_ = try decoder.decode(StubDecoder.self, from: value)
10101010
return proxy.result!
@@ -1019,14 +1019,14 @@ private extension KeyValueDecoder {
10191019
return try closure(&container)
10201020
}
10211021

1022-
let decoder = KeyValueDecoder()
1022+
var decoder = KeyValueDecoder()
10231023
decoder.userInfo[.decoder] = proxy as any DecodingProxy
10241024
_ = try decoder.decode(StubDecoder.self, from: value)
10251025
return proxy.result!
10261026
}
10271027

10281028
static func makeJSONCompatible() -> KeyValueDecoder {
1029-
let decoder = KeyValueDecoder()
1029+
var decoder = KeyValueDecoder()
10301030
decoder.nilDecodingStrategy = .nsNull
10311031
return decoder
10321032
}

Tests/KeyValueDecoderXCTests.swift

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
import XCTest
3636

37-
final class KeyValueDecoderTests: XCTestCase {
37+
final class KeyValueDecoderXCTests: XCTestCase {
3838

3939
func testDecodes_String() {
4040
let decoder = KeyValueDecoder()
@@ -149,7 +149,7 @@ final class KeyValueDecoderTests: XCTestCase {
149149
}
150150

151151
func testDecodesRounded_Ints() {
152-
let decoder = KeyValueDecoder()
152+
var decoder = KeyValueDecoder()
153153
decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero)
154154

155155
XCTAssertEqual(
@@ -247,7 +247,7 @@ final class KeyValueDecoderTests: XCTestCase {
247247
}
248248

249249
func testDecodesRounded_UInts() {
250-
let decoder = KeyValueDecoder()
250+
var decoder = KeyValueDecoder()
251251
decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero)
252252

253253
XCTAssertEqual(
@@ -460,7 +460,7 @@ final class KeyValueDecoderTests: XCTestCase {
460460
}
461461

462462
func testDecodes_Null() {
463-
let decoder = KeyValueDecoder()
463+
var decoder = KeyValueDecoder()
464464
decoder.nilDecodingStrategy = .default
465465

466466
XCTAssertThrowsError(
@@ -498,7 +498,7 @@ final class KeyValueDecoderTests: XCTestCase {
498498
}
499499

500500
func testDecodes_UnkeyedOptionals() {
501-
let decoder = KeyValueDecoder()
501+
var decoder = KeyValueDecoder()
502502

503503
decoder.nilDecodingStrategy = .removed
504504
XCTAssertEqual(
@@ -749,7 +749,7 @@ final class KeyValueDecoderTests: XCTestCase {
749749
}
750750

751751
func testDecodes_UnkeyedNil() {
752-
let decoder = KeyValueDecoder()
752+
var decoder = KeyValueDecoder()
753753
decoder.nilDecodingStrategy = .default
754754

755755
XCTAssertEqual(
@@ -963,7 +963,7 @@ final class KeyValueDecoderTests: XCTestCase {
963963
)
964964

965965
// [10, , 20.5, 1000, -Double.infinity]
966-
let decoder = KeyValueDecoder()
966+
var decoder = KeyValueDecoder()
967967
decoder.intDecodingStrategy = .clamping(roundingRule: .toNearestOrAwayFromZero)
968968
XCTAssertEqual(
969969
try decoder.decode([Int8].self, from: [10, 20.5, 1000, -Double.infinity]),
@@ -1037,32 +1037,39 @@ private extension KeyValueDecoder {
10371037

10381038
}
10391039

1040-
static func decodeValue<K: CodingKey, T>(from value: [String: Any], keyedBy: K.Type = K.self, with closure: @escaping (inout KeyedDecodingContainer<K>) throws -> T) throws -> T {
1040+
static func decodeValue<K: CodingKey, T>(
1041+
from value: [String: Any],
1042+
keyedBy: K.Type = K.self,
1043+
with closure: @escaping (inout KeyedDecodingContainer<K>
1044+
) throws -> T) throws -> T {
10411045
let proxy = StubDecoder.Proxy { decoder in
10421046
var container = try decoder.container(keyedBy: K.self)
10431047
return try closure(&container)
10441048
}
10451049

1046-
let decoder = KeyValueDecoder()
1047-
decoder.userInfo[.decoder] = proxy.decode(from:)
1050+
var decoder = KeyValueDecoder()
1051+
decoder.userInfo[.decoder] = proxy as any DecodingProxy
10481052
_ = try decoder.decode(StubDecoder.self, from: value)
10491053
return proxy.result!
10501054
}
10511055

1052-
static func decodeUnkeyedValue<T>(from value: [Any], with closure: @escaping (inout any UnkeyedDecodingContainer) throws -> T) throws -> T {
1056+
static func decodeUnkeyedValue<T>(
1057+
from value: [Any],
1058+
with closure: @escaping (inout any UnkeyedDecodingContainer) throws -> T
1059+
) throws -> T {
10531060
let proxy = StubDecoder.Proxy { decoder in
10541061
var container = try decoder.unkeyedContainer()
10551062
return try closure(&container)
10561063
}
10571064

1058-
let decoder = KeyValueDecoder()
1059-
decoder.userInfo[.decoder] = proxy.decode(from:)
1065+
var decoder = KeyValueDecoder()
1066+
decoder.userInfo[.decoder] = proxy as any DecodingProxy
10601067
_ = try decoder.decode(StubDecoder.self, from: value)
10611068
return proxy.result!
10621069
}
10631070

10641071
static func makeJSONCompatible() -> KeyValueDecoder {
1065-
let decoder = KeyValueDecoder()
1072+
var decoder = KeyValueDecoder()
10661073
decoder.nilDecodingStrategy = .nsNull
10671074
return decoder
10681075
}
@@ -1072,9 +1079,13 @@ private extension CodingUserInfoKey {
10721079
static let decoder = CodingUserInfoKey(rawValue: "decoder")!
10731080
}
10741081

1082+
private protocol DecodingProxy: Sendable {
1083+
func decode(from decoder: any Decoder) throws
1084+
}
1085+
10751086
private struct StubDecoder: Decodable {
10761087

1077-
final class Proxy<T> {
1088+
final class Proxy<T>: @unchecked Sendable, DecodingProxy {
10781089
private let closure: (any Decoder) throws -> T
10791090
private(set) var result: T?
10801091

@@ -1088,8 +1099,8 @@ private struct StubDecoder: Decodable {
10881099
}
10891100

10901101
init(from decoder: any Decoder) throws {
1091-
let closure = decoder.userInfo[.decoder] as! (any Decoder) throws -> Void
1092-
try closure(decoder)
1102+
let proxy = decoder.userInfo[.decoder] as! any DecodingProxy
1103+
try proxy.decode(from: decoder)
10931104
}
10941105
}
10951106

Tests/KeyValueEncoderTests.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ struct KeyValueEncodedTests {
540540

541541
@Test
542542
func nilEncodingStrategy_SingleContainer() throws {
543-
let encoder = KeyValueEncoder()
543+
var encoder = KeyValueEncoder()
544544

545545
encoder.nilEncodingStrategy = .removed
546546
#expect(
@@ -568,7 +568,7 @@ struct KeyValueEncodedTests {
568568

569569
@Test
570570
func nilEncodingStrategy_UnkeyedContainer() throws {
571-
let encoder = KeyValueEncoder()
571+
var encoder = KeyValueEncoder()
572572

573573
encoder.nilEncodingStrategy = .removed
574574
#expect(
@@ -677,7 +677,7 @@ private extension KeyValueEncoder {
677677

678678
static func encodeSingleValue(nilEncodingStrategy: NilEncodingStrategy = .default,
679679
with closure: (inout any SingleValueEncodingContainer) throws -> Void) throws -> EncodedValue {
680-
let encoder = KeyValueEncoder()
680+
var encoder = KeyValueEncoder()
681681
encoder.nilEncodingStrategy = nilEncodingStrategy
682682
return try encoder.encodeValue {
683683
var container = $0.singleValueContainer()
@@ -692,11 +692,12 @@ private extension KeyValueEncoder {
692692
}
693693
}
694694

695-
static func encodeKeyedValue<K: CodingKey>(keyedBy: K.Type = K.self,
696-
nilEncodingStrategy: NilEncodingStrategy = .default,
697-
with closure: @escaping (inout KeyedEncodingContainer<K>) throws -> Void) throws
698-
-> EncodedValue {
699-
let encoder = KeyValueEncoder()
695+
static func encodeKeyedValue<K: CodingKey>(
696+
keyedBy: K.Type = K.self,
697+
nilEncodingStrategy: NilEncodingStrategy = .default,
698+
with closure: @escaping (inout KeyedEncodingContainer<K>) throws -> Void
699+
) throws -> EncodedValue {
700+
var encoder = KeyValueEncoder()
700701
encoder.nilEncodingStrategy = nilEncodingStrategy
701702
return try encoder.encodeValue {
702703
var container = $0.container(keyedBy: K.self)
@@ -711,7 +712,7 @@ private extension KeyValueEncoder {
711712
}
712713

713714
static func makeJSONCompatible() -> KeyValueEncoder {
714-
let encoder = KeyValueEncoder()
715+
var encoder = KeyValueEncoder()
715716
encoder.nilEncodingStrategy = .nsNull
716717
return encoder
717718
}

Tests/KeyValueEncoderXCTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
import XCTest
3636

37-
final class KeyValueEncodedTests: XCTestCase {
37+
final class KeyValueEncodedXCTests: XCTestCase {
3838

3939
typealias EncodedValue = KeyValueEncoder.EncodedValue
4040

@@ -543,7 +543,7 @@ final class KeyValueEncodedTests: XCTestCase {
543543
}
544544

545545
func testNilEncodingStrategy_SingleContainer() {
546-
let encoder = KeyValueEncoder()
546+
var encoder = KeyValueEncoder()
547547

548548
encoder.nilEncodingStrategy = .removed
549549
XCTAssertNil(
@@ -572,7 +572,7 @@ final class KeyValueEncodedTests: XCTestCase {
572572
}
573573

574574
func testNilEncodingStrategy_UnkeyedContainer() {
575-
let encoder = KeyValueEncoder()
575+
var encoder = KeyValueEncoder()
576576

577577
encoder.nilEncodingStrategy = .removed
578578
XCTAssertEqual(
@@ -670,7 +670,7 @@ private extension KeyValueEncoder {
670670

671671
static func encodeSingleValue(nilEncodingStrategy: NilEncodingStrategy = .default,
672672
with closure: (inout any SingleValueEncodingContainer) throws -> Void) throws -> EncodedValue {
673-
let encoder = KeyValueEncoder()
673+
var encoder = KeyValueEncoder()
674674
encoder.nilEncodingStrategy = nilEncodingStrategy
675675
return try encoder.encodeValue {
676676
var container = $0.singleValueContainer()
@@ -688,7 +688,7 @@ private extension KeyValueEncoder {
688688
static func encodeKeyedValue<K: CodingKey>(keyedBy: K.Type = K.self,
689689
nilEncodingStrategy: NilEncodingStrategy = .default,
690690
with closure: @escaping (inout KeyedEncodingContainer<K>) throws -> Void) throws -> EncodedValue {
691-
let encoder = KeyValueEncoder()
691+
var encoder = KeyValueEncoder()
692692
encoder.nilEncodingStrategy = nilEncodingStrategy
693693
return try encoder.encodeValue {
694694
var container = $0.container(keyedBy: K.self)
@@ -703,7 +703,7 @@ private extension KeyValueEncoder {
703703
}
704704

705705
static func makeJSONCompatible() -> KeyValueEncoder {
706-
let encoder = KeyValueEncoder()
706+
var encoder = KeyValueEncoder()
707707
encoder.nilEncodingStrategy = .nsNull
708708
return encoder
709709
}

0 commit comments

Comments
 (0)