Skip to content

[NSError bridging] Use embedded NSError when erasing types to Error e… #3953

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
Aug 3, 2016
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
77 changes: 77 additions & 0 deletions lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,83 @@ SILGenModule::getConformanceToObjectiveCBridgeable(SILLocation loc, Type type) {
return nullptr;
}

ProtocolDecl *SILGenModule::getBridgedStoredNSError(SILLocation loc) {
if (BridgedStoredNSError)
return *BridgedStoredNSError;

// Find the _BridgedStoredNSError protocol.
auto &ctx = getASTContext();
auto proto = ctx.getProtocol(KnownProtocolKind::BridgedStoredNSError);
BridgedStoredNSError = proto;
return proto;
}

VarDecl *SILGenModule::getNSErrorRequirement(SILLocation loc) {
if (NSErrorRequirement)
return *NSErrorRequirement;

// Find the _BridgedStoredNSError protocol.
auto proto = getBridgedStoredNSError(loc);
if (!proto) {
NSErrorRequirement = nullptr;
return nullptr;
}

// Look for _nsError.
auto &ctx = getASTContext();
VarDecl *found = nullptr;
for (auto member : proto->lookupDirect(ctx.Id_nsError, true)) {
if (auto var = dyn_cast<VarDecl>(member)) {
found = var;
break;
}
}

NSErrorRequirement = found;
return found;
}

ProtocolConformance *
SILGenModule::getConformanceToBridgedStoredNSError(SILLocation loc, Type type) {
auto proto = getBridgedStoredNSError(loc);
if (!proto) return nullptr;

// Find the conformance to _BridgedStoredNSError.
auto result = SwiftModule->lookupConformance(type, proto, nullptr);
if (result) return result->getConcrete();
return nullptr;
}

ProtocolConformance *SILGenModule::getNSErrorConformanceToError() {
if (NSErrorConformanceToError)
return *NSErrorConformanceToError;

auto &ctx = getASTContext();
auto nsError = ctx.getNSErrorDecl();
if (!nsError) {
NSErrorConformanceToError = nullptr;
return nullptr;
}

auto error = ctx.getErrorDecl();
if (!error) {
NSErrorConformanceToError = nullptr;
return nullptr;
}

auto conformance =
SwiftModule->lookupConformance(nsError->getDeclaredInterfaceType(),
cast<ProtocolDecl>(error),
nullptr);

if (conformance && conformance->isConcrete())
NSErrorConformanceToError = conformance->getConcrete();
else
NSErrorConformanceToError = nullptr;
return *NSErrorConformanceToError;
}


