Merge pull request #10685 from itaiferber/codingerror-bridging-and-conveniences

EncodingError/DecodingError bridging fixes and conveniences
diff --git a/stdlib/public/SDK/Foundation/Codable.swift b/stdlib/public/SDK/Foundation/Codable.swift
index 4410933..200cbb1 100644
--- a/stdlib/public/SDK/Foundation/Codable.swift
+++ b/stdlib/public/SDK/Foundation/Codable.swift
@@ -14,59 +14,9 @@
 // Errors
 //===----------------------------------------------------------------------===//
 
-// Adding the following extensions to EncodingError and DecodingError allows them to bridge to NSErrors implicitly.
-
-fileprivate let NSCodingPathErrorKey = "NSCodingPath"
-fileprivate let NSDebugDescriptionErrorKey = "NSDebugDescription"
-
-extension EncodingError : CustomNSError {
-    public static var errorDomain: String = NSCocoaErrorDomain
-
-    public var errorCode: Int {
-        switch self {
-        case .invalidValue(_, _): return CocoaError.coderInvalidValue.rawValue
-        }
-    }
-
-    public var errorUserInfo: [String : Any] {
-        let context: Context
-        switch self {
-        case .invalidValue(_, let c): context = c
-        }
-
-        return [NSCodingPathErrorKey: context.codingPath,
-                NSDebugDescriptionErrorKey: context.debugDescription]
-    }
-}
-
-extension DecodingError : CustomNSError {
-    public static var errorDomain: String = NSCocoaErrorDomain
-
-    public var errorCode: Int {
-        switch self {
-        case .valueNotFound(_, _): fallthrough
-        case .keyNotFound(_, _):
-            return CocoaError._coderValueNotFound.rawValue
-
-        case .typeMismatch(_, _): fallthrough
-        case .dataCorrupted(_):
-            return CocoaError._coderReadCorrupt.rawValue
-        }
-    }
-
-    public var errorUserInfo: [String : Any]? {
-        let context: Context
-        switch self {
-        case .typeMismatch(_, let c): context = c
-        case .valueNotFound(_, let c): context = c
-        case .keyNotFound(_, let c): context = c
-        case .dataCorrupted(let c): context = c
-        }
-
-        return [NSCodingPathErrorKey: context.codingPath,
-                NSDebugDescriptionErrorKey: context.debugDescription]
-    }
-}
+// Both of these error types bridge to NSError, and through the entry points they use, no further work is needed to make them localized.
+extension EncodingError : LocalizedError {}
+extension DecodingError : LocalizedError {}
 
 //===----------------------------------------------------------------------===//
 // Error Utilities
diff --git a/stdlib/public/SDK/Foundation/JSONEncoder.swift b/stdlib/public/SDK/Foundation/JSONEncoder.swift
index 9dba0f0..c6367be 100644
--- a/stdlib/public/SDK/Foundation/JSONEncoder.swift
+++ b/stdlib/public/SDK/Foundation/JSONEncoder.swift
@@ -145,7 +145,11 @@
         }
 
         let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue)
-        return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
+        do {
+           return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
+        } catch {
+            throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error))
+        }
     }
 }
 
@@ -883,7 +887,13 @@
     /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
     /// - throws: An error if any value throws an error during decoding.
     open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
-        let topLevel = try JSONSerialization.jsonObject(with: data)
+        let topLevel: Any
+        do {
+           topLevel = try JSONSerialization.jsonObject(with: data)
+        } catch {
+            throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
+        }
+
         let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
         return try T(from: decoder)
     }
diff --git a/stdlib/public/SDK/Foundation/NSError.swift b/stdlib/public/SDK/Foundation/NSError.swift
index 768fb26..3b3c585 100644
--- a/stdlib/public/SDK/Foundation/NSError.swift
+++ b/stdlib/public/SDK/Foundation/NSError.swift
@@ -850,19 +850,11 @@
 
   @available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
   public static var coderReadCorrupt: CocoaError.Code {
-    return _coderReadCorrupt
-  }
-
-  internal static var _coderReadCorrupt: CocoaError.Code {
     return CocoaError.Code(rawValue: 4864)
   }
 
   @available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
   public static var coderValueNotFound: CocoaError.Code {
-    return _coderValueNotFound
-  }
-
-  internal static var _coderValueNotFound: CocoaError.Code {
     return CocoaError.Code(rawValue: 4865)
   }
 
@@ -1301,19 +1293,11 @@
 
   @available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
   public static var coderReadCorrupt: CocoaError.Code {
-    return _coderReadCorrupt
-  }
-
-  public static var _coderReadCorrupt: CocoaError.Code {
     return CocoaError.Code(rawValue: 4864)
   }
 
   @available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
   public static var coderValueNotFound: CocoaError.Code {
-    return _coderValueNotFound
-  }
-
-  internal static var _coderValueNotFound: CocoaError.Code {
     return CocoaError.Code(rawValue: 4865)
   }
 
