Skip to content

Commit 4654a23

Browse files
authored
Merge pull request #9715 from itaiferber/foundation-codable-conformances
Add Codable conformance to common Foundation types
2 parents 8f97333 + 012ea93 commit 4654a23

22 files changed

+1135
-113
lines changed

stdlib/public/SDK/CoreGraphics/CGFloat.swift.gyb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,3 +807,35 @@ extension CGFloat : _CVarArgPassedAsDouble, _CVarArgAligned {
807807
return native._cVarArgAlignment
808808
}
809809
}
810+
811+
extension CGFloat : Codable {
812+
@_transparent
813+
public init(from decoder: Decoder) throws {
814+
let container = try decoder.singleValueContainer()
815+
do {
816+
self.native = try container.decode(NativeType.self)
817+
} catch DecodingError.typeMismatch(let type, let context) {
818+
// We may have encoded as a different type on a different platform. A
819+
// strict fixed-format decoder may disallow a conversion, so let's try the
820+
// other type.
821+
do {
822+
if NativeType.self == Float.self {
823+
self.native = NativeType(try container.decode(Double.self))
824+
} else {
825+
self.native = NativeType(try container.decode(Float.self))
826+
}
827+
} catch {
828+
// Failed to decode as the other type, too. This is neither a Float nor
829+
// a Double. Throw the old error; we don't want to clobber the original
830+
// info.
831+
throw DecodingError.typeMismatch(type, context)
832+
}
833+
}
834+
}
835+
836+
@_transparent
837+
public func encode(to encoder: Encoder) throws {
838+
var container = encoder.singleValueContainer()
839+
try container.encode(self.native)
840+
}
841+
}

stdlib/public/SDK/Foundation/AffineTransform.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,26 @@ extension NSAffineTransform : _HasCustomAnyHashableRepresentation {
330330
}
331331
}
332332

333+
extension AffineTransform : Codable {
334+
public init(from decoder: Decoder) throws {
335+
var container = try decoder.unkeyedContainer()
336+
m11 = try container.decode(CGFloat.self)
337+
m12 = try container.decode(CGFloat.self)
338+
m21 = try container.decode(CGFloat.self)
339+
m22 = try container.decode(CGFloat.self)
340+
tX = try container.decode(CGFloat.self)
341+
tY = try container.decode(CGFloat.self)
342+
}
343+
344+
public func encode(to encoder: Encoder) throws {
345+
var container = encoder.unkeyedContainer()
346+
try container.encode(self.m11)
347+
try container.encode(self.m12)
348+
try container.encode(self.m21)
349+
try container.encode(self.m22)
350+
try container.encode(self.tX)
351+
try container.encode(self.tY)
352+
}
353+
}
354+
333355
#endif

stdlib/public/SDK/Foundation/Calendar.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,7 @@ public struct Calendar : Hashable, Equatable, ReferenceConvertible, _MutableBoxi
10091009
}
10101010
}
10111011

