From 54414b4cf4e0622cc1095960a20edb1fb67defae Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Tue, 27 Oct 2020 11:49:48 +0100 Subject: [PATCH 01/33] Added protocol StringKeyCodable which allows you to annotate types that you would like to use as keys for keyed containers --- stdlib/public/core/Codable.swift | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 3c9d02bcd839f..6260b9c0fc13b 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5520,6 +5520,11 @@ internal struct _DictionaryCodingKey: CodingKey { } } +public protocol StringKeyCodable { + var rawValue: String { get } + init?(rawValue: String) +} + extension Dictionary: Encodable where Key: Encodable, Value: Encodable { /// Encodes the contents of this dictionary into the given encoder. /// @@ -5546,6 +5551,14 @@ extension Dictionary: Encodable where Key: Encodable, Value: Encodable { let codingKey = _DictionaryCodingKey(intValue: key as! Int)! try container.encode(value, forKey: codingKey) } + } else if let _ = Key.self as? StringKeyCodable.Type { + // Since the keys are StringKeyCodable, we can use their rawValues as keys. + var container = encoder.container(keyedBy: _DictionaryCodingKey.self) + for (key, value) in self { + let stringKey = (key as! StringKeyCodable).rawValue + let codingKey = _DictionaryCodingKey(stringValue: stringKey)! + try container.encode(value, forKey: codingKey) + } } else { // Keys are Encodable but not Strings or Ints, so we cannot arbitrarily // convert to keys. We can encode as an array of alternating key-value @@ -5600,6 +5613,14 @@ extension Dictionary: Decodable where Key: Decodable, Value: Decodable { let value = try container.decode(Value.self, forKey: key) self[key.intValue! as! Key] = value } + } else if let stringKeyCodableType = Key.self as? StringKeyCodable.Type { + // The keys are StringKeyCodable, so we should be able to expect a keyed container. + let container = try decoder.container(keyedBy: _DictionaryCodingKey.self) + for key in container.allKeys { + let value = try container.decode(Value.self, forKey: key) + guard let sko = stringKeyCodableType.init(rawValue: key.stringValue) else { continue } + self[sko as! Key] = value + } } else { // We should have encoded as an array of alternating key-value pairs. var container = try decoder.unkeyedContainer() From 3e9e7dd6c4e7075045ff0d88bab3f353b3d2173a Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sun, 24 Jan 2021 14:17:33 +0100 Subject: [PATCH 02/33] Updated the code to be in line with the updated pitch --- stdlib/public/core/Codable.swift | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 6260b9c0fc13b..558081aab5dd6 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5518,11 +5518,16 @@ internal struct _DictionaryCodingKey: CodingKey { self.stringValue = "\(intValue)" self.intValue = intValue } + + fileprivate init(codingKey: CodingKey) { + self.stringValue = codingKey.stringValue + self.intValue = codingKey.intValue + } } -public protocol StringKeyCodable { - var rawValue: String { get } - init?(rawValue: String) +public protocol CodingKeyRepresentable { + var codingKey: CodingKey { get } + init?(codingKey: CodingKey) } extension Dictionary: Encodable where Key: Encodable, Value: Encodable { @@ -5551,13 +5556,14 @@ extension Dictionary: Encodable where Key: Encodable, Value: Encodable { let codingKey = _DictionaryCodingKey(intValue: key as! Int)! try container.encode(value, forKey: codingKey) } - } else if let _ = Key.self as? StringKeyCodable.Type { - // Since the keys are StringKeyCodable, we can use their rawValues as keys. + } else if let _ = Key.self as? CodingKeyRepresentable.Type { + // Since the keys are CodingKeyRepresentable, we can use the `codingKey` + // to create `_DictionaryCodingKey` instances. var container = encoder.container(keyedBy: _DictionaryCodingKey.self) - for (key, value) in self { - let stringKey = (key as! StringKeyCodable).rawValue - let codingKey = _DictionaryCodingKey(stringValue: stringKey)! - try container.encode(value, forKey: codingKey) + for (key, value) in self.dict { + let codingKey = (key as! CodingKeyRepresentable).codingKey + let dictionaryCodingKey = _DictionaryCodingKey(codingKey: codingKey) + try container.encode(value, forKey: dictionaryCodingKey) } } else { // Keys are Encodable but not Strings or Ints, so we cannot arbitrarily @@ -5613,13 +5619,13 @@ extension Dictionary: Decodable where Key: Decodable, Value: Decodable { let value = try container.decode(Value.self, forKey: key) self[key.intValue! as! Key] = value } - } else if let stringKeyCodableType = Key.self as? StringKeyCodable.Type { - // The keys are StringKeyCodable, so we should be able to expect a keyed container. + } else if let codingKeyRepresentableType = Key.self as? CodingKeyRepresentable.Type { + // The keys are CodingKeyRepresentable, so we should be able to expect a keyed container. let container = try decoder.container(keyedBy: _DictionaryCodingKey.self) for key in container.allKeys { let value = try container.decode(Value.self, forKey: key) - guard let sko = stringKeyCodableType.init(rawValue: key.stringValue) else { continue } - self[sko as! Key] = value + let k = codingKeyRepresentableType.init(codingKey: key) + self.dict[k as! Key] = value } } else { // We should have encoded as an array of alternating key-value pairs. From 65cb3636826b82cbefb804f9748993407de63d92 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sat, 14 Aug 2021 13:04:51 +0200 Subject: [PATCH 03/33] Update stdlib/public/core/Codable.swift Co-authored-by: Ben Rimmington --- stdlib/public/core/Codable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 558081aab5dd6..25f892e555d02 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5556,7 +5556,7 @@ extension Dictionary: Encodable where Key: Encodable, Value: Encodable { let codingKey = _DictionaryCodingKey(intValue: key as! Int)! try container.encode(value, forKey: codingKey) } - } else if let _ = Key.self as? CodingKeyRepresentable.Type { + } else if Key.self is CodingKeyRepresentable.Type { // Since the keys are CodingKeyRepresentable, we can use the `codingKey` // to create `_DictionaryCodingKey` instances. var container = encoder.container(keyedBy: _DictionaryCodingKey.self) From aad2b7da74aa1bf2f771b0eeec452fc19d2312bc Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sat, 14 Aug 2021 13:06:09 +0200 Subject: [PATCH 04/33] Update stdlib/public/core/Codable.swift Co-authored-by: Ben Rimmington --- stdlib/public/core/Codable.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 25f892e555d02..7f90615fc755d 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5525,6 +5525,7 @@ internal struct _DictionaryCodingKey: CodingKey { } } +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public protocol CodingKeyRepresentable { var codingKey: CodingKey { get } init?(codingKey: CodingKey) From 353eb150537c1244b5e9b21069fa568a9964516a Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sat, 14 Aug 2021 13:07:14 +0200 Subject: [PATCH 05/33] Update stdlib/public/core/Codable.swift Co-authored-by: Ben Rimmington --- stdlib/public/core/Codable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 7f90615fc755d..8a0689ed386fb 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5561,7 +5561,7 @@ extension Dictionary: Encodable where Key: Encodable, Value: Encodable { // Since the keys are CodingKeyRepresentable, we can use the `codingKey` // to create `_DictionaryCodingKey` instances. var container = encoder.container(keyedBy: _DictionaryCodingKey.self) - for (key, value) in self.dict { + for (key, value) in self { let codingKey = (key as! CodingKeyRepresentable).codingKey let dictionaryCodingKey = _DictionaryCodingKey(codingKey: codingKey) try container.encode(value, forKey: dictionaryCodingKey) From aef4ddbf3938b83401ba14e29e90b362176179d1 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sat, 14 Aug 2021 13:37:13 +0200 Subject: [PATCH 06/33] Update stdlib/public/core/Codable.swift Will update debugDescription in next commit. Co-authored-by: Ben Rimmington --- stdlib/public/core/Codable.swift | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 8a0689ed386fb..b5ad161169dfb 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5623,10 +5623,21 @@ extension Dictionary: Decodable where Key: Decodable, Value: Decodable { } else if let codingKeyRepresentableType = Key.self as? CodingKeyRepresentable.Type { // The keys are CodingKeyRepresentable, so we should be able to expect a keyed container. let container = try decoder.container(keyedBy: _DictionaryCodingKey.self) - for key in container.allKeys { - let value = try container.decode(Value.self, forKey: key) - let k = codingKeyRepresentableType.init(codingKey: key) - self.dict[k as! Key] = value + for dictionaryCodingKey in container.allKeys { + guard let key: Key = codingKeyRepresentableType.init( + codingKey: dictionaryCodingKey + ) as? Key else { + throw DecodingError.dataCorruptedError( + forKey: dictionaryCodingKey, + in: container, + debugDescription: "..." + ) + } + let value: Value = try container.decode( + Value.self, + forKey: dictionaryCodingKey + ) + self[key] = value } } else { // We should have encoded as an array of alternating key-value pairs. From 676ec26dd6b53780d1e9d2bcdafdf3754a6297a0 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sat, 14 Aug 2021 13:41:21 +0200 Subject: [PATCH 07/33] Added debugDescription to thrown error --- stdlib/public/core/Codable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index b5ad161169dfb..4266a77be4bb7 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5630,7 +5630,7 @@ extension Dictionary: Decodable where Key: Decodable, Value: Decodable { throw DecodingError.dataCorruptedError( forKey: dictionaryCodingKey, in: container, - debugDescription: "..." + debugDescription: "Could not convert key to type \(Key.self)" ) } let value: Value = try container.decode( From 5d496ead3acf79eafdbbe5c7b3eaca7dfc801234 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Tue, 17 Aug 2021 08:15:25 +0200 Subject: [PATCH 08/33] Added tests for CodingKeyRepresentable encoding and decoding --- test/stdlib/CodableTests.swift | 64 ++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/test/stdlib/CodableTests.swift b/test/stdlib/CodableTests.swift index bae397b0521c6..796a1500300e5 100644 --- a/test/stdlib/CodableTests.swift +++ b/test/stdlib/CodableTests.swift @@ -849,6 +849,69 @@ class TestCodable : TestCodableSuper { expectRoundTripEqualityThroughPlist(for: UUIDCodingWrapper(uuid), lineNumber: testLine) } } + + func test_Dictionary_JSON() { + enum NotCodingKeyRepresentable: String, Codable { + case a + } + + enum CodingKeyRepresentableEnum: String, Codable, CodingKeyRepresentable { + case a + + init?(codingKey: CodingKey) { + self.init(rawValue: codingKey.stringValue) + } + var codingKey: CodingKey { GenericStringCodingKey(stringValue: self.rawValue) } + } + + struct GenericStringCodingKey: CodingKey { + var stringValue: String + var intValue: Int? { nil } + + init(stringValue: String) { + self.stringValue = stringValue + } + + init?(intValue: Int) { + return nil + } + } + + let notCodingKeyRepresentableValues: [Int: [NotCodingKeyRepresentable: Bool]] = [ + #line : [.a: true] + ] + + for (testLine, dictionary) in notCodingKeyRepresentableValues { + expectRoundTripEqualityThroughJSON(for: dictionary, lineNumber: testLine) + let encoder = JSONEncoder() + let decoder = JSONDecoder() + do { + let data = try encoder.encode(dictionary) + let stringValue = String(decoding: data, as: UTF8.self) + expectEqual("[\"a\",true]", stringValue, "\(stringValue)") + expectEqual(dictionary, try? decoder.decode([NotCodingKeyRepresentable: Bool].self, from: data), "Expecting dictionary to encode as an array of pairs") + } catch { + fatalError("\(#file):\(testLine): Unable to encode [NotCodingKeyRepresentable: Bool]: \(error)") + } + } + + let codingKeyRepresentableValues: [Int: [CodingKeyRepresentableEnum: Bool]] = [ + #line : [.a: true] + ] + for (testLine, dictionary) in codingKeyRepresentableValues { + expectRoundTripEqualityThroughJSON(for: dictionary, lineNumber: testLine) + let encoder = JSONEncoder() + let decoder = JSONDecoder() + do { + let data = try encoder.encode(dictionary) + let stringValue = String(decoding: data, as: UTF8.self) + expectEqual("{\"a\":true}", stringValue, "\(stringValue)") + expectEqual(dictionary, try? decoder.decode([CodingKeyRepresentableEnum: Bool].self, from: data), "Expecting dictionary to encode as a JSON object") + } catch { + fatalError("\(#file):\(testLine): Unable to encode [CodingKeyRepresentableEnum: Bool]: \(error)") + } + } + } } // MARK: - Helper Types @@ -869,6 +932,7 @@ struct TopLevelWrapper : Codable, Equatable where T : Codable, T : Equatable #if !FOUNDATION_XCTEST var tests = [ + "test_Dictionary_JSON": TestCodable.test_Dictionary_JSON, "test_Calendar_JSON" : TestCodable.test_Calendar_JSON, "test_Calendar_Plist" : TestCodable.test_Calendar_Plist, "test_CharacterSet_JSON" : TestCodable.test_CharacterSet_JSON, From 1800329e8f63260c9143bf6e06ec9cb685178146 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Tue, 17 Aug 2021 21:28:50 +0200 Subject: [PATCH 09/33] Incorporated feedback from Jordan Rose: Int and String conforms to CodingKeyRepresentable. Incorporated feedback from Ben Rimmington: _DictionaryCodingKey initializer made non-failable, Added default implementations of CodingKeyRepresentable for RawRepresentable, String and Int. --- stdlib/public/core/Codable.swift | 62 ++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 3f08170dfdeec..490b4b3cf6f19 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5510,12 +5510,12 @@ internal struct _DictionaryCodingKey: CodingKey { internal let stringValue: String internal let intValue: Int? - internal init?(stringValue: String) { + internal init(stringValue: String) { self.stringValue = stringValue self.intValue = Int(stringValue) } - internal init?(intValue: Int) { + internal init(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } @@ -5529,7 +5529,55 @@ internal struct _DictionaryCodingKey: CodingKey { @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public protocol CodingKeyRepresentable { var codingKey: CodingKey { get } - init?(codingKey: CodingKey) + init?(codingKey: T) +} + +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension RawRepresentable where Self: CodingKeyRepresentable, RawValue == String { + public var codingKey: CodingKey { + _DictionaryCodingKey(stringValue: rawValue) + } + public init?(codingKey: T) { + self.init(rawValue: codingKey.stringValue) + } +} + +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension RawRepresentable where Self: CodingKeyRepresentable, RawValue == Int { + public var codingKey: CodingKey { + _DictionaryCodingKey(intValue: rawValue) + } + public init?(codingKey: T) { + if let intValue = codingKey.intValue { + self.init(rawValue: intValue) + } else { + return nil + } + } +} + +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension Int: CodingKeyRepresentable { + public var codingKey: CodingKey { + _DictionaryCodingKey(intValue: self) + } + public init?(codingKey: T) { + if let intValue = codingKey.intValue { + self = intValue + } else { + return nil + } + } +} + +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension String: CodingKeyRepresentable { + public var codingKey: CodingKey { + _DictionaryCodingKey(stringValue: self) + } + public init?(codingKey: T) { + self = codingKey.stringValue + } } extension Dictionary: Encodable where Key: Encodable, Value: Encodable { @@ -5548,17 +5596,17 @@ extension Dictionary: Encodable where Key: Encodable, Value: Encodable { // Since the keys are already Strings, we can use them as keys directly. var container = encoder.container(keyedBy: _DictionaryCodingKey.self) for (key, value) in self { - let codingKey = _DictionaryCodingKey(stringValue: key as! String)! + let codingKey = _DictionaryCodingKey(stringValue: key as! String) try container.encode(value, forKey: codingKey) } } else if Key.self == Int.self { // Since the keys are already Ints, we can use them as keys directly. var container = encoder.container(keyedBy: _DictionaryCodingKey.self) for (key, value) in self { - let codingKey = _DictionaryCodingKey(intValue: key as! Int)! + let codingKey = _DictionaryCodingKey(intValue: key as! Int) try container.encode(value, forKey: codingKey) } - } else if Key.self is CodingKeyRepresentable.Type { + } else if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *), Key.self is CodingKeyRepresentable.Type { // Since the keys are CodingKeyRepresentable, we can use the `codingKey` // to create `_DictionaryCodingKey` instances. var container = encoder.container(keyedBy: _DictionaryCodingKey.self) @@ -5621,7 +5669,7 @@ extension Dictionary: Decodable where Key: Decodable, Value: Decodable { let value = try container.decode(Value.self, forKey: key) self[key.intValue! as! Key] = value } - } else if let codingKeyRepresentableType = Key.self as? CodingKeyRepresentable.Type { + } else if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *), let codingKeyRepresentableType = Key.self as? CodingKeyRepresentable.Type { // The keys are CodingKeyRepresentable, so we should be able to expect a keyed container. let container = try decoder.container(keyedBy: _DictionaryCodingKey.self) for dictionaryCodingKey in container.allKeys { From 9442a19566bd804715a2f94a58e31e0eaabaef14 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Tue, 17 Aug 2021 21:29:20 +0200 Subject: [PATCH 10/33] Refactoring test_Dictionary_JSON --- test/stdlib/CodableTests.swift | 114 ++++++++++++++------------------- 1 file changed, 48 insertions(+), 66 deletions(-) diff --git a/test/stdlib/CodableTests.swift b/test/stdlib/CodableTests.swift index 796a1500300e5..c0fda3b209d6b 100644 --- a/test/stdlib/CodableTests.swift +++ b/test/stdlib/CodableTests.swift @@ -76,14 +76,20 @@ func expectRoundTripEquality(of value: T, encode: (T) throws -> Dat expectEqual(value, decoded, "\(#file):\(lineNumber): Decoded \(T.self) <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>") } -func expectRoundTripEqualityThroughJSON(for value: T, lineNumber: Int) where T : Equatable { +func expectRoundTripEqualityThroughJSON(for value: T, lineNumber: Int, _ expectedJSON: String? = nil) where T : Equatable { let inf = "INF", negInf = "-INF", nan = "NaN" let encode = { (_ value: T) throws -> Data in let encoder = JSONEncoder() encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: inf, negativeInfinity: negInf, nan: nan) - return try encoder.encode(value) + let encoded = try encoder.encode(value) + + if let expectedJSON = expectedJSON { + let stringValue = String(decoding: encoded, as: UTF8.self) + expectEqual(expectedJSON, stringValue, "\(#file):\(lineNumber): Encoded \(T.self) <\(stringValue)> not equal to expected <\(expectedJSON)>") + } + return encoded } let decode = { (_ type: T.Type, _ data: Data) throws -> T in @@ -462,6 +468,45 @@ class TestCodable : TestCodableSuper { } } + func test_Dictionary_JSON() { + enum NotCodingKeyRepresentable: String, Codable { + case a + } + + enum CodingKeyRepresentableEnum: String, Codable, CodingKeyRepresentable { + case a + } + + struct GenericStringCodingKey: CodingKey { + var stringValue: String + var intValue: Int? { nil } + + init(stringValue: String) { + self.stringValue = stringValue + } + + init?(intValue: Int) { + return nil + } + } + + let notCodingKeyRepresentableValues: [Int: [NotCodingKeyRepresentable: Bool]] = [ + #line : [.a: true] + ] + + for (testLine, dictionary) in notCodingKeyRepresentableValues { + expectRoundTripEqualityThroughJSON(for: dictionary, lineNumber: testLine, #"["a",true]"#) + } + + let codingKeyRepresentableValues: [Int: [CodingKeyRepresentableEnum: Bool]] = [ + #line : [.a: true] + ] + for (testLine, dictionary) in codingKeyRepresentableValues { + expectRoundTripEqualityThroughJSON(for: dictionary, lineNumber: testLine, #"{"a":true}"#) + } + } + + // MARK: - IndexPath lazy var indexPathValues: [Int : IndexPath] = [ #line : IndexPath(), // empty @@ -849,69 +894,6 @@ class TestCodable : TestCodableSuper { expectRoundTripEqualityThroughPlist(for: UUIDCodingWrapper(uuid), lineNumber: testLine) } } - - func test_Dictionary_JSON() { - enum NotCodingKeyRepresentable: String, Codable { - case a - } - - enum CodingKeyRepresentableEnum: String, Codable, CodingKeyRepresentable { - case a - - init?(codingKey: CodingKey) { - self.init(rawValue: codingKey.stringValue) - } - var codingKey: CodingKey { GenericStringCodingKey(stringValue: self.rawValue) } - } - - struct GenericStringCodingKey: CodingKey { - var stringValue: String - var intValue: Int? { nil } - - init(stringValue: String) { - self.stringValue = stringValue - } - - init?(intValue: Int) { - return nil - } - } - - let notCodingKeyRepresentableValues: [Int: [NotCodingKeyRepresentable: Bool]] = [ - #line : [.a: true] - ] - - for (testLine, dictionary) in notCodingKeyRepresentableValues { - expectRoundTripEqualityThroughJSON(for: dictionary, lineNumber: testLine) - let encoder = JSONEncoder() - let decoder = JSONDecoder() - do { - let data = try encoder.encode(dictionary) - let stringValue = String(decoding: data, as: UTF8.self) - expectEqual("[\"a\",true]", stringValue, "\(stringValue)") - expectEqual(dictionary, try? decoder.decode([NotCodingKeyRepresentable: Bool].self, from: data), "Expecting dictionary to encode as an array of pairs") - } catch { - fatalError("\(#file):\(testLine): Unable to encode [NotCodingKeyRepresentable: Bool]: \(error)") - } - } - - let codingKeyRepresentableValues: [Int: [CodingKeyRepresentableEnum: Bool]] = [ - #line : [.a: true] - ] - for (testLine, dictionary) in codingKeyRepresentableValues { - expectRoundTripEqualityThroughJSON(for: dictionary, lineNumber: testLine) - let encoder = JSONEncoder() - let decoder = JSONDecoder() - do { - let data = try encoder.encode(dictionary) - let stringValue = String(decoding: data, as: UTF8.self) - expectEqual("{\"a\":true}", stringValue, "\(stringValue)") - expectEqual(dictionary, try? decoder.decode([CodingKeyRepresentableEnum: Bool].self, from: data), "Expecting dictionary to encode as a JSON object") - } catch { - fatalError("\(#file):\(testLine): Unable to encode [CodingKeyRepresentableEnum: Bool]: \(error)") - } - } - } } // MARK: - Helper Types @@ -932,7 +914,6 @@ struct TopLevelWrapper : Codable, Equatable where T : Codable, T : Equatable #if !FOUNDATION_XCTEST var tests = [ - "test_Dictionary_JSON": TestCodable.test_Dictionary_JSON, "test_Calendar_JSON" : TestCodable.test_Calendar_JSON, "test_Calendar_Plist" : TestCodable.test_Calendar_Plist, "test_CharacterSet_JSON" : TestCodable.test_CharacterSet_JSON, @@ -955,6 +936,7 @@ var tests = [ "test_DateComponents_Plist" : TestCodable.test_DateComponents_Plist, "test_Decimal_JSON" : TestCodable.test_Decimal_JSON, "test_Decimal_Plist" : TestCodable.test_Decimal_Plist, + "test_Dictionary_JSON": TestCodable.test_Dictionary_JSON, "test_IndexPath_JSON" : TestCodable.test_IndexPath_JSON, "test_IndexPath_Plist" : TestCodable.test_IndexPath_Plist, "test_IndexSet_JSON" : TestCodable.test_IndexSet_JSON, From 7f260c961a51373ab6ec596605e2909e54ea28c9 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Tue, 17 Aug 2021 21:51:07 +0200 Subject: [PATCH 11/33] Refactored and expanded test_Dictionary_JSON --- test/stdlib/CodableTests.swift | 58 ++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/test/stdlib/CodableTests.swift b/test/stdlib/CodableTests.swift index c0fda3b209d6b..0636683ae0d2d 100644 --- a/test/stdlib/CodableTests.swift +++ b/test/stdlib/CodableTests.swift @@ -76,13 +76,16 @@ func expectRoundTripEquality(of value: T, encode: (T) throws -> Dat expectEqual(value, decoded, "\(#file):\(lineNumber): Decoded \(T.self) <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>") } -func expectRoundTripEqualityThroughJSON(for value: T, lineNumber: Int, _ expectedJSON: String? = nil) where T : Equatable { +func expectRoundTripEqualityThroughJSON(for value: T, _ expectedJSON: String? = nil, lineNumber: Int) where T : Equatable { let inf = "INF", negInf = "-INF", nan = "NaN" let encode = { (_ value: T) throws -> Data in let encoder = JSONEncoder() encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: inf, negativeInfinity: negInf, nan: nan) + if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) { + encoder.outputFormatting = .sortedKeys + } let encoded = try encoder.encode(value) if let expectedJSON = expectedJSON { @@ -469,41 +472,54 @@ class TestCodable : TestCodableSuper { } func test_Dictionary_JSON() { - enum NotCodingKeyRepresentable: String, Codable { + enum X: String, Codable { case a, b } + enum Y: String, Codable, CodingKeyRepresentable { case a, b } + enum Z: String, Codable, CodingKeyRepresentable { case a + case b + init?(codingKey: T) { + self.init(rawValue: codingKey.stringValue) + } + var codingKey: CodingKey { + GenericCodingKey(stringValue: self.rawValue) + } } - enum CodingKeyRepresentableEnum: String, Codable, CodingKeyRepresentable { - case a + enum U: Int, Codable { case a = 0, b} + enum V: Int, Codable, CodingKeyRepresentable { case a = 0, b } + enum W: Int, Codable, CodingKeyRepresentable { + case a = 0 + case b + init?(codingKey: T) { + self.init(rawValue: codingKey.intValue!) + } + var codingKey: CodingKey { + GenericCodingKey(intValue: self.rawValue) + } } - struct GenericStringCodingKey: CodingKey { + + struct GenericCodingKey: CodingKey { var stringValue: String - var intValue: Int? { nil } + var intValue: Int? init(stringValue: String) { self.stringValue = stringValue } - init?(intValue: Int) { - return nil + init(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue } } - let notCodingKeyRepresentableValues: [Int: [NotCodingKeyRepresentable: Bool]] = [ - #line : [.a: true] - ] - - for (testLine, dictionary) in notCodingKeyRepresentableValues { - expectRoundTripEqualityThroughJSON(for: dictionary, lineNumber: testLine, #"["a",true]"#) - } + expectRoundTripEqualityThroughJSON(for: [X.a: true], #"["a",true]"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [Y.a: true, Y.b: false], #"{"a":true,"b":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [Z.a: true, Z.b: false], #"{"a":true,"b":false}"#, lineNumber: #line) - let codingKeyRepresentableValues: [Int: [CodingKeyRepresentableEnum: Bool]] = [ - #line : [.a: true] - ] - for (testLine, dictionary) in codingKeyRepresentableValues { - expectRoundTripEqualityThroughJSON(for: dictionary, lineNumber: testLine, #"{"a":true}"#) - } + expectRoundTripEqualityThroughJSON(for: [U.a: true], #"[0,true]"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [V.a: true, V.b: false], #"{"0":true,"1":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [W.a: true, W.b: false], #"{"0":true,"1":false}"#, lineNumber: #line) } From 766a5983359698dfd3d01ccb8529a3fbd91be085 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Tue, 17 Aug 2021 21:59:58 +0200 Subject: [PATCH 12/33] Fix some indentation --- stdlib/public/core/Codable.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 490b4b3cf6f19..90f52fd228f9d 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5549,9 +5549,9 @@ extension RawRepresentable where Self: CodingKeyRepresentable, RawValue == Int { } public init?(codingKey: T) { if let intValue = codingKey.intValue { - self.init(rawValue: intValue) + self.init(rawValue: intValue) } else { - return nil + return nil } } } @@ -5563,9 +5563,9 @@ extension Int: CodingKeyRepresentable { } public init?(codingKey: T) { if let intValue = codingKey.intValue { - self = intValue + self = intValue } else { - return nil + return nil } } } From 74e03c09001ae8e53cdb3efaf35bcf01492b2f51 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Wed, 18 Aug 2021 08:30:10 +0200 Subject: [PATCH 13/33] Update test/stdlib/CodableTests.swift Co-authored-by: Ben Rimmington --- test/stdlib/CodableTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/stdlib/CodableTests.swift b/test/stdlib/CodableTests.swift index 0636683ae0d2d..af83f7565c9c9 100644 --- a/test/stdlib/CodableTests.swift +++ b/test/stdlib/CodableTests.swift @@ -89,8 +89,8 @@ func expectRoundTripEqualityThroughJSON(for value: T, _ expectedJSO let encoded = try encoder.encode(value) if let expectedJSON = expectedJSON { - let stringValue = String(decoding: encoded, as: UTF8.self) - expectEqual(expectedJSON, stringValue, "\(#file):\(lineNumber): Encoded \(T.self) <\(stringValue)> not equal to expected <\(expectedJSON)>") + let actualJSON = String(decoding: encoded, as: UTF8.self) + expectEqual(expectedJSON, actualJSON, line: UInt(lineNumber)) } return encoded } From 0e75b32f3eb218747ff4abd5ccbed91d5fa67ab8 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Wed, 18 Aug 2021 08:38:39 +0200 Subject: [PATCH 14/33] Incorporated feedback for tests --- test/stdlib/CodableTests.swift | 63 ++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/test/stdlib/CodableTests.swift b/test/stdlib/CodableTests.swift index 0636683ae0d2d..a162348c1e054 100644 --- a/test/stdlib/CodableTests.swift +++ b/test/stdlib/CodableTests.swift @@ -76,7 +76,7 @@ func expectRoundTripEquality(of value: T, encode: (T) throws -> Dat expectEqual(value, decoded, "\(#file):\(lineNumber): Decoded \(T.self) <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>") } -func expectRoundTripEqualityThroughJSON(for value: T, _ expectedJSON: String? = nil, lineNumber: Int) where T : Equatable { +func expectRoundTripEqualityThroughJSON(for value: T, expectedJSON: String? = nil, lineNumber: Int) where T : Equatable { let inf = "INF", negInf = "-INF", nan = "NaN" let encode = { (_ value: T) throws -> Data in let encoder = JSONEncoder() @@ -471,30 +471,60 @@ class TestCodable : TestCodableSuper { } } + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) func test_Dictionary_JSON() { enum X: String, Codable { case a, b } enum Y: String, Codable, CodingKeyRepresentable { case a, b } - enum Z: String, Codable, CodingKeyRepresentable { + enum Z: Codable, CodingKeyRepresentable { case a case b init?(codingKey: T) { - self.init(rawValue: codingKey.stringValue) + switch codingKey.stringValue { + case "α": + self = .a + case "β": + self = .b + default: + return nil + } } + var codingKey: CodingKey { - GenericCodingKey(stringValue: self.rawValue) + GenericCodingKey(stringValue: encoded) + } + + var encoded: String { + switch self { + case .a: return "α" + case .b: return "β" + } } } enum U: Int, Codable { case a = 0, b} enum V: Int, Codable, CodingKeyRepresentable { case a = 0, b } - enum W: Int, Codable, CodingKeyRepresentable { - case a = 0 + enum W: Codable, CodingKeyRepresentable { + case a case b init?(codingKey: T) { - self.init(rawValue: codingKey.intValue!) + guard let intValue = codingKey.intValue else { return nil } + switch intValue { + case 42: + self = .a + case 64: + self = .b + default: + return nil + } } var codingKey: CodingKey { - GenericCodingKey(intValue: self.rawValue) + GenericCodingKey(intValue: self.encoded) + } + var encoded: Int { + switch self { + case .a: return 42 + case .b: return 64 + } } } @@ -513,13 +543,13 @@ class TestCodable : TestCodableSuper { } } - expectRoundTripEqualityThroughJSON(for: [X.a: true], #"["a",true]"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [Y.a: true, Y.b: false], #"{"a":true,"b":false}"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [Z.a: true, Z.b: false], #"{"a":true,"b":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [X.a: true], expectedJSON: #"["a",true]"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [Y.a: true, Y.b: false], expectedJSON: #"{"a":true,"b":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [Z.a: true, Z.b: false], expectedJSON: #"{"α":true,"β":false}"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [U.a: true], #"[0,true]"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [V.a: true, V.b: false], #"{"0":true,"1":false}"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [W.a: true, W.b: false], #"{"0":true,"1":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [U.a: true], expectedJSON: #"[0,true]"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [V.a: true, V.b: false], expectedJSON: #"{"0":true,"1":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [W.a: true, W.b: false], expectedJSON: #"{"42":true,"64":false}"#, lineNumber: #line) } @@ -952,7 +982,6 @@ var tests = [ "test_DateComponents_Plist" : TestCodable.test_DateComponents_Plist, "test_Decimal_JSON" : TestCodable.test_Decimal_JSON, "test_Decimal_Plist" : TestCodable.test_Decimal_Plist, - "test_Dictionary_JSON": TestCodable.test_Dictionary_JSON, "test_IndexPath_JSON" : TestCodable.test_IndexPath_JSON, "test_IndexPath_Plist" : TestCodable.test_IndexPath_Plist, "test_IndexSet_JSON" : TestCodable.test_IndexSet_JSON, @@ -999,6 +1028,10 @@ if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { tests["test_URLComponents_Plist"] = TestCodable.test_URLComponents_Plist } +if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) { + tests["test_Dictionary_JSON"] = TestCodable.test_Dictionary_JSON +} + var CodableTests = TestSuite("TestCodable") for (name, test) in tests { CodableTests.test(name) { test(TestCodable())() } From a929c33c47dbf2af398cab941af8d8419d357eaa Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Wed, 18 Aug 2021 08:49:01 +0200 Subject: [PATCH 15/33] Updated documentation comment for Dictionary.encode(to:) and CodingKeyRepresentable --- stdlib/public/core/Codable.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 90f52fd228f9d..6ace9c4e3e347 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5526,6 +5526,15 @@ internal struct _DictionaryCodingKey: CodingKey { } } +/// A type that can be converted to and from a `CodingKey` value. +/// +/// With a `CodingKeyRepresentable` type, you can switch back and forth between a +/// custom type and a `CodingKey` type without losing the value of +/// the original `CodingKeyRepresentable` type. +/// +/// Conforming a type to `CodingKeyRepresentable` lets you opt-in to encoding and +/// decoding `Dictionary` values keyed by the conforming type to and from a keyed +/// container - rather than an unkeyed container of alternating key-value pairs. @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public protocol CodingKeyRepresentable { var codingKey: CodingKey { get } @@ -5583,9 +5592,9 @@ extension String: CodingKeyRepresentable { extension Dictionary: Encodable where Key: Encodable, Value: Encodable { /// Encodes the contents of this dictionary into the given encoder. /// - /// If the dictionary uses `String` or `Int` keys, the contents are encoded - /// in a keyed container. Otherwise, the contents are encoded as alternating - /// key-value pairs in an unkeyed container. + /// If the dictionary uses keys that are `String`, `Int` or a type conforming to + /// `CodingKeyRepresentable`, the contents are encoded in a keyed container. + /// Otherwise, the contents are encoded as alternating key-value pairs in an unkeyed container. /// /// This function throws an error if any values are invalid for the given /// encoder's format. From 4cea780cbb98777222a8a847276efdc0740bbb47 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Wed, 18 Aug 2021 12:04:59 +0200 Subject: [PATCH 16/33] Added test for non-Int,String RawRepresentable --- test/stdlib/CodableTests.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/stdlib/CodableTests.swift b/test/stdlib/CodableTests.swift index 6ed77288fb5c1..a54ae0ad507f9 100644 --- a/test/stdlib/CodableTests.swift +++ b/test/stdlib/CodableTests.swift @@ -500,6 +500,19 @@ class TestCodable : TestCodableSuper { } } } + enum S: Character, Codable, CodingKeyRepresentable { + case a = "a" + case b = "b" + + init?(codingKey: T) { + guard codingKey.stringValue.count == 1 else { return nil } + self.init(rawValue: codingKey.stringValue.first!) + } + + var codingKey: CodingKey { + GenericCodingKey(stringValue: "\(self.rawValue)") + } + } enum U: Int, Codable { case a = 0, b} enum V: Int, Codable, CodingKeyRepresentable { case a = 0, b } @@ -528,7 +541,6 @@ class TestCodable : TestCodableSuper { } } - struct GenericCodingKey: CodingKey { var stringValue: String var intValue: Int? @@ -546,6 +558,7 @@ class TestCodable : TestCodableSuper { expectRoundTripEqualityThroughJSON(for: [X.a: true], expectedJSON: #"["a",true]"#, lineNumber: #line) expectRoundTripEqualityThroughJSON(for: [Y.a: true, Y.b: false], expectedJSON: #"{"a":true,"b":false}"#, lineNumber: #line) expectRoundTripEqualityThroughJSON(for: [Z.a: true, Z.b: false], expectedJSON: #"{"α":true,"β":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [S.a: true, S.b: false], expectedJSON: #"{"a":true,"b":false}"#, lineNumber: #line) expectRoundTripEqualityThroughJSON(for: [U.a: true], expectedJSON: #"[0,true]"#, lineNumber: #line) expectRoundTripEqualityThroughJSON(for: [V.a: true, V.b: false], expectedJSON: #"{"0":true,"1":false}"#, lineNumber: #line) From 089b0c98cbb411927f2dcd5916d69cbfd0ce7e67 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Wed, 18 Aug 2021 13:48:15 +0200 Subject: [PATCH 17/33] Added tests for dictionaries keyed by UUID and UUIDCodingWrapper --- test/stdlib/CodableTests.swift | 57 +++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/test/stdlib/CodableTests.swift b/test/stdlib/CodableTests.swift index a54ae0ad507f9..6160ced7e9c64 100644 --- a/test/stdlib/CodableTests.swift +++ b/test/stdlib/CodableTests.swift @@ -120,13 +120,22 @@ func expectRoundTripEqualityThroughPlist(for value: T, lineNumber: // MARK: - Helper Types // A wrapper around a UUID that will allow it to be encoded at the top level of an encoder. -struct UUIDCodingWrapper : Codable, Equatable { +struct UUIDCodingWrapper : Codable, Equatable, Hashable, CodingKeyRepresentable { let value: UUID init(_ value: UUID) { self.value = value } + init?(codingKey: T) { + guard let uuid = UUID(uuidString: codingKey.stringValue) else { return nil } + self.value = uuid + } + + var codingKey: CodingKey { + GenericCodingKey(stringValue: value.uuidString) + } + static func ==(_ lhs: UUIDCodingWrapper, _ rhs: UUIDCodingWrapper) -> Bool { return lhs.value == rhs.value } @@ -541,28 +550,18 @@ class TestCodable : TestCodableSuper { } } - struct GenericCodingKey: CodingKey { - var stringValue: String - var intValue: Int? - - init(stringValue: String) { - self.stringValue = stringValue - } - - init(intValue: Int) { - self.stringValue = "\(intValue)" - self.intValue = intValue - } - } - - expectRoundTripEqualityThroughJSON(for: [X.a: true], expectedJSON: #"["a",true]"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [Y.a: true, Y.b: false], expectedJSON: #"{"a":true,"b":false}"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [Z.a: true, Z.b: false], expectedJSON: #"{"α":true,"β":false}"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [S.a: true, S.b: false], expectedJSON: #"{"a":true,"b":false}"#, lineNumber: #line) + let uuid = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")! + let uuidWrapper = UUIDCodingWrapper(uuid) - expectRoundTripEqualityThroughJSON(for: [U.a: true], expectedJSON: #"[0,true]"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [V.a: true, V.b: false], expectedJSON: #"{"0":true,"1":false}"#, lineNumber: #line) - expectRoundTripEqualityThroughJSON(for: [W.a: true, W.b: false], expectedJSON: #"{"42":true,"64":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [X.a: true], expectedJSON: #"["a",true]"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [Y.a: true, Y.b: false], expectedJSON: #"{"a":true,"b":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [Z.a: true, Z.b: false], expectedJSON: #"{"α":true,"β":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [S.a: true, S.b: false], expectedJSON: #"{"a":true,"b":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [uuidWrapper: true], expectedJSON: #"{"E621E1F8-C36C-495A-93FC-0C247A3E6E5F":true}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [uuid: true], expectedJSON: #"["E621E1F8-C36C-495A-93FC-0C247A3E6E5F",true]"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [U.a: true], expectedJSON: #"[0,true]"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [V.a: true, V.b: false], expectedJSON: #"{"0":true,"1":false}"#, lineNumber: #line) + expectRoundTripEqualityThroughJSON(for: [W.a: true, W.b: false], expectedJSON: #"{"42":true,"64":false}"#, lineNumber: #line) } @@ -957,6 +956,20 @@ class TestCodable : TestCodableSuper { // MARK: - Helper Types +struct GenericCodingKey: CodingKey { + var stringValue: String + var intValue: Int? + + init(stringValue: String) { + self.stringValue = stringValue + } + + init(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } +} + struct TopLevelWrapper : Codable, Equatable where T : Codable, T : Equatable { let value: T From 8054c78fc8a9ab95ebaec8ad3d36658fca27d094 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Thu, 16 Sep 2021 09:31:25 +0200 Subject: [PATCH 18/33] Added availability checks for all public api --- stdlib/public/core/Codable.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 6ace9c4e3e347..602d74896f606 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5543,9 +5543,11 @@ public protocol CodingKeyRepresentable { @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension RawRepresentable where Self: CodingKeyRepresentable, RawValue == String { + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public var codingKey: CodingKey { _DictionaryCodingKey(stringValue: rawValue) } + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public init?(codingKey: T) { self.init(rawValue: codingKey.stringValue) } @@ -5553,9 +5555,11 @@ extension RawRepresentable where Self: CodingKeyRepresentable, RawValue == Strin @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension RawRepresentable where Self: CodingKeyRepresentable, RawValue == Int { + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public var codingKey: CodingKey { _DictionaryCodingKey(intValue: rawValue) } + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public init?(codingKey: T) { if let intValue = codingKey.intValue { self.init(rawValue: intValue) @@ -5567,9 +5571,11 @@ extension RawRepresentable where Self: CodingKeyRepresentable, RawValue == Int { @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension Int: CodingKeyRepresentable { + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public var codingKey: CodingKey { _DictionaryCodingKey(intValue: self) } + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public init?(codingKey: T) { if let intValue = codingKey.intValue { self = intValue @@ -5581,9 +5587,11 @@ extension Int: CodingKeyRepresentable { @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension String: CodingKeyRepresentable { + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public var codingKey: CodingKey { _DictionaryCodingKey(stringValue: self) } + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public init?(codingKey: T) { self = codingKey.stringValue } From ec6c1720a85dde02e82675b8f97be4ec337e6af9 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Thu, 16 Sep 2021 09:33:12 +0200 Subject: [PATCH 19/33] Also added availability checks for protocol requirements --- stdlib/public/core/Codable.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 602d74896f606..3ceba7f311c09 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5537,7 +5537,9 @@ internal struct _DictionaryCodingKey: CodingKey { /// container - rather than an unkeyed container of alternating key-value pairs. @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public protocol CodingKeyRepresentable { + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) var codingKey: CodingKey { get } + @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) init?(codingKey: T) } From e9ad151eff43d3e64c3414b83e9e099ba0e44bbc Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Fri, 17 Sep 2021 08:11:51 +0200 Subject: [PATCH 20/33] Added requested change to cake-abi.json --- test/api-digester/Outputs/cake-abi.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/api-digester/Outputs/cake-abi.json b/test/api-digester/Outputs/cake-abi.json index 07653dcfcea68..d0b34f1702728 100644 --- a/test/api-digester/Outputs/cake-abi.json +++ b/test/api-digester/Outputs/cake-abi.json @@ -1730,6 +1730,13 @@ "printedName": "Decodable", "usr": "s:Se" }, + { + "kind": "Conformance", + "name": "CodingKeyRepresentable", + "printedName": "CodingKeyRepresentable", + "usr": "s:s22CodingKeyRepresentableP", + "isABIPlaceholder": true + }, { "kind": "Conformance", "name": "CustomReflectable", @@ -1881,4 +1888,4 @@ } ], "json_format_version": 6 -} \ No newline at end of file +} From 97eb27e590b043408b4a02090266597767ba9fea Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sat, 18 Sep 2021 06:36:35 +0200 Subject: [PATCH 21/33] Added requested change to cake.json --- test/api-digester/Outputs/cake.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/api-digester/Outputs/cake.json b/test/api-digester/Outputs/cake.json index 4007f46e7115f..44e21bfc56b46 100644 --- a/test/api-digester/Outputs/cake.json +++ b/test/api-digester/Outputs/cake.json @@ -1604,6 +1604,13 @@ "printedName": "Decodable", "usr": "s:Se" }, + { + "kind": "Conformance", + "name": "CodingKeyRepresentable", + "printedName": "CodingKeyRepresentable", + "usr": "s:s22CodingKeyRepresentableP", + "isABIPlaceholder": true + }, { "kind": "Conformance", "name": "CustomReflectable", @@ -1750,4 +1757,4 @@ } ], "json_format_version": 6 -} \ No newline at end of file +} From 8b50f485e1bbacffa3fc4484a68c44a40e059dac Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sun, 19 Sep 2021 11:01:54 +0200 Subject: [PATCH 22/33] Remove extra newline at end of file From 2dce12a5013bca656d11acbc0a72eb04e4e3866c Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sun, 19 Sep 2021 11:02:29 +0200 Subject: [PATCH 23/33] Update cake-abi.json From ae2430fb19f147830ff55fd3a25da2a2f34926e3 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sun, 19 Sep 2021 11:05:58 +0200 Subject: [PATCH 24/33] Remove newlines at end of cake.json and cake-abi.json --- test/api-digester/Outputs/cake-abi.json | 2 +- test/api-digester/Outputs/cake.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/api-digester/Outputs/cake-abi.json b/test/api-digester/Outputs/cake-abi.json index d0b34f1702728..a68f8fb8d5f36 100644 --- a/test/api-digester/Outputs/cake-abi.json +++ b/test/api-digester/Outputs/cake-abi.json @@ -1888,4 +1888,4 @@ } ], "json_format_version": 6 -} +} \ No newline at end of file diff --git a/test/api-digester/Outputs/cake.json b/test/api-digester/Outputs/cake.json index 44e21bfc56b46..02840da3e8fcf 100644 --- a/test/api-digester/Outputs/cake.json +++ b/test/api-digester/Outputs/cake.json @@ -1757,4 +1757,4 @@ } ], "json_format_version": 6 -} +} \ No newline at end of file From bd015f426863d9ad4c65228ed1ac2a457ba66fdc Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Wed, 29 Sep 2021 14:21:13 +0200 Subject: [PATCH 25/33] Accept documentation comment change suggestions. Co-authored-by: Alex Martini --- stdlib/public/core/Codable.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 3ceba7f311c09..e42f3dc31bc19 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5526,15 +5526,15 @@ internal struct _DictionaryCodingKey: CodingKey { } } -/// A type that can be converted to and from a `CodingKey` value. +/// A type that can be converted to and from a coding key. /// -/// With a `CodingKeyRepresentable` type, you can switch back and forth between a +/// With a `CodingKeyRepresentable` type, you can convert between a /// custom type and a `CodingKey` type without losing the value of /// the original `CodingKeyRepresentable` type. /// -/// Conforming a type to `CodingKeyRepresentable` lets you opt-in to encoding and +/// Conforming a type to `CodingKeyRepresentable` lets you opt in to encoding and /// decoding `Dictionary` values keyed by the conforming type to and from a keyed -/// container - rather than an unkeyed container of alternating key-value pairs. +/// container, rather than encoding the dictionary as an unkeyed container of alternating key-value pairs. @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public protocol CodingKeyRepresentable { @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) From 79f05ccd97850915e709474097594c6565a4c087 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Wed, 29 Sep 2021 14:23:30 +0200 Subject: [PATCH 26/33] Updated documentation comments based on suggestions from review --- stdlib/public/core/Codable.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index e42f3dc31bc19..1f99c1e16dc10 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5528,13 +5528,13 @@ internal struct _DictionaryCodingKey: CodingKey { /// A type that can be converted to and from a coding key. /// -/// With a `CodingKeyRepresentable` type, you can convert between a -/// custom type and a `CodingKey` type without losing the value of -/// the original `CodingKeyRepresentable` type. +/// With a `CodingKeyRepresentable` type, you can losslessly convert between a +/// custom type and a `CodingKey` type. /// /// Conforming a type to `CodingKeyRepresentable` lets you opt in to encoding and /// decoding `Dictionary` values keyed by the conforming type to and from a keyed -/// container, rather than encoding the dictionary as an unkeyed container of alternating key-value pairs. +/// container, rather than encoding and decoding the dictionary as an unkeyed container +/// of alternating key-value pairs. @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public protocol CodingKeyRepresentable { @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) From 6cb65c5df6f42bda21ac07836639fccee8fefeed Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Thu, 30 Sep 2021 09:05:54 +0200 Subject: [PATCH 27/33] Documentation change: Added serial comma before 'or' Co-authored-by: Alex Martini --- stdlib/public/core/Codable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 1f99c1e16dc10..97b131ac146ea 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5602,7 +5602,7 @@ extension String: CodingKeyRepresentable { extension Dictionary: Encodable where Key: Encodable, Value: Encodable { /// Encodes the contents of this dictionary into the given encoder. /// - /// If the dictionary uses keys that are `String`, `Int` or a type conforming to + /// If the dictionary uses keys that are `String`, `Int`, or a type conforming to /// `CodingKeyRepresentable`, the contents are encoded in a keyed container. /// Otherwise, the contents are encoded as alternating key-value pairs in an unkeyed container. /// From 729212b8f0cb9ed193128121c00d8ad3da9a368d Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Mon, 11 Oct 2021 15:08:54 +0300 Subject: [PATCH 28/33] Respect 80-character line limit. Co-authored-by: Ben Rimmington --- stdlib/public/core/Codable.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 97b131ac146ea..0708772085949 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5531,10 +5531,10 @@ internal struct _DictionaryCodingKey: CodingKey { /// With a `CodingKeyRepresentable` type, you can losslessly convert between a /// custom type and a `CodingKey` type. /// -/// Conforming a type to `CodingKeyRepresentable` lets you opt in to encoding and -/// decoding `Dictionary` values keyed by the conforming type to and from a keyed -/// container, rather than encoding and decoding the dictionary as an unkeyed container -/// of alternating key-value pairs. +/// Conforming a type to `CodingKeyRepresentable` lets you opt in to encoding +/// and decoding `Dictionary` values keyed by the conforming type to and from +/// a keyed container, rather than encoding and decoding the dictionary as an +/// unkeyed container of alternating key-value pairs. @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public protocol CodingKeyRepresentable { @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) From 0e27ae6d466c3b7ee9cc3b13bd39aa360ecbc942 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Mon, 11 Oct 2021 15:09:17 +0300 Subject: [PATCH 29/33] Respect 80-character line limit. Co-authored-by: Ben Rimmington --- stdlib/public/core/Codable.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 0708772085949..02f0493264c6f 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5602,9 +5602,10 @@ extension String: CodingKeyRepresentable { extension Dictionary: Encodable where Key: Encodable, Value: Encodable { /// Encodes the contents of this dictionary into the given encoder. /// - /// If the dictionary uses keys that are `String`, `Int`, or a type conforming to - /// `CodingKeyRepresentable`, the contents are encoded in a keyed container. - /// Otherwise, the contents are encoded as alternating key-value pairs in an unkeyed container. + /// If the dictionary uses keys that are `String`, `Int`, or a type conforming + /// to `CodingKeyRepresentable`, the contents are encoded in a keyed container. + /// Otherwise, the contents are encoded as alternating key-value pairs in an + /// unkeyed container. /// /// This function throws an error if any values are invalid for the given /// encoder's format. From 9c2dae588251ebc21b2fae3175889d63f9939f94 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Mon, 11 Oct 2021 15:09:29 +0300 Subject: [PATCH 30/33] Respect 80-character line limit. Co-authored-by: Ben Rimmington --- stdlib/public/core/Codable.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 02f0493264c6f..344057cc008f1 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5626,7 +5626,8 @@ extension Dictionary: Encodable where Key: Encodable, Value: Encodable { let codingKey = _DictionaryCodingKey(intValue: key as! Int) try container.encode(value, forKey: codingKey) } - } else if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *), Key.self is CodingKeyRepresentable.Type { + } else if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *), + Key.self is CodingKeyRepresentable.Type { // Since the keys are CodingKeyRepresentable, we can use the `codingKey` // to create `_DictionaryCodingKey` instances. var container = encoder.container(keyedBy: _DictionaryCodingKey.self) From 46a311bccd889b30eb35db886f3d26452bf60a5e Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Mon, 11 Oct 2021 15:09:42 +0300 Subject: [PATCH 31/33] Respect 80-character line limit. Co-authored-by: Ben Rimmington --- stdlib/public/core/Codable.swift | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift index 344057cc008f1..f675dc5d943dd 100644 --- a/stdlib/public/core/Codable.swift +++ b/stdlib/public/core/Codable.swift @@ -5690,23 +5690,20 @@ extension Dictionary: Decodable where Key: Decodable, Value: Decodable { let value = try container.decode(Value.self, forKey: key) self[key.intValue! as! Key] = value } - } else if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *), let codingKeyRepresentableType = Key.self as? CodingKeyRepresentable.Type { - // The keys are CodingKeyRepresentable, so we should be able to expect a keyed container. + } else if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *), + let keyType = Key.self as? CodingKeyRepresentable.Type { + // The keys are CodingKeyRepresentable, so we should be able to expect + // a keyed container. let container = try decoder.container(keyedBy: _DictionaryCodingKey.self) - for dictionaryCodingKey in container.allKeys { - guard let key: Key = codingKeyRepresentableType.init( - codingKey: dictionaryCodingKey - ) as? Key else { + for codingKey in container.allKeys { + guard let key: Key = keyType.init(codingKey: codingKey) as? Key else { throw DecodingError.dataCorruptedError( - forKey: dictionaryCodingKey, + forKey: codingKey, in: container, debugDescription: "Could not convert key to type \(Key.self)" ) } - let value: Value = try container.decode( - Value.self, - forKey: dictionaryCodingKey - ) + let value: Value = try container.decode(Value.self, forKey: codingKey) self[key] = value } } else { From 871fc88340c4f7bfb70b78786a619e47ad35a89c Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sat, 30 Oct 2021 07:48:40 +0200 Subject: [PATCH 32/33] Update test/api-digester/Outputs/cake-abi.json Co-authored-by: Ben Rimmington --- test/api-digester/Outputs/cake-abi.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/api-digester/Outputs/cake-abi.json b/test/api-digester/Outputs/cake-abi.json index a68f8fb8d5f36..18a86af9c7d14 100644 --- a/test/api-digester/Outputs/cake-abi.json +++ b/test/api-digester/Outputs/cake-abi.json @@ -1735,6 +1735,7 @@ "name": "CodingKeyRepresentable", "printedName": "CodingKeyRepresentable", "usr": "s:s22CodingKeyRepresentableP", + "mangledName": "$ss22CodingKeyRepresentableP", "isABIPlaceholder": true }, { From 27f7abe0ed0b1e9b0dd8bd60c237ef1a7dce0a59 Mon Sep 17 00:00:00 2001 From: Morten Bek Ditlevsen Date: Sat, 30 Oct 2021 07:49:07 +0200 Subject: [PATCH 33/33] Update test/api-digester/Outputs/cake.json Co-authored-by: Ben Rimmington --- test/api-digester/Outputs/cake.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/api-digester/Outputs/cake.json b/test/api-digester/Outputs/cake.json index 02840da3e8fcf..f2e2615acb4fe 100644 --- a/test/api-digester/Outputs/cake.json +++ b/test/api-digester/Outputs/cake.json @@ -1609,6 +1609,7 @@ "name": "CodingKeyRepresentable", "printedName": "CodingKeyRepresentable", "usr": "s:s22CodingKeyRepresentableP", + "mangledName": "$ss22CodingKeyRepresentableP", "isABIPlaceholder": true }, {