Merge pull request #17515 from mikeash/nested-nserror-to-error-bridging
[Runtime] Extend ObjC bridging casts to convert NSError to Error when nested in a container type.
diff --git a/stdlib/public/runtime/Casting.cpp b/stdlib/public/runtime/Casting.cpp
index 626e18f..32c7440 100644
--- a/stdlib/public/runtime/Casting.cpp
+++ b/stdlib/public/runtime/Casting.cpp
@@ -2941,6 +2941,12 @@
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 43358f0..8d4cdfe 100644
--- a/stdlib/public/runtime/ErrorObject.h
+++ b/stdlib/public/runtime/ErrorObject.h
@@ -223,8 +223,17 @@
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 d67b4cd..e5b3a39 100644
--- a/stdlib/public/runtime/ErrorObject.mm
+++ b/stdlib/public/runtime/ErrorObject.mm
@@ -462,47 +462,27 @@
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<NSError * const*>(src);
-
- // A _SwiftNativeNSError box can always be unwrapped to cast the value back
- // out as an Error existential.
- if (!reinterpret_cast<SwiftError*>(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<CFTypeRef *>(src);
- if (CFGetTypeID(srcInstance) != CFErrorTypeID)
- return false;
- break;
- }
- // Not a class.
- default:
+ // The object must be an NSError subclass.
+ if (![reinterpret_cast<id>(object) isKindOfClass: NSErrorClass])
return false;
+
+ NSError *srcInstance = reinterpret_cast<NSError *>(object);
+
+ // A _SwiftNativeNSError box can always be unwrapped to cast the value back
+ // out as an Error existential.
+ if (!reinterpret_cast<SwiftError*>(srcInstance)->isPureNSError()) {
+ auto theErrorProtocol = &PROTOCOL_DESCR_SYM(s5Error);
+ auto theErrorTy =
+ swift_getExistentialTypeMetadata(ProtocolClassConstraint::Any,
+ nullptr, 1, &theErrorProtocol);
+ return swift_dynamicCast(dest, reinterpret_cast<OpaqueValue *>(&object),
+ theErrorTy, destType, flags);
}
// public func Foundation._bridgeNSErrorToError<
@@ -521,19 +501,47 @@
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<ExistentialTypeMetadata>(destType);
+ if (destTypeExistential &&
+ destTypeExistential->getRepresentation() == ExistentialTypeRepresentation::Error) {
+ auto destBoxAddr = reinterpret_cast<NSError**>(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<HeapObject **>(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 dcce4ae..05121d1 100644
--- a/test/stdlib/ErrorBridged.swift
+++ b/test/stdlib/ErrorBridged.swift
@@ -276,6 +276,16 @@
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<String, Error>
+ expectNotNil(dictionary)
+ expectEqual(error, dictionary?["error"] as NSError?)
+ }
+}
+
ErrorBridgingTests.test("enum-to-NSError round trip") {
autoreleasepool {
// Emulate throwing an error from Objective-C.