Skip to content

Commit 7ac79ae

Browse files
committed
[Runtime][4.2] Extend ObjC bridging casts to convert NSError to Error when nested in a container type.
rdar://problem/39349762
1 parent dab0d8e commit 7ac79ae

File tree

4 files changed

+79
-57
lines changed

4 files changed

+79
-57
lines changed

stdlib/public/runtime/Casting.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3150,6 +3150,12 @@ static bool tryBridgeNonVerbatimFromObjectiveCUniversal(
31503150
return true;
31513151
}
31523152
}
3153+
// Try to bridge NSError to Error.
3154+
if (tryDynamicCastNSErrorObjectToValue(sourceValue, destValue, nativeType,
3155+
DynamicCastFlags::Default)) {
3156+
return true;
3157+
}
3158+
31533159

31543160
return false;
31553161
}

stdlib/public/runtime/ErrorObject.h

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

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

stdlib/public/runtime/ErrorObject.mm

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

465465
bool
466-
swift::tryDynamicCastNSErrorToValue(OpaqueValue *dest,
467-
OpaqueValue *src,
468-
const Metadata *srcType,
469-
const Metadata *destType,
470-
DynamicCastFlags flags) {
466+
swift::tryDynamicCastNSErrorObjectToValue(HeapObject *object,
467+
OpaqueValue *dest,
468+
const Metadata *destType,
469+
DynamicCastFlags flags) {
471470
Class NSErrorClass = getNSErrorClass();
472-
auto CFErrorTypeID = SWIFT_LAZY_CONSTANT(CFErrorGetTypeID());
473-
474-
NSError *srcInstance;
475471

476-
// Is the input type an NSError?
477-
switch (srcType->getKind()) {
478-
case MetadataKind::Class:
479-
case MetadataKind::ObjCClassWrapper:
480-
// Native class or ObjC class should be an NSError subclass.
481-
if (![srcType->getObjCClassObject() isSubclassOfClass: NSErrorClass])
482-
return false;
483-
484-
srcInstance = *reinterpret_cast<NSError * const*>(src);
485-
486-
// A _SwiftNativeNSError box can always be unwrapped to cast the value back
487-
// out as an Error existential.
488-
if (!reinterpret_cast<SwiftError*>(srcInstance)->isPureNSError()) {
489-
auto theErrorProtocol = &PROTOCOL_DESCR_SYM(s5Error);
490-
auto theErrorTy =
491-
swift_getExistentialTypeMetadata(ProtocolClassConstraint::Any,
492-
nullptr, 1, &theErrorProtocol);
493-
return swift_dynamicCast(dest, src, theErrorTy, destType, flags);
494-
}
495-
496-
break;
497-
case MetadataKind::ForeignClass: {
498-
// Foreign class should be CFError.
499-
CFTypeRef srcInstance = *reinterpret_cast<CFTypeRef *>(src);
500-
if (CFGetTypeID(srcInstance) != CFErrorTypeID)
501-
return false;
502-
break;
503-
}
504-
// Not a class.
505-
case MetadataKind::Enum:
506-
case MetadataKind::Optional:
507-
case MetadataKind::Existential:
508-
case MetadataKind::ExistentialMetatype:
509-
case MetadataKind::Function:
510-
case MetadataKind::HeapLocalVariable:
511-
case MetadataKind::HeapGenericLocalVariable:
512-
case MetadataKind::ErrorObject:
513-
case MetadataKind::Metatype:
514-
case MetadataKind::Opaque:
515-
case MetadataKind::Struct:
516-
case MetadataKind::Tuple:
472+
// The object must be an NSError subclass.
473+
if (![reinterpret_cast<id>(object) isKindOfClass: NSErrorClass])
517474
return false;
475+
476+
NSError *srcInstance = reinterpret_cast<NSError *>(object);
477+
478+
// A _SwiftNativeNSError box can always be unwrapped to cast the value back
479+
// out as an Error existential.
480+
if (!reinterpret_cast<SwiftError*>(srcInstance)->isPureNSError()) {
481+
auto theErrorProtocol = &PROTOCOL_DESCR_SYM(s5Error);
482+
auto theErrorTy =
483+
swift_getExistentialTypeMetadata(ProtocolClassConstraint::Any,
484+
nullptr, 1, &theErrorProtocol);
485+
return swift_dynamicCast(dest, reinterpret_cast<OpaqueValue *>(&object),
486+
theErrorTy, destType, flags);
518487
}
519488

520489
// public func Foundation._bridgeNSErrorToError<
@@ -533,19 +502,47 @@ NSInteger getErrorCode(const OpaqueValue *error,
533502
auto witness = swift_conformsToProtocol(destType,
534503
TheObjectiveCBridgeableError);
535504

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

539-
// If so, attempt the bridge.
540-
SWIFT_CC_PLUSONE_GUARD(objc_retain(srcInstance));
541-
if (bridgeNSErrorToError(srcInstance, dest, destType, witness)) {
542-
if (flags & DynamicCastFlags::TakeOnSuccess)
543-
objc_release(srcInstance);
514+
// If the destination is just an Error then we can bridge directly.
515+
auto *destTypeExistential = dyn_cast<ExistentialTypeMetadata>(destType);
516+
if (destTypeExistential &&
517+
destTypeExistential->getRepresentation() == ExistentialTypeRepresentation::Error) {
518+
auto destBoxAddr = reinterpret_cast<NSError**>(dest);
519+
*destBoxAddr = objc_retain(srcInstance);
544520
return true;
545521
}
522+
546523
return false;
547524
}
548525

526+
bool
527+
swift::tryDynamicCastNSErrorToValue(OpaqueValue *dest,
528+
OpaqueValue *src,
529+
const Metadata *srcType,
530+
const Metadata *destType,
531+
DynamicCastFlags flags) {
532+
// NSError instances must be class instances, anything else automatically fails.
533+
switch (srcType->getKind()) {
534+
case MetadataKind::Class:
535+
case MetadataKind::ObjCClassWrapper:
536+
case MetadataKind::ForeignClass:
537+
return tryDynamicCastNSErrorObjectToValue(*reinterpret_cast<HeapObject **>(src),
538+
dest, destType, flags);
539+
540+
// Not a class.
541+
default:
542+
return false;
543+
}
544+
}
545+
549546
SwiftError *
550547
swift::swift_errorRetain(SwiftError *error) {
551548
// 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)