diff --git a/stdlib/public/runtime/Casting.cpp b/stdlib/public/runtime/Casting.cpp index 626e18f01f56e..32c7440c569aa 100644 --- a/stdlib/public/runtime/Casting.cpp +++ b/stdlib/public/runtime/Casting.cpp @@ -2941,6 +2941,12 @@ static bool tryBridgeNonVerbatimFromObjectiveCUniversal( return true; } } + // Try to bridge NSError to Error. + if (tryDynamicCastNSErrorObjectToValue(sourceValue, destValue, nativeType, + DynamicCastFlags::Default)) { + return true; + } + return false; } diff --git a/stdlib/public/runtime/ErrorObject.h b/stdlib/public/runtime/ErrorObject.h index 43358f0f7b0cd..8d4cdfe15fe76 100644 --- a/stdlib/public/runtime/ErrorObject.h +++ b/stdlib/public/runtime/ErrorObject.h @@ -223,8 +223,17 @@ void swift_unexpectedError(SwiftError *object); SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_SPI id _swift_stdlib_bridgeErrorToNSError(SwiftError *errorObject); +/// Attempt to dynamically cast an NSError object to a Swift ErrorType +/// implementation using the _ObjectiveCBridgeableErrorType protocol or by +/// putting it directly into an Error existential. +bool tryDynamicCastNSErrorObjectToValue(HeapObject *object, + OpaqueValue *dest, + const Metadata *destType, + DynamicCastFlags flags); + /// Attempt to dynamically cast an NSError instance to a Swift ErrorType -/// implementation using the _ObjectiveCBridgeableErrorType protocol. +/// implementation using the _ObjectiveCBridgeableErrorType protocol or by +/// putting it directly into an Error existential. /// /// srcType must be some kind of class metadata. bool tryDynamicCastNSErrorToValue(OpaqueValue *dest, diff --git a/stdlib/public/runtime/ErrorObject.mm b/stdlib/public/runtime/ErrorObject.mm index d67b4cd78f517..e5b3a395e86ec 100644 --- a/stdlib/public/runtime/ErrorObject.mm +++ b/stdlib/public/runtime/ErrorObject.mm @@ -462,47 +462,27 @@ NSInteger getErrorCode(const OpaqueValue *error, extern "C" const ProtocolDescriptor PROTOCOL_DESCR_SYM(s5Error); bool -swift::tryDynamicCastNSErrorToValue(OpaqueValue *dest, - OpaqueValue *src, - const Metadata *srcType, - const Metadata *destType, - DynamicCastFlags flags) { +swift::tryDynamicCastNSErrorObjectToValue(HeapObject *object, + OpaqueValue *dest, + const Metadata *destType, + DynamicCastFlags flags) { Class NSErrorClass = getNSErrorClass(); - auto CFErrorTypeID = SWIFT_LAZY_CONSTANT(CFErrorGetTypeID()); - NSError *srcInstance; - - // Is the input type an NSError? - switch (srcType->getKind()) { - case MetadataKind::Class: - case MetadataKind::ObjCClassWrapper: - // Native class or ObjC class should be an NSError subclass. - if (![srcType->getObjCClassObject() isSubclassOfClass: NSErrorClass]) - return false; - - srcInstance = *reinterpret_cast(src); - - // A _SwiftNativeNSError box can always be unwrapped to cast the value back - // out as an Error existential. - if (!reinterpret_cast(srcInstance)->isPureNSError()) { - auto theErrorProtocol = &PROTOCOL_DESCR_SYM(s5Error); - auto theErrorTy = - swift_getExistentialTypeMetadata(ProtocolClassConstraint::Any, - nullptr, 1, &theErrorProtocol); - return swift_dynamicCast(dest, src, theErrorTy, destType, flags); - } - - break; - case MetadataKind::ForeignClass: { - // Foreign class should be CFError. - CFTypeRef srcInstance = *reinterpret_cast(src); - if (CFGetTypeID(srcInstance) != CFErrorTypeID) - return false; - break; - } - // Not a class. - default: + // The object must be an NSError subclass. + if (![reinterpret_cast(object) isKindOfClass: NSErrorClass]) return false; + + NSError *srcInstance = reinterpret_cast(object); + + // A _SwiftNativeNSError box can always be unwrapped to cast the value back + // out as an Error existential. + if (!reinterpret_cast(srcInstance)->isPureNSError()) { + auto theErrorProtocol = &PROTOCOL_DESCR_SYM(s5Error); + auto theErrorTy = + swift_getExistentialTypeMetadata(ProtocolClassConstraint::Any, + nullptr, 1, &theErrorProtocol); + return swift_dynamicCast(dest, reinterpret_cast(&object), + theErrorTy, destType, flags); } // public func Foundation._bridgeNSErrorToError< @@ -521,19 +501,47 @@ NSInteger getErrorCode(const OpaqueValue *error, auto witness = swift_conformsToProtocol(destType, TheObjectiveCBridgeableError); - if (!witness) - return false; + if (witness) { + // If so, attempt the bridge. + if (bridgeNSErrorToError(srcInstance, dest, destType, witness)) { + if (flags & DynamicCastFlags::TakeOnSuccess) + objc_release(srcInstance); + return true; + } + } - // If so, attempt the bridge. - SWIFT_CC_PLUSONE_GUARD(objc_retain(srcInstance)); - if (bridgeNSErrorToError(srcInstance, dest, destType, witness)) { - if (flags & DynamicCastFlags::TakeOnSuccess) - objc_release(srcInstance); + // If the destination is just an Error then we can bridge directly. + auto *destTypeExistential = dyn_cast(destType); + if (destTypeExistential && + destTypeExistential->getRepresentation() == ExistentialTypeRepresentation::Error) { + auto destBoxAddr = reinterpret_cast(dest); + *destBoxAddr = objc_retain(srcInstance); return true; } + return false; } +bool +swift::tryDynamicCastNSErrorToValue(OpaqueValue *dest, + OpaqueValue *src, + const Metadata *srcType, + const Metadata *destType, + DynamicCastFlags flags) { + // NSError instances must be class instances, anything else automatically fails. + switch (srcType->getKind()) { + case MetadataKind::Class: + case MetadataKind::ObjCClassWrapper: + case MetadataKind::ForeignClass: + return tryDynamicCastNSErrorObjectToValue(*reinterpret_cast(src), + dest, destType, flags); + + // Not a class. + default: + return false; + } +} + SwiftError * swift::swift_errorRetain(SwiftError *error) { // For now, SwiftError is always objc-refcounted. diff --git a/test/stdlib/ErrorBridged.swift b/test/stdlib/ErrorBridged.swift index dcce4ae45cbcf..05121d19c82bc 100644 --- a/test/stdlib/ErrorBridged.swift +++ b/test/stdlib/ErrorBridged.swift @@ -276,6 +276,16 @@ ErrorBridgingTests.test("Error-to-NSError bridging") { expectEqual(NoisyErrorDeathCount, NoisyErrorLifeCount) } +ErrorBridgingTests.test("NSError-to-error bridging in bridged container") { + autoreleasepool { + let error = NSError(domain: "domain", code: 42, userInfo: nil) + let nsdictionary = ["error": error] as NSDictionary + let dictionary = nsdictionary as? Dictionary + expectNotNil(dictionary) + expectEqual(error, dictionary?["error"] as NSError?) + } +} + ErrorBridgingTests.test("enum-to-NSError round trip") { autoreleasepool { // Emulate throwing an error from Objective-C.