Obviously, I’m in favor of this one. +1!

I think I did prefer the older name of CustomUserInfoError for the 
domain/code/userInfo protocol, rather than CustomNSError. This is just because 
I’d like to be able to do a global search through my project for “NSError” and 
have it turn up empty. Maybe a silly reason, I know. ;-)

I sent a few more comments off-list before I realized you’d posted the proposal 
already, so you can peruse, consider, and/or discard those at your leisure. 
It’s nothing terribly important, though; this proposal is pretty great as is.

Thanks so much for doing this! I’m really excited for better error handling in 
Swift 3.

Charles

> On Jun 27, 2016, at 1:17 PM, Douglas Gregor <[email protected]> wrote:
> 
> Hi all,
> 
> Proposal link: 
> https://github.com/DougGregor/swift-evolution/blob/nserror-bridging/proposals/0000-nserror-bridging.md
> 
> Here is a detailed proposal draft for bridging NSError to ErrorProtocol. 
> Getting this right is surprisingly involved, so the detailed design on this 
> proposal is fairly large. Comments welcome!
> 
>       - Doug
> 
> NSError Bridging
> 
>       • Proposal: SE-NNNN
>       • Author: Doug Gregor, Charles Srstka
>       • Status: Awaiting review
>       • Review manager: TBD
> Introduction
> 
> Swift's error handling model interoperates directly with Cocoa's NSError 
> conventions. For example, an Objective-C method with an NSError** parameter, 
> e.g.,
> 
> - (nullable NSURL *)replaceItemAtURL:(NSURL *)url 
> options:(NSFileVersionReplacingOptions)options error:(NSError **)error;
> will be imported as a throwing method:
> 
> func replaceItem(at url: URL, options: ReplacingOptions = []) throws -> URL
> Swift bridges between ErrorProtocol-conforming types and NSError so, for 
> example, a Swift enum that conforms toErrorProtocol can be thrown and will be 
> reflected as an NSError with a suitable domain and code. Moreover, an 
> NSErrorproduced with that domain and code can be caught as the Swift enum 
> type, providing round-tripping so that Swift can deal in ErrorProtocol values 
> while Objective-C deals in NSError objects.
> 
> However, the interoperability is incomplete in a number of ways, which 
> results in Swift programs having to walk a careful line between the 
> ErrorProtocol-based Swift way and the NSError-based way. This proposal 
> attempts to bridge those gaps.
> 
> Swift-evolution thread: Charles Srstka's pitch for Consistent bridging for 
> NSErrors at the language boundary, which discussed Charles' original proposal 
> that addressed these issues by providing NSError to ErrorProtocol bridging 
> and exposing the domain, code, and user-info dictionary for all errors. This 
> proposal expands upon that work, but without directly exposing the domain, 
> code, and user-info.
> 
> Motivation
> 
> There are a number of weaknesses in Swift's interoperability with Cocoa's 
> error model, including:
> 
>       • There is no good way to provide important error information when 
> throwing an error from Swift. For example, let's consider a simple 
> application-defined error in Swift:
> 
> enum HomeworkError : Int
> , ErrorProtocol {
>   
> case
>  forgotten
>   
> case
>  lost
>   
> case
>  dogAteIt
> }
> 
> One can throw HomeworkError.dogAteIt and it can be interpreted as an NSError 
> by Objective-C with an appropriate error domain (effectively, the mangled 
> name of the HomeworkError type) and code (effectively, the case 
> discriminator). However, one cannot provide a localized description, help 
> anchor, recovery attempter, or any other information commonly placed into the 
> userInfo dictionary of an NSError. To provide these values, one must 
> specifically construct an NSError in Swift, e.g.,
> 
> throw NSError(code: HomeworkError.dogAteIt.rawValue
> ,
>               domain: HomeworkError
> .
> _domain,
>               userInfo: [ NSLocalizedDescriptionKey 
> : "the dog ate it" ])
>       • There is no good way to get information typically associated with 
> NSError's userInfo in Swift. For example, the Swift-natural way to catch a 
> specific error in the AVError error domain doesn't give one access to the 
> userInfo dictionary, e.g.,:
> 
> catch let error as AVError where error == .
> diskFull {
>   
> // AVError is an enum, so one only gets the equivalent of the code.
> 
>   
> // There is no way to access the localized description (for example) or
> 
>   
> // any other information typically stored in the ``userInfo`` dictionary.
> 
> }
> 
> The workaround is to catch as an NSError, which is quite a bit more ugly:
> 
> catch let error as NSError where error._domain == AVFoundationErrorDomain && 
> error._code == AVFoundationErrorDomain.diskFull.rawValue
>  {
>   
> // okay: userInfo is finally accessible, but still weakly typed
> 
> }
> 
> This makes it extremely hard to access common information, such as the 
> localized description. Moreover, the userInfo dictionary is effectively 
> untyped so, for example, one has to know a priori that the value associated 
> with the known AVErrorDeviceKey will be typed as CMTime:
> 
> catch let error as NSError where error._domain =
>  AVFoundationErrorDomain {
>   
> if let time = error.userInfo[AVErrorDeviceKey] as?
>  CMTime {
>     
> // ...
> 
>   }
> }
> 
> It would be far better if one could catch an AVError directly and query the 
> time in a type-safe manner:
> 
> catch let error as
>  AVError {
>   
> if let time = error.
> time {
>     
> // ...
> 
>   }
> }
> 
>       • NSError is inconsistently bridged with ErrorProtocol. Swift 
> interoperates by translating between NSError and ErrorProtocol when mapping 
> between a throwing Swift method/initializer and an Objective-C method with an 
> NSError** parameter. However, an Objective-C method that takes an NSError* 
> parameter (e.g., to render it) does not bridge to ErrorProtocol, meaning that 
> NSError is part of the API in Swift in some places (but not others). For 
> example, NSError leaks through when the following UIDocument API in 
> Objective-C:
> 
> - (void)handleError:(NSError *)error 
> userInteractionPermitted:(BOOL)userInteractionPermitted;
> is imported into Swift as follows:
> 
> func handleError(_ error: NSError, userInteractionPermitted: Bool)
> One would expect the first parameter to be imported as ErrorProtocol. 
> 
> Proposed solution
> 
> This proposal involves directly addressing (1)-(3) with new protocols and a 
> different way of bridging Objective-C error code types into Swift, along with 
> some conveniences for working with Cocoa errors:
> 
>       • Introduce three new protocols for describing more information about 
> errors: LocalizedError, RecoverableError, andCustomNSError. For example, an 
> error type can provide a localized description by conforming to 
> LocalizedError:
> 
> extension HomeworkError :
>  LocalizedError {
>   
> var errorDescription: String
> ? {
>     
> switch self
>  {
>     
> case .forgotten: return NSLocalizedString("I forgot it"
> )
>     
> case .lost: return NSLocalizedString("I lost it"
> )
>     
> case .dogAteIt: return NSLocalizedString("The dog ate it"
> )
>     }
>   }
> }
> 
>       • Imported Objective-C error types should be mapped to struct types 
> that store an NSError so that no information is lost when bridging from an 
> NSError to the Swift error types. We propose to introduce a new macro, 
> NS_ERROR_ENUM, that one can use to both declare an enumeration type used to 
> describe the error codes as well as tying that type to a specific domain 
> constant, e.g.,
> 
> typedef NS_ERROR_ENUM(NSInteger
> , AVError, AVFoundationErrorDomain) {
>   AVErrorUnknown                                      = -
> 11800
> ,
>   AVErrorOutOfMemory                                  = -
> 11801
> ,
>   AVErrorSessionNotRunning                            = -
> 11803
> ,
>   AVErrorDeviceAlreadyUsedByAnotherSession            = -
> 11804
> ,
>   
> // ...
> 
> }
> 
> The imported AVError will have a struct that allows one to access the 
> userInfo dictionary directly. This retains the ability to catch via a 
> specific code, e.g.,
> 
> catch AVError.
> outOfMemory {
>  
> // ...
> 
> }
> 
> However, catching a specific error as a value doesn't lose information:
> 
> catch let error as AVError where error.code == .
> sessionNotRunning {
>  
> // able to access userInfo here!
> 
> }
> 
> This also gives the ability for one to add typed accessors for known keys 
> within the userInfo dictionary:
> 
> extension
>  AVError {
>  
> var
>  time: CMTime? {
>    
> get
>  {
>      
> return userInfo[AVErrorTimeKey] as?
>  CMTime?
>    }
> 
>    
> set
>  {
>      userInfo[AVErrorTimeKey] 
> = newValue.map { $0 as
>  CMTime }
>    }
>  }
> }
> 
>       • Bridge NSError to ErrorProtocol, so that all NSError uses are bridged 
> consistently. For example, this means that the Objective-C API:
> 
> - (void)handleError:(NSError *)error 
> userInteractionPermitted:(BOOL)userInteractionPermitted;
> is imported into Swift as:
> 
> func handleError(_ error: ErrorProtocol, userInteractionPermitted: Bool)
> This will use the same bridging logic in the Clang importer that we use for 
> other value types (Array, String, URL, etc.), but with the runtime 
> translation we've already been doing for catching/throwing errors.
> 
> When we introduce this bridging, we will need to remove NSError's conformance 
> to ErrorProtocol to avoid creating cyclic implicit conversions. However, we 
> still need an easy way to create an ErrorProtocol instance from an arbitrary 
> NSError, e.g.,
> 
> extension
>  NSError {
>   
> var asError: ErrorProtocol { ...
>  }
> }
> 
>       • In Foundation, add an extension to ErrorProtocol that provides typed 
> access to the common user-info keys. Note that we focus only on those 
> user-info keys that are read by user code (vs. only accessed by frameworks):
> 
> extension
>  ErrorProtocol {
>   
> // Note: for exposition only. Not actual API.
> 
>   
> private var userInfo: [NSObject : AnyObject
> ] {
>     
> return (self as! NSError).
> userInfo
>   }
> 
>   
> var localizedDescription: String
>  {
>     
> return (self as! NSError).
> localizedDescription
>   }
> 
>   
> var filePath: String
> ? {
>     
> return userInfo[NSFilePathErrorKey] as? String
> 
>   }
> 
>   
> var stringEncoding: String.
> Encoding? {
>     
> return (userInfo[NSStringEncodingErrorKey] as?
>  NSNumber)
>              
> .map { String.Encoding(rawValue: $0.uintValue
> ) }
>   }
> 
>   
> var
>  underlying: ErrorProtocol? {
>     
> return (userInfo[NSUnderlyingErrorKey] as? NSError)?.
> asError
>   }
> 
>   
> var
>  url: URL? {
>     
> return userInfo[NSURLErrorKey] as?
>  URL
>   }
> }
> 
>       • Rename ErrorProtocol to Error: once we've completed the bridging 
> story, Error becomes the primary way to work with error types in Swift, and 
> the value type to which NSError is bridged:
> 
> func handleError(_ error: Error, userInteractionPermitted: Bool)
> Detailed design
> 
> This section details both the design (including the various new protocols, 
> mapping from Objective-C error code enumeration types into Swift types, etc.) 
> and the efficient implementation of this design to interoperate with NSError. 
> Throughout the detailed design, we already assume the name change from 
> ErrorProtocol to Error.
> 
> New protocols
> 
> This proposal introduces several new protocols that allow error types to 
> expose more information about error types.
> 
> The LocalizedError protocol describes an error that provides localized 
> messages for display to the end user, all of which provide default 
> implementations. The conforming type can provide implementations for any 
> subset of these requirements:
> 
> protocol LocalizedError :
>  Error {
>   
> /// A localized message describing what error occurred.
> 
>   
> var errorDescription: String? { get
>  }
> 
>   
> /// A localized message describing the reason for the failure.
> 
>   
> var failureReason: String? { get
>  }
> 
>   
> /// A localized message describing how one might recover from the failure.
> 
>   
> var recoverySuggestion: String? { get
>  }
> 
>   
> /// A localized message providing "help" text if the user requests help.
> 
>   
> var helpAnchor: String? { get
>  }
> }
> 
> 
> extension
>  LocalizedError {
>   
> var errorDescription: String? { return nil
>  }
>   
> var failureReason: String? { return nil
>  }
>   
> var recoverySuggestion: String? { return nil
>  }
>   
> var helpAnchor: String? { return nil
>  }
> }
> 
> The RecoverableError protocol describes an error that might be recoverable: 
> 
> protocol RecoverableError :
>  Error {
>   
> /// Provides a set of possible recovery options to present to the user.
> 
>   
> var recoveryOptions: [String] { get
>  }
> 
>   
> /// Attempt to recover from this error when the user selected the
> 
>   
> /// option at the given index. This routine must call resultHandler and
> 
>   
> /// indicate whether recovery was successful (or not).
> 
>   
> ///
> 
>   
> /// This entry point is used for recovery of errors handled at a
> 
>   
> /// "document" granularity, that do not affect the entire
> 
>   
> /// application.
> 
>   
> func attemptRecovery(optionIndex recoveryOptionIndex: Int
> ,
>                        
> andThen resultHandler: (recovered: Bool) -> Void
> )
> 
>   
> /// Attempt to recover from this error when the user selected the
> 
>   
> /// option at the given index. Returns true to indicate
> 
>   
> /// successful recovery, and false otherwise.
> 
>   
> ///
> 
>   
> /// This entry point is used for recovery of errors handled at
> 
>   
> /// the "application" granularity, where nothing else in the
> 
>   
> /// application can proceed until the attmpted error recovery
> 
>   
> /// completes.
> 
>   
> func attemptRecovery(optionIndex recoveryOptionIndex: Int) -> Bool
> 
> }
> 
> 
> extension
>  RecoverableError {
>   
> /// By default, implements document-modal recovery via application-model
> 
>   
> /// recovery.
> 
>   
> func attemptRecovery(optionIndex recoveryOptionIndex: Int
> ,
>                        
> andThen resultHandler: (Bool) -> Void
> ) {
>     resultHandler(recovered: attemptRecovery(optionIndex: 
> recoveryOptionIndex))
>   }
> }
> 
> Error types that conform to RecoverableError may be given an opportunity to 
> recover from the error. The user can be presented with some number of 
> (localized) recovery options, described by recoveryOptions, and the selected 
> option will be passed to the appropriate attemptRecovery method.
> 
> The CustomNSError protocol describes an error that wants to provide custom 
> NSError information. This can be used, e.g., to provide a specific 
> domain/code or to populate NSError's userInfo dictionary with values for 
> custom keys that can be accessed from Objective-C code but are not covered by 
> the other protocols.
> 
> /// Describes an error type that fills in the userInfo directly.
> protocol CustomNSError :
>  Error {
>   
> var errorDomain: String { get
>  }
>   
> var errorCode: Int { get
>  }
>   
> var errorUserInfo: [String : AnyObject] { get
>  }
> }
> 
> Note that, unlike with NSError, the provided errorUserInfo requires String 
> keys. This is in line with common practice for NSError and is important for 
> the implementation (see below). All of these properties are defaulted, so one 
> can provide any subset:
> 
> extension
>  CustomNSError {
>   
> var errorDomain: String { ...
>  }
>   
> var errorCode: Int { ...
>  }
>   
> var errorUserInfo: [String : AnyObject] { ...
>  }
> }
> 
> Mapping error types to NSError
> 
> Every type that conforms to the Error protocol is implicitly bridged to 
> NSError. This has been the case since Swift 2, where the compiler provides a 
> domain (i.e., the mangled name of the type) and code (based on the 
> discriminator of the enumeration type). This proposal also allows for the 
> userInfo dictionary to be populated by the runtime, which will check for 
> conformance to the various protocols (LocalizedError, RecoverableError, or 
> CustomNSError) to retrieve information.
> 
> Conceptually, this could be implemented by eagerly creating a userInfo 
> dictionary for a given instance of Error:
> 
> func createUserInfo(error: Error) -> [NSObject : AnyObject
> ] {
>   
> var userInfo: [NSObject : AnyObject] = [:
> ]
> 
>   
> // Retrieve custom userInfo information.
> 
>   
> if let customUserInfoError = error as?
>  CustomNSError {
>     userInfo 
> = customUserInfoError.
> userInfo
>   }
> 
>   
> if let localizedError = error as?
>  LocalizedError {
>     
> if let description = localizedError.
> errorDescription {
>       userInfo[NSLocalizedDescriptionKey] 
> =
>  description
>     }
> 
>     
> if let reason = localizedError.
> failureReason {
>       userInfo[NSLocalizedFailureReasonErrorKey] 
> =
>  reason
>     }
> 
>     
> if let suggestion = localizedError.
> recoverySuggestion {   
>       userInfo[NSLocalizedRecoverySuggestionErrorKey] 
> =
>  suggestion
>     }
> 
>     
> if let helpAnchor = localizedError.
> helpAnchor {   
>       userInfo[NSHelpAnchorErrorKey] 
> =
>  helpAnchor
>     }
>   }
> 
>   
> if let recoverableError = error as?
>  RecoverableError {
>     userInfo[NSLocalizedRecoveryOptionsErrorKey] 
> = recoverableError.
> recoveryOptions
>     userInfo[NSRecoveryAttempterErrorKey] 
> =
>  RecoveryAttempter()
>   }
> }
> 
> The RecoveryAttempter class is an implementation detail. It will implement 
> the informal protocol NSErrorRecoveryAttempting for the given error:
> 
> class RecoveryAttempter :
>  NSObject {
>   
> @objc
> (attemptRecoveryFromError:optionIndex:delegate:didRecoverSelector:contextInfo:)
>   
> func attemptRecovery(fromError nsError
> : NSError,
>                        
> optionIndex recoveryOptionIndex: Int
> ,
>                        delegate: 
> AnyObject
> ?,
>                        didRecoverSelector: Selector,
>                        contextInfo: 
> UnsafeMutablePointer<Void>
> ) {
>     
> let error = nsError as!
>  RecoverableError
>     error
> .attemptRecovery(optionIndex: recoveryOptionIndex) { success in
> 
>       
> // Exposition only: this part will actually have to be
> 
>       
> // implemented in Objective-C to pass the BOOL and void* through.
> 
>       delegate?
> .
> perform(didRecoverSelector, with: success, with: contextInfo)
>     }
>   }
> 
>   
> @objc
> (attemptRecoveryFromError:optionIndex:)
>   
> func attemptRecovery(fromError nsError
> : NSError,
>                        
> optionIndex recoveryOptionIndex: Int) -> Bool
>  {
>     
> let error = nsError as!
>  RecoverableError
>     
> return error.
> attemptRecovery(optionIndex: recoveryOptionIndex)
>   }
> }
> 
> The actual the population of the userInfo dictionary should not be eager. 
> NSError provides the notion of global "user info value providers" that it 
> uses to lazily request the values for certain keys, via 
> setUserInfoValueProvider(forDomain:provider:), which is declared as:
> 
> extension
>  NSError {
>   
> @available(OSX 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *
> )
>   
> class func setUserInfoValueProvider(forDomain errorDomain: String
> ,
>                                       provider: ((NSError, 
> String) -> AnyObject
> ?)? = nil)
> }
> 
> The runtime would need to register a user info value provider for each error 
> type the first time it is bridged into NSError, supplying the domain and the 
> following user info value provider function:
> 
> func userInfoValueProvider(nsError: NSError, key: String) -> AnyObject
> ? {
>   
> let error = nsError as!
>  Error
>   
> switch
>  key {
>   
> case
>  NSLocalizedDescriptionKey:
>     
> return (error as? LocalizedError)?.
> errorDescription
> 
>   
> case
>  NSLocalizedFailureReasonErrorKey:
>     
> return (error as? LocalizedError)?.
> failureReason
> 
>   
> case
>  NSLocalizedRecoverySuggestionErrorKey:
>     
> return (error as? LocalizedError)?.
> recoverySuggestion
> 
>   
> case
>  NSHelpAnchorErrorKey:
>     
> return (error as? LocalizedError)?.
> helpAnchor
> 
>   
> case
>  NSLocalizedRecoveryOptionsErrorKey:
>     
> return (error as? RecoverableError)?.
> recoveryOptions
> 
>   
> case
>  NSRecoveryAttempterErrorKey:
>     
> return error is RecoverableError ? RecoveryAttempter() : nil
> 
> 
>   
> default
> :
>     
> guard let customUserInfoError = error as? CustomNSError else { return nil
>  }
>     
> return customUserInfoError.
> userInfo[key]
>   }
> }
> 
> On platforms that predate the introduction of user info value providers, 
> there are alternate implementation strategies, including introducing a custom 
> NSDictionary subclass to use as the userInfo in the NSError that lazily 
> populates the dictionary by, effectively, calling the userInfoValueProvider 
> function above for each requested key. Or, one could eagerly populate 
> userInfo on older platforms.
> 
> Importing error types from Objective-C
> 
> In Objective-C, error domains are typically constructed using an enumeration 
> describing the error codes and a constant describing the error domain, e.g,
> 
> extern NSString *const
>  AVFoundationErrorDomain;
> 
> 
> typedef NS_ENUM(NSInteger
> , AVError) {
>   AVErrorUnknown                                      = -
> 11800
> ,
>   AVErrorOutOfMemory                                  = -
> 11801
> ,
>   AVErrorSessionNotRunning                            = -
> 11803
> ,
>   AVErrorDeviceAlreadyUsedByAnotherSession            = -
> 11804
> ,
>   
> // ...
> 
> }
> 
> This is currently imported as an enum that conforms to Error:
> 
> enum AVError : Int
>  {
>   
> case unknown                                      = -11800
> 
>   
> case outOfMemory                                  = -11801
> 
>   
> case sessionNotRunning                            = -11803
> 
>   
> case deviceAlreadyUsedByAnotherSession            = -11804
> 
> 
>   
> static var _domain: String { return
>  AVFoundationErrorDomain }
> }
> 
> and Swift code introduces an extension that makes it an Error, along with 
> some implementation magic to allow bridging from an NSError (losing userInfo 
> in the process):
> 
> extension AVError :
>  Error {
>   
> static var _domain: String { return
>  AVFoundationErrorDomain }
> }
> 
> Instead, error enums should be expressed with a new macro, NS_ERROR_ENUM, 
> that ties together the code and domain in the Objective-C header:
> 
> extern NSString *const
>  AVFoundationErrorDomain;
> 
> 
> typedef NS_ERROR_ENUM(NSInteger
> , AVError, AVFoundationErrorDomain) {
>   AVErrorUnknown                                      = -
> 11800
> ,
>   AVErrorOutOfMemory                                  = -
> 11801
> ,
>   AVErrorSessionNotRunning                            = -
> 11803
> ,
>   AVErrorDeviceAlreadyUsedByAnotherSession            = -
> 11804
> ,
>   
> // ...
> 
> }
> 
> This will import as a new struct AVError that contains an NSError, so there 
> is no information loss. The actual enum will become a nested type Code, so 
> that it is still accessible. The resulting struct will be as follows:
> 
> struct
>  AVError {
>   
> /// Stored NSError. Note that error.domain == AVFoundationErrorDomain is an 
> invariant.
> 
>   
> private var
>  error: NSError
> 
>   
> /// Describes the error codes; directly imported from AVError
> 
>   
> enum Code : Int
> , ErrorCodeProtocol {
>     
> typealias ErrorType =
>  AVError
> 
>     
> case unknown                                      = -11800
> 
>     
> case outOfMemory                                  = -11801
> 
>     
> case sessionNotRunning                            = -11803
> 
>     
> case deviceAlreadyUsedByAnotherSession            = -11804
> 
> 
>     
> func errorMatchesCode(_ error: AVError) -> Bool
>  {
>       
> return error.code == self
> 
>     }
>   }
> 
>   
> /// Allow one to create an error (optionally) with a userInfo dictionary.
> 
>   
> init(_ code: Code, userInfo: [NSObject: AnyObject] = [:
> ]) {
>     error 
> = NSError(code: code.rawValue
> , domain: _domain, userInfo: userInfo)
>   }
> 
>   
> /// Retrieve the code.
> 
>   
> var code: Code { return Code(rawValue: error.code)!
>  }
> 
>   
> /// Allow direct access to the userInfo dictionary.
> 
>   
> var userInfo: [NSObject: AnyObject] { return error.
> userInfo }
> 
>   
> /// Make it easy to refer to constants without context.
> 
>   
> static let unknown: Code = .
> unknown
>   
> static let outOfMemory: Code = .
> outOfMemory
>   
> static let sessionNotRunning: Code = .
> sessionNotRunning
>   
> static let deviceAlreadyUsedByAnotherSession: Code = .
> deviceAlreadyUsedByAnotherSession
> }
> 
> 
> // Implementation detail: makes AVError conform to Error
> extension AVError :
>  Error {
>   
> static var _domain: String { return
>  AVFoundationErrorDomain }
> 
>   
> var _code: Int { return error.
> code }
> }
> 
> This syntax allows one to throw specific errors fairly easily, with or 
> without userInfo dictionaries:
> 
> throw AVError(.
> sessionNotRunning)
> 
> throw AVError(.sessionNotRunning, userInfo: [ ... ])
> The ImportedErrorCode protocol is a helper so that we can define a general ~= 
> operator, which is used by both switchcase matching and catch blocks:
> 
> protocol
>  ErrorCodeProtocol {
>   
> typealias ErrorType :
>  Error
> 
>   
> func errorMatchesCode(_ error: ErrorType) -> Bool
> 
> }
> 
> 
> func ~= <EC: ErrorCodeProtocol> (error: Error, code: EC) -> Bool
>  {
>   
> guard let myError = error as? EC.ErrorType else { return false
>  }
>   
> return code.
> errorMatchesCode(myError)
> }
> 
> Mapping NSError types back into Swift
> 
> When an NSError object bridged to an Error instance, it may be immediately 
> mapped back to a Swift error type (e.g., if the error was created as a 
> HomeworkError instance in Swift and then passed through NSError unmodified) 
> or it might be leave as an instance of NSError. The error might then be catch 
> as a particular Swift error type, e.g.,
> 
> catch let error as AVError where error.code == .
> sessionNotRunning {
>   
> // able to access userInfo here!
> 
> }
> 
> In this case, the mapping from an NSError instance to AVError goes through an 
> implementation-detail protocol_ObjectiveCBridgeableError:
> 
> protocol _ObjectiveCBridgeableError :
>  Error {
>   
> /// Produce a value of the error type corresponding to the given NSError,
> 
>   
> /// or return nil if it cannot be bridged.
> 
>   
> init
> ?(_bridgedNSError error: NSError)
> }
> 
> The initializer is responsible for checking the domain and (optionally) the 
> code of the incoming NSError to map it to an instance of the Swift error 
> type. For example, AVError would adopt this protocol as follows:
> 
> // Implementation detail: makes AVError conform to _ObjectiveCBridgeableError
> extension AVError :
>  _ObjectiveCBridgeableError {
>   
> init
> ?(_bridgedNSError error: NSError) {
>     
> // Check whether the error comes from the AVFoundation error domain
> 
>     
> if error.domain != AVFoundationErrorDomain { return nil
>  }
> 
>     
> // Save the error
> 
>     
> self.error =
>  error
>   }
> }
> 
> We do not propose that _ObjectiveCBridgeableError become a public protocol, 
> because the core team has already deferred a similar proposal (SE-0058) to 
> make the related protocol _ObjectiveCBridgeable public. 
> 
> Other Issues
> 
> NSError codes and domains are important for localization of error messages. 
> This is barely supported today by genstrings, but becomes considerably harder 
> when the domain and code are hidden (as they are in Swift). We would need to 
> consider tooling to make it easier to localize error descriptions, recovery 
> options, etc. in a sensible way. Although this is out of the scope of the 
> Swift language per se, it's an important part of the developer story.
> 
> Impact on existing code
> 
> This is a major source-breaking change for Objective-C APIs that operate on 
> NSError values, because those parameter/return/property types will change 
> from NSError to Error. There are ~400 such APIs in the macOS SDK, and closer 
> to 500 in the iOS SDK, which is a sizable number. Fortunately, this is 
> similar in scope to the Foundation value types proposal, and can use the same 
> code migration mechanism. That said, the scale of this change means that it 
> should either happen in Swift 3 or not at all.
> 
> Future directions
> 
> Better tooling for describing errors
> 
> When adopting one of the new protocols (e.g., LocalizedError) in an enum, one 
> will inevitably end up with a number of switch statements that have to 
> enumerate all of the cases, leading to a lot of boilerplate. Better tooling 
> could improve the situation considerably: for example, one could use 
> something like Cocoa's stringsdict files to provide localized strings 
> identified by the enum name, case name, and property. That would eliminate 
> the need for the switch-on-all-cases implementations of each property.
> 
> Round-tripping errors through userInfo
> 
> The CustomNSError protocol allows one to place arbitrary key/value pairs into 
> NSError's userInfo dictionary. The implementation-detail 
> _ObjectiveCBridgeableError protocol allows one to control how a raw NSError 
> is mapped to a particular error type. One could effectively serialize the 
> entire state of a particular error type into the userInfo dictionary 
> viaCustomNSError, then restore it via _ObjectiveCBridgeableError, allowing 
> one to form a complete NSError in Objective-C that can reconstitute itself as 
> a particular Swift error type, which can be useful both for mixed-source 
> projects and (possibly) as a weak form of serialization for NSErrors.
> 
> Alternatives considered
> 
> Exposing the domain, code, and user-info dictionary directly
> 
> This proposal does not directly expose the domain, code, or user-info 
> dictionary on ErrorProtocol, because these notions are superseded by Swift's 
> strong typing of errors. The domain is effectively subsumed by the type of 
> the error (e.g., a Swift-defined error type uses its mangled name as the 
> domain); the code is some type-specific value (e.g., the discriminator of the 
> enum); and the user-info dictionary is an untyped set of key-value pairs that 
> are better expressed in Swift as data on the specific error type.
> 
> Bridging NSError to a new value type Error
> 
> One could introduce a new value type, Error, that stores a domain, code, and 
> user-info dictionary but provides them with value semantics. Doing so would 
> make it easier to create "generic" errors that carry some information. 
> However, we feel that introducing new error types in Swift is already easier 
> than establishing a new domain and a set of codes, because a new enum type 
> provides this information naturally in Swift.
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to