Skip to content

Commit c4437e0

Browse files
authored
Merge pull request #9280 from DougGregor/referencing-encoder-fix-4.0
Referencing encoders should use parent codingPath
2 parents b6fdb93 + efe0ff8 commit c4437e0

File tree

2 files changed

+46
-8
lines changed

2 files changed

+46
-8
lines changed

stdlib/public/SDK/Foundation/JSONEncoder.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fileprivate class _JSONEncoder : Encoder {
148148
let options: JSONEncoder._Options
149149

150150
/// The path to the current point in encoding.
151-
var codingPath: [CodingKey?] = []
151+
var codingPath: [CodingKey?]
152152

153153
/// Contextual user-provided information for use during encoding.
154154
var userInfo: [CodingUserInfoKey : Any] {
@@ -158,9 +158,10 @@ fileprivate class _JSONEncoder : Encoder {
158158
// MARK: - Initialization
159159

160160
/// Initializes `self` with the given top-level encoder options.
161-
init(options: JSONEncoder._Options) {
161+
init(options: JSONEncoder._Options, codingPath: [CodingKey?] = []) {
162162
self.options = options
163163
self.storage = _JSONEncodingStorage()
164+
self.codingPath = codingPath
164165
}
165166

166167
// MARK: - Coding Path Actions
@@ -697,16 +698,34 @@ fileprivate class _JSONReferencingEncoder : _JSONEncoder {
697698
init(referencing encoder: _JSONEncoder, wrapping array: NSMutableArray, at index: Int) {
698699
self.encoder = encoder
699700
self.reference = .array(array, index)
700-
super.init(options: encoder.options)
701+
super.init(options: encoder.options, codingPath: encoder.codingPath)
701702
}
702703

703704
/// Initializes `self` by referencing the given dictionary container in the given encoder.
704705
init(referencing encoder: _JSONEncoder, wrapping dictionary: NSMutableDictionary, key: String) {
705706
self.encoder = encoder
706707
self.reference = .dictionary(dictionary, key)
707-
super.init(options: encoder.options)
708+
super.init(options: encoder.options, codingPath: encoder.codingPath)
708709
}
709710

711+
// MARK: - Overridden Implementations
712+
713+
/// Asserts that we can add a new container at this coding path. See _JSONEncoder.assertCanRequestNewContainer for the logic behind this.
714+
/// This differs from super's implementation only in the condition: we need to account for the fact that we copied our reference's coding path, but not its list of containers, so the counts are mismatched.
715+
override func assertCanRequestNewContainer() {
716+
guard self.storage.count == self.codingPath.count - self.encoder.codingPath.count else {
717+
let previousContainerType: String
718+
if self.storage.containers.last is NSDictionary {
719+
previousContainerType = "keyed"
720+
} else if self.storage.containers.last is NSArray {
721+
previousContainerType = "unkeyed"
722+
} else {
723+
previousContainerType = "single value"
724+
}
725+
726+
preconditionFailure("Attempt to encode with new container when already encoded with \(previousContainerType) container.")
727+
}
728+
}
710729

711730
// MARK: - Deinitialization
712731

stdlib/public/SDK/Foundation/PlistEncoder.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ fileprivate class _PlistEncoder : Encoder {
9090
let options: PropertyListEncoder._Options
9191

9292
/// The path to the current point in encoding.
93-
var codingPath: [CodingKey?] = []
93+
var codingPath: [CodingKey?]
9494

9595
/// Contextual user-provided information for use during encoding.
9696
var userInfo: [CodingUserInfoKey : Any] {
@@ -100,9 +100,10 @@ fileprivate class _PlistEncoder : Encoder {
100100
// MARK: - Initialization
101101

102102
/// Initializes `self` with the given top-level encoder options.
103-
init(options: PropertyListEncoder._Options) {
103+
init(options: PropertyListEncoder._Options, codingPath: [CodingKey?] = []) {
104104
self.options = options
105105
self.storage = _PlistEncodingStorage()
106+
self.codingPath = codingPath
106107
}
107108

108109
// MARK: - Coding Path Actions
@@ -507,16 +508,34 @@ fileprivate class _PlistReferencingEncoder : _PlistEncoder {
507508
init(referencing encoder: _PlistEncoder, wrapping array: NSMutableArray, at index: Int) {
508509
self.encoder = encoder
509510
self.reference = .array(array, index)
510-
super.init(options: encoder.options)
511+
super.init(options: encoder.options, codingPath: encoder.codingPath)
511512
}
512513

513514
/// Initializes `self` by referencing the given dictionary container in the given encoder.
514515
init(referencing encoder: _PlistEncoder, wrapping dictionary: NSMutableDictionary, key: String) {
515516
self.encoder = encoder
516517
self.reference = .dictionary(dictionary, key)
517-
super.init(options: encoder.options)
518+
super.init(options: encoder.options, codingPath: encoder.codingPath)
518519
}
519520

521+
// MARK: - Overridden Implementations
522+
523+
/// Asserts that we can add a new container at this coding path. See _PlistEncoder.assertCanRequestNewContainer for the logic behind this.
524+
/// This differs from super's implementation only in the condition: we need to account for the fact that we copied our reference's coding path, but not its list of containers, so the counts are mismatched.
525+
override func assertCanRequestNewContainer() {
526+
guard self.storage.count == self.codingPath.count - self.encoder.codingPath.count else {
527+
let previousContainerType: String
528+
if self.storage.containers.last is NSDictionary {
529+
previousContainerType = "keyed"
530+
} else if self.storage.containers.last is NSArray {
531+
previousContainerType = "unkeyed"
532+
} else {
533+
previousContainerType = "single value"
534+
}
535+
536+
preconditionFailure("Attempt to encode with new container when already encoded with \(previousContainerType) container.")
537+
}
538+
}
520539

521540
// MARK: - Deinitialization
522541

0 commit comments

Comments
 (0)