Skip to content

Commit ea8e2a2

Browse files
authored
Merge pull request #21452 from brentdax/to-error-is-human-to-forgive-domain
Change error domains on @objc enums to ensure they're unique
2 parents decd65d + 4a7a44d commit ea8e2a2

File tree

7 files changed

+130
-14
lines changed

7 files changed

+130
-14
lines changed

include/swift/AST/AccessScope.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class AccessScope {
4747
bool isPublic() const { return !Value.getPointer(); }
4848
bool isPrivate() const { return Value.getInt(); }
4949
bool isFileScope() const;
50+
bool isInternal() const;
5051

5152
/// Returns true if this is a child scope of the specified other access scope.
5253
///

include/swift/AST/SwiftNameTranslation.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
namespace swift {
2020
class ValueDecl;
21+
class EnumDecl;
2122
class EnumElementDecl;
2223

2324
namespace objc_translation {
@@ -29,6 +30,8 @@ namespace objc_translation {
2930
StringRef getNameForObjC(const ValueDecl *VD,
3031
CustomNamesOnly_t customNamesOnly = Normal);
3132

33+
std::string getErrorDomainStringForObjC(const EnumDecl *ED);
34+
3235
/// Print the ObjC name of an enum element decl to OS, also allowing the client
3336
/// to specify a preferred name other than the decl's original name.
3437
///

lib/AST/DeclContext.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,11 @@ bool AccessScope::isFileScope() const {
839839
return DC && isa<FileUnit>(DC);
840840
}
841841

842+
bool AccessScope::isInternal() const {
843+
auto DC = getDeclContext();
844+
return DC && isa<ModuleDecl>(DC);
845+
}
846+
842847
AccessLevel AccessScope::accessLevelForDiagnostics() const {
843848
if (isPublic())
844849
return AccessLevel::Public;

lib/AST/SwiftNameTranslation.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "swift/AST/SwiftNameTranslation.h"
1818
#include "swift/AST/ASTContext.h"
19+
#include "swift/AST/Module.h"
1920
#include "swift/AST/Decl.h"
2021
#include "swift/AST/LazyResolver.h"
2122
#include "swift/Basic/StringExtras.h"
@@ -51,6 +52,34 @@ getNameForObjC(const ValueDecl *VD, CustomNamesOnly_t customNamesOnly) {
5152
return VD->getBaseName().getIdentifier().str();
5253
}
5354

55+
std::string swift::objc_translation::
56+
getErrorDomainStringForObjC(const EnumDecl *ED) {
57+
// Should have already been diagnosed as diag::objc_enum_generic.
58+
assert(!ED->isGenericContext() && "Trying to bridge generic enum error to Obj-C");
59+
60+
// Clang decls have custom domains, but we shouldn't see them here anyway.
61+
assert(!ED->getClangDecl() && "clang decls shouldn't be re-exported");
62+
63+
SmallVector<const NominalTypeDecl *, 4> outerTypes;
64+
for (const NominalTypeDecl * D = ED;
65+
D != nullptr;
66+
D = D->getDeclContext()->getSelfNominalTypeDecl()) {
67+
// We don't currently PrintAsObjC any types whose parents are private or
68+
// fileprivate.
69+
assert(D->getFormalAccess() >= AccessLevel::Internal &&
70+
"We don't currently append private discriminators");
71+
outerTypes.push_back(D);
72+
}
73+
74+
std::string buffer = ED->getParentModule()->getNameStr();
75+
for (auto D : reversed(outerTypes)) {
76+
buffer += ".";
77+
buffer += D->getNameStr();
78+
}
79+
80+
return buffer;
81+
}
82+
5483
bool swift::objc_translation::
5584
printSwiftEnumElemNameInObjC(const EnumElementDecl *EL, llvm::raw_ostream &OS,
5685
Identifier PreferredName) {

lib/PrintAsObjC/PrintAsObjC.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2511,7 +2511,7 @@ class ModuleWriter {
25112511
});
25122512
if (!hasDomainCase) {
25132513
os << "static NSString * _Nonnull const " << getNameForObjC(ED)
2514-
<< "Domain = @\"" << M.getName() << "." << ED->getName() << "\";\n";
2514+
<< "Domain = @\"" << getErrorDomainStringForObjC(ED) << "\";\n";
25152515
}
25162516
}
25172517

lib/Sema/DerivedConformanceError.cpp

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,40 @@
2222
#include "swift/AST/Expr.h"
2323
#include "swift/AST/Module.h"
2424
#include "swift/AST/Types.h"
25+
#include "swift/AST/SwiftNameTranslation.h"
2526

2627
using namespace swift;
28+
using namespace swift::objc_translation;
2729

2830
static void deriveBodyBridgedNSError_enum_nsErrorDomain(
29-
AbstractFunctionDecl *domainDecl, void *) {
31+
AbstractFunctionDecl *domainDecl, void *) {
32+
// enum SomeEnum {
33+
// @derived
34+
// static var _nsErrorDomain: String {
35+
// return String(reflecting: self)
36+
// }
37+
// }
38+
39+
auto M = domainDecl->getParentModule();
40+
auto &C = M->getASTContext();
41+
auto self = domainDecl->getImplicitSelfDecl();
42+
43+
auto selfRef = new (C) DeclRefExpr(self, DeclNameLoc(), /*implicit*/ true);
44+
auto stringType = TypeExpr::createForDecl(SourceLoc(), C.getStringDecl(),
45+
domainDecl, /*implicit*/ true);
46+
auto initReflectingCall =
47+
CallExpr::createImplicit(C, stringType,
48+
{ selfRef }, { C.getIdentifier("reflecting") });
49+
auto ret =
50+
new (C) ReturnStmt(SourceLoc(), initReflectingCall, /*implicit*/ true);
51+
52+
auto body = BraceStmt::create(C, SourceLoc(), ASTNode(ret), SourceLoc());
53+
54+
domainDecl->setBody(body);
55+
}
56+
57+
static void deriveBodyBridgedNSError_printAsObjCEnum_nsErrorDomain(
58+
AbstractFunctionDecl *domainDecl, void *) {
3059
// enum SomeEnum {
3160
// @derived
3261
// static var _nsErrorDomain: String {
@@ -39,10 +68,7 @@ static void deriveBodyBridgedNSError_enum_nsErrorDomain(
3968
auto TC = domainDecl->getInnermostTypeContext();
4069
auto ED = TC->getSelfEnumDecl();
4170

42-
std::string buffer = M->getNameStr();
43-
buffer += ".";
44-
buffer += ED->getNameStr();
45-
StringRef value(C.AllocateCopy(buffer));
71+
StringRef value(C.AllocateCopy(getErrorDomainStringForObjC(ED)));
4672

4773
auto string = new (C) StringLiteralExpr(value, SourceRange(), /*implicit*/ true);
4874
auto ret = new (C) ReturnStmt(SourceLoc(), string, /*implicit*/ true);
@@ -53,17 +79,15 @@ static void deriveBodyBridgedNSError_enum_nsErrorDomain(
5379
}
5480

5581
static ValueDecl *
56-
deriveBridgedNSError_enum_nsErrorDomain(DerivedConformance &derived) {
82+
deriveBridgedNSError_enum_nsErrorDomain(DerivedConformance &derived,
83+
void (*synthesizer)(AbstractFunctionDecl *, void*)) {
5784
// enum SomeEnum {
5885
// @derived
5986
// static var _nsErrorDomain: String {
60-
// return "\(self)"
87+
// ...
6188
// }
6289
// }
6390

64-
// Note that for @objc enums the format is assumed to be "MyModule.SomeEnum".
65-
// If this changes, please change PrintAsObjC as well.
66-
6791
ASTContext &C = derived.TC.Context;
6892

6993
auto stringTy = C.getStringDecl()->getDeclaredType();
@@ -78,7 +102,7 @@ deriveBridgedNSError_enum_nsErrorDomain(DerivedConformance &derived) {
78102
// Define the getter.
79103
auto getterDecl = derived.addGetterToReadOnlyDerivedProperty(
80104
derived.TC, propDecl, stringTy);
81-
getterDecl->setBodySynthesizer(&deriveBodyBridgedNSError_enum_nsErrorDomain);
105+
getterDecl->setBodySynthesizer(synthesizer);
82106

83107
derived.addMembersToConformanceContext({getterDecl, propDecl, pbDecl});
84108

@@ -89,8 +113,17 @@ ValueDecl *DerivedConformance::deriveBridgedNSError(ValueDecl *requirement) {
89113
if (!isa<EnumDecl>(Nominal))
90114
return nullptr;
91115

92-
if (requirement->getBaseName() == TC.Context.Id_nsErrorDomain)
93-
return deriveBridgedNSError_enum_nsErrorDomain(*this);
116+
if (requirement->getBaseName() == TC.Context.Id_nsErrorDomain) {
117+
auto synthesizer = deriveBodyBridgedNSError_enum_nsErrorDomain;
118+
119+
auto scope = Nominal->getFormalAccessScope(Nominal->getModuleScopeContext());
120+
if (scope.isPublic() || scope.isInternal())
121+
// PrintAsObjC may print this domain, so we should make sure we use the
122+
// same string it will.
123+
synthesizer = deriveBodyBridgedNSError_printAsObjCEnum_nsErrorDomain;
124+
125+
return deriveBridgedNSError_enum_nsErrorDomain(*this, synthesizer);
126+
}
94127

95128
TC.diagnose(requirement->getLoc(), diag::broken_errortype_requirement);
96129
return nullptr;

test/stdlib/ErrorBridged.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,4 +722,49 @@ ErrorBridgingTests.test("Error archetype identity") {
722722
=== nsError)
723723
}
724724

725+
// SR-9389
726+
class ParentA: NSObject {
727+
@objc(ParentAError) enum Error: Int, Swift.Error {
728+
case failed
729+
}
730+
}
731+
class ParentB: NSObject {
732+
@objc(ParentBError) enum Error: Int, Swift.Error {
733+
case failed
734+
}
735+
}
736+
private class NonPrintAsObjCClass: NSObject {
737+
@objc enum Error: Int, Swift.Error {
738+
case foo
739+
}
740+
}
741+
@objc private enum NonPrintAsObjCError: Int, Error {
742+
case bar
743+
}
744+
745+
ErrorBridgingTests.test("@objc error domains for nested types") {
746+
// Domain strings should correspond to Swift types, including parent types.
747+
expectEqual(ParentA.Error.failed._domain, "main.ParentA.Error")
748+
expectEqual(ParentB.Error.failed._domain, "main.ParentB.Error")
749+
750+
func makeNSError(like error: Error) -> NSError {
751+
return NSError(domain: error._domain, code: error._code)
752+
}
753+
754+
// NSErrors corresponding to Error types with the same name but nested in
755+
// different enclosing types should not be castable to the wrong error type.
756+
expectTrue(makeNSError(like: ParentA.Error.failed) is ParentA.Error)
757+
expectFalse(makeNSError(like: ParentA.Error.failed) is ParentB.Error)
758+
expectFalse(makeNSError(like: ParentB.Error.failed) is ParentA.Error)
759+
expectTrue(makeNSError(like: ParentB.Error.failed) is ParentB.Error)
760+
761+
// If an @objc enum error is not eligible for PrintAsObjC, we should treat it
762+
// as though it inherited the default implementation, which calls
763+
// String(reflecting:).
764+
expectEqual(NonPrintAsObjCClass.Error.foo._domain,
765+
String(reflecting: NonPrintAsObjCClass.Error.self))
766+
expectEqual(NonPrintAsObjCError.bar._domain,
767+
String(reflecting: NonPrintAsObjCError.self))
768+
}
769+
725770
runAllTests()

0 commit comments

Comments
 (0)