I believe the capability for Error
to be convertible to NSError
is hardcoded into the compiler, and the actual bridging is implemented in the Swift runtime.
In runtime/ErrorObject.mm
, I found this comment:
// This implements the object representation of the standard Error
// type, which represents recoverable errors in the language. This
// implementation is designed to interoperate efficiently with Cocoa libraries
// by:
// - ...
// - allowing a native Swift error to lazily "become" an NSError when
// passed into Cocoa, allowing for cheap Swift to Cocoa interop
And this function:
/// Take an Error box and turn it into a valid NSError instance.
id
swift::_swift_stdlib_bridgeErrorToNSError(SwiftError *errorObject) {
...
// Otherwise, calculate the domain, code, and user info, and
// initialize the NSError.
auto value = SwiftError::getIndirectValue(&errorObject);
auto type = errorObject->getType();
auto witness = errorObject->getErrorConformance();
NSString *domain = getErrorDomainNSString(value, type, witness);
NSInteger code = getErrorCode(value, type, witness);
NSDictionary *userInfo = getErrorUserInfoNSDictionary(value, type, witness);
...
}
The ErrorHandling.rst document says this about the rationale:
It should be possible to turn an arbitrary Swift enum that conforms to Error
into an NSError
by using the qualified type name as the domain key, the enumerator as the error code, and turning the payload into user data.
(Parts of the document may be outdated.)
And this is (I think) at least one part in the type checker were the information that Error
is convertible to NSError
is encoded (there are probably more):
// Check whether the type is an existential that contains
// Error. If so, it's bridged to NSError.
if (type->isExistentialWithError()) {
if (auto nsErrorDecl = getNSErrorDecl()) {
// The corresponding value type is Error.
if (bridgedValueType)
*bridgedValueType = getErrorDecl()->getDeclaredInterfaceType();
return nsErrorDecl->getDeclaredInterfaceType();
}
}