Skip to content

Commit e32b77a

Browse files
authored
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.
2 parents a4230ab + e992f46 commit e32b77a

File tree

4 files changed

+79
-46
lines changed

4 files changed

+79
-46
lines changed

stdlib/public/runtime/Casting.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2941,6 +2941,12 @@ static bool tryBridgeNonVerbatimFromObjectiveCUniversal(
29412941
return true;
29422942
}
29432943
}
2944+
// Try to bridge NSError to Error.
2945+
if (tryDynamicCastNSErrorObjectToValue(sourceValue, destValue, nativeType,
2946+
DynamicCastFlags::Default)) {
2947+
return true;
2948+
}
2949+
29442950

29452951
return false;
29462952
}

stdlib/public/runtime/ErrorObject.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,17 @@ void swift_unexpectedError(SwiftError *object);
223223
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_SPI
224224
id _swift_stdlib_bridgeErrorToNSError(SwiftError *errorObject);
225225

226+
/// Attempt to dynamically cast an NSError object to a Swift ErrorType
227+
/// implementation using the _ObjectiveCBridgeableErrorType protocol or by
228+
/// putting it directly into an Error existential.
229+
bool tryDynamicCastNSErrorObjectToValue(HeapObject *object,
230+
OpaqueValue *dest,
231+
const Metadata *destType,
232+
DynamicCastFlags flags);
233+
226234
/// Attempt to dynamically cast an NSError instance to a Swift ErrorType
227-
/// implementation using the _ObjectiveCBridgeableErrorType protocol.
235+
/// implementation using the _ObjectiveCBridgeableErrorType protocol or by
236+
/// putting it directly into an Error existential.
228237
///
229238
/// srcType must be some kind of class metadata.
230239
bool tryDynamicCastNSErrorToValue(OpaqueValue *dest,

stdlib/public/runtime/ErrorObject.mm

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -462,47 +462,27 @@ NSInteger getErrorCode(const OpaqueValue *error,
462462
extern "C" const ProtocolDescriptor PROTOCOL_DESCR_SYM(s5Error);
463463

464464
bool
465-
swift::tryDynamicCastNSErrorToValue(OpaqueValue *dest,
466-
OpaqueValue *src,
467-
const Metadata *srcType,
468-
const Metadata *destType,
469-
DynamicCastFlags flags) {
465+
swift::tryDynamicCastNSErrorObjectToValue(HeapObject *object,
466+
OpaqueValue *dest,
467+
const Metadata *destType,
468+
DynamicCastFlags flags) {
470469
Class NSErrorClass = getNSErrorClass();
471-
auto CFErrorTypeID = SWIFT_LAZY_CONSTANT(CFErrorGetTypeID());
472470

473-
NSError *srcInstance;
474-
475-
// Is the input type an NSError?
476-
switch (srcType->getKind()) {
477-
case MetadataKind::Class:
478-
case MetadataKind::ObjCClassWrapper:
479-
// Native class or ObjC class should be an NSError subclass.
480-
if (![srcType->getObjCClassObject() isSubclassOfClass: NSErrorClass])
481-
return false;
482-
483-
srcInstance = *reinterpret_cast<NSError * const*>(src);
484-
485-
// A _SwiftNativeNSError box can always be unwrapped to cast the value back
486-
// out as an Error existential.
487-
if (!reinterpret_cast<SwiftError*>(srcInstance)->isPureNSError()) {
488-
auto theErrorProtocol = &PROTOCOL_DESCR_SYM(s5Error);
489-
auto theErrorTy =
490-
swift_getExistentialTypeMetadata(ProtocolClassConstraint::Any,
491-
nullptr, 1, &theErrorProtocol);
492-
return swift_dynamicCast(dest, src, theErrorTy, destType, flags);
493-
}
494-
495-
break;
496-
case MetadataKind::ForeignClass: {
497-
// Foreign class should be CFError.
498-
CFTypeRef srcInstance = *reinterpret_cast<CFTypeRef *>(src);
499-
if (CFGetTypeID(srcInstance) != CFErrorTypeID)
500-
return false;
501-
break;
502-
}
503-
// Not a class.
504-
default:
471+
// The object must be an NSError subclass.
472+
if (![reinterpret_cast<id>(object) isKindOfClass: NSErrorClass])
505473
return false;
474+
475+
NSError *srcInstance = reinterpret_cast<NSError *>(object);
476+
477+
// A _SwiftNativeNSError box can always be unwrapped to cast the value back
478+
// out as an Error existential.
479+
if (!reinterpret_cast<SwiftError*>(srcInstance)->isPureNSError()) {
480+
auto theErrorProtocol = &PROTOCOL_DESCR_SYM(s5Error);
481+
auto theErrorTy =
482+
swift_getExistentialTypeMetadata(ProtocolClassConstraint::Any,
483+
nullptr, 1, &theErrorProtocol);
484+
return swift_dynamicCast(dest, reinterpret_cast<OpaqueValue *>(&object),
485+
theErrorTy, destType, flags);
506486
}
507487

508488
// public func Foundation._bridgeNSErrorToError<
@@ -521,19 +501,47 @@ NSInteger getErrorCode(const OpaqueValue *error,
521501
auto witness = swift_conformsToProtocol(destType,
522502
TheObjectiveCBridgeableError);
523503

524-
if (!witness)
525-
return false;
504+
if (witness) {
505+
// If so, attempt the bridge.
506+
if (bridgeNSErrorToError(srcInstance, dest, destType, witness)) {
507+
if (flags & DynamicCastFlags::TakeOnSuccess)
508+
objc_release(srcInstance);
509+
return true;
510+
}
511+
}
526512

527-
// If so, attempt the bridge.
528-
SWIFT_CC_PLUSONE_GUARD(objc_retain(srcInstance));
529-
if (bridgeNSErrorToError(srcInstance, dest, destType, witness)) {
530-
if (flags & DynamicCastFlags::TakeOnSuccess)
531-
objc_release(srcInstance);
513+
// If the destination is just an Error then we can bridge directly.
514+
auto *destTypeExistential = dyn_cast<ExistentialTypeMetadata>(destType);
515+
if (destTypeExistential &&
516+
destTypeExistential->getRepresentation() == ExistentialTypeRepresentation::Error) {
517+
auto destBoxAddr = reinterpret_cast<NSError**>(dest);
518+
*destBoxAddr = objc_retain(srcInstance);
532519
return true;
533520
}
521+
534522
return false;
535523
}
536524

525+
bool
526+
swift::tryDynamicCastNSErrorToValue(OpaqueValue *dest,
527+
OpaqueValue *src,
528+
const Metadata *srcType,
529+
const Metadata *destType,
530+
DynamicCastFlags flags) {
531+
// NSError instances must be class instances, anything else automatically fails.
532+
switch (srcType->getKind()) {
533+
case MetadataKind::Class:
534+
case MetadataKind::ObjCClassWrapper:
535+
case MetadataKind::ForeignClass:
536+
return tryDynamicCastNSErrorObjectToValue(*reinterpret_cast<HeapObject **>(src),
537+
dest, destType, flags);
538+
539+
// Not a class.
540+
default:
541+
return false;
542+
}
543+
}
544+
537545
SwiftError *
538546
swift::swift_errorRetain(SwiftError *error) {
539547
// For now, SwiftError is always objc-refcounted.

test/stdlib/ErrorBridged.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,16 @@ ErrorBridgingTests.test("Error-to-NSError bridging") {
276276
expectEqual(NoisyErrorDeathCount, NoisyErrorLifeCount)
277277
}
278278

279+
ErrorBridgingTests.test("NSError-to-error bridging in bridged container") {
280+
autoreleasepool {
281+
let error = NSError(domain: "domain", code: 42, userInfo: nil)
282+
let nsdictionary = ["error": error] as NSDictionary
283+
let dictionary = nsdictionary as? Dictionary<String, Error>
284+
expectNotNil(dictionary)
285+
expectEqual(error, dictionary?["error"] as NSError?)
286+
}
287+
}
288+
279289
ErrorBridgingTests.test("enum-to-NSError round trip") {
280290
autoreleasepool {
281291
// Emulate throwing an error from Objective-C.

0 commit comments

Comments
 (0)