1012-
private static func _fromNSCalendarIdentifier(_ identifier : NSCalendar.Identifier) -> Identifier {
1012+
internal static func _fromNSCalendarIdentifier(_ identifier : NSCalendar.Identifier) -> Identifier {
10131013
if #available(OSX 10.10, iOS 8.0, *) {
10141014
let identifierMap : [NSCalendar.Identifier : Identifier] =
10151015
[.gregorian : .gregorian,
@@ -1128,3 +1128,35 @@ extension NSCalendar : _HasCustomAnyHashableRepresentation {
11281128
}
11291129
}
11301130

1131+
extension Calendar : Codable {
1132+
private enum CodingKeys : Int, CodingKey {
1133+
case identifier
1134+
case locale
1135+
case timeZone
1136+
case firstWeekday
1137+
case minimumDaysInFirstWeek
1138+
}
1139+
1140+
public init(from decoder: Decoder) throws {
1141+
let container = try decoder.container(keyedBy: CodingKeys.self)
1142+
let identifierString = try container.decode(String.self, forKey: .identifier)
1143+
let identifier = Calendar._fromNSCalendarIdentifier(NSCalendar.Identifier(rawValue: identifierString))
1144+
self.init(identifier: identifier)
1145+
1146+
self.locale = try container.decodeIfPresent(Locale.self, forKey: .locale)
1147+
self.timeZone = try container.decode(TimeZone.self, forKey: .timeZone)
1148+
self.firstWeekday = try container.decode(Int.self, forKey: .firstWeekday)
1149+
self.minimumDaysInFirstWeek = try container.decode(Int.self, forKey: .minimumDaysInFirstWeek)
1150+
}
1151+
1152+
public func encode(to encoder: Encoder) throws {
1153+
var container = encoder.container(keyedBy: CodingKeys.self)
1154+
1155+
let identifier = Calendar._toNSCalendarIdentifier(self.identifier).rawValue
1156+
try container.encode(identifier, forKey: .identifier)
1157+
try container.encode(self.locale, forKey: .locale)
1158+
try container.encode(self.timeZone, forKey: .timeZone)
1159+
try container.encode(self.firstWeekday, forKey: .firstWeekday)
1160+
try container.encode(self.minimumDaysInFirstWeek, forKey: .minimumDaysInFirstWeek)
1161+
}
1162+
}

stdlib/public/SDK/Foundation/CharacterSet.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,3 +807,19 @@ extension NSCharacterSet : _HasCustomAnyHashableRepresentation {
807807
}
808808
}
809809

810+
extension CharacterSet : Codable {
811+
private enum CodingKeys : Int, CodingKey {
812+
case bitmap
813+
}
814+
815+
public init(from decoder: Decoder) throws {
816+
let container = try decoder.container(keyedBy: CodingKeys.self)
817+
let bitmap = try container.decode(Data.self, forKey: .bitmap)
818+
self.init(bitmapRepresentation: bitmap)
819+
}
820+
821+
public func encode(to encoder: Encoder) throws {
822+
var container = encoder.container(keyedBy: CodingKeys.self)
823+
try container.encode(self.bitmapRepresentation, forKey: .bitmap)
824+
}
825+
}

stdlib/public/SDK/Foundation/Codable.swift

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -10,68 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
//===----------------------------------------------------------------------===//
14-
// Codable Extensions
15-
//===----------------------------------------------------------------------===//
16-
17-
extension Date : Codable {
18-
public init(from decoder: Decoder) throws {
19-
let timestamp = try decoder.singleValueContainer().decode(Double.self)
20-
self.init(timeIntervalSinceReferenceDate: timestamp)
21-
}
22-
23-
public func encode(to encoder: Encoder) throws {
24-
var container = encoder.singleValueContainer()
25-
try container.encode(self.timeIntervalSinceReferenceDate)
26-
}
27-
}
28-
29-
extension Data : Codable {
30-
private enum CodingKeys : Int, CodingKey {
31-
case length
32-
case bytes
33-
}
34-
35-
public init(from decoder: Decoder) throws {
36-
let container = try decoder.container(keyedBy: CodingKeys.self)
37-
let length = try container.decode(Int.self, forKey: .length)
38-
guard length >= 0 else {
39-
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
40-
debugDescription: "Cannot decode a Data of negative length \(length)."))
41-
}
42-
43-
var bytesContainer = try container.nestedUnkeyedContainer(forKey: .bytes)
44-
45-
self.init(capacity: length)
46-
for i in 0 ..< length {
47-
let byte = try bytesContainer.decode(UInt8.self)
48-
self[i] = byte
49-
}
50-
}
51-
52-
public func encode(to encoder: Encoder) throws {
53-
var container = encoder.container(keyedBy: CodingKeys.self)
54-
try container.encode(self.count, forKey: .length)
55-
56-
var bytesContainer = container.nestedUnkeyedContainer(forKey: .bytes)
57-
58-
// Since enumerateBytes does not rethrow, we need to catch the error, stow it away, and rethrow if we stopped.
59-
var caughtError: Error? = nil
60-
self.enumerateBytes { (buffer: UnsafeBufferPointer<UInt8>, byteIndex: Data.Index, stop: inout Bool) in
61-
do {
62-
try bytesContainer.encode(contentsOf: buffer)
63-
} catch {
64-
caughtError = error
65-
stop = true
66-
}
67-
}
68-
69-
if let error = caughtError {
70-
throw error
71-
}
72-
}
73-
}
74-
7513
//===----------------------------------------------------------------------===//
7614
// Errors
7715
//===----------------------------------------------------------------------===//

