From 74050648de95c949de0f3a8dd562b5955eedfcf0 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 20 May 2015 00:16:39 +0000 Subject: [PATCH] Make @objc enums that conform to ErrorType also conform to _ObjectiveCBridgeableErrorType. This allows @objc enum error types produced in Objective-C (e.g., via +[NSError errorWithDomain:code:userInfo:]) to be bridged back to their original enum types in Swift via pattern matching/catch blocks. This finishes rdar://problem/20577517. Swift SVN r28803 --- include/swift/AST/KnownIdentifiers.def | 1 + include/swift/AST/KnownProtocols.def | 1 + lib/AST/ASTContext.cpp | 22 +++++- lib/AST/Decl.cpp | 6 +- lib/AST/ProtocolConformance.cpp | 17 +++- lib/IRGen/GenMeta.cpp | 1 + lib/Sema/DerivedConformanceErrorType.cpp | 78 +++++++++++++++++++ .../DerivedConformanceRawRepresentable.cpp | 27 ++++--- lib/Sema/DerivedConformances.h | 13 +++- lib/Sema/TypeCheckProtocol.cpp | 6 +- test/decl/enum/objc_enum_errortype.swift | 26 +++++++ 11 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 test/decl/enum/objc_enum_errortype.swift diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index af182216ff8f4..bc469a997e841 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -44,6 +44,7 @@ IDENTIFIER(init) IDENTIFIER(load) IDENTIFIER(next) IDENTIFIER(NSError) +IDENTIFIER(_NSErrorDomain) IDENTIFIER(NSObject) IDENTIFIER(objectAtIndexedSubscript) IDENTIFIER(objectForKeyedSubscript) diff --git a/include/swift/AST/KnownProtocols.def b/include/swift/AST/KnownProtocols.def index 5cdcf1a3ba411..4dabe8e641560 100644 --- a/include/swift/AST/KnownProtocols.def +++ b/include/swift/AST/KnownProtocols.def @@ -47,6 +47,7 @@ PROTOCOL(Hashable) PROTOCOL(Comparable) PROTOCOL(ErrorType) PROTOCOL(_OptionSetType) +PROTOCOL(_BridgedNSError) PROTOCOL(_ObjectiveCBridgeable) PROTOCOL(_DestructorSafeContainer) diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index d5b5c9356cd94..11b0e4dd80af6 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -740,9 +740,23 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const { if (Impl.KnownProtocols[index]) return Impl.KnownProtocols[index]; - // Find all of the declarations with this name in the Swift module. + // Find all of the declarations with this name in the appropriate module. SmallVector results; - lookupInSwiftModule(getProtocolName(kind), results); + + // _BridgedNSError is in the Foundation module. + if (kind == KnownProtocolKind::_BridgedNSError) { + Module *foundation = const_cast(this)->getModuleByName( + FOUNDATION_MODULE_NAME); + if (!foundation) + return nullptr; + + auto identifier = getIdentifier(getProtocolName(kind)); + foundation->lookupValue({ }, identifier, NLKind::UnqualifiedLookup, + results); + } else { + lookupInSwiftModule(getProtocolName(kind), results); + } + for (auto result : results) { if (auto protocol = dyn_cast(result)) { Impl.KnownProtocols[index] = protocol; @@ -1248,7 +1262,9 @@ ASTContext::getModule(ArrayRef> ModulePath) { auto moduleID = ModulePath[0]; for (auto &importer : Impl.ModuleLoaders) { if (Module *M = importer->loadModule(moduleID.second, ModulePath)) { - if (ModulePath.size() == 1 && ModulePath[0].first == StdlibModuleName) + if (ModulePath.size() == 1 && + (ModulePath[0].first == StdlibModuleName || + ModulePath[0].first == Id_Foundation)) recordKnownProtocols(M); return M; } diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 501f92339f35e..0bf4e86fe7a4c 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -1804,7 +1804,11 @@ bool NominalTypeDecl::derivesProtocolConformance(ProtocolDecl *protocol) const { // Enums can explicitly derive their ErrorType conformance. case KnownProtocolKind::ErrorType: return true; - + + // @objc enums can explicitly derive their _BridgedNSError conformance. + case KnownProtocolKind::_BridgedNSError: + return isObjC(); + default: return false; } diff --git a/lib/AST/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index 26dca61e14951..698f2a487e4d4 100644 --- a/lib/AST/ProtocolConformance.cpp +++ b/lib/AST/ProtocolConformance.cpp @@ -1389,6 +1389,7 @@ bool ConformanceLookupTable::addProtocol(NominalTypeDecl *nominal, // Record this as a conformance within the given declaration // context. AllConformances[dc].push_back(entry); + return true; } @@ -1422,7 +1423,20 @@ void ConformanceLookupTable::expandImpliedConformances(NominalTypeDecl *nominal, // implied conformances. if (resolver) resolver->resolveInheritanceClause(dc); - + + // An @objc enum that explicitly conforms to the ErrorType protocol also + // implicitly conforms to _ObjectiveCBridgeableErrorType, via the known + // protocol _BridgedNSError. + if (conformingProtocol->isSpecificProtocol(KnownProtocolKind::ErrorType) && + isa(nominal) && nominal->isObjC()) { + ASTContext &ctx = nominal->getASTContext(); + if (auto bridgedNSError + = ctx.getProtocol(KnownProtocolKind::_BridgedNSError)) { + addProtocol(nominal, bridgedNSError, SourceLoc(), + ConformanceSource::forImplied(conformanceEntry)); + } + } + // FIXME: It's odd that the inheritance clause isn't loaded at all during // deserialization, so we have this weird separate path. if (!conformingProtocol->getParentSourceFile()) { @@ -2036,7 +2050,6 @@ void NominalTypeDecl::prepareConformanceTable() const { if (resolver) resolver->resolveRawType(theEnum); if (theEnum->hasRawType()) { - // Simple enumerations conform to Equatable and Hashable. if (auto rawRepresentable = getASTContext().getProtocol( KnownProtocolKind::RawRepresentable)) { ConformanceTable->addSynthesizedConformance(mutableThis, diff --git a/lib/IRGen/GenMeta.cpp b/lib/IRGen/GenMeta.cpp index 22f6acc53b552..28c7c1b6af357 100644 --- a/lib/IRGen/GenMeta.cpp +++ b/lib/IRGen/GenMeta.cpp @@ -4538,6 +4538,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) { case KnownProtocolKind::_BuiltinUTF16StringLiteralConvertible: case KnownProtocolKind::_BuiltinUnicodeScalarLiteralConvertible: case KnownProtocolKind::_OptionSetType: + case KnownProtocolKind::_BridgedNSError: return SpecialProtocol::None; } } diff --git a/lib/Sema/DerivedConformanceErrorType.cpp b/lib/Sema/DerivedConformanceErrorType.cpp index 028f6fbb20e1b..352de81517c76 100644 --- a/lib/Sema/DerivedConformanceErrorType.cpp +++ b/lib/Sema/DerivedConformanceErrorType.cpp @@ -233,3 +233,81 @@ ValueDecl *DerivedConformance::deriveErrorType(TypeChecker &tc, diag::broken_errortype_requirement); return nullptr; } + +static void deriveBodyBridgedNSError_enum_NSErrorDomain( + AbstractFunctionDecl *domainDecl) { + // enum SomeEnum { + // @derived + // static var _NSErrorDomain: String { + // return "\(self)" + // } + // } + + auto &C = domainDecl->getASTContext(); + + auto selfRef = createSelfDeclRef(domainDecl); + Expr *segmentElts[] = { selfRef }; + auto segments = C.AllocateCopy(segmentElts); + auto string = new (C) InterpolatedStringLiteralExpr(SourceLoc(), segments); + string->setImplicit(); + + auto ret = new (C) ReturnStmt(SourceLoc(), string, /*implicit*/ true); + auto body = BraceStmt::create(C, SourceLoc(), + ASTNode(ret), + SourceLoc()); + domainDecl->setBody(body); +} + +static ValueDecl *deriveBridgedNSError_enum_NSErrorDomain(TypeChecker &tc, + EnumDecl *enumDecl) { + // enum SomeEnum { + // @derived + // static var _NSErrorDomain: String { + // return "\(self)" + // } + // } + + // Note that for @objc enums the format is assumed to be "MyModule.SomeEnum". + // If this changes, please change PrintAsObjC as well. + + ASTContext &C = tc.Context; + + auto stringTy = C.getStringDecl()->getDeclaredType(); + Type enumType = enumDecl->getDeclaredTypeInContext(); + + // Define the getter. + auto getterDecl = declareDerivedPropertyGetter(tc, enumDecl, enumType, + stringTy, stringTy, + /*isStatic=*/true); + getterDecl->setBodySynthesizer(&deriveBodyBridgedNSError_enum_NSErrorDomain); + + // Define the property. + VarDecl *propDecl; + PatternBindingDecl *pbDecl; + std::tie(propDecl, pbDecl) + = declareDerivedReadOnlyProperty(tc, enumDecl, C.Id__NSErrorDomain, + stringTy, stringTy, + getterDecl, /*isStatic=*/true); + + enumDecl->addMember(getterDecl); + enumDecl->addMember(propDecl); + enumDecl->addMember(pbDecl); + return propDecl; +} + +ValueDecl *DerivedConformance::deriveBridgedNSError(TypeChecker &tc, + NominalTypeDecl *type, + ValueDecl *requirement) { + if (!canDeriveConformance(type)) + return nullptr; + + auto enumType = cast(type); + + if (requirement->getName() == tc.Context.Id__NSErrorDomain) + return deriveBridgedNSError_enum_NSErrorDomain(tc, enumType); + + tc.diagnose(requirement->getLoc(), + diag::broken_errortype_requirement); + return nullptr; +} + diff --git a/lib/Sema/DerivedConformanceRawRepresentable.cpp b/lib/Sema/DerivedConformanceRawRepresentable.cpp index a34fe981adb1c..b107a958b1a42 100644 --- a/lib/Sema/DerivedConformanceRawRepresentable.cpp +++ b/lib/Sema/DerivedConformanceRawRepresentable.cpp @@ -154,22 +154,27 @@ FuncDecl *DerivedConformance::declareDerivedPropertyGetter(TypeChecker &tc, NominalTypeDecl *typeDecl, Type contextType, Type propertyInterfaceType, - Type propertyContextType) { + Type propertyContextType, + bool isStatic) { auto &C = tc.Context; - + + Type selfType = contextType; + if (isStatic) + selfType = MetatypeType::get(selfType); + VarDecl *selfDecl = new (C) ParamDecl(/*IsLet*/true, SourceLoc(), Identifier(), SourceLoc(), C.Id_self, - contextType, + selfType, typeDecl); selfDecl->setImplicit(); Pattern *selfParam = new (C) NamedPattern(selfDecl, /*implicit*/ true); - selfParam->setType(contextType); + selfParam->setType(selfType); selfParam = new (C) TypedPattern(selfParam, - TypeLoc::withoutLoc(contextType)); - selfParam->setType(contextType); + TypeLoc::withoutLoc(selfType)); + selfParam->setType(selfType); Pattern *methodParam = TuplePattern::create(C, SourceLoc(),{},SourceLoc()); methodParam->setType(TupleType::getEmpty(C)); Pattern *params[] = {selfParam, methodParam}; @@ -180,12 +185,13 @@ FuncDecl *DerivedConformance::declareDerivedPropertyGetter(TypeChecker &tc, params, TypeLoc::withoutLoc(propertyContextType), typeDecl); getterDecl->setImplicit(); - + getterDecl->setStatic(isStatic); + // Compute the type of the getter. GenericParamList *genericParams = nullptr; Type type = FunctionType::get(TupleType::getEmpty(C), propertyContextType); - Type selfType = getterDecl->computeSelfType(&genericParams); + selfType = getterDecl->computeSelfType(&genericParams); if (genericParams) type = PolymorphicFunctionType::get(selfType, type, genericParams); else @@ -218,10 +224,11 @@ DerivedConformance::declareDerivedReadOnlyProperty(TypeChecker &tc, Identifier name, Type propertyInterfaceType, Type propertyContextType, - FuncDecl *getterDecl) { + FuncDecl *getterDecl, + bool isStatic) { auto &C = tc.Context; - VarDecl *propDecl = new (C) VarDecl(/*static*/ false, + VarDecl *propDecl = new (C) VarDecl(isStatic, /*let*/ false, SourceLoc(), name, propertyContextType, diff --git a/lib/Sema/DerivedConformances.h b/lib/Sema/DerivedConformances.h index b039bf8038118..65a5ab863c221 100644 --- a/lib/Sema/DerivedConformances.h +++ b/lib/Sema/DerivedConformances.h @@ -78,6 +78,13 @@ ValueDecl *deriveErrorType(TypeChecker &tc, NominalTypeDecl *type, ValueDecl *requirement); +/// Derive a _BridgedNSError requirement for an @objc enum type. +/// +/// \returns the derived member, which will also be added to the type. +ValueDecl *deriveBridgedNSError(TypeChecker &tc, + NominalTypeDecl *type, + ValueDecl *requirement); + /// Insert an operator declaration associated with a nominal type. The /// declaration is added at global scope. void _insertOperatorDecl(NominalTypeDecl *scope, Decl *member); @@ -95,7 +102,8 @@ FuncDecl *declareDerivedPropertyGetter(TypeChecker &tc, NominalTypeDecl *typeDecl, Type contextType, Type propertyInterfaceType, - Type propertyContextType); + Type propertyContextType, + bool isStatic = false); /// Declare a read-only property with an existing getter. std::pair @@ -104,7 +112,8 @@ declareDerivedReadOnlyProperty(TypeChecker &tc, Identifier name, Type propertyInterfaceType, Type propertyContextType, - FuncDecl *getterDecl); + FuncDecl *getterDecl, + bool isStatic = false); /// Build a reference to the 'self' decl of a derived function. diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index b2d0e877201b0..64279a517854a 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -3852,7 +3852,11 @@ ValueDecl *TypeChecker::deriveProtocolRequirement(NominalTypeDecl *TypeDecl, case KnownProtocolKind::ErrorType: return DerivedConformance::deriveErrorType(*this, TypeDecl, Requirement); - + + case KnownProtocolKind::_BridgedNSError: + return DerivedConformance::deriveBridgedNSError(*this, TypeDecl, + Requirement); + default: return nullptr; } diff --git a/test/decl/enum/objc_enum_errortype.swift b/test/decl/enum/objc_enum_errortype.swift new file mode 100644 index 0000000000000..0f1b996d1f21e --- /dev/null +++ b/test/decl/enum/objc_enum_errortype.swift @@ -0,0 +1,26 @@ +// RUN: %target-parse-verify-swift + +import Foundation + +func acceptBridgeableNSError(e: E) { } + +@objc enum E1 : Int, ErrorType, _BridgedNSError { + case A = 1 +} + +acceptBridgeableNSError(E1.A) + +@objc enum E2 : Int, ErrorType { + case A = 1 +} + +acceptBridgeableNSError(E2.A) + + +@objc enum E3 : Int { + case A = 1 +} + +acceptBridgeableNSError(E3.A) +// expected-error@-1{{cannot invoke 'acceptBridgeableNSError' with an argument list of type '(E3)'}} +// expected-note@-2{{expected an argument list of type '(E)'}}