diff --git a/lib/ClangImporter/CMakeLists.txt b/lib/ClangImporter/CMakeLists.txt index ec2930e042d07..4ede274350af0 100644 --- a/lib/ClangImporter/CMakeLists.txt +++ b/lib/ClangImporter/CMakeLists.txt @@ -12,6 +12,7 @@ add_swift_library(swiftClangImporter ClangDiagnosticConsumer.cpp ClangImporter.cpp ImportDecl.cpp + ImportEnumInfo.cpp ImportMacro.cpp ImportType.cpp SwiftLookupTable.cpp diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 1f745d29a1bdc..c8ce48d4b0584 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -60,6 +60,7 @@ #include using namespace swift; +using namespace importer; // Commonly-used Clang classes. using clang::CompilerInstance; @@ -1359,235 +1360,6 @@ ClangImporter::Implementation::exportName(Identifier name) { return ident; } -/// \brief Returns the common prefix of two strings at camel-case word -/// granularity. -/// -/// For example, given "NSFooBar" and "NSFooBas", returns "NSFoo" -/// (not "NSFooBa"). The returned StringRef is a slice of the "a" argument. -/// -/// If either string has a non-identifier character immediately after the -/// prefix, \p followedByNonIdentifier will be set to \c true. If both strings -/// have identifier characters after the prefix, \p followedByNonIdentifier will -/// be set to \c false. Otherwise, \p followedByNonIdentifier will not be -/// changed from its initial value. -/// -/// This is used to derive the common prefix of enum constants so we can elide -/// it from the Swift interface. -static StringRef getCommonWordPrefix(StringRef a, StringRef b, - bool &followedByNonIdentifier) { - auto aWords = camel_case::getWords(a), bWords = camel_case::getWords(b); - auto aI = aWords.begin(), aE = aWords.end(), - bI = bWords.begin(), bE = bWords.end(); - - unsigned prevLength = 0; - unsigned prefixLength = 0; - for ( ; aI != aE && bI != bE; ++aI, ++bI) { - if (*aI != *bI) { - followedByNonIdentifier = false; - break; - } - - prevLength = prefixLength; - prefixLength = aI.getPosition() + aI->size(); - } - - // Avoid creating a prefix where the rest of the string starts with a number. - if ((aI != aE && !Lexer::isIdentifier(*aI)) || - (bI != bE && !Lexer::isIdentifier(*bI))) { - followedByNonIdentifier = true; - prefixLength = prevLength; - } - - return a.slice(0, prefixLength); -} - -/// Returns the common word-prefix of two strings, allowing the second string -/// to be a common English plural form of the first. -/// -/// For example, given "NSProperty" and "NSProperties", the full "NSProperty" -/// is returned. Given "NSMagicArmor" and "NSMagicArmory", only -/// "NSMagic" is returned. -/// -/// The "-s", "-es", and "-ies" patterns cover every plural NS_OPTIONS name -/// in Cocoa and Cocoa Touch. -/// -/// \see getCommonWordPrefix -static StringRef getCommonPluralPrefix(StringRef singular, StringRef plural) { - assert(!plural.empty()); - - if (singular.empty()) - return singular; - - bool ignored; - StringRef commonPrefix = getCommonWordPrefix(singular, plural, ignored); - if (commonPrefix.size() == singular.size() || plural.back() != 's') - return commonPrefix; - - StringRef leftover = singular.substr(commonPrefix.size()); - StringRef firstLeftoverWord = camel_case::getFirstWord(leftover); - StringRef commonPrefixPlusWord = - singular.substr(0, commonPrefix.size() + firstLeftoverWord.size()); - - // Is the plural string just "[singular]s"? - plural = plural.drop_back(); - if (plural.endswith(firstLeftoverWord)) - return commonPrefixPlusWord; - - if (plural.empty() || plural.back() != 'e') - return commonPrefix; - - // Is the plural string "[singular]es"? - plural = plural.drop_back(); - if (plural.endswith(firstLeftoverWord)) - return commonPrefixPlusWord; - - if (plural.empty() || !(plural.back() == 'i' && singular.back() == 'y')) - return commonPrefix; - - // Is the plural string "[prefix]ies" and the singular "[prefix]y"? - plural = plural.drop_back(); - firstLeftoverWord = firstLeftoverWord.drop_back(); - if (plural.endswith(firstLeftoverWord)) - return commonPrefixPlusWord; - - return commonPrefix; -} - -StringRef ClangImporter::Implementation::getEnumConstantNamePrefix( - clang::Sema &sema, - const clang::EnumDecl *decl) { - switch (classifyEnum(sema.getPreprocessor(), decl)) { - case EnumKind::Enum: - case EnumKind::Options: - // Enums are mapped to Swift enums, Options to Swift option sets, both - // of which attempt prefix-stripping. - break; - - case EnumKind::Constants: - case EnumKind::Unknown: - // Nothing to do. - return StringRef(); - } - - // If there are no enumers, there is no prefix to compute. - auto ec = decl->enumerator_begin(), ecEnd = decl->enumerator_end(); - if (ec == ecEnd) - return StringRef(); - - // Determine whether we can cache the result. - // FIXME: Pass in a cache? - bool useCache = &sema == &getClangSema(); - - // If we've already computed the prefix, return it. - auto known = useCache ? EnumConstantNamePrefixes.find(decl) - : EnumConstantNamePrefixes.end(); - if (known != EnumConstantNamePrefixes.end()) - return known->second; - - // Determine whether the given enumerator is non-deprecated and has no - // specifically-provided name. - auto isNonDeprecatedWithoutCustomName = - [](const clang::EnumConstantDecl *elem) -> bool { - if (elem->hasAttr()) - return false; - - clang::VersionTuple maxVersion{~0U, ~0U, ~0U}; - switch (elem->getAvailability(nullptr, maxVersion)) { - case clang::AR_Available: - case clang::AR_NotYetIntroduced: - for (auto attr : elem->attrs()) { - if (auto annotate = dyn_cast(attr)) { - if (annotate->getAnnotation() == "swift1_unavailable") - return false; - } - if (auto avail = dyn_cast(attr)) { - if (avail->getPlatform()->getName() == "swift") - return false; - } - } - return true; - - case clang::AR_Deprecated: - case clang::AR_Unavailable: - return false; - } - }; - - // Move to the first non-deprecated enumerator, or non-swift_name'd - // enumerator, if present. - auto firstNonDeprecated = std::find_if(ec, ecEnd, - isNonDeprecatedWithoutCustomName); - bool hasNonDeprecated = (firstNonDeprecated != ecEnd); - if (hasNonDeprecated) { - ec = firstNonDeprecated; - } else { - // Advance to the first case without a custom name, deprecated or not. - while (ec != ecEnd && (*ec)->hasAttr()) - ++ec; - if (ec == ecEnd) { - if (useCache) - EnumConstantNamePrefixes.insert({decl, StringRef()}); - return StringRef(); - } - } - - // Compute the common prefix. - StringRef commonPrefix = (*ec)->getName(); - bool followedByNonIdentifier = false; - for (++ec; ec != ecEnd; ++ec) { - // Skip deprecated or swift_name'd enumerators. - const clang::EnumConstantDecl *elem = *ec; - if (hasNonDeprecated) { - if (!isNonDeprecatedWithoutCustomName(elem)) - continue; - } else { - if (elem->hasAttr()) - continue; - } - - commonPrefix = getCommonWordPrefix(commonPrefix, elem->getName(), - followedByNonIdentifier); - if (commonPrefix.empty()) - break; - } - - if (!commonPrefix.empty()) { - StringRef checkPrefix = commonPrefix; - - // Account for the 'kConstant' naming convention on enumerators. - if (checkPrefix[0] == 'k') { - bool canDropK; - if (checkPrefix.size() >= 2) - canDropK = clang::isUppercase(checkPrefix[1]); - else - canDropK = !followedByNonIdentifier; - - if (canDropK) - checkPrefix = checkPrefix.drop_front(); - } - - // Don't use importFullName() here, we want to ignore the swift_name - // and swift_private attributes. - StringRef enumNameStr = decl->getName(); - StringRef commonWithEnum = getCommonPluralPrefix(checkPrefix, - enumNameStr); - size_t delta = commonPrefix.size() - checkPrefix.size(); - - // Account for the 'EnumName_Constant' convention on enumerators. - if (commonWithEnum.size() < checkPrefix.size() && - checkPrefix[commonWithEnum.size()] == '_' && - !followedByNonIdentifier) { - delta += 1; - } - - commonPrefix = commonPrefix.slice(0, commonWithEnum.size() + delta); - } - - if (useCache) - EnumConstantNamePrefixes.insert({decl, commonPrefix}); - return commonPrefix; -} - /// Determine whether the given Clang selector matches the given /// selector pieces. static bool isNonNullarySelector(clang::Selector selector, @@ -2038,7 +1810,7 @@ auto ClangImporter::Implementation::importFullName( // scope, depending how their enclosing enumeration is imported. if (isa(D)) { auto enumDecl = cast(dc); - switch (classifyEnum(clangSema.getPreprocessor(), enumDecl)) { + switch (getEnumKind(enumDecl, &clangSema.getPreprocessor())) { case EnumKind::Enum: case EnumKind::Options: // Enums are mapped to Swift enums, Options to Swift option sets. @@ -2289,7 +2061,8 @@ auto ClangImporter::Implementation::importFullName( // Enumeration constants may have common prefixes stripped. if (isa(D)) { auto enumDecl = cast(D->getDeclContext()); - StringRef removePrefix = getEnumConstantNamePrefix(clangSema, enumDecl); + StringRef removePrefix = + getEnumConstantNamePrefix(enumDecl, &clangSema.getPreprocessor()); if (baseName.startswith(removePrefix)) baseName = baseName.substr(removePrefix.size()); } @@ -2382,7 +2155,7 @@ auto ClangImporter::Implementation::importFullName( // Local function to determine whether the given declaration is subject to // a swift_private attribute. - auto hasSwiftPrivate = [&clangSema](const clang::NamedDecl *D) { + auto hasSwiftPrivate = [&clangSema, this](const clang::NamedDecl *D) { if (D->hasAttr()) return true; @@ -2390,7 +2163,7 @@ auto ClangImporter::Implementation::importFullName( // private if the parent enum is marked private. if (auto *ECD = dyn_cast(D)) { auto *ED = cast(ECD->getDeclContext()); - switch (classifyEnum(clangSema.getPreprocessor(), ED)) { + switch (getEnumKind(ED, &clangSema.getPreprocessor())) { case EnumKind::Constants: case EnumKind::Unknown: if (ED->hasAttr()) diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 19a34aa273022..0740bab4f4c3d 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -53,6 +53,7 @@ STATISTIC(NumFactoryMethodsAsInitializers, "# of factory methods mapped to initializers"); using namespace swift; +using namespace importer; namespace swift { namespace inferred_attributes { @@ -1166,8 +1167,6 @@ static void inferProtocolMemberAvailability(ClangImporter::Implementation &impl, } namespace { - typedef ClangImporter::Implementation::EnumKind EnumKind; - /// \brief Convert Clang declarations into the corresponding Swift /// declarations. class SwiftDeclConverter @@ -1871,7 +1870,8 @@ namespace { // Create the enum declaration and record it. NominalTypeDecl *result; - auto enumKind = Impl.classifyEnum(Impl.getClangPreprocessor(), decl); + auto enumInfo = Impl.getEnumInfo(decl); + auto enumKind = enumInfo.getKind(); switch (enumKind) { case EnumKind::Constants: { // There is no declaration. Rather, the type is mapped to the @@ -1955,57 +1955,62 @@ namespace { } case EnumKind::Enum: { + auto &swiftCtx = Impl.SwiftContext; EnumDecl *nativeDecl; bool declaredNative = hasNativeSwiftDecl(decl, name, dc, nativeDecl); if (declaredNative && nativeDecl) return nativeDecl; // Compute the underlying type. - auto underlyingType = Impl.importType(decl->getIntegerType(), - ImportTypeKind::Enum, - isInSystemModule(dc), - /*isFullyBridgeable*/false); + auto underlyingType = Impl.importType( + decl->getIntegerType(), ImportTypeKind::Enum, isInSystemModule(dc), + /*isFullyBridgeable*/ false); if (!underlyingType) return nullptr; - - auto enumDecl = Impl.createDeclWithClangNode(decl, - Impl.importSourceLoc(decl->getLocStart()), - name, Impl.importSourceLoc(decl->getLocation()), - None, nullptr, dc); + + auto enumDecl = Impl.createDeclWithClangNode( + decl, Impl.importSourceLoc(decl->getLocStart()), name, + Impl.importSourceLoc(decl->getLocation()), None, nullptr, dc); enumDecl->computeType(); - + // Set up the C underlying type as its Swift raw type. enumDecl->setRawType(underlyingType); - + // Add protocol declarations to the enum declaration. - enumDecl->setInherited( - Impl.SwiftContext.AllocateCopy( - llvm::makeArrayRef(TypeLoc::withoutLoc(underlyingType)))); + SmallVector inheritedTypes; + inheritedTypes.push_back(TypeLoc::withoutLoc(underlyingType)); + if (enumInfo.isErrorEnum()) + inheritedTypes.push_back(TypeLoc::withoutLoc( + swiftCtx.getProtocol(KnownProtocolKind::BridgedNSError) + ->getDeclaredType())); + enumDecl->setInherited(swiftCtx.AllocateCopy(inheritedTypes)); enumDecl->setCheckedInheritanceClause(); + // Set up error conformance to be lazily expanded + if (enumInfo.isErrorEnum()) + enumDecl->getAttrs().add(new (swiftCtx) SynthesizedProtocolAttr( + KnownProtocolKind::BridgedNSError)); + // Provide custom implementations of the init(rawValue:) and rawValue // conversions that just do a bitcast. We can't reliably filter a // C enum without additional knowledge that the type has no // undeclared values, and won't ever add cases. auto rawValueConstructor = makeEnumRawValueConstructor(Impl, enumDecl); - auto varName = Impl.SwiftContext.Id_rawValue; - auto rawValue = new (Impl.SwiftContext) VarDecl(/*static*/ false, - /*IsLet*/ false, - SourceLoc(), varName, - underlyingType, - enumDecl); + auto varName = swiftCtx.Id_rawValue; + auto rawValue = new (swiftCtx) VarDecl( + /*static*/ false, + /*IsLet*/ false, SourceLoc(), varName, underlyingType, enumDecl); rawValue->setImplicit(); rawValue->setAccessibility(Accessibility::Public); rawValue->setSetterAccessibility(Accessibility::Private); - + // Create a pattern binding to describe the variable. Pattern *varPattern = createTypedNamedPattern(rawValue); - - auto rawValueBinding = - PatternBindingDecl::create(Impl.SwiftContext, SourceLoc(), - StaticSpellingKind::None, SourceLoc(), - varPattern, nullptr, enumDecl); + + auto rawValueBinding = PatternBindingDecl::create( + swiftCtx, SourceLoc(), StaticSpellingKind::None, SourceLoc(), + varPattern, nullptr, enumDecl); auto rawValueGetter = makeEnumRawValueGetter(Impl, enumDecl, rawValue); @@ -2013,11 +2018,76 @@ namespace { enumDecl->addMember(rawValueGetter); enumDecl->addMember(rawValue); enumDecl->addMember(rawValueBinding); - result = enumDecl; + + // Set up the domain error member to be lazily added + if (enumInfo.isErrorEnum()) { + auto clangDomainIdent = enumInfo.getErrorDomain(); + auto &clangSema = Impl.getClangSema(); + clang::LookupResult lookupResult( + clangSema, clang::DeclarationName(clangDomainIdent), + clang::SourceLocation(), + clang::Sema::LookupNameKind::LookupOrdinaryName); + + if (!clangSema.LookupName(lookupResult, clangSema.TUScope)) { + // Couldn't actually import it as an error enum, fall back to enum + break; + } + + auto clangDecl = lookupResult.getAsSingle(); + if (!clangDecl) { + // Couldn't actually import it as an error enum, fall back to enum + break; + } + auto swiftDecl = Impl.importDecl(clangDecl); + if (!swiftDecl || !isa(swiftDecl)) { + // Couldn't actually import it as an error enum, fall back to enum + break; + } + SourceLoc noLoc = SourceLoc(); + bool isStatic = true; + bool isImplicit = true; + + DeclRefExpr *domainDeclRef = new (swiftCtx) DeclRefExpr( + ConcreteDeclRef(cast(swiftDecl)), {}, isImplicit); + auto stringTy = swiftCtx.getStringDecl()->getDeclaredType(); + ParameterList *params[] = { + ParameterList::createWithoutLoc( + ParamDecl::createSelf(noLoc, enumDecl, isStatic)), + ParameterList::createEmpty(swiftCtx)}; + auto toStringTy = ParameterList::getFullType(stringTy, params); + + FuncDecl *getterDecl = + FuncDecl::create(swiftCtx, noLoc, StaticSpellingKind::None, noLoc, + {}, noLoc, noLoc, noLoc, nullptr, toStringTy, + params, TypeLoc::withoutLoc(stringTy), enumDecl); + + // Make the property decl + auto errorDomainPropertyDecl = new (swiftCtx) + VarDecl(isStatic, + /*isLet=*/false, noLoc, swiftCtx.Id_NSErrorDomain, + stringTy, enumDecl); + errorDomainPropertyDecl->setAccessibility(Accessibility::Public); + + enumDecl->addMember(errorDomainPropertyDecl); + enumDecl->addMember(getterDecl); + errorDomainPropertyDecl->makeComputed( + noLoc, getterDecl, /*Set=*/nullptr, + /*MaterializeForSet=*/nullptr, noLoc); + + getterDecl->setImplicit(); + getterDecl->setStatic(isStatic); + getterDecl->setBodyResultType(stringTy); + getterDecl->setAccessibility(Accessibility::Public); + + auto ret = new (swiftCtx) ReturnStmt(noLoc, {domainDeclRef}); + getterDecl->setBody( + BraceStmt::create(swiftCtx, noLoc, {ret}, noLoc, isImplicit)); + Impl.registerExternalDecl(getterDecl); + } break; } - + case EnumKind::Options: { result = importAsOptionSetType(dc, name, decl); if (!result) @@ -2312,7 +2382,7 @@ namespace { if (name.empty()) return nullptr; - switch (Impl.classifyEnum(Impl.getClangPreprocessor(), clangEnum)) { + switch (Impl.getEnumKind(clangEnum)) { case EnumKind::Constants: { // The enumeration was simply mapped to an integral type. Create a // constant with that integral type. @@ -4925,37 +4995,6 @@ namespace { }; } -/// \brief Classify the given Clang enumeration to describe how to import it. -EnumKind ClangImporter::Implementation:: -classifyEnum(clang::Preprocessor &pp, const clang::EnumDecl *decl) { - // Anonymous enumerations simply get mapped to constants of the - // underlying type of the enum, because there is no way to conjure up a - // name for the Swift type. - if (!decl->hasNameForLinkage()) - return EnumKind::Constants; - - // Was the enum declared using *_ENUM or *_OPTIONS? - // FIXME: Use Clang attributes instead of grovelling the macro expansion loc. - auto loc = decl->getLocStart(); - if (loc.isMacroID()) { - StringRef MacroName = pp.getImmediateMacroName(loc); - if (MacroName == "CF_ENUM" || MacroName == "__CF_NAMED_ENUM" || - MacroName == "OBJC_ENUM" || - MacroName == "SWIFT_ENUM" || MacroName == "SWIFT_ENUM_NAMED") - return EnumKind::Enum; - if (MacroName == "CF_OPTIONS" || MacroName == "OBJC_OPTIONS" - || MacroName == "SWIFT_OPTIONS") - return EnumKind::Options; - } - - // Hardcode a particular annoying case in the OS X headers. - if (decl->getName() == "DYLD_BOOL") - return EnumKind::Enum; - - // Fall back to the 'Unknown' path. - return EnumKind::Unknown; -} - Decl *ClangImporter::Implementation::importDeclCached( const clang::NamedDecl *ClangDecl) { auto Known = ImportedDecls.find(ClangDecl->getCanonicalDecl()); diff --git a/lib/ClangImporter/ImportEnumInfo.cpp b/lib/ClangImporter/ImportEnumInfo.cpp new file mode 100644 index 0000000000000..402b1ba477e2e --- /dev/null +++ b/lib/ClangImporter/ImportEnumInfo.cpp @@ -0,0 +1,293 @@ +//===--- ImportEnumInfo.cpp - Information about importable Clang enums ----===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file provides EnumInfo, which describes a Clang enum ready to be +// imported +// +//===----------------------------------------------------------------------===// + +#include "ImportEnumInfo.h" +#include "swift/Basic/StringExtras.h" +#include "swift/Parse/Lexer.h" +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" +#include "clang/Lex/MacroInfo.h" +#include "clang/Lex/Preprocessor.h" + +using namespace swift; +using namespace importer; + +/// \brief Classify the given Clang enumeration to describe how to import it. +void EnumInfo::classifyEnum(const clang::EnumDecl *decl, + clang::Preprocessor &pp) { + // Anonymous enumerations simply get mapped to constants of the + // underlying type of the enum, because there is no way to conjure up a + // name for the Swift type. + if (!decl->hasNameForLinkage()) { + kind = EnumKind::Constants; + return; + } + + // First, check for attributes that denote the classification + clang::NSErrorDomainAttr *domainAttr = nullptr; + for (auto attr : decl->attrs()) { + if (isa(attr)) { + domainAttr = cast(attr); + break; + } + } + if (domainAttr) { + kind = EnumKind::Enum; + attribute = domainAttr; + return; + } + + // Was the enum declared using *_ENUM or *_OPTIONS? + // FIXME: Use Clang attributes instead of grovelling the macro expansion loc. + auto loc = decl->getLocStart(); + if (loc.isMacroID()) { + StringRef MacroName = pp.getImmediateMacroName(loc); + if (MacroName == "CF_ENUM" || MacroName == "__CF_NAMED_ENUM" || + MacroName == "OBJC_ENUM" || MacroName == "SWIFT_ENUM" || + MacroName == "SWIFT_ENUM_NAMED") { + kind = EnumKind::Enum; + return; + } + if (MacroName == "CF_OPTIONS" || MacroName == "OBJC_OPTIONS" || + MacroName == "SWIFT_OPTIONS") { + kind = EnumKind::Options; + return; + } + } + + // Hardcode a particular annoying case in the OS X headers. + if (decl->getName() == "DYLD_BOOL") { + kind = EnumKind::Enum; + return; + } + + // Fall back to the 'Unknown' path. + kind = EnumKind::Unknown; +} + +/// \brief Returns the common prefix of two strings at camel-case word +/// granularity. +/// +/// For example, given "NSFooBar" and "NSFooBas", returns "NSFoo" +/// (not "NSFooBa"). The returned StringRef is a slice of the "a" argument. +/// +/// If either string has a non-identifier character immediately after the +/// prefix, \p followedByNonIdentifier will be set to \c true. If both strings +/// have identifier characters after the prefix, \p followedByNonIdentifier will +/// be set to \c false. Otherwise, \p followedByNonIdentifier will not be +/// changed from its initial value. +/// +/// This is used to derive the common prefix of enum constants so we can elide +/// it from the Swift interface. +static StringRef getCommonWordPrefix(StringRef a, StringRef b, + bool &followedByNonIdentifier) { + auto aWords = camel_case::getWords(a), bWords = camel_case::getWords(b); + auto aI = aWords.begin(), aE = aWords.end(), bI = bWords.begin(), + bE = bWords.end(); + + unsigned prevLength = 0; + unsigned prefixLength = 0; + for (; aI != aE && bI != bE; ++aI, ++bI) { + if (*aI != *bI) { + followedByNonIdentifier = false; + break; + } + + prevLength = prefixLength; + prefixLength = aI.getPosition() + aI->size(); + } + + // Avoid creating a prefix where the rest of the string starts with a number. + if ((aI != aE && !Lexer::isIdentifier(*aI)) || + (bI != bE && !Lexer::isIdentifier(*bI))) { + followedByNonIdentifier = true; + prefixLength = prevLength; + } + + return a.slice(0, prefixLength); +} + +/// Returns the common word-prefix of two strings, allowing the second string +/// to be a common English plural form of the first. +/// +/// For example, given "NSProperty" and "NSProperties", the full "NSProperty" +/// is returned. Given "NSMagicArmor" and "NSMagicArmory", only +/// "NSMagic" is returned. +/// +/// The "-s", "-es", and "-ies" patterns cover every plural NS_OPTIONS name +/// in Cocoa and Cocoa Touch. +/// +/// \see getCommonWordPrefix +static StringRef getCommonPluralPrefix(StringRef singular, StringRef plural) { + assert(!plural.empty()); + + if (singular.empty()) + return singular; + + bool ignored; + StringRef commonPrefix = getCommonWordPrefix(singular, plural, ignored); + if (commonPrefix.size() == singular.size() || plural.back() != 's') + return commonPrefix; + + StringRef leftover = singular.substr(commonPrefix.size()); + StringRef firstLeftoverWord = camel_case::getFirstWord(leftover); + StringRef commonPrefixPlusWord = + singular.substr(0, commonPrefix.size() + firstLeftoverWord.size()); + + // Is the plural string just "[singular]s"? + plural = plural.drop_back(); + if (plural.endswith(firstLeftoverWord)) + return commonPrefixPlusWord; + + if (plural.empty() || plural.back() != 'e') + return commonPrefix; + + // Is the plural string "[singular]es"? + plural = plural.drop_back(); + if (plural.endswith(firstLeftoverWord)) + return commonPrefixPlusWord; + + if (plural.empty() || !(plural.back() == 'i' && singular.back() == 'y')) + return commonPrefix; + + // Is the plural string "[prefix]ies" and the singular "[prefix]y"? + plural = plural.drop_back(); + firstLeftoverWord = firstLeftoverWord.drop_back(); + if (plural.endswith(firstLeftoverWord)) + return commonPrefixPlusWord; + + return commonPrefix; +} + +/// Determine the prefix to be stripped from the names of the enum constants +/// within the given enum. +void EnumInfo::determineConstantNamePrefix(const clang::EnumDecl *decl) { + switch (getKind()) { + case EnumKind::Enum: + case EnumKind::Options: + // Enums are mapped to Swift enums, Options to Swift option sets, both + // of which attempt prefix-stripping. + break; + + case EnumKind::Constants: + case EnumKind::Unknown: + // Nothing to do. + return; + } + + // If there are no enumers, there is no prefix to compute. + auto ec = decl->enumerator_begin(), ecEnd = decl->enumerator_end(); + if (ec == ecEnd) + return; + + // Determine whether the given enumerator is non-deprecated and has no + // specifically-provided name. + auto isNonDeprecatedWithoutCustomName = []( + const clang::EnumConstantDecl *elem) -> bool { + if (elem->hasAttr()) + return false; + + clang::VersionTuple maxVersion{~0U, ~0U, ~0U}; + switch (elem->getAvailability(nullptr, maxVersion)) { + case clang::AR_Available: + case clang::AR_NotYetIntroduced: + for (auto attr : elem->attrs()) { + if (auto annotate = dyn_cast(attr)) { + if (annotate->getAnnotation() == "swift1_unavailable") + return false; + } + if (auto avail = dyn_cast(attr)) { + if (avail->getPlatform()->getName() == "swift") + return false; + } + } + return true; + + case clang::AR_Deprecated: + case clang::AR_Unavailable: + return false; + } + }; + + // Move to the first non-deprecated enumerator, or non-swift_name'd + // enumerator, if present. + auto firstNonDeprecated = + std::find_if(ec, ecEnd, isNonDeprecatedWithoutCustomName); + bool hasNonDeprecated = (firstNonDeprecated != ecEnd); + if (hasNonDeprecated) { + ec = firstNonDeprecated; + } else { + // Advance to the first case without a custom name, deprecated or not. + while (ec != ecEnd && (*ec)->hasAttr()) + ++ec; + if (ec == ecEnd) { + return; + } + } + + // Compute the common prefix. + StringRef commonPrefix = (*ec)->getName(); + bool followedByNonIdentifier = false; + for (++ec; ec != ecEnd; ++ec) { + // Skip deprecated or swift_name'd enumerators. + const clang::EnumConstantDecl *elem = *ec; + if (hasNonDeprecated) { + if (!isNonDeprecatedWithoutCustomName(elem)) + continue; + } else { + if (elem->hasAttr()) + continue; + } + + commonPrefix = getCommonWordPrefix(commonPrefix, elem->getName(), + followedByNonIdentifier); + if (commonPrefix.empty()) + break; + } + + if (!commonPrefix.empty()) { + StringRef checkPrefix = commonPrefix; + + // Account for the 'kConstant' naming convention on enumerators. + if (checkPrefix[0] == 'k') { + bool canDropK; + if (checkPrefix.size() >= 2) + canDropK = clang::isUppercase(checkPrefix[1]); + else + canDropK = !followedByNonIdentifier; + + if (canDropK) + checkPrefix = checkPrefix.drop_front(); + } + + // Don't use importFullName() here, we want to ignore the swift_name + // and swift_private attributes. + StringRef enumNameStr = decl->getName(); + StringRef commonWithEnum = getCommonPluralPrefix(checkPrefix, enumNameStr); + size_t delta = commonPrefix.size() - checkPrefix.size(); + + // Account for the 'EnumName_Constant' convention on enumerators. + if (commonWithEnum.size() < checkPrefix.size() && + checkPrefix[commonWithEnum.size()] == '_' && !followedByNonIdentifier) { + delta += 1; + } + + commonPrefix = commonPrefix.slice(0, commonWithEnum.size() + delta); + } + + constantNamePrefix = commonPrefix; +} diff --git a/lib/ClangImporter/ImportEnumInfo.h b/lib/ClangImporter/ImportEnumInfo.h new file mode 100644 index 0000000000000..46db4b046a871 --- /dev/null +++ b/lib/ClangImporter/ImportEnumInfo.h @@ -0,0 +1,101 @@ +//===--- EnumInfo.h - Information about importable Clang enums ------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file provides ImportEnumInfo, which describes a Clang enum ready to be +// imported +// +//===----------------------------------------------------------------------===// +#ifndef SWIFT_CLANG_IMPORT_ENUM_H +#define SWIFT_CLANG_IMPORT_ENUM_H + +#include "swift/AST/Decl.h" +#include "clang/AST/Attr.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang { +class EnumDecl; +class Preprocessor; +class MacroInfo; +} + +namespace swift { +namespace importer { + +/// \brief Describes how a particular C enumeration type will be imported +/// into Swift. All of the possibilities have the same storage +/// representation, but can be used in different ways. +enum class EnumKind { + /// \brief The enumeration type should map to an enum, which means that + /// all of the cases are independent. + Enum, + /// \brief The enumeration type should map to an option set, which means + /// that + /// the constants represent combinations of independent flags. + Options, + /// \brief The enumeration type should map to a distinct type, but we don't + /// know the intended semantics of the enum constants, so conservatively + /// map them to independent constants. + Unknown, + /// \brief The enumeration constants should simply map to the appropriate + /// integer values. + Constants, +}; + +class EnumInfo { + using AttributePtrUnion = clang::NSErrorDomainAttr *; + + /// \brief The kind + EnumKind kind = EnumKind::Unknown; + + /// \brief The enum's common constant name prefix, which will be stripped from + /// constants + StringRef constantNamePrefix = StringRef(); + + /// \brief The identifying attribute for specially imported enums + /// + /// Currently, only NS_ERROR_ENUM creates one for its error domain, but others + /// should in the future. + AttributePtrUnion attribute = nullptr; + +public: + EnumInfo() = default; + + EnumInfo(const clang::EnumDecl *decl, clang::Preprocessor &pp) { + classifyEnum(decl, pp); + determineConstantNamePrefix(decl); + } + + EnumKind getKind() const { return kind; } + + StringRef getConstantNamePrefix() const { return constantNamePrefix; } + + /// \brief Whether this maps to an enum who also provides an error domain + bool isErrorEnum() const { + return getKind() == EnumKind::Enum && attribute; + } + + /// \brief For this error enum, extract the name of the error domain constant + clang::IdentifierInfo *getErrorDomain() const { + assert(isErrorEnum() && "not error enum"); + return attribute->getErrorDomain(); + } + +private: + void determineConstantNamePrefix(const clang::EnumDecl *); + void classifyEnum(const clang::EnumDecl *, clang::Preprocessor &); +}; +} +} + +#endif // SWIFT_CLANG_IMPORT_ENUM_H diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 6e1a95e0712c8..e6d030750b997 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -39,6 +39,7 @@ #include "llvm/ADT/StringExtras.h" using namespace swift; +using namespace importer; /// Given that a type is the result of a special typedef import, was /// it originally a CF pointer? @@ -657,8 +658,8 @@ namespace { ImportResult VisitEnumType(const clang::EnumType *type) { auto clangDecl = type->getDecl(); - switch (Impl.classifyEnum(Impl.getClangPreprocessor(), clangDecl)) { - case ClangImporter::Implementation::EnumKind::Constants: { + switch (Impl.getEnumKind(clangDecl)) { + case EnumKind::Constants: { auto clangDef = clangDecl->getDefinition(); // Map anonymous enums with no fixed underlying type to Int /if/ // they fit in an Int32. If not, this mapping isn't guaranteed to be @@ -671,9 +672,9 @@ namespace { // Import the underlying integer type. return Visit(clangDecl->getIntegerType()); } - case ClangImporter::Implementation::EnumKind::Enum: - case ClangImporter::Implementation::EnumKind::Unknown: - case ClangImporter::Implementation::EnumKind::Options: { + case EnumKind::Enum: + case EnumKind::Unknown: + case EnumKind::Options: { auto decl = dyn_cast_or_null(Impl.importDecl(clangDecl)); if (!decl) return nullptr; @@ -1873,7 +1874,7 @@ DefaultArgumentKind ClangImporter::Implementation::inferDefaultArgument( // Option sets default to "[]" if they have "Options" in their name. if (const clang::EnumType *enumTy = type->getAs()) - if (classifyEnum(pp, enumTy->getDecl()) == EnumKind::Options) { + if (getEnumKind(enumTy->getDecl(), &pp) == EnumKind::Options) { auto enumName = enumTy->getDecl()->getName(); for (auto word : reversed(camel_case::getWords(enumName))) { if (camel_case::sameWordIgnoreFirstCase(word, "options")) diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 614e0f566e1e8..b1c36ec3fefa7 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -17,6 +17,7 @@ #ifndef SWIFT_CLANG_IMPORTER_IMPL_H #define SWIFT_CLANG_IMPORTER_IMPL_H +#include "ImportEnumInfo.h" #include "SwiftLookupTable.h" #include "swift/ClangImporter/ClangImporter.h" #include "swift/AST/ASTContext.h" @@ -246,25 +247,6 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation friend class SwiftNameLookupExtension; public: - /// \brief Describes how a particular C enumeration type will be imported - /// into Swift. All of the possibilities have the same storage - /// representation, but can be used in different ways. - enum class EnumKind { - /// \brief The enumeration type should map to an enum, which means that - /// all of the cases are independent. - Enum, - /// \brief The enumeration type should map to an option set, which means that - /// the constants represent combinations of independent flags. - Options, - /// \brief The enumeration type should map to a distinct type, but we don't - /// know the intended semantics of the enum constants, so conservatively - /// map them to independent constants. - Unknown, - /// \brief The enumeration constants should simply map to the appropriate - /// integer values. - Constants - }; - Implementation(ASTContext &ctx, const ClangImporterOptions &opts); ~Implementation(); @@ -519,13 +501,38 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// \brief Cache of the class extensions. llvm::DenseMap ClassExtensions; + /// \brief Cache enum infos + llvm::DenseMap enumInfos; + public: /// \brief Keep track of subscript declarations based on getter/setter /// pairs. llvm::DenseMap, SubscriptDecl *> Subscripts; - /// \brief Keep track of enum constant name prefixes in enums. - llvm::DenseMap EnumConstantNamePrefixes; + importer::EnumInfo getEnumInfo(const clang::EnumDecl *decl, + clang::Preprocessor *ppOverride = nullptr) { + if (enumInfos.count(decl)) + return enumInfos[decl]; + // Due to the semaOverride present in importFullName(), we might be using a + // decl from a different context. + auto &preprocessor = ppOverride ? *ppOverride : getClangPreprocessor(); + + importer::EnumInfo enumInfo(decl, preprocessor); + enumInfos[decl] = enumInfo; + return enumInfo; + } + importer::EnumKind getEnumKind(const clang::EnumDecl *decl, + clang::Preprocessor *ppOverride = nullptr) { + return getEnumInfo(decl, ppOverride).getKind(); + } + + /// \brief the prefix to be stripped from the names of the enum constants + /// within the given enum. + StringRef + getEnumConstantNamePrefix(const clang::EnumDecl *decl, + clang::Preprocessor *ppOverride = nullptr) { + return getEnumInfo(decl, ppOverride).getConstantNamePrefix(); + } private: class EnumConstantDenseMapInfo { @@ -547,11 +554,6 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation } }; - /// Retrieve the prefix to be stripped from the names of the enum constants - /// within the given enum. - StringRef getEnumConstantNamePrefix(clang::Sema &sema, - const clang::EnumDecl *enumDecl); - public: /// \brief Keep track of enum constant values that have been imported. llvm::DenseMap, @@ -909,11 +911,6 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// Returns true if it is expected that the macro is ignored. bool shouldIgnoreMacro(StringRef name, const clang::MacroInfo *macro); - /// \brief Classify the given Clang enumeration type to describe how it - /// should be imported - static EnumKind classifyEnum(clang::Preprocessor &pp, - const clang::EnumDecl *decl); - /// Import attributes from the given Clang declaration to its Swift /// equivalent. /// diff --git a/test/ClangModules/Inputs/enum-new.h b/test/ClangModules/Inputs/enum-new.h index 0ccba08a0b520..05ba7d2b3bc17 100644 --- a/test/ClangModules/Inputs/enum-new.h +++ b/test/ClangModules/Inputs/enum-new.h @@ -50,3 +50,19 @@ typedef CF_OPTIONS(unsigned, ColorOptions) { Pastel, Swift }; ColorOptions getColorOptions(); + +#define NS_ERROR_ENUM(_type, _name, _domain) \ + enum _name : _type _name; \ + enum __attribute__((ns_error_domain(_domain))) _name : _type + +@class NSString; +extern NSString *const TestErrorDomain; +typedef NS_ERROR_ENUM(int, TestError, TestErrorDomain) { + TENone, + TEOne, + TETwo, +}; + +TestError getErr(); + + diff --git a/test/ClangModules/Inputs/enum-new.m b/test/ClangModules/Inputs/enum-new.m new file mode 100644 index 0000000000000..06db3728e3e17 --- /dev/null +++ b/test/ClangModules/Inputs/enum-new.m @@ -0,0 +1,7 @@ +#include "enum-new.h" + +NSString *const TestErrorDomain; + +TestError getErr() { + return TEOne; +} diff --git a/test/ClangModules/enum-new-execute.swift b/test/ClangModules/enum-new-execute.swift new file mode 100644 index 0000000000000..981212fc025f8 --- /dev/null +++ b/test/ClangModules/enum-new-execute.swift @@ -0,0 +1,52 @@ +// RUN: mkdir -p %t +// RUN: %target-clang %S/Inputs/enum-new.m -c -o %t/enum-new.o +// RUN: %target-build-swift -import-objc-header %S/Inputs/enum-new.h -Xlinker %t/enum-new.o %s -o %t/a.out +// RUN: %target-run %t/a.out | FileCheck %s + +// REQUIRES: executable_test + +import Foundation + +func printError(err: TestError) { + switch (err) { + case .TENone: + print("TestError: TENone") + break + + case .TEOne: + print("TestError: TEOne") + break + + case .TETwo: + print("TestError: TETwo") + break + } +} + +func testError() { + let terr = getErr() + switch (terr) { case .TENone, .TEOne, .TETwo: break } // ok + printError(terr) + // CHECK: TestError: TEOne + + do { + throw TestError.TETwo + } catch let error as TestError { + printError(error) + // CHECK-NEXT: TestError: TETwo + } catch { + assert(false) + } + + do { + enum LocalError : ErrorType { case Err } + throw LocalError.Err + } catch let error as TestError { + printError(error) + } catch { + print("other error found") + // CHECK-NEXT: other error found + } +} + +testError() \ No newline at end of file diff --git a/test/ClangModules/enum-new.swift b/test/ClangModules/enum-new.swift index 19322b8d28767..5db904dc58e27 100644 --- a/test/ClangModules/enum-new.swift +++ b/test/ClangModules/enum-new.swift @@ -23,3 +23,19 @@ func test() { } // expected-error {{switch must be exhaustive, consider adding a default clause}} } +func testError() { + let terr = getErr() + switch (terr) { case .TENone, .TEOne, .TETwo: break } // ok + + switch (terr) { case .TENone, .TEOne: break } + // expected-error@-1 {{switch must be exhaustive, consider adding a default clause}} + + let _ = TestError(rawValue: 2)! + + do { + throw TestError.TEOne + } catch is TestError { + } catch { + } + +}