stdlib/public/SDK/Foundation/Data.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,3 +1743,47 @@ extension NSData : _HasCustomAnyHashableRepresentation {
17431743
return AnyHashable(Data._unconditionallyBridgeFromObjectiveC(self))
17441744
}
17451745
}
1746+
1747+
extension Data : Codable {
1748+
public init(from decoder: Decoder) throws {
1749+
var container = try decoder.unkeyedContainer()
1750+
1751+
// It's more efficient to pre-allocate the buffer if we can.
1752+
if let count = container.count {
1753+
self.init(count: count)
1754+
1755+
// Loop only until count, not while !container.isAtEnd, in case count is underestimated (this is misbehavior) and we haven't allocated enough space.
1756+
// We don't want to write past the end of what we allocated.
1757+
for i in 0 ..< count {
1758+
let byte = try container.decode(UInt8.self)
1759+
self[i] = byte
1760+
}
1761+
} else {
1762+
self.init()
1763+
}
1764+
1765+
while !container.isAtEnd {
1766+
var byte = try container.decode(UInt8.self)
1767+
self.append(&byte, count: 1)
1768+
}
1769+
}
1770+
1771+
public func encode(to encoder: Encoder) throws {
1772+
var container = encoder.unkeyedContainer()
1773+
1774+
// Since enumerateBytes does not rethrow, we need to catch the error, stow it away, and rethrow if we stopped.
1775+
var caughtError: Error? = nil
1776+
self.enumerateBytes { (buffer: UnsafeBufferPointer<UInt8>, byteIndex: Data.Index, stop: inout Bool) in
1777+
do {
1778+
try container.encode(contentsOf: buffer)
1779+
} catch {
1780+
caughtError = error
1781+
stop = true
1782+
}
1783+
}
1784+
1785+
if let error = caughtError {
1786+
throw error
1787+
}
1788+
}
1789+
}

stdlib/public/SDK/Foundation/Date.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,16 @@ extension Date : CustomPlaygroundQuickLookable {
284284
return .text(summary)
285285
}
286286
}
287+
288+
extension Date : Codable {
289+
public init(from decoder: Decoder) throws {
290+
let container = try decoder.singleValueContainer()
291+
let timestamp = try container.decode(Double.self)
292+
self.init(timeIntervalSinceReferenceDate: timestamp)
293+
}
294+
295+
public func encode(to encoder: Encoder) throws {
296+
var container = encoder.singleValueContainer()
297+
try container.encode(self.timeIntervalSinceReferenceDate)
298+
}
299+
}

stdlib/public/SDK/Foundation/DateComponents.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,83 @@ extension NSDateComponents : _HasCustomAnyHashableRepresentation {
352352
}
353353
}
354354