diff --git a/stdlib/public/SDK/Foundation/PlistEncoder.swift b/stdlib/public/SDK/Foundation/PlistEncoder.swift
index df898d2..f5514ff 100644
--- a/stdlib/public/SDK/Foundation/PlistEncoder.swift
+++ b/stdlib/public/SDK/Foundation/PlistEncoder.swift
@@ -74,7 +74,11 @@
                                                                    debugDescription: "Top-level \(Value.self) encoded as date property list fragment."))
         }
 
-        return try PropertyListSerialization.data(fromPropertyList: topLevel, format: self.outputFormat, options: 0)
+        do {
+            return try PropertyListSerialization.data(fromPropertyList: topLevel, format: self.outputFormat, options: 0)
+        } catch {
+            throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value as a property list", underlyingError: error))
+        }
     }
 }
 
@@ -640,7 +644,12 @@
     /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not a valid property list.
     /// - throws: An error if any value throws an error during decoding.
     open func decode<T : Decodable>(_ type: T.Type, from data: Data, format: inout PropertyListSerialization.PropertyListFormat) throws -> T {
-        let topLevel = try PropertyListSerialization.propertyList(from: data, options: [], format: &format)
+        let topLevel: Any
+        do {
+            topLevel = try PropertyListSerialization.propertyList(from: data, options: [], format: &format)
+        } catch {
+            throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not a valid property list.", underlyingError: error))
+        }
         let decoder = _PlistDecoder(referencing: topLevel, options: self.options)
         return try T(from: decoder)
     }
diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift
index 5dc5d49..8f5b5a0 100644
--- a/stdlib/public/core/Codable.swift
+++ b/stdlib/public/core/Codable.swift
@@ -1740,6 +1740,10 @@
 /// A container that can support the storage and direct encoding of a single
 /// non-keyed value.
 public protocol SingleValueEncodingContainer {
+    /// The path of coding keys taken to get to this point in encoding.
+    /// A `nil` value indicates an unkeyed container.
+    var codingPath: [CodingKey?] { get }
+
     /// Encodes a null value.
     ///
     /// - throws: `EncodingError.invalidValue` if a null value is invalid in the current context for this format.
@@ -1854,6 +1858,10 @@
 
 /// A `SingleValueDecodingContainer` is a container which can support the storage and direct decoding of a single non-keyed value.
 public protocol SingleValueDecodingContainer {
+    /// The path of coding keys taken to get to this point in encoding.
+    /// A `nil` value indicates an unkeyed container.
+    var codingPath: [CodingKey?] { get }
+
     /// Decodes a null value.
     ///
     /// - returns: Whether the encountered value was null.
@@ -2026,13 +2034,18 @@
         /// A description of what went wrong, for debugging purposes.
         public let debugDescription: String
 
+        /// The underlying error which caused this error, if any.
+        public let underlyingError: Error?
+
         /// Initializes `self` with the given path of `CodingKey`s and a description of what went wrong.
         ///
         /// - parameter codingPath: The path of `CodingKey`s taken to get to the point of the failing encode call.
         /// - parameter debugDescription: A description of what went wrong, for debugging purposes.
-        public init(codingPath: [CodingKey?], debugDescription: String) {
+        /// - parameter underlyingError: The underlying error which caused this error, if any.
+        public init(codingPath: [CodingKey?], debugDescription: String, underlyingError: Error? = nil) {
             self.codingPath = codingPath
             self.debugDescription = debugDescription
+            self.underlyingError = underlyingError
         }
     }
 
@@ -2040,6 +2053,44 @@
     ///
     /// Contains the attempted value, along with context for debugging.
     case invalidValue(Any, Context)
+
+    // MARK: - NSError Bridging
+
+    // CustomNSError bridging applies only when the CustomNSError conformance is applied in the same module as the declared error type.
+    // Since we cannot access CustomNSError (which is defined in Foundation) from here, we can use the "hidden" entry points.
+
+    public var _domain: String {
+        return "NSCocoaErrorDomain"
+    }
+
+    public var _code: Int {
+        switch self {
+        case .invalidValue(_, _): return 4866
+        }
+    }
+
+    public var _userInfo: AnyObject? {
+        // The error dictionary must be returned as an AnyObject. We can do this only on platforms with bridging, unfortunately.
+        #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
+            let context: Context
+            switch self {
+            case .invalidValue(_, let c): context = c
+            }
+
+            var userInfo: [String : Any] = [
+                "NSCodingPath": context.codingPath,
+                "NSDebugDescription": context.debugDescription
+            ]
+
+            if let underlyingError = context.underlyingError {
+                userInfo["NSUnderlyingError"] = underlyingError
+            }
+
+            return userInfo as AnyObject
+        #else
+            return nil
+        #endif
+    }
 }
 
 /// An error that occurs during the decoding of a value.
@@ -2052,13 +2103,18 @@
         /// A description of what went wrong, for debugging purposes.
         public let debugDescription: String
 
+        /// The underlying error which caused this error, if any.
+        public let underlyingError: Error?
+
         /// Initializes `self` with the given path of `CodingKey`s and a description of what went wrong.
         ///
         /// - parameter codingPath: The path of `CodingKey`s taken to get to the point of the failing decode call.
         /// - parameter debugDescription: A description of what went wrong, for debugging purposes.
-        public init(codingPath: [CodingKey?], debugDescription: String) {
+        /// - parameter underlyingError: The underlying error which caused this error, if any.
+        public init(codingPath: [CodingKey?], debugDescription: String, underlyingError: Error? = nil) {
             self.codingPath = codingPath
             self.debugDescription = debugDescription
+            self.underlyingError = underlyingError
         }
     }
 
@@ -2081,6 +2137,91 @@
     ///
     /// Contains context for debugging.
     case dataCorrupted(Context)
+
+    // MARK: - NSError Bridging
+
+    // CustomNSError bridging applies only when the CustomNSError conformance is applied in the same module as the declared error type.
+    // Since we cannot access CustomNSError (which is defined in Foundation) from here, we can use the "hidden" entry points.
+
+    public var _domain: String {
+        return "NSCocoaErrorDomain"
+    }
+
+    public var _code: Int {
+        switch self {
+        case .keyNotFound(_, _):   fallthrough
+        case .valueNotFound(_, _): return 4865
+        case .typeMismatch(_, _):  fallthrough
+        case .dataCorrupted(_):    return 4864
+        }
+    }
+
+    public var _userInfo: AnyObject? {
+        // The error dictionary must be returned as an AnyObject. We can do this only on platforms with bridging, unfortunately.
+        #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
+            let context: Context
+            switch self {
+            case .keyNotFound(_,   let c): context = c
+            case .valueNotFound(_, let c): context = c
+            case .typeMismatch(_,  let c): context = c
+            case .dataCorrupted(   let c): context = c
+            }
+
+            var userInfo: [String : Any] = [
+                "NSCodingPath": context.codingPath,
+                "NSDebugDescription": context.debugDescription
+            ]
+
+            if let underlyingError = context.underlyingError {
+                userInfo["NSUnderlyingError"] = underlyingError
+            }
+
+            return userInfo as AnyObject
+        #else
+            return nil
+        #endif
+    }
+}
+
+// The following extensions allow for easier error construction.
+
+public extension DecodingError {
+    /// A convenience method which creates a new .dataCorrupted error using a constructed coding path and the given debug description.
+    ///
+    /// Constructs a coding path by appending the given key to the given container's coding path.
+    ///
+    /// - param key: The key which caused the failure.
+    /// - param container: The container in which the corrupted data was accessed.
+    /// - param debugDescription: A description of the error to aid in debugging.
+    static func dataCorruptedError<C : KeyedDecodingContainerProtocol>(forKey key: C.Key, in container: C, debugDescription: String) -> DecodingError {
+        let context = DecodingError.Context(codingPath: container.codingPath + [key],
+                                            debugDescription: debugDescription)
+        return .dataCorrupted(context)
+    }
+
+    /// A convenience method which creates a new .dataCorrupted error using a constructed coding path and the given debug description.
+    ///
+    /// Constructs a coding path by appending a nil key to the given container's coding path.
+    ///
+    /// - param container: The container in which the corrupted data was accessed.
+    /// - param debugDescription: A description of the error to aid in debugging.
+    static func dataCorruptedError(in container: UnkeyedDecodingContainer, debugDescription: String) -> DecodingError {
+        let context = DecodingError.Context(codingPath: container.codingPath + [nil],
+                                            debugDescription: debugDescription)
+        return .dataCorrupted(context)
+    }
+
+    /// A convenience method which creates a new .dataCorrupted error using a constructed coding path and the given debug description.
+    ///
+    /// Uses the given container's coding path as the constructed path.
+    ///
+    /// - param container: The container in which the corrupted data was accessed.
+    /// - param debugDescription: A description of the error to aid in debugging.
+    static func dataCorruptedError(in container: SingleValueDecodingContainer, debugDescription: String) -> DecodingError {
+        let context = DecodingError.Context(codingPath: container.codingPath,
+                                            debugDescription: debugDescription)
+        return .dataCorrupted(context)
+    }
 }
 
 //===----------------------------------------------------------------------===//