From ab05d64406bafd9d8d12f730edfe52b7c67007a0 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Thu, 28 Jan 2016 16:33:46 -0800 Subject: [PATCH 1/2] [Clang Importer] Refactor information pertaining to enums out Introduces new class, EnumInfo, just for the implementation of the ClangImporter to encapsulate various computed information about the enum we're importing. This helps refactor some functionality, aids clarity, and also prevents us from repeating calculations multiple times, as we were doing with classifyEnum's macro-expansion tracking. Provides a base where we can add more heavy lifting in classifyEnum in the future. --- lib/ClangImporter/CMakeLists.txt | 1 + lib/ClangImporter/ClangImporter.cpp | 239 +---------------------- lib/ClangImporter/ImportDecl.cpp | 38 +--- lib/ClangImporter/ImportEnumInfo.cpp | 279 +++++++++++++++++++++++++++ lib/ClangImporter/ImportEnumInfo.h | 82 ++++++++ lib/ClangImporter/ImportType.cpp | 13 +- lib/ClangImporter/ImporterImpl.h | 59 +++--- 7 files changed, 406 insertions(+), 305 deletions(-) create mode 100644 lib/ClangImporter/ImportEnumInfo.cpp create mode 100644 lib/ClangImporter/ImportEnumInfo.h 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..8a6ecf79c7aaa 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,7 @@ namespace { // Create the enum declaration and record it. NominalTypeDecl *result; - auto enumKind = Impl.classifyEnum(Impl.getClangPreprocessor(), decl); + auto enumKind = Impl.getEnumKind(decl); switch (enumKind) { case EnumKind::Constants: { // There is no declaration. Rather, the type is mapped to the @@ -2312,7 +2311,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 +4924,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..cdd301275997e --- /dev/null +++ b/lib/ClangImporter/ImportEnumInfo.cpp @@ -0,0 +1,279 @@ +//===--- 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; + } + + // 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..c941f0e55fbca --- /dev/null +++ b/lib/ClangImporter/ImportEnumInfo.h @@ -0,0 +1,82 @@ +//===--- 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 { + /// \brief The kind + EnumKind kind = EnumKind::Unknown; + + /// \brief The enum's common constant name prefix, which will be stripped from + /// constants + StringRef constantNamePrefix = StringRef(); + +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; } + +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. /// From 55ca60aa65ec55257849b39c27f1eb243a8932ed Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Wed, 3 Feb 2016 12:57:15 -0800 Subject: [PATCH 2/2] [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. --- lib/ClangImporter/ImportDecl.cpp | 129 ++++++++++++++++++----- lib/ClangImporter/ImportEnumInfo.cpp | 14 +++ lib/ClangImporter/ImportEnumInfo.h | 19 ++++ test/ClangModules/Inputs/enum-new.h | 16 +++ test/ClangModules/Inputs/enum-new.m | 7 ++ test/ClangModules/enum-new-execute.swift | 52 +++++++++ test/ClangModules/enum-new.swift | 16 +++ 7 files changed, 224 insertions(+), 29 deletions(-) create mode 100644 test/ClangModules/Inputs/enum-new.m create mode 100644 test/ClangModules/enum-new-execute.swift diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 8a6ecf79c7aaa..0740bab4f4c3d 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -1870,7 +1870,8 @@ namespace { // Create the enum declaration and record it. NominalTypeDecl *result; - auto enumKind = Impl.getEnumKind(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 @@ -1954,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); @@ -2012,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) diff --git a/lib/ClangImporter/ImportEnumInfo.cpp b/lib/ClangImporter/ImportEnumInfo.cpp index cdd301275997e..402b1ba477e2e 100644 --- a/lib/ClangImporter/ImportEnumInfo.cpp +++ b/lib/ClangImporter/ImportEnumInfo.cpp @@ -37,6 +37,20 @@ void EnumInfo::classifyEnum(const clang::EnumDecl *decl, 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(); diff --git a/lib/ClangImporter/ImportEnumInfo.h b/lib/ClangImporter/ImportEnumInfo.h index c941f0e55fbca..46db4b046a871 100644 --- a/lib/ClangImporter/ImportEnumInfo.h +++ b/lib/ClangImporter/ImportEnumInfo.h @@ -53,6 +53,8 @@ enum class EnumKind { }; class EnumInfo { + using AttributePtrUnion = clang::NSErrorDomainAttr *; + /// \brief The kind EnumKind kind = EnumKind::Unknown; @@ -60,6 +62,12 @@ class EnumInfo { /// 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; @@ -72,6 +80,17 @@ class EnumInfo { 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 &); 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 { + } + +}