355+
extension DateComponents : Codable {
356+
private enum CodingKeys : Int, CodingKey {
357+
case calendar
358+
case timeZone
359+
case era
360+
case year
361+
case month
362+
case day
363+
case hour
364+
case minute
365+
case second
366+
case nanosecond
367+
case weekday
368+
case weekdayOrdinal
369+
case quarter
370+
case weekOfMonth
371+
case weekOfYear
372+
case yearForWeekOfYear
373+
}
374+
375+
public init(from decoder: Decoder) throws {
376+
let container = try decoder.container(keyedBy: CodingKeys.self)
377+
let calendar = try container.decodeIfPresent(Calendar.self, forKey: .calendar)
378+
let timeZone = try container.decodeIfPresent(TimeZone.self, forKey: .timeZone)
379+
let era = try container.decodeIfPresent(Int.self, forKey: .era)
380+
let year = try container.decodeIfPresent(Int.self, forKey: .year)
381+
let month = try container.decodeIfPresent(Int.self, forKey: .month)
382+
let day = try container.decodeIfPresent(Int.self, forKey: .day)
383+
let hour = try container.decodeIfPresent(Int.self, forKey: .hour)
384+
let minute = try container.decodeIfPresent(Int.self, forKey: .minute)
385+
let second = try container.decodeIfPresent(Int.self, forKey: .second)
386+
let nanosecond = try container.decodeIfPresent(Int.self, forKey: .nanosecond)
387+
388+
let weekday = try container.decodeIfPresent(Int.self, forKey: .weekday)
389+
let weekdayOrdinal = try container.decodeIfPresent(Int.self, forKey: .weekdayOrdinal)
390+
let quarter = try container.decodeIfPresent(Int.self, forKey: .quarter)
391+
let weekOfMonth = try container.decodeIfPresent(Int.self, forKey: .weekOfMonth)
392+
let weekOfYear = try container.decodeIfPresent(Int.self, forKey: .weekOfYear)
393+
let yearForWeekOfYear = try container.decodeIfPresent(Int.self, forKey: .yearForWeekOfYear)
394+
395+
self.init(calendar: calendar,
396+
timeZone: timeZone,
397+
era: era,
398+
year: year,
399+
month: month,
400+
day: day,
401+
hour: hour,
402+
minute: minute,
403+
second: second,
404+
nanosecond: nanosecond,
405+
weekday: weekday,
406+
weekdayOrdinal: weekdayOrdinal,
407+
quarter: quarter,
408+
weekOfMonth: weekOfMonth,
409+
weekOfYear: weekOfYear,
410+
yearForWeekOfYear: yearForWeekOfYear)
411+
}
412+
413+
public func encode(to encoder: Encoder) throws {
414+
// TODO: Replace all with encodeIfPresent, when added.
415+
var container = encoder.container(keyedBy: CodingKeys.self)
416+
if self.calendar != nil { try container.encode(self.calendar!, forKey: .calendar) }
417+
if self.timeZone != nil { try container.encode(self.timeZone!, forKey: .timeZone) }
418+
if self.era != nil { try container.encode(self.era!, forKey: .era) }
419+
if self.year != nil { try container.encode(self.year!, forKey: .year) }
420+
if self.month != nil { try container.encode(self.month!, forKey: .month) }
421+
if self.day != nil { try container.encode(self.day!, forKey: .day) }
422+
if self.hour != nil { try container.encode(self.hour!, forKey: .hour) }
423+
if self.minute != nil { try container.encode(self.minute!, forKey: .minute) }
424+
if self.second != nil { try container.encode(self.second!, forKey: .second) }
425+
if self.nanosecond != nil { try container.encode(self.nanosecond!, forKey: .nanosecond) }
426+
427+
if self.weekday != nil { try container.encode(self.weekday!, forKey: .weekday) }
428+
if self.weekdayOrdinal != nil { try container.encode(self.weekdayOrdinal!, forKey: .weekdayOrdinal) }
429+
if self.quarter != nil { try container.encode(self.quarter!, forKey: .quarter) }
430+
if self.weekOfMonth != nil { try container.encode(self.weekOfMonth!, forKey: .weekOfMonth) }
431+
if self.weekOfYear != nil { try container.encode(self.weekOfYear!, forKey: .weekOfYear) }
432+
if self.yearForWeekOfYear != nil { try container.encode(self.yearForWeekOfYear!, forKey: .yearForWeekOfYear) }
433+
}
434+
}

stdlib/public/SDK/Foundation/DateInterval.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import _SwiftCoreFoundationOverlayShims
1515

1616
/// DateInterval represents a closed date interval in the form of [startDate, endDate]. It is possible for the start and end dates to be the same with a duration of 0. DateInterval does not support reverse intervals i.e. intervals where the duration is less than 0 and the end date occurs earlier in time than the start date.
1717
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
18-
public struct DateInterval : ReferenceConvertible, Comparable, Hashable {
18+
public struct DateInterval : ReferenceConvertible, Comparable, Hashable, Codable {
1919
public typealias ReferenceType = NSDateInterval
2020

2121
/// The start date.

0 commit comments

Comments
 (0)