Skip to content

Commit b86b812

Browse files
committed
[SE-0112] Import an Objective-C error enum as a struct wrapping NSError.
A given Objective-C error enum, which is effectively an NS_ENUM that specifies its corresponding error domain, will now be mapped to an ErrorProtocol-conforming struct that wraps an NSError, much like NSCocoaError does. The actual enum is mapped to a nested "Code" enum. For example, CoreLocation's CLError becomes: struct CLError : ErrorProtocol { let _nsError: NSError // ... @objc enum Code : Int { case ... } } This implements bullet (2) in the proposed solution of SE-0112, so that Cocoa error types are mapped into structures that maintain the underlying NSError to allow more information to be extracted from it.
1 parent b1777b1 commit b86b812

20 files changed

+375
-95
lines changed

include/swift/AST/Attr.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,10 +1250,10 @@ class DeclAttributes {
12501250
template <typename ATTR, bool AllowInvalid> struct ToAttributeKind {
12511251
ToAttributeKind() {}
12521252

1253-
Optional<const DeclAttribute *>
1253+
Optional<const ATTR *>
12541254
operator()(const DeclAttribute *Attr) const {
12551255
if (isa<ATTR>(Attr) && (Attr->isValid() || AllowInvalid))
1256-
return Attr;
1256+
return cast<ATTR>(Attr);
12571257
return None;
12581258
}
12591259
};

include/swift/AST/KnownIdentifiers.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ IDENTIFIER(dictionaryLiteral)
111111
IDENTIFIER_(getBuiltinLogicValue)
112112
IDENTIFIER(className)
113113

114+
IDENTIFIER_(ErrorType)
115+
IDENTIFIER(Code)
116+
IDENTIFIER_(nsError)
117+
114118
#undef IDENTIFIER
115119
#undef IDENTIFIER_
116120
#undef IDENTIFIER_WITH_NAME

include/swift/AST/KnownProtocols.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ PROTOCOL(Equatable)
5757
PROTOCOL(Hashable)
5858
PROTOCOL(Comparable)
5959
PROTOCOL(ErrorProtocol)
60+
PROTOCOL_(ErrorCodeProtocol)
6061
PROTOCOL(OptionSet)
6162
PROTOCOL_(BridgedNSError)
63+
PROTOCOL_(BridgedStoredNSError)
6264

6365
PROTOCOL_(ObjectiveCBridgeable)
6466
PROTOCOL_(DestructorSafeContainer)

lib/AST/ASTContext.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -871,8 +871,11 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
871871
// Find all of the declarations with this name in the appropriate module.
872872
SmallVector<ValueDecl *, 1> results;
873873

874-
// _BridgedNSError is in the Foundation module.
875-
if (kind == KnownProtocolKind::BridgedNSError) {
874+
// _BridgedNSError, _BridgedStoredNSError, and _ErrorCodeProtocol
875+
// are in the Foundation module.
876+
if (kind == KnownProtocolKind::BridgedNSError ||
877+
kind == KnownProtocolKind::BridgedStoredNSError ||
878+
kind == KnownProtocolKind::ErrorCodeProtocol) {
876879
Module *foundation =
877880
const_cast<ASTContext *>(this)->getLoadedModule(Id_Foundation);
878881
if (!foundation)

lib/AST/ConformanceLookupTable.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,8 @@ void ConformanceLookupTable::expandImpliedConformances(NominalTypeDecl *nominal,
519519
// An @objc enum that explicitly conforms to the ErrorProtocol protocol
520520
// also implicitly conforms to _ObjectiveCBridgeableErrorProtocol, via the
521521
// known protocol _BridgedNSError.
522-
if (conformingProtocol->isSpecificProtocol(KnownProtocolKind::ErrorProtocol) &&
522+
if (conformingProtocol->isSpecificProtocol(
523+
KnownProtocolKind::ErrorProtocol) &&
523524
isa<EnumDecl>(nominal) && nominal->isObjC() &&
524525
cast<EnumDecl>(nominal)->hasOnlyCasesWithoutAssociatedValues()) {
525526
ASTContext &ctx = nominal->getASTContext();

lib/ClangImporter/ClangImporter.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,12 +2715,21 @@ auto ClangImporter::Implementation::importFullName(
27152715
bool strippedPrefix = false;
27162716
if (isa<clang::EnumConstantDecl>(D)) {
27172717
auto enumDecl = cast<clang::EnumDecl>(D->getDeclContext());
2718-
StringRef removePrefix =
2719-
getEnumConstantNamePrefix(enumDecl, &clangSema.getPreprocessor());
2718+
auto enumInfo = getEnumInfo(enumDecl, &clangSema.getPreprocessor());
2719+
2720+
StringRef removePrefix = enumInfo.getConstantNamePrefix();
27202721
if (!removePrefix.empty() && baseName.startswith(removePrefix)) {
27212722
baseName = baseName.substr(removePrefix.size());
27222723
strippedPrefix = true;
27232724
}
2725+
2726+
// If the error is an error enum, it will be mapped to the 'Code'
2727+
// enum nested within an NSError-containing struct. Strip the word
2728+
// "Code" off the end of the name, if it's there, because it's
2729+
// redundant.
2730+
if (enumInfo.isErrorEnum() && baseName.size() > 4 &&
2731+
camel_case::getLastWord(baseName) == "Code")
2732+
baseName = baseName.substr(0, baseName.size() - 4);
27242733
}
27252734

27262735
auto hasConflict = [&](const clang::IdentifierInfo *proposedName,

lib/ClangImporter/ImportDecl.cpp

Lines changed: 150 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -944,8 +944,9 @@ static void inferProtocolMemberAvailability(ClangImporter::Implementation &impl,
944944
applyAvailableAttribute(valueDecl, requiredRange, C);
945945
}
946946

947-
/// Add a domain error member, as required by conformance to _BridgedNSError
948-
/// Returns true on success, false on failure
947+
/// Add a domain error member, as required by conformance to
948+
/// _BridgedStoredNSError.
949+
/// \returns true on success, false on failure
949950
static bool addErrorDomain(NominalTypeDecl *swiftDecl,
950951
clang::NamedDecl *errorDomainDecl,
951952
ClangImporter::Implementation &importer) {
@@ -2151,12 +2152,17 @@ namespace {
21512152
/// TypeCheckPattern.cpp as well.
21522153
Decl *importEnumCaseAlias(Identifier name,
21532154
const clang::EnumConstantDecl *alias,
2154-
EnumElementDecl *original,
2155+
ValueDecl *original,
21552156
const clang::EnumDecl *clangEnum,
2156-
NominalTypeDecl *importedEnum) {
2157+
NominalTypeDecl *importedEnum,
2158+
DeclContext *importIntoDC = nullptr) {
21572159
if (name.empty())
21582160
return nullptr;
2159-
2161+
2162+
// Default the DeclContext to the enum type.
2163+
if (!importIntoDC)
2164+
importIntoDC = importedEnum;
2165+
21602166
// Construct the original constant. Enum constants without payloads look
21612167
// like simple values, but actually have type 'MyEnum.Type -> MyEnum'.
21622168
auto constantRef = new (Impl.SwiftContext) DeclRefExpr(original,
@@ -2170,7 +2176,7 @@ namespace {
21702176
typeRef);
21712177
instantiate->setType(importedEnumTy);
21722178

2173-
Decl *CD = Impl.createConstant(name, importedEnum, importedEnumTy,
2179+
Decl *CD = Impl.createConstant(name, importIntoDC, importedEnumTy,
21742180
instantiate, ConstantConvertKind::None,
21752181
/*isStatic*/ true, alias);
21762182
Impl.importAttributes(alias, CD);
@@ -2256,7 +2262,9 @@ namespace {
22562262
auto name = importedName.Imported.getBaseName();
22572263

22582264
// Create the enum declaration and record it.
2265+
StructDecl *errorWrapper = nullptr;
22592266
NominalTypeDecl *result;
2267+
NominalTypeDecl *enumeratorContext;
22602268
auto enumInfo = Impl.getEnumInfo(decl);
22612269
auto enumKind = enumInfo.getKind();
22622270
switch (enumKind) {
@@ -2295,6 +2303,7 @@ namespace {
22952303
/*setterAccessibility=*/Accessibility::Public);
22962304

22972305
result = structDecl;
2306+
enumeratorContext = structDecl;
22982307
break;
22992308
}
23002309

@@ -2312,9 +2321,80 @@ namespace {
23122321
if (!underlyingType)
23132322
return nullptr;
23142323

2324+
/// Basic information about the enum type we're building.
2325+
Identifier enumName = name;
2326+
DeclContext *enumDC = dc;
2327+
SourceLoc loc = Impl.importSourceLoc(decl->getLocStart());
2328+
2329+
// If this is an error enum, form the error wrapper type,
2330+
// which is a struct containing an NSError instance.
2331+
ProtocolDecl *bridgedNSError = nullptr;
2332+
ClassDecl *nsErrorDecl = nullptr;
2333+
ProtocolDecl *errorCodeProto = nullptr;
2334+
if (enumInfo.isErrorEnum() &&
2335+
(bridgedNSError =
2336+
C.getProtocol(KnownProtocolKind::BridgedStoredNSError)) &&
2337+
(nsErrorDecl = C.getNSErrorDecl()) &&
2338+
(errorCodeProto =
2339+
C.getProtocol(KnownProtocolKind::ErrorCodeProtocol))) {
2340+
// Create the wrapper struct.
2341+
errorWrapper = Impl.createDeclWithClangNode<StructDecl>(
2342+
decl, loc, name, loc, None, nullptr, dc);
2343+
errorWrapper->computeType();
2344+
2345+
// Add inheritance clause.
2346+
TypeLoc inheritedTypes[1] = {
2347+
TypeLoc::withoutLoc(bridgedNSError->getDeclaredType())
2348+
};
2349+
errorWrapper->setInherited(C.AllocateCopy(inheritedTypes));
2350+
errorWrapper->setCheckedInheritanceClause();
2351+
2352+
// Set up error conformance to be lazily expanded
2353+
errorWrapper->getAttrs().add(new (C) SynthesizedProtocolAttr(
2354+
KnownProtocolKind::BridgedStoredNSError));
2355+
2356+
// Create the _nsError member.
2357+
// public let _nsError: NSError
2358+
auto nsErrorType = nsErrorDecl->getDeclaredInterfaceType();
2359+
auto nsErrorProp = new (C) VarDecl(/*static*/ false, /*IsLet*/ true,
2360+
loc, C.Id_nsError, nsErrorType,
2361+
errorWrapper);
2362+
nsErrorProp->setImplicit();
2363+
nsErrorProp->setAccessibility(Accessibility::Public);
2364+
2365+
// Create a pattern binding to describe the variable.
2366+
Pattern *nsErrorPattern = createTypedNamedPattern(nsErrorProp);
2367+
2368+
auto nsErrorBinding = PatternBindingDecl::create(
2369+
C, loc, StaticSpellingKind::None, loc,
2370+
nsErrorPattern, nullptr, errorWrapper);
2371+
errorWrapper->addMember(nsErrorProp);
2372+
errorWrapper->addMember(nsErrorBinding);
2373+
2374+
// Create the _nsError initializer.
2375+
// public init(_nsError error: NSError)
2376+
VarDecl *members[1] = { nsErrorProp };
2377+
auto nsErrorInit = createValueConstructor(errorWrapper, members,
2378+
/*wantCtorParamNames=*/true,
2379+
/*wantBody=*/true);
2380+
errorWrapper->addMember(nsErrorInit);
2381+
2382+
// Add the domain error member.
2383+
// public static var _nsErrorDomain: String { return error-domain }
2384+
addErrorDomain(errorWrapper, enumInfo.getErrorDomain(), Impl);
2385+
2386+
// Note: the Code will be added after it's created.
2387+
2388+
// The enum itself will be nested within the error wrapper,
2389+
// and be named Code.
2390+
enumDC = errorWrapper;
2391+
enumName = C.Id_Code;
2392+
}
2393+
2394+
// Create the enumeration.
23152395
auto enumDecl = Impl.createDeclWithClangNode<EnumDecl>(
2316-
decl, Impl.importSourceLoc(decl->getLocStart()), name,
2317-
Impl.importSourceLoc(decl->getLocation()), None, nullptr, dc);
2396+
decl, loc, enumName,
2397+
Impl.importSourceLoc(decl->getLocation()), None, nullptr, enumDC);
23182398
enumDecl->computeType();
23192399

23202400
// Set up the C underlying type as its Swift raw type.
@@ -2327,18 +2407,13 @@ namespace {
23272407
// Add protocol declarations to the enum declaration.
23282408
SmallVector<TypeLoc, 2> inheritedTypes;
23292409
inheritedTypes.push_back(TypeLoc::withoutLoc(underlyingType));
2330-
if (enumInfo.isErrorEnum())
2331-
inheritedTypes.push_back(TypeLoc::withoutLoc(
2332-
C.getProtocol(KnownProtocolKind::BridgedNSError)
2333-
->getDeclaredType()));
2410+
if (errorWrapper) {
2411+
inheritedTypes.push_back(
2412+
TypeLoc::withoutLoc(errorCodeProto->getDeclaredType()));
2413+
}
23342414
enumDecl->setInherited(C.AllocateCopy(inheritedTypes));
23352415
enumDecl->setCheckedInheritanceClause();
23362416

2337-
// Set up error conformance to be lazily expanded
2338-
if (enumInfo.isErrorEnum())
2339-
enumDecl->getAttrs().add(new (C) SynthesizedProtocolAttr(
2340-
KnownProtocolKind::BridgedNSError));
2341-
23422417
// Provide custom implementations of the init(rawValue:) and rawValue
23432418
// conversions that just do a bitcast. We can't reliably filter a
23442419
// C enum without additional knowledge that the type has no
@@ -2367,20 +2442,38 @@ namespace {
23672442
enumDecl->addMember(rawValueGetter);
23682443
enumDecl->addMember(rawValue);
23692444
enumDecl->addMember(rawValueBinding);
2370-
result = enumDecl;
23712445

2372-
// Add the domain error member
2373-
if (enumInfo.isErrorEnum())
2374-
addErrorDomain(enumDecl, enumInfo.getErrorDomain(), Impl);
2446+
// If we have an error wrapper, finish it up now that its
2447+
// nested enum has been constructed.
2448+
if (errorWrapper) {
2449+
// Add the ErrorType alias:
2450+
// public typealias ErrorType
2451+
auto alias = Impl.createDeclWithClangNode<TypeAliasDecl>(
2452+
decl, loc, C.Id_ErrorType, loc,
2453+
TypeLoc::withoutLoc(
2454+
errorWrapper->getDeclaredInterfaceType()),
2455+
/*genericSignature=*/nullptr, enumDecl);
2456+
alias->computeType();
2457+
enumDecl->addMember(alias);
2458+
2459+
// Add the 'Code' enum to the error wrapper.
2460+
errorWrapper->addMember(enumDecl);
2461+
result = errorWrapper;
2462+
} else {
2463+
result = enumDecl;
2464+
}
23752465

2466+
// The enumerators go into this enumeration.
2467+
enumeratorContext = enumDecl;
23762468
break;
23772469
}
23782470

23792471
case EnumKind::Options: {
23802472
result = importAsOptionSetType(dc, name, decl);
23812473
if (!result)
23822474
return nullptr;
2383-
2475+
2476+
enumeratorContext = result;
23842477
break;
23852478
}
23862479
}
@@ -2412,31 +2505,50 @@ namespace {
24122505
break;
24132506
case EnumKind::Options:
24142507
enumeratorDecl = SwiftDeclConverter(Impl, /*useSwift2Name=*/false)
2415-
.importOptionConstant(*ec, decl, result);
2508+
.importOptionConstant(*ec, decl,
2509+
enumeratorContext);
24162510
swift2EnumeratorDecl = SwiftDeclConverter(Impl,/*useSwift2Name=*/true)
2417-
.importOptionConstant(*ec, decl, result);
2511+
.importOptionConstant(*ec, decl,
2512+
enumeratorContext);
24182513
break;
24192514
case EnumKind::Enum:
24202515
enumeratorDecl = SwiftDeclConverter(Impl, /*useSwift2Name=*/false)
2421-
.importEnumCase(*ec, decl, cast<EnumDecl>(result));
2516+
.importEnumCase(*ec, decl,
2517+
cast<EnumDecl>(enumeratorContext));
24222518
swift2EnumeratorDecl = SwiftDeclConverter(Impl,/*useSwift2Name=*/true)
2423-
.importEnumCase(*ec, decl,
2424-
cast<EnumDecl>(result),
2425-
enumeratorDecl);
2519+
.importEnumCase(
2520+
*ec, decl,
2521+
cast<EnumDecl>(enumeratorContext),
2522+
enumeratorDecl);
24262523
break;
24272524
}
24282525
if (!enumeratorDecl)
24292526
continue;
24302527

24312528
if (addEnumeratorsAsMembers) {
2432-
result->addMember(enumeratorDecl);
2433-
if (auto *var = dyn_cast<VarDecl>(enumeratorDecl))
2434-
result->addMember(var->getGetter());
2435-
2436-
if (swift2EnumeratorDecl) {
2437-
result->addMember(swift2EnumeratorDecl);
2438-
if (auto *var = dyn_cast<VarDecl>(swift2EnumeratorDecl))
2439-
result->addMember(var->getGetter());
2529+
// Add a member enumerator to the given nominal type.
2530+
auto addDecl = [&](NominalTypeDecl *nominal, Decl *decl) {
2531+
if (!decl) return;
2532+
nominal->addMember(decl);
2533+
if (auto *var = dyn_cast<VarDecl>(decl))
2534+
nominal->addMember(var->getGetter());
2535+
};
2536+
2537+
addDecl(enumeratorContext, enumeratorDecl);
2538+
addDecl(enumeratorContext, swift2EnumeratorDecl);
2539+
2540+
// If there is an error wrapper, add an alias within the
2541+
// wrapper to the corresponding value within the enumerator
2542+
// context.
2543+
if (errorWrapper) {
2544+
auto enumeratorValue = cast<ValueDecl>(enumeratorDecl);
2545+
auto alias = importEnumCaseAlias(enumeratorValue->getName(),
2546+
*ec,
2547+
enumeratorValue,
2548+
decl,
2549+
enumeratorContext,
2550+
result);
2551+
addDecl(result, alias);
24402552
}
24412553
}
24422554
}
@@ -2445,6 +2557,8 @@ namespace {
24452557
// raw values and SILGen can emit witness tables for derived conformances.
24462558
// FIXME: There might be better ways to do this.
24472559
Impl.registerExternalDecl(result);
2560+
if (result != enumeratorContext)
2561+
Impl.registerExternalDecl(enumeratorContext);
24482562
return result;
24492563
}
24502564

0 commit comments

Comments
 (0)