SILFunction *SILGenModule::emitTopLevelFunction(SILLocation Loc) {
ASTContext &C = M.getASTContext();
auto extInfo = SILFunctionType::ExtInfo()
Expand Down
19 changes: 19 additions & 0 deletions lib/SILGen/SILGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
Optional<FuncDecl*> UnconditionallyBridgeFromObjectiveCRequirement;
Optional<AssociatedTypeDecl*> BridgedObjectiveCType;

Optional<ProtocolDecl*> BridgedStoredNSError;
Optional<VarDecl*> NSErrorRequirement;

Optional<ProtocolConformance *> NSErrorConformanceToError;

public:
SILGenModule(SILModule &M, Module *SM, bool makeModuleFragile);
~SILGenModule();
Expand Down Expand Up @@ -380,6 +385,20 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
ProtocolConformance *getConformanceToObjectiveCBridgeable(SILLocation loc,
Type type);

/// Retrieve the _BridgedStoredNSError protocol definition.
ProtocolDecl *getBridgedStoredNSError(SILLocation loc);

/// Retrieve the _BridgedStoredNSError._nsError requirement.
VarDecl *getNSErrorRequirement(SILLocation loc);

/// Find the conformance of the given Swift type to the
/// _BridgedStoredNSError protocol.
ProtocolConformance *getConformanceToBridgedStoredNSError(SILLocation loc,
Type type);

/// Retrieve the conformance of NSError to the Error protocol.
ProtocolConformance *getNSErrorConformanceToError();

/// Report a diagnostic.
template<typename...T, typename...U>
InFlightDiagnostic diagnose(SourceLoc loc, Diag<T...> diag,
Expand Down
51 changes: 51 additions & 0 deletions lib/SILGen/SILGenConvert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,57 @@ ManagedValue SILGenFunction::emitExistentialErasure(
for (auto conformance : conformances)
SGM.useConformance(conformance);

// If we're erasing to the 'Error' type, and the concrete type conforms to the
// _BridgedStoredNSError protocol, call the _nsError witness getter to extract
// the NSError directly.
auto &ctx = getASTContext();
if (existentialTL.getSemanticType().getSwiftRValueType()->getAnyNominal() ==
ctx.getErrorDecl()) {
auto conformance =
SGM.getConformanceToBridgedStoredNSError(loc, concreteFormalType);
auto nsError = ctx.getNSErrorDecl();
if (conformance && nsError && SGM.getNSErrorConformanceToError()) {
CanType nsErrorType =
nsError->getDeclaredInterfaceType()->getCanonicalType();
if (auto witness =
conformance->getWitness(SGM.getNSErrorRequirement(loc), nullptr)) {
// Create a reference to the getter witness.
SILDeclRef getter =
getGetterDeclRef(cast<VarDecl>(witness.getDecl()),
/*isDirectAccessorUse=*/true);

// Compute the substitutions.
ArrayRef<Substitution> substitutions =
concreteFormalType->gatherAllSubstitutions(
SGM.SwiftModule, nullptr);

// Emit the erasure, through the getter to _nsError.
ProtocolConformanceRef nsErrorConformances[1] = {
ProtocolConformanceRef(SGM.getNSErrorConformanceToError())
};

return emitExistentialErasure(
loc, nsErrorType,
getTypeLowering(nsErrorType),
existentialTL,
ctx.AllocateCopy(nsErrorConformances),
C,
[&](SGFContext innerC) -> ManagedValue {
// Call the getter.
return emitGetAccessor(loc, getter, substitutions,
ArgumentSource(loc,
RValue(*this, loc,
concreteFormalType,
F(SGFContext()))),
/*isSuper=*/false,
/*isDirectAccessorUse=*/true,
RValue(), innerC)
.getAsSingleValue(*this, loc);
});
}
}
}

switch (existentialTL.getLoweredType().getObjectType()
.getPreferredExistentialRepresentation(SGM.M, concreteFormalType)) {
case ExistentialRepresentation::None:
Expand Down
5 changes: 5 additions & 0 deletions stdlib/public/SDK/Foundation/NSError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,11 @@ extension _ErrorCodeProtocol where Self._ErrorType: _BridgedStoredNSError {
}

extension _BridgedStoredNSError {
/// Retrieve the embedded NSError from a bridged, stored NSError.
public func _getEmbeddedNSError() -> AnyObject? {
return _nsError
}

public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs._nsError.isEqual(rhs._nsError)
}
Expand Down
21 changes: 19 additions & 2 deletions stdlib/public/core/ErrorType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,22 @@ public protocol Error {
var _domain: String { get }
var _code: Int { get }
var _userInfo: Any? { get }

#if _runtime(_ObjC)
func _getEmbeddedNSError() -> AnyObject?
#endif
}

#if _runtime(_ObjC)
// Helper functions for the C++ runtime to have easy access to domain,
// code, and userInfo as Objective-C values.
extension Error {
/// Default implementation: there is no embedded NSError.
public func _getEmbeddedNSError() -> AnyObject? { return nil }
}
#endif

#if _runtime(_ObjC)
// Helper functions for the C++ runtime to have easy access to embedded error,
// domain, code, and userInfo as Objective-C values.
@_silgen_name("swift_stdlib_getErrorDomainNSString")
public func _stdlib_getErrorDomainNSString<T : Error>(_ x: UnsafePointer<T>)
-> AnyObject {
Expand All @@ -138,6 +149,12 @@ public func _stdlib_getErrorUserInfoNSDictionary<T : Error>(_ x: UnsafePointer<T
return x.pointee._userInfo.map { $0 as AnyObject }
}

@_silgen_name("swift_stdlib_getErrorEmbeddedNSError")
public func _stdlib_getErrorEmbeddedNSError<T : Error>(_ x: UnsafePointer<T>)
-> AnyObject? {
return x.pointee._getEmbeddedNSError()
}

@_silgen_name("swift_stdlib_getErrorDefaultUserInfo")
public func _stdlib_getErrorDefaultUserInfo(_ error: Error) -> AnyObject?

Expand Down
18 changes: 18 additions & 0 deletions stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2006,14 +2006,32 @@ static bool _dynamicCastToFunction(OpaqueValue *dest,
}

#if SWIFT_OBJC_INTEROP
// @_silgen_name("swift_stdlib_getErrorEmbeddedNSError")
// public func _stdlib_getErrorEmbeddedNSError<T : Error>(_ x: UnsafePointer<T>)
// -> AnyObject?
SWIFT_CC(swift)
extern "C" id swift_stdlib_getErrorEmbeddedNSError(const OpaqueValue *error,
const Metadata *T,
const WitnessTable *Error);

static id dynamicCastValueToNSError(OpaqueValue *src,
const Metadata *srcType,
const WitnessTable *srcErrorWitness,
DynamicCastFlags flags) {
// Check whether there is an embedded error.
if (auto embedded = swift_stdlib_getErrorEmbeddedNSError(src, srcType,
srcErrorWitness)) {
if (flags & DynamicCastFlags::TakeOnSuccess)
srcType->vw_destroy(src);

return embedded;
}

BoxPair errorBox = swift_allocError(srcType, srcErrorWitness, src,
/*isTake*/ flags & DynamicCastFlags::TakeOnSuccess);
return swift_bridgeErrorToNSError((SwiftError*)errorBox.first);
}

#endif

namespace {
Expand Down
37 changes: 37 additions & 0 deletions test/1_stdlib/ErrorBridged.swift
Original file line number Diff line number Diff line change
Expand Up @@ -590,4 +590,41 @@ ErrorBridgingTests.test("NSError subclass identity") {
expectTrue(type(of: nsError) == MyNSError.self)
}

ErrorBridgingTests.test("Wrapped NSError identity") {
let nsError = NSError(domain: NSCocoaErrorDomain,
code: NSFileNoSuchFileError,
userInfo: [
AnyHashable(NSFilePathErrorKey) : "/dev/null",
AnyHashable(NSStringEncodingErrorKey): /*ASCII=*/1,
])

let error: Error = nsError
let nsError2: NSError = error as NSError
expectTrue(nsError === nsError2)

// Extracting the NSError via the runtime.
let cocoaErrorAny: Any = error as! CocoaError
let nsError3: NSError = cocoaErrorAny as! NSError
expectTrue(nsError === nsError3)

if let cocoaErrorAny2: Any = error as? CocoaError {
let nsError4: NSError = cocoaErrorAny2 as! NSError
expectTrue(nsError === nsError4)
} else {
expectUnreachable()
}

// Extracting the NSError via direct call.
let cocoaError = error as! CocoaError
let nsError5: NSError = cocoaError as NSError
expectTrue(nsError === nsError5)

if let cocoaError2 = error as? CocoaError {
let nsError6: NSError = cocoaError as NSError
expectTrue(nsError === nsError6)
} else {
expectUnreachable()
}
}

runAllTests()
10 changes: 10 additions & 0 deletions test/SILGen/objc_error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,13 @@ class MyNSError : NSError {
func eraseMyNSError() -> Error {
return MyNSError()
}

// CHECK-LABEL: sil hidden @_TF10objc_error25eraseFictionalServerErrorFT_Ps5Error_
func eraseFictionalServerError() -> Error {
// CHECK-NOT: return
// CHECK: [[NSERROR_GETTER:%[0-9]+]] = function_ref @_TFVSC20FictionalServerErrorg8_nsErrorCSo7NSError
// CHECK: [[NSERROR:%[0-9]+]] = apply [[NSERROR_GETTER]]
// CHECK: [[ERROR:%[0-9]+]] = init_existential_ref [[NSERROR]]
// CHECK: return [[ERROR]]
return FictionalServerError(.meltedDown)
}