Skip to content

[Runtime] Extend ObjC bridging casts to convert NSError to Error when nested in a container type. #17515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
11 changes: 10 additions & 1 deletion stdlib/public/runtime/ErrorObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
98 changes: 53 additions & 45 deletions stdlib/public/runtime/ErrorObject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<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<
Expand All @@ -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<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.
Expand Down
10 changes: 10 additions & 0 deletions test/stdlib/ErrorBridged.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<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.
Expand Down