Skip to content

Commit 55ca60a

Browse files
committed
[Clang Importer] Import ns_error_domain attribute with _BridgedNSError
ns_error_domain can now be used to communicate with the ClangImporter when an enum has an associated error domain string. In this case, when we import it as a Swift enum, we can also synthesize a conformance to _BridgedNSError. This allows the creation of something like NS_ERROR_ENUM, in which the developer can declare an enum for the purposes of error handling. Adds Sema and executable tests demonstrating this funcionality.
1 parent ab05d64 commit 55ca60a

File tree

7 files changed

+224
-29
lines changed

7 files changed

+224
-29
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 100 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1870,7 +1870,8 @@ namespace {
18701870

18711871
// Create the enum declaration and record it.
18721872
NominalTypeDecl *result;
1873-
auto enumKind = Impl.getEnumKind(decl);
1873+
auto enumInfo = Impl.getEnumInfo(decl);
1874+
auto enumKind = enumInfo.getKind();
18741875
switch (enumKind) {
18751876
case EnumKind::Constants: {
18761877
// There is no declaration. Rather, the type is mapped to the
@@ -1954,69 +1955,139 @@ namespace {
19541955
}
19551956

19561957
case EnumKind::Enum: {
1958+
auto &swiftCtx = Impl.SwiftContext;
19571959
EnumDecl *nativeDecl;
19581960
bool declaredNative = hasNativeSwiftDecl(decl, name, dc, nativeDecl);
19591961
if (declaredNative && nativeDecl)
19601962
return nativeDecl;
19611963

19621964
// Compute the underlying type.
1963-
auto underlyingType = Impl.importType(decl->getIntegerType(),
1964-
ImportTypeKind::Enum,
1965-
isInSystemModule(dc),
1966-
/*isFullyBridgeable*/false);
1965+
auto underlyingType = Impl.importType(
1966+
decl->getIntegerType(), ImportTypeKind::Enum, isInSystemModule(dc),
1967+
/*isFullyBridgeable*/ false);
19671968
if (!underlyingType)
19681969
return nullptr;
1969-
1970-
auto enumDecl = Impl.createDeclWithClangNode<EnumDecl>(decl,
1971-
Impl.importSourceLoc(decl->getLocStart()),
1972-
name, Impl.importSourceLoc(decl->getLocation()),
1973-
None, nullptr, dc);
1970+
1971+
auto enumDecl = Impl.createDeclWithClangNode<EnumDecl>(
1972+
decl, Impl.importSourceLoc(decl->getLocStart()), name,
1973+
Impl.importSourceLoc(decl->getLocation()), None, nullptr, dc);
19741974
enumDecl->computeType();
1975-
1975+
19761976
// Set up the C underlying type as its Swift raw type.
19771977
enumDecl->setRawType(underlyingType);
1978-
1978+
19791979
// Add protocol declarations to the enum declaration.
1980-
enumDecl->setInherited(
1981-
Impl.SwiftContext.AllocateCopy(
1982-
llvm::makeArrayRef(TypeLoc::withoutLoc(underlyingType))));
1980+
SmallVector<TypeLoc, 2> inheritedTypes;
1981+
inheritedTypes.push_back(TypeLoc::withoutLoc(underlyingType));
1982+
if (enumInfo.isErrorEnum())
1983+
inheritedTypes.push_back(TypeLoc::withoutLoc(
1984+
swiftCtx.getProtocol(KnownProtocolKind::BridgedNSError)
1985+
->getDeclaredType()));
1986+
enumDecl->setInherited(swiftCtx.AllocateCopy(inheritedTypes));
19831987
enumDecl->setCheckedInheritanceClause();
19841988

1989+
// Set up error conformance to be lazily expanded
1990+
if (enumInfo.isErrorEnum())
1991+
enumDecl->getAttrs().add(new (swiftCtx) SynthesizedProtocolAttr(
1992+
KnownProtocolKind::BridgedNSError));
1993+
19851994
// Provide custom implementations of the init(rawValue:) and rawValue
19861995
// conversions that just do a bitcast. We can't reliably filter a
19871996
// C enum without additional knowledge that the type has no
19881997
// undeclared values, and won't ever add cases.
19891998
auto rawValueConstructor = makeEnumRawValueConstructor(Impl, enumDecl);
19901999

1991-
auto varName = Impl.SwiftContext.Id_rawValue;
1992-
auto rawValue = new (Impl.SwiftContext) VarDecl(/*static*/ false,
1993-
/*IsLet*/ false,
1994-
SourceLoc(), varName,
1995-
underlyingType,
1996-
enumDecl);
2000+
auto varName = swiftCtx.Id_rawValue;
2001+
auto rawValue = new (swiftCtx) VarDecl(
2002+
/*static*/ false,
2003+
/*IsLet*/ false, SourceLoc(), varName, underlyingType, enumDecl);
19972004
rawValue->setImplicit();
19982005
rawValue->setAccessibility(Accessibility::Public);
19992006
rawValue->setSetterAccessibility(Accessibility::Private);
2000-
2007+
20012008
// Create a pattern binding to describe the variable.
20022009
Pattern *varPattern = createTypedNamedPattern(rawValue);
2003-
2004-
auto rawValueBinding =
2005-
PatternBindingDecl::create(Impl.SwiftContext, SourceLoc(),
2006-
StaticSpellingKind::None, SourceLoc(),
2007-
varPattern, nullptr, enumDecl);
2010+
2011+
auto rawValueBinding = PatternBindingDecl::create(
2012+
swiftCtx, SourceLoc(), StaticSpellingKind::None, SourceLoc(),
2013+
varPattern, nullptr, enumDecl);
20082014

20092015
auto rawValueGetter = makeEnumRawValueGetter(Impl, enumDecl, rawValue);
20102016

20112017
enumDecl->addMember(rawValueConstructor);
20122018
enumDecl->addMember(rawValueGetter);
20132019
enumDecl->addMember(rawValue);
20142020
enumDecl->addMember(rawValueBinding);
2015-
20162021
result = enumDecl;
2022+
2023+
// Set up the domain error member to be lazily added
2024+
if (enumInfo.isErrorEnum()) {
2025+
auto clangDomainIdent = enumInfo.getErrorDomain();
2026+
auto &clangSema = Impl.getClangSema();
2027+
clang::LookupResult lookupResult(
2028+
clangSema, clang::DeclarationName(clangDomainIdent),
2029+
clang::SourceLocation(),
2030+
clang::Sema::LookupNameKind::LookupOrdinaryName);
2031+
2032+
if (!clangSema.LookupName(lookupResult, clangSema.TUScope)) {
2033+
// Couldn't actually import it as an error enum, fall back to enum
2034+
break;
2035+
}
2036+
2037+
auto clangDecl = lookupResult.getAsSingle<clang::NamedDecl>();
2038+
if (!clangDecl) {
2039+
// Couldn't actually import it as an error enum, fall back to enum
2040+
break;
2041+
}
2042+
auto swiftDecl = Impl.importDecl(clangDecl);
2043+
if (!swiftDecl || !isa<ValueDecl>(swiftDecl)) {
2044+
// Couldn't actually import it as an error enum, fall back to enum
2045+
break;
2046+
}
2047+
SourceLoc noLoc = SourceLoc();
2048+
bool isStatic = true;
2049+
bool isImplicit = true;
2050+
2051+
DeclRefExpr *domainDeclRef = new (swiftCtx) DeclRefExpr(
2052+
ConcreteDeclRef(cast<ValueDecl>(swiftDecl)), {}, isImplicit);
2053+
auto stringTy = swiftCtx.getStringDecl()->getDeclaredType();
2054+
ParameterList *params[] = {
2055+
ParameterList::createWithoutLoc(
2056+
ParamDecl::createSelf(noLoc, enumDecl, isStatic)),
2057+
ParameterList::createEmpty(swiftCtx)};
2058+
auto toStringTy = ParameterList::getFullType(stringTy, params);
2059+
2060+
FuncDecl *getterDecl =
2061+
FuncDecl::create(swiftCtx, noLoc, StaticSpellingKind::None, noLoc,
2062+
{}, noLoc, noLoc, noLoc, nullptr, toStringTy,
2063+
params, TypeLoc::withoutLoc(stringTy), enumDecl);
2064+
2065+
// Make the property decl
2066+
auto errorDomainPropertyDecl = new (swiftCtx)
2067+
VarDecl(isStatic,
2068+
/*isLet=*/false, noLoc, swiftCtx.Id_NSErrorDomain,
2069+
stringTy, enumDecl);
2070+
errorDomainPropertyDecl->setAccessibility(Accessibility::Public);
2071+
2072+
enumDecl->addMember(errorDomainPropertyDecl);
2073+
enumDecl->addMember(getterDecl);
2074+
errorDomainPropertyDecl->makeComputed(
2075+
noLoc, getterDecl, /*Set=*/nullptr,
2076+
/*MaterializeForSet=*/nullptr, noLoc);
2077+
2078+
getterDecl->setImplicit();
2079+
getterDecl->setStatic(isStatic);
2080+
getterDecl->setBodyResultType(stringTy);
2081+
getterDecl->setAccessibility(Accessibility::Public);
2082+
2083+
auto ret = new (swiftCtx) ReturnStmt(noLoc, {domainDeclRef});
2084+
getterDecl->setBody(
2085+
BraceStmt::create(swiftCtx, noLoc, {ret}, noLoc, isImplicit));
2086+
Impl.registerExternalDecl(getterDecl);
2087+
}
20172088
break;
20182089
}
2019-
2090+
20202091
case EnumKind::Options: {
20212092
result = importAsOptionSetType(dc, name, decl);
20222093
if (!result)

lib/ClangImporter/ImportEnumInfo.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ void EnumInfo::classifyEnum(const clang::EnumDecl *decl,
3737
return;
3838
}
3939

40+
// First, check for attributes that denote the classification
41+
clang::NSErrorDomainAttr *domainAttr = nullptr;
42+
for (auto attr : decl->attrs()) {
43+
if (isa<clang::NSErrorDomainAttr>(attr)) {
44+
domainAttr = cast<clang::NSErrorDomainAttr>(attr);
45+
break;
46+
}
47+
}
48+
if (domainAttr) {
49+
kind = EnumKind::Enum;
50+
attribute = domainAttr;
51+
return;
52+
}
53+
4054
// Was the enum declared using *_ENUM or *_OPTIONS?
4155
// FIXME: Use Clang attributes instead of grovelling the macro expansion loc.
4256
auto loc = decl->getLocStart();

lib/ClangImporter/ImportEnumInfo.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,21 @@ enum class EnumKind {
5353
};
5454

5555
class EnumInfo {
56+
using AttributePtrUnion = clang::NSErrorDomainAttr *;
57+
5658
/// \brief The kind
5759
EnumKind kind = EnumKind::Unknown;
5860

5961
/// \brief The enum's common constant name prefix, which will be stripped from
6062
/// constants
6163
StringRef constantNamePrefix = StringRef();
6264

65+
/// \brief The identifying attribute for specially imported enums
66+
///
67+
/// Currently, only NS_ERROR_ENUM creates one for its error domain, but others
68+
/// should in the future.
69+
AttributePtrUnion attribute = nullptr;
70+
6371
public:
6472
EnumInfo() = default;
6573

@@ -72,6 +80,17 @@ class EnumInfo {
7280

7381
StringRef getConstantNamePrefix() const { return constantNamePrefix; }
7482

83+
/// \brief Whether this maps to an enum who also provides an error domain
84+
bool isErrorEnum() const {
85+
return getKind() == EnumKind::Enum && attribute;
86+
}
87+
88+
/// \brief For this error enum, extract the name of the error domain constant
89+
clang::IdentifierInfo *getErrorDomain() const {
90+
assert(isErrorEnum() && "not error enum");
91+
return attribute->getErrorDomain();
92+
}
93+
7594
private:
7695
void determineConstantNamePrefix(const clang::EnumDecl *);
7796
void classifyEnum(const clang::EnumDecl *, clang::Preprocessor &);

test/ClangModules/Inputs/enum-new.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,19 @@ typedef CF_OPTIONS(unsigned, ColorOptions) {
5050
Pastel, Swift
5151
};
5252
ColorOptions getColorOptions();
53+
54+
#define NS_ERROR_ENUM(_type, _name, _domain) \
55+
enum _name : _type _name; \
56+
enum __attribute__((ns_error_domain(_domain))) _name : _type
57+
58+
@class NSString;
59+
extern NSString *const TestErrorDomain;
60+
typedef NS_ERROR_ENUM(int, TestError, TestErrorDomain) {
61+
TENone,
62+
TEOne,
63+
TETwo,
64+
};
65+
66+
TestError getErr();
67+
68+

test/ClangModules/Inputs/enum-new.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include "enum-new.h"
2+
3+
NSString *const TestErrorDomain;
4+
5+
TestError getErr() {
6+
return TEOne;
7+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// RUN: mkdir -p %t
2+
// RUN: %target-clang %S/Inputs/enum-new.m -c -o %t/enum-new.o
3+
// RUN: %target-build-swift -import-objc-header %S/Inputs/enum-new.h -Xlinker %t/enum-new.o %s -o %t/a.out
4+
// RUN: %target-run %t/a.out | FileCheck %s
5+
6+
// REQUIRES: executable_test
7+
8+
import Foundation
9+
10+
func printError(err: TestError) {
11+
switch (err) {
12+
case .TENone:
13+
print("TestError: TENone")
14+
break
15+
16+
case .TEOne:
17+
print("TestError: TEOne")
18+
break
19+
20+
case .TETwo:
21+
print("TestError: TETwo")
22+
break
23+
}
24+
}
25+
26+
func testError() {
27+
let terr = getErr()
28+
switch (terr) { case .TENone, .TEOne, .TETwo: break } // ok
29+
printError(terr)
30+
// CHECK: TestError: TEOne
31+
32+
do {
33+
throw TestError.TETwo
34+
} catch let error as TestError {
35+
printError(error)
36+
// CHECK-NEXT: TestError: TETwo
37+
} catch {
38+
assert(false)
39+
}
40+
41+
do {
42+
enum LocalError : ErrorType { case Err }
43+
throw LocalError.Err
44+
} catch let error as TestError {
45+
printError(error)
46+
} catch {
47+
print("other error found")
48+
// CHECK-NEXT: other error found
49+
}
50+
}
51+
52+
testError()

test/ClangModules/enum-new.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,19 @@ func test() {
2323
} // expected-error {{switch must be exhaustive, consider adding a default clause}}
2424
}
2525

26+
func testError() {
27+
let terr = getErr()
28+
switch (terr) { case .TENone, .TEOne, .TETwo: break } // ok
29+
30+
switch (terr) { case .TENone, .TEOne: break }
31+
// expected-error@-1 {{switch must be exhaustive, consider adding a default clause}}
32+
33+
let _ = TestError(rawValue: 2)!
34+
35+
do {
36+
throw TestError.TEOne
37+
} catch is TestError {
38+
} catch {
39+
}
40+
41+
}

0 commit comments

Comments
 (0)