Skip to content

Add Codable conformance to common Foundation types #9715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
32 changes: 32 additions & 0 deletions stdlib/public/SDK/CoreGraphics/CGFloat.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -807,3 +807,35 @@ extension CGFloat : _CVarArgPassedAsDouble, _CVarArgAligned {
return native._cVarArgAlignment
}
}

extension CGFloat : Codable {
@_transparent
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self.native = try container.decode(NativeType.self)
} catch DecodingError.typeMismatch(let type, let context) {
// We may have encoded as a different type on a different platform. A
// strict fixed-format decoder may disallow a conversion, so let's try the
// other type.
do {
if NativeType.self == Float.self {
self.native = NativeType(try container.decode(Double.self))
} else {
self.native = NativeType(try container.decode(Float.self))
}
} catch {
// Failed to decode as the other type, too. This is neither a Float nor
// a Double. Throw the old error; we don't want to clobber the original
// info.
throw DecodingError.typeMismatch(type, context)
}
}
}

@_transparent
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.native)
}
}
22 changes: 22 additions & 0 deletions stdlib/public/SDK/Foundation/AffineTransform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,26 @@ extension NSAffineTransform : _HasCustomAnyHashableRepresentation {
}
}

extension AffineTransform : Codable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
m11 = try container.decode(CGFloat.self)
m12 = try container.decode(CGFloat.self)
m21 = try container.decode(CGFloat.self)
m22 = try container.decode(CGFloat.self)
tX = try container.decode(CGFloat.self)
tY = try container.decode(CGFloat.self)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(self.m11)
try container.encode(self.m12)
try container.encode(self.m21)
try container.encode(self.m22)
try container.encode(self.tX)
try container.encode(self.tY)
}
}

#endif
34 changes: 33 additions & 1 deletion stdlib/public/SDK/Foundation/Calendar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,7 @@ public struct Calendar : Hashable, Equatable, ReferenceConvertible, _MutableBoxi
}
}

private static func _fromNSCalendarIdentifier(_ identifier : NSCalendar.Identifier) -> Identifier {
internal static func _fromNSCalendarIdentifier(_ identifier : NSCalendar.Identifier) -> Identifier {
if #available(OSX 10.10, iOS 8.0, *) {
let identifierMap : [NSCalendar.Identifier : Identifier] =
[.gregorian : .gregorian,
Expand Down Expand Up @@ -1128,3 +1128,35 @@ extension NSCalendar : _HasCustomAnyHashableRepresentation {
}
}

extension Calendar : Codable {
private enum CodingKeys : Int, CodingKey {
case identifier
case locale
case timeZone
case firstWeekday
case minimumDaysInFirstWeek
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let identifierString = try container.decode(String.self, forKey: .identifier)
let identifier = Calendar._fromNSCalendarIdentifier(NSCalendar.Identifier(rawValue: identifierString))
self.init(identifier: identifier)

self.locale = try container.decodeIfPresent(Locale.self, forKey: .locale)
self.timeZone = try container.decode(TimeZone.self, forKey: .timeZone)
self.firstWeekday = try container.decode(Int.self, forKey: .firstWeekday)
self.minimumDaysInFirstWeek = try container.decode(Int.self, forKey: .minimumDaysInFirstWeek)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

let identifier = Calendar._toNSCalendarIdentifier(self.identifier).rawValue
try container.encode(identifier, forKey: .identifier)
try container.encode(self.locale, forKey: .locale)
try container.encode(self.timeZone, forKey: .timeZone)
try container.encode(self.firstWeekday, forKey: .firstWeekday)
try container.encode(self.minimumDaysInFirstWeek, forKey: .minimumDaysInFirstWeek)
}
}
16 changes: 16 additions & 0 deletions stdlib/public/SDK/Foundation/CharacterSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -807,3 +807,19 @@ extension NSCharacterSet : _HasCustomAnyHashableRepresentation {
}
}

extension CharacterSet : Codable {
private enum CodingKeys : Int, CodingKey {
case bitmap
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let bitmap = try container.decode(Data.self, forKey: .bitmap)
self.init(bitmapRepresentation: bitmap)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.bitmapRepresentation, forKey: .bitmap)
}
}
62 changes: 0 additions & 62 deletions stdlib/public/SDK/Foundation/Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,68 +10,6 @@
//
//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
// Codable Extensions
//===----------------------------------------------------------------------===//

extension Date : Codable {
public init(from decoder: Decoder) throws {
let timestamp = try decoder.singleValueContainer().decode(Double.self)
self.init(timeIntervalSinceReferenceDate: timestamp)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.timeIntervalSinceReferenceDate)
}
}

extension Data : Codable {
private enum CodingKeys : Int, CodingKey {
case length
case bytes
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let length = try container.decode(Int.self, forKey: .length)
guard length >= 0 else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Cannot decode a Data of negative length \(length)."))
}

var bytesContainer = try container.nestedUnkeyedContainer(forKey: .bytes)

self.init(capacity: length)
for i in 0 ..< length {
let byte = try bytesContainer.decode(UInt8.self)
self[i] = byte
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.count, forKey: .length)

var bytesContainer = container.nestedUnkeyedContainer(forKey: .bytes)

// Since enumerateBytes does not rethrow, we need to catch the error, stow it away, and rethrow if we stopped.
var caughtError: Error? = nil
self.enumerateBytes { (buffer: UnsafeBufferPointer<UInt8>, byteIndex: Data.Index, stop: inout Bool) in
do {
try bytesContainer.encode(contentsOf: buffer)
} catch {
caughtError = error
stop = true
}
}

if let error = caughtError {
throw error
}
}
}

//===----------------------------------------------------------------------===//
// Errors
//===----------------------------------------------------------------------===//
Expand Down
44 changes: 44 additions & 0 deletions stdlib/public/SDK/Foundation/Data.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1743,3 +1743,47 @@ extension NSData : _HasCustomAnyHashableRepresentation {
return AnyHashable(Data._unconditionallyBridgeFromObjectiveC(self))
}
}

extension Data : Codable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()

// It's more efficient to pre-allocate the buffer if we can.
if let count = container.count {
self.init(count: count)

// Loop only until count, not while !container.isAtEnd, in case count is underestimated (this is misbehavior) and we haven't allocated enough space.
// We don't want to write past the end of what we allocated.
for i in 0 ..< count {
let byte = try container.decode(UInt8.self)
self[i] = byte
}
} else {
self.init()
}

while !container.isAtEnd {
var byte = try container.decode(UInt8.self)
self.append(&byte, count: 1)
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()

// Since enumerateBytes does not rethrow, we need to catch the error, stow it away, and rethrow if we stopped.
var caughtError: Error? = nil
self.enumerateBytes { (buffer: UnsafeBufferPointer<UInt8>, byteIndex: Data.Index, stop: inout Bool) in
do {
try container.encode(contentsOf: buffer)
} catch {
caughtError = error
stop = true
}
}

if let error = caughtError {
throw error
}
}
}
13 changes: 13 additions & 0 deletions stdlib/public/SDK/Foundation/Date.swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,16 @@ extension Date : CustomPlaygroundQuickLookable {
return .text(summary)
}
}

extension Date : Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let timestamp = try container.decode(Double.self)
self.init(timeIntervalSinceReferenceDate: timestamp)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.timeIntervalSinceReferenceDate)
}
}
80 changes: 80 additions & 0 deletions stdlib/public/SDK/Foundation/DateComponents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,83 @@ extension NSDateComponents : _HasCustomAnyHashableRepresentation {
}
}

extension DateComponents : Codable {
private enum CodingKeys : Int, CodingKey {
case calendar
case timeZone
case era
case year
case month
case day
case hour
case minute
case second
case nanosecond
case weekday
case weekdayOrdinal
case quarter
case weekOfMonth
case weekOfYear
case yearForWeekOfYear
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let calendar = try container.decodeIfPresent(Calendar.self, forKey: .calendar)
let timeZone = try container.decodeIfPresent(TimeZone.self, forKey: .timeZone)
let era = try container.decodeIfPresent(Int.self, forKey: .era)
let year = try container.decodeIfPresent(Int.self, forKey: .year)
let month = try container.decodeIfPresent(Int.self, forKey: .month)
let day = try container.decodeIfPresent(Int.self, forKey: .day)
let hour = try container.decodeIfPresent(Int.self, forKey: .hour)
let minute = try container.decodeIfPresent(Int.self, forKey: .minute)
let second = try container.decodeIfPresent(Int.self, forKey: .second)
let nanosecond = try container.decodeIfPresent(Int.self, forKey: .nanosecond)

let weekday = try container.decodeIfPresent(Int.self, forKey: .weekday)
let weekdayOrdinal = try container.decodeIfPresent(Int.self, forKey: .weekdayOrdinal)
let quarter = try container.decodeIfPresent(Int.self, forKey: .quarter)
let weekOfMonth = try container.decodeIfPresent(Int.self, forKey: .weekOfMonth)
let weekOfYear = try container.decodeIfPresent(Int.self, forKey: .weekOfYear)
let yearForWeekOfYear = try container.decodeIfPresent(Int.self, forKey: .yearForWeekOfYear)

self.init(calendar: calendar,
timeZone: timeZone,
era: era,
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
nanosecond: nanosecond,
weekday: weekday,
weekdayOrdinal: weekdayOrdinal,
quarter: quarter,
weekOfMonth: weekOfMonth,
weekOfYear: weekOfYear,
yearForWeekOfYear: yearForWeekOfYear)
}

public func encode(to encoder: Encoder) throws {
// TODO: Replace all with encodeIfPresent, when added.
var container = encoder.container(keyedBy: CodingKeys.self)
if self.calendar != nil { try container.encode(self.calendar!, forKey: .calendar) }
if self.timeZone != nil { try container.encode(self.timeZone!, forKey: .timeZone) }
if self.era != nil { try container.encode(self.era!, forKey: .era) }
if self.year != nil { try container.encode(self.year!, forKey: .year) }
if self.month != nil { try container.encode(self.month!, forKey: .month) }
if self.day != nil { try container.encode(self.day!, forKey: .day) }
if self.hour != nil { try container.encode(self.hour!, forKey: .hour) }
if self.minute != nil { try container.encode(self.minute!, forKey: .minute) }
if self.second != nil { try container.encode(self.second!, forKey: .second) }
if self.nanosecond != nil { try container.encode(self.nanosecond!, forKey: .nanosecond) }

if self.weekday != nil { try container.encode(self.weekday!, forKey: .weekday) }
if self.weekdayOrdinal != nil { try container.encode(self.weekdayOrdinal!, forKey: .weekdayOrdinal) }
if self.quarter != nil { try container.encode(self.quarter!, forKey: .quarter) }
if self.weekOfMonth != nil { try container.encode(self.weekOfMonth!, forKey: .weekOfMonth) }
if self.weekOfYear != nil { try container.encode(self.weekOfYear!, forKey: .weekOfYear) }
if self.yearForWeekOfYear != nil { try container.encode(self.yearForWeekOfYear!, forKey: .yearForWeekOfYear) }
}
}
2 changes: 1 addition & 1 deletion stdlib/public/SDK/Foundation/DateInterval.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import _SwiftCoreFoundationOverlayShims

/// 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.
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
public struct DateInterval : ReferenceConvertible, Comparable, Hashable {
public struct DateInterval : ReferenceConvertible, Comparable, Hashable, Codable {
public typealias ReferenceType = NSDateInterval

/// The start date.
Expand Down
Loading