diff --git a/include/swift/AST/Identifier.h b/include/swift/AST/Identifier.h index e0c1d95cd2392..7cdc01d263ed1 100644 --- a/include/swift/AST/Identifier.h +++ b/include/swift/AST/Identifier.h @@ -438,34 +438,6 @@ class ObjCSelector { /// \param scratch Scratch space to use. StringRef getString(llvm::SmallVectorImpl &scratch) const; - /// Ask whether this selector is a nullary selector (taking no - /// arguments) whose name matches the given piece. - bool isNullarySelector(StringRef piece) const { - if (Storage.isSimpleName()) { - return Storage.getBaseName().str() == piece; - } else { - return false; - } - } - - /// Ask whether this selector is a non-nullary selector matching the - /// given literal pieces. - bool isNonNullarySelector(ArrayRef pieces) const { - if (Storage.isSimpleName()) { - return false; - } - - ArrayRef args = Storage.getArgumentNames(); - if (args.size() != pieces.size()) - return false; - - for (size_t i = 0, e = args.size(); i != e; ++i) { - if (args[i].str() != pieces[i]) - return false; - } - return true; - } - void *getOpaqueValue() const { return Storage.getOpaqueValue(); } static ObjCSelector getFromOpaqueValue(void *p) { return ObjCSelector(DeclName::getFromOpaqueValue(p)); diff --git a/include/swift/Basic/StringExtras.h b/include/swift/Basic/StringExtras.h index 71b7b6f41804f..7d297ce92a0f0 100644 --- a/include/swift/Basic/StringExtras.h +++ b/include/swift/Basic/StringExtras.h @@ -51,6 +51,14 @@ namespace swift { /// Determine the part of speech for the given word. PartOfSpeech getPartOfSpeech(StringRef word); + /// Scratch space used for returning a set of StringRefs. + class StringScratchSpace { + llvm::BumpPtrAllocator Allocator; + + public: + StringRef copyString(StringRef string); + }; + namespace camel_case { class WordIterator; @@ -219,6 +227,16 @@ namespace swift { /// unchanged. StringRef toLowercaseWord(StringRef string, SmallVectorImpl &scratch); + /// Lowercase the first word within the given camelCase string. + /// + /// \param string The string to lowercase. + /// \param scratch Scratch buffer used to form the resulting string. + /// + /// \returns the string with the first word lowercased. When the + /// first word is an acronym, the string will be returned + /// unchanged. + StringRef toLowercaseWord(StringRef string, StringScratchSpace &scratch); + /// Sentence-case the given camelCase string by turning the first /// letter into an uppercase letter. /// @@ -358,14 +376,6 @@ struct OmissionTypeName { /// would produce "ByAppendingString". StringRef matchLeadingTypeName(StringRef name, OmissionTypeName typeName); -/// Scratch space used for returning a set of StringRefs. -class StringScratchSpace { - llvm::BumpPtrAllocator Allocator; - -public: - StringRef copyString(StringRef string); -}; - /// Describes a set of names with an inheritance relationship. class InheritedNameSet { const InheritedNameSet *Parent; diff --git a/include/swift/ClangImporter/ClangImporter.h b/include/swift/ClangImporter/ClangImporter.h index b38b28b7a903c..8720ea5716573 100644 --- a/include/swift/ClangImporter/ClangImporter.h +++ b/include/swift/ClangImporter/ClangImporter.h @@ -259,6 +259,9 @@ class ClangImporter final : public ClangModuleLoader { // Print statistics from the Clang AST reader. void printStatistics() const override; + /// Dump Swift lookup tables. + void dumpSwiftLookupTables(); + /// Given the path of a Clang module, collect the names of all its submodules /// and their corresponding visibility. Calling this function does not load the /// module. diff --git a/include/swift/ClangImporter/ClangImporterOptions.h b/include/swift/ClangImporter/ClangImporterOptions.h index d4292224ea39d..b15d8ab6a0ead 100644 --- a/include/swift/ClangImporter/ClangImporterOptions.h +++ b/include/swift/ClangImporter/ClangImporterOptions.h @@ -73,6 +73,10 @@ class ClangImporterOptions { // If true, infer default arguments for nullable pointers (nil) and // option sets ([]). bool InferDefaultArguments = false; + + /// If true, we should use the Swift name lookup tables rather than + /// Clang's name lookup facilities. + bool UseSwiftLookupTables = false; }; } // end namespace swift diff --git a/lib/Basic/StringExtras.cpp b/lib/Basic/StringExtras.cpp index a1d51488e53b3..0716959bf4c2c 100644 --- a/lib/Basic/StringExtras.cpp +++ b/lib/Basic/StringExtras.cpp @@ -309,12 +309,18 @@ static bool isKeyword(StringRef identifier) { static Optional skipTypeSuffix(StringRef typeName) { if (typeName.empty()) return None; + auto lastWord = camel_case::getLastWord(typeName); + // "Type" suffix. - if (camel_case::getLastWord(typeName) == "Type" && - typeName.size() > 4) { + if (lastWord == "Type" && typeName.size() > 4) { return typeName.drop_back(4); } + // "Ref" suffix. + if (lastWord == "Ref" && typeName.size() > 3) { + return typeName.drop_back(3); + } + // \d+D for dimensionality. if (typeName.back() == 'D' && typeName.size() > 1) { unsigned firstDigit = typeName.size() - 1; @@ -436,9 +442,10 @@ bool InheritedNameSet::contains(StringRef name) const { } /// Wrapper for camel_case::toLowercaseWord that uses string scratch space. -static StringRef toLowercaseWord(StringRef string, StringScratchSpace &scratch){ +StringRef camel_case::toLowercaseWord(StringRef string, + StringScratchSpace &scratch){ llvm::SmallString<32> scratchStr; - StringRef result = camel_case::toLowercaseWord(string, scratchStr); + StringRef result = toLowercaseWord(string, scratchStr); if (string == result) return string; diff --git a/lib/ClangImporter/CMakeLists.txt b/lib/ClangImporter/CMakeLists.txt index 568159ff6d863..90fcf4f0764e2 100644 --- a/lib/ClangImporter/CMakeLists.txt +++ b/lib/ClangImporter/CMakeLists.txt @@ -14,6 +14,7 @@ add_swift_library(swiftClangImporter ImportDecl.cpp ImportMacro.cpp ImportType.cpp + SwiftLookupTable.cpp LINK_LIBRARIES swiftAST ) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index c21b8910fac87..95e900def22a2 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -51,7 +51,6 @@ #include "clang/Rewrite/Frontend/Rewriters.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Sema.h" -#include "llvm/ADT/Statistic.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/CrashRecoveryContext.h" #include "llvm/Support/Path.h" @@ -60,29 +59,6 @@ using namespace swift; -//===--------------------------------------------------------------------===// -// Importer statistics -//===--------------------------------------------------------------------===// -#define DEBUG_TYPE "Clang module importer" -STATISTIC(NumNullaryMethodNames, - "nullary selectors imported"); -STATISTIC(NumUnaryMethodNames, - "unary selectors imported"); -STATISTIC(NumNullaryInitMethodsMadeUnary, - "nullary Objective-C init methods turned into unary initializers"); -STATISTIC(NumMultiMethodNames, - "multi-part selector method names imported"); -STATISTIC(NumMethodsMissingFirstArgName, - "selectors where the first argument name is missing"); -STATISTIC(NumFactoryMethodsNullary, - "# of factory methods not mapped due to nullary with long name"); -STATISTIC(NumInitsDroppedWith, - "# of initializer selectors from which \"with\" was dropped"); -STATISTIC(NumInitsPrepositionSplit, - "# of initializer selectors where the split was on a preposition"); -STATISTIC(NumInitsNonPrepositionSplit, - "# of initializer selectors where the split wasn't on a preposition"); - // Commonly-used Clang classes. using clang::CompilerInstance; using clang::CompilerInvocation; @@ -702,6 +678,53 @@ bool ClangImporter::addSearchPath(StringRef newSearchPath, bool isFramework) { return false; } +void ClangImporter::Implementation::addEntryToLookupTable( + SwiftLookupTable &table, clang::NamedDecl *named) +{ + // Determine whether this declaration is suppressed in Swift. + bool suppressDecl = false; + if (auto objcMethod = dyn_cast(named)) { + // If this member is a method that is a getter or setter for a + // property, don't add it into the table. property names and + // getter names (by choosing to only have a property). + // + // Note that this is suppressed for certain accessibility declarations, + // which are imported as getter/setter pairs and not properties. + if (objcMethod->isPropertyAccessor() && !isAccessibilityDecl(objcMethod)) { + suppressDecl = true; + } + } else if (auto objcProperty = dyn_cast(named)) { + // Suppress certain accessibility properties; they're imported as + // getter/setter pairs instead. + if (isAccessibilityDecl(objcProperty)) + suppressDecl = true; + } + + if (!suppressDecl) { + // If we have a name to import as, add this entry to the table. + clang::DeclContext *effectiveContext; + if (auto importedName = importFullName(named, None, &effectiveContext)) { + table.addEntry(importedName.Imported, named, effectiveContext); + + // Also add the alias, if needed. + if (importedName.Alias) + table.addEntry(importedName.Alias, named, effectiveContext); + } + } + + // Walk the members of any context that can have nested members. + if (isa(named) || + isa(named) || + isa(named) || + isa(named)) { + clang::DeclContext *dc = cast(named); + for (auto member : dc->decls()) { + if (auto namedMember = dyn_cast(member)) + addEntryToLookupTable(table, namedMember); + } + } +} + bool ClangImporter::Implementation::importHeader( Module *adapter, StringRef headerName, SourceLoc diagLoc, bool trackParsedSymbols, @@ -736,9 +759,16 @@ bool ClangImporter::Implementation::importHeader( clang::Parser::DeclGroupPtrTy parsed; while (!Parser->ParseTopLevelDecl(parsed)) { - if (trackParsedSymbols && parsed) { + if (parsed && (trackParsedSymbols || UseSwiftLookupTables)) { for (auto *D : parsed.get()) { - addBridgeHeaderTopLevelDecls(D); + if (trackParsedSymbols) + addBridgeHeaderTopLevelDecls(D); + + if (UseSwiftLookupTables) { + if (auto named = dyn_cast(D)) { + addEntryToLookupTable(BridgingHeaderLookupTable, named); + } + } } } } @@ -1021,7 +1051,8 @@ ClangImporter::Implementation::Implementation(ASTContext &ctx, InferImplicitProperties(opts.InferImplicitProperties), ImportForwardDeclarations(opts.ImportForwardDeclarations), OmitNeedlessWords(opts.OmitNeedlessWords), - InferDefaultArguments(opts.InferDefaultArguments) + InferDefaultArguments(opts.InferDefaultArguments), + UseSwiftLookupTables(opts.UseSwiftLookupTables) { // Add filters to determine if a Clang availability attribute // applies in Swift, and if so, what is the cutoff for deprecated @@ -1278,40 +1309,1046 @@ ClangImporter::Implementation::exportName(Identifier name) { return ident; } -Identifier -ClangImporter::Implementation::importDeclName(clang::DeclarationName name, - StringRef removePrefix) { - // FIXME: At some point, we'll be able to import operators as well. - if (!name || name.getNameKind() != clang::DeclarationName::Identifier) - return Identifier(); +/// Parse a stringified Swift declaration name, e.g. "init(frame:)". +static StringRef parseDeclName(StringRef Name, + SmallVectorImpl &ArgNames, + bool &IsFunctionName) { + if (Name.back() != ')') { + IsFunctionName = false; + if (Lexer::isIdentifier(Name) && Name != "_") + return Name; - StringRef nameStr = name.getAsIdentifierInfo()->getName(); - // Remove the prefix, if any. - if (!removePrefix.empty()) { - if (nameStr.startswith(removePrefix)) { - nameStr = nameStr.slice(removePrefix.size(), nameStr.size()); + return ""; + } + + IsFunctionName = true; + + StringRef BaseName, Parameters; + std::tie(BaseName, Parameters) = Name.split('('); + if (!Lexer::isIdentifier(BaseName) || BaseName == "_") + return ""; + + if (Parameters.empty()) + return ""; + Parameters = Parameters.drop_back(); // ')' + + if (Parameters.empty()) + return BaseName; + + if (Parameters.back() != ':') + return ""; + + do { + StringRef NextParam; + std::tie(NextParam, Parameters) = Parameters.split(':'); + + if (!Lexer::isIdentifier(NextParam)) + return ""; + Identifier NextParamID; + if (NextParam == "_") + ArgNames.push_back(""); + else + ArgNames.push_back(NextParam); + } while (!Parameters.empty()); + + return BaseName; +} + +/// \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(); } - // Get the Swift identifier. - return SwiftContext.getIdentifier(nameStr); + // 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); } -Identifier -ClangImporter::Implementation::importName(const clang::NamedDecl *D, - StringRef removePrefix) { +/// 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( + const clang::EnumDecl *decl) { + switch (classifyEnum(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(); + + // If we've already computed the prefix, return it. + auto known = EnumConstantNamePrefixes.find(decl); + 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) { + EnumConstantNamePrefixes.insert({decl, StringRef()}); + return StringRef(); + } + } + + // Compute th e 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(); + } + + // Account for the enum being imported using + // __attribute__((swift_private)). This is a little ad hoc, but it's a + // rare case anyway. + Identifier enumName = importFullName(decl).Imported.getBaseName(); + StringRef enumNameStr = enumName.str(); + if (enumNameStr.startswith("__") && !checkPrefix.startswith("__")) + enumNameStr = enumNameStr.drop_front(2); + + 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); + } + + EnumConstantNamePrefixes.insert({decl, commonPrefix}); + return commonPrefix; +} + +/// Determine whether the given Clang selector matches the given +/// selector pieces. +static bool isNonNullarySelector(clang::Selector selector, + ArrayRef pieces) { + unsigned n = selector.getNumArgs(); + if (n == 0) return false; + if (n != pieces.size()) return false; + + for (unsigned i = 0; i != n; ++i) { + if (selector.getNameForSlot(i) != pieces[i]) return false; + } + + return true; +} + +/// Whether we should make a variadic method with the given selector +/// non-variadic. +static bool shouldMakeSelectorNonVariadic(clang::Selector selector) { + // This is UIActionSheet's designated initializer. + if (isNonNullarySelector(selector, + { "initWithTitle", + "delegate", + "cancelButtonTitle", + "destructiveButtonTitle", + "otherButtonTitles" })) + return true; + + // This is UIAlertView's designated initializer. + if (isNonNullarySelector(selector, + { "initWithTitle", + "message", + "delegate", + "cancelButtonTitle", + "otherButtonTitles" })) + return true; + + // Nothing else for now. + return false; +} + +static bool isBlockParameter(const clang::ParmVarDecl *param) { + return param->getType()->isBlockPointerType(); +} + +static bool isErrorOutParameter(const clang::ParmVarDecl *param, + ForeignErrorConvention::IsOwned_t &isErrorOwned) { + clang::QualType type = param->getType(); + + // Must be a pointer. + auto ptrType = type->getAs(); + if (!ptrType) return false; + type = ptrType->getPointeeType(); + + // For NSError**, take ownership from the qualifier. + if (auto objcPtrType = type->getAs()) { + auto iface = objcPtrType->getInterfaceDecl(); + if (iface && iface->getName() == "NSError") { + switch (type.getObjCLifetime()) { + case clang::Qualifiers::OCL_None: + llvm_unreachable("not in ARC?"); + + case clang::Qualifiers::OCL_ExplicitNone: + case clang::Qualifiers::OCL_Autoreleasing: + isErrorOwned = ForeignErrorConvention::IsNotOwned; + return true; + + case clang::Qualifiers::OCL_Weak: + // We just don't know how to handle this. + return false; + + case clang::Qualifiers::OCL_Strong: + isErrorOwned = ForeignErrorConvention::IsOwned; + return false; + } + llvm_unreachable("bad error ownership"); + } + } + return false; +} + +static bool isBoolType(ClangImporter::Implementation &importer, + clang::QualType type) { + auto &ctx = importer.getClangASTContext(); + do { + // Check whether we have a typedef for "BOOL" or "Boolean". + if (auto typedefType = dyn_cast(type.getTypePtr())) { + auto typedefDecl = typedefType->getDecl(); + if (typedefDecl->getName() == "BOOL" || + typedefDecl->getName() == "Boolean") + return true; + + type = typedefDecl->getUnderlyingType(); + continue; + } + + // Try to desugar one level... + clang::QualType desugared = type.getSingleStepDesugaredType(ctx); + if (desugared.getTypePtr() == type.getTypePtr()) + break; + + type = desugared; + } while (!type.isNull()); + + return false; +} + +static bool isIntegerType(clang::QualType clangType) { + if (auto builtinTy = clangType->getAs()) { + return (builtinTy->getKind() >= clang::BuiltinType::Char_U && + builtinTy->getKind() <= clang::BuiltinType::UInt128) || + (builtinTy->getKind() >= clang::BuiltinType::SChar && + builtinTy->getKind() <= clang::BuiltinType::Int128); + } + + return false; +} + +/// Whether the given Objective-C type can be imported as an optional type. +static bool canImportAsOptional(clang::ASTContext &ctx, clang::QualType type) { + // Note: this mimics ImportHint::canImportAsOptional. + + // Objective-C object pointers. + if (type->getAs()) return true; + + // Block and function pointers. + if (type->isBlockPointerType() || type->isFunctionPointerType()) return true; + + // CF types. + do { + // Check whether we have a typedef that refers to a CoreFoundation type. + if (auto typedefType = dyn_cast(type.getTypePtr())) { + if (ClangImporter::Implementation::isCFTypeDecl(typedefType->getDecl())) + return true; + + type = typedefType->getDecl()->getUnderlyingType(); + continue; + } + + // Try to desugar one level... + clang::QualType desugared = type.getSingleStepDesugaredType(ctx); + if (desugared.getTypePtr() == type.getTypePtr()) + break; + + type = desugared; + } while (!type.isNull()); + + return false; +} + +static Optional +classifyMethodErrorHandling(ClangImporter::Implementation &importer, + const clang::ObjCMethodDecl *clangDecl, + OptionalTypeKind resultOptionality) { + // TODO: opt out any non-standard methods here? + + // Check for an explicit attribute. + if (auto attr = clangDecl->getAttr()) { + switch (attr->getConvention()) { + case clang::SwiftErrorAttr::None: + return None; + + case clang::SwiftErrorAttr::NonNullError: + return ForeignErrorConvention::NonNilError; + + // Only honor null_result if we actually imported as a + // non-optional type. + case clang::SwiftErrorAttr::NullResult: + if (resultOptionality != OTK_None && + canImportAsOptional(importer.getClangASTContext(), + clangDecl->getReturnType())) + return ForeignErrorConvention::NilResult; + return None; + + // Preserve the original result type on a zero_result unless we + // imported it as Bool. + case clang::SwiftErrorAttr::ZeroResult: + if (isBoolType(importer, clangDecl->getReturnType())) { + return ForeignErrorConvention::ZeroResult; + } else if (isIntegerType(clangDecl->getReturnType())) { + return ForeignErrorConvention::ZeroPreservedResult; + } + return None; + + // There's no reason to do the same for nonzero_result because the + // only meaningful value remaining would be zero. + case clang::SwiftErrorAttr::NonZeroResult: + if (isIntegerType(clangDecl->getReturnType())) + return ForeignErrorConvention::NonZeroResult; + return None; + } + llvm_unreachable("bad swift_error kind"); + } + + // Otherwise, apply the default rules. + + // For bool results, a zero value is an error. + if (isBoolType(importer, clangDecl->getReturnType())) { + return ForeignErrorConvention::ZeroResult; + } + + // For optional reference results, a nil value is normally an error. + if (resultOptionality != OTK_None && + canImportAsOptional(importer.getClangASTContext(), + clangDecl->getReturnType())) { + return ForeignErrorConvention::NilResult; + } + + return None; +} + +static const char ErrorSuffix[] = "AndReturnError"; +static const char AltErrorSuffix[] = "WithError"; + +/// Look for a method that will import to have the same name as the +/// given method after importing the Nth parameter as an elided error +/// parameter. +static bool hasErrorMethodNameCollision(ClangImporter::Implementation &importer, + const clang::ObjCMethodDecl *method, + unsigned paramIndex, + StringRef suffixToStrip) { + // Copy the existing selector pieces into an array. + auto selector = method->getSelector(); + unsigned numArgs = selector.getNumArgs(); + assert(numArgs > 0); + + SmallVector chunks; + for (unsigned i = 0, e = selector.getNumArgs(); i != e; ++i) { + chunks.push_back(selector.getIdentifierInfoForSlot(i)); + } + + auto &ctx = method->getASTContext(); + if (paramIndex == 0 && !suffixToStrip.empty()) { + StringRef name = chunks[0]->getName(); + assert(name.endswith(suffixToStrip)); + name = name.drop_back(suffixToStrip.size()); + chunks[0] = &ctx.Idents.get(name); + } else if (paramIndex != 0) { + chunks.erase(chunks.begin() + paramIndex); + } + + auto newSelector = ctx.Selectors.getSelector(numArgs - 1, chunks.data()); + const clang::ObjCMethodDecl *conflict; + if (auto iface = method->getClassInterface()) { + conflict = iface->lookupMethod(newSelector, method->isInstanceMethod()); + } else { + auto protocol = cast(method->getDeclContext()); + conflict = protocol->getMethod(newSelector, method->isInstanceMethod()); + } + + if (conflict == nullptr) + return false; + + // Look to see if the conflicting decl is unavailable, either because it's + // been marked NS_SWIFT_UNAVAILABLE, because it's actually marked unavailable, + // or because it was deprecated before our API sunset. We can handle + // "conflicts" where one form is unavailable. + // FIXME: Somewhat duplicated from Implementation::importAttributes. + clang::AvailabilityResult availability = conflict->getAvailability(); + if (availability != clang::AR_Unavailable && + importer.DeprecatedAsUnavailableFilter) { + for (auto *attr : conflict->specific_attrs()) { + if (attr->getPlatform()->getName() == "swift") { + availability = clang::AR_Unavailable; + break; + } + if (importer.PlatformAvailabilityFilter && + !importer.PlatformAvailabilityFilter(attr->getPlatform()->getName())){ + continue; + } + clang::VersionTuple version = attr->getDeprecated(); + if (version.empty()) + continue; + if (importer.DeprecatedAsUnavailableFilter(version.getMajor(), + version.getMinor())) { + availability = clang::AR_Unavailable; + break; + } + } + } + return availability != clang::AR_Unavailable; +} + +/// Determine the optionality of the given Objective-C method. +/// +/// \param method The Clang method. +/// +/// \param knownNullability When API notes describe the nullability of this +/// parameter, that nullability. +static OptionalTypeKind getResultOptionality( + const clang::ObjCMethodDecl *method, + Optional knownNullability) { + auto &clangCtx = method->getASTContext(); + + // If nullability is available on the type, use it. + if (auto nullability = method->getReturnType()->getNullability(clangCtx)) { + return ClangImporter::Implementation::translateNullability(*nullability); + } + + // If there is a returns_nonnull attribute, non-null. + if (method->hasAttr()) + return OTK_None; + + // If API notes gives us nullability, use that. + if (knownNullability) { + return ClangImporter::Implementation::translateNullability( + *knownNullability); + } + + // Default to implicitly unwrapped optionals. + return OTK_ImplicitlyUnwrappedOptional; +} + +static Optional +considerErrorImport(ClangImporter::Implementation &importer, + const clang::ObjCMethodDecl *clangDecl, + StringRef &baseName, + SmallVectorImpl ¶mNames, + ArrayRef params, + bool isInitializer, + bool hasCustomName) { + // If the declaration name isn't parallel to the actual parameter + // list (e.g. if the method has C-style parameter declarations), + // don't try to apply error conventions. + bool expectsToRemoveError = + hasCustomName && paramNames.size() + 1 == params.size(); + if (!expectsToRemoveError && paramNames.size() != params.size()) + return None; + + for (unsigned index = params.size(); index-- != 0; ) { + // Allow an arbitrary number of trailing blocks. + if (isBlockParameter(params[index])) + continue; + + // Otherwise, require the last parameter to be an out-parameter. + auto isErrorOwned = ForeignErrorConvention::IsNotOwned; + if (!isErrorOutParameter(params[index], isErrorOwned)) + break; + + // Determine the nullability of the result. + Optional knownResultNullability; + if (auto knownMethod = importer.getKnownObjCMethod(clangDecl)) { + if (knownMethod->NullabilityAudited) + knownResultNullability = knownMethod->getReturnTypeInfo(); + } + + auto errorKind = + classifyMethodErrorHandling(importer, clangDecl, + getResultOptionality(clangDecl, + knownResultNullability)); + if (!errorKind) return None; + + // Consider adjusting the imported declaration name to remove the + // parameter. + bool adjustName = !hasCustomName; + + // Never do this if it's the first parameter of a constructor. + if (isInitializer && index == 0) { + adjustName = false; + } + + // If the error parameter is the first parameter, try removing the + // standard error suffix from the base name. + StringRef suffixToStrip; + StringRef origBaseName = baseName; + if (adjustName && index == 0 && paramNames[0].empty()) { + if (baseName.endswith(ErrorSuffix)) + suffixToStrip = ErrorSuffix; + else if (baseName.endswith(AltErrorSuffix)) + suffixToStrip = AltErrorSuffix; + + if (!suffixToStrip.empty()) { + StringRef newBaseName = baseName.drop_back(suffixToStrip.size()); + if (newBaseName.empty() || importer.isSwiftReservedName(newBaseName)) { + adjustName = false; + suffixToStrip = {}; + } else { + baseName = newBaseName; + } + } + } + + // Also suppress name changes if there's a collision. + // TODO: this logic doesn't really work with init methods + // TODO: this privileges the old API over the new one + if (adjustName && + hasErrorMethodNameCollision(importer, clangDecl, index, + suffixToStrip)) { + // If there was a conflict on the first argument, and this was + // the first argument and we're not stripping error suffixes, just + // give up completely on error import. + if (index == 0 && suffixToStrip.empty()) { + return None; + + // If there was a conflict stripping an error suffix, adjust the + // name but don't change the base name. This avoids creating a + // spurious _: () argument. + } else if (index == 0 && !suffixToStrip.empty()) { + suffixToStrip = {}; + baseName = origBaseName; + + // Otherwise, give up on adjusting the name. + } else { + adjustName = false; + baseName = origBaseName; + } + } + + // If we're adjusting the name, erase the error parameter. + if (adjustName) { + paramNames.erase(paramNames.begin() + index); + } + + bool replaceParamWithVoid = !adjustName && !expectsToRemoveError; + ClangImporter::Implementation::ImportedErrorInfo errorInfo { + *errorKind, isErrorOwned, index, replaceParamWithVoid + }; + return errorInfo; + } + + // Didn't find an error parameter. + return None; +} + +auto ClangImporter::Implementation::importFullName( + const clang::NamedDecl *D, + ImportNameOptions options, + clang::DeclContext **effectiveContext) -> ImportedName { + ImportedName result; + + // Objective-C categories and extensions don't have names, despite + // being "named" declarations. + if (isa(D)) + return result; + + // Compute the effective context, if requested. + if (effectiveContext) { + auto dc = const_cast(D->getDeclContext()); + + // Enumerators can end up within their enclosing enum or in the global + // scope, depending how their enclosing enumeration is imported. + if (isa(D)) { + auto enumDecl = cast(dc); + switch (classifyEnum(enumDecl)) { + case EnumKind::Enum: + case EnumKind::Options: + // Enums are mapped to Swift enums, Options to Swift option sets. + *effectiveContext = enumDecl; + break; + + case EnumKind::Constants: + case EnumKind::Unknown: + // The enum constant goes into the redeclaration context of the + // enum. + *effectiveContext = enumDecl->getRedeclContext(); + break; + } + } else { + // Everything else goes into its redeclaration context. + *effectiveContext = dc->getRedeclContext(); + } + + // Anything in an Objective-C category or extension is adjusted to the + // class context. + if (auto category = dyn_cast(*effectiveContext)) { + *effectiveContext = category->getClassInterface(); + } + } + + // Local function that forms a DeclName from the given strings. + auto formDeclName = [&](StringRef baseName, + ArrayRef argumentNames, + bool isFunction) -> DeclName { + // We cannot import when the base name is not an identifier. + if (!Lexer::isIdentifier(baseName)) + return DeclName(); + + // Get the identifier for the base name. + Identifier baseNameId = SwiftContext.getIdentifier(baseName); + + // For non-functions, just use the base name. + if (!isFunction) return baseNameId; + + // For functions, we need to form a complete name. + + // Convert the argument names. + SmallVector argumentNameIds; + for (auto argName : argumentNames) { + if (argumentNames.empty() || !Lexer::isIdentifier(argName)) { + argumentNameIds.push_back(Identifier()); + continue; + } + + argumentNameIds.push_back(SwiftContext.getIdentifier(argName)); + } + + // Build the result. + return DeclName(SwiftContext, baseNameId, argumentNameIds); + }; + + // If we have a swift_name attribute, use that. if (auto *nameAttr = D->getAttr()) { - StringRef customName = nameAttr->getName(); - if (Lexer::isIdentifier(customName)) - return SwiftContext.getIdentifier(customName); + bool skipCustomName = false; + + // If we have an Objective-C method that is being mapped to an + // initializer (e.g., a factory method whose name doesn't fit the + // convention for factory methods), make sure that it can be + // imported as an initializer. + bool isInitializer = false; + auto method = dyn_cast(D); + if (method) { + unsigned initPrefixLength; + if (nameAttr->getName().startswith("init(")) { + if (!shouldImportAsInitializer(method, initPrefixLength, + result.InitKind)) { + // We cannot import this as an initializer anyway. + return { }; + } + + // If this swift_name attribute maps a factory method to an + // initializer and we were asked not to do so, ignore the + // custom name. + if (options.contains(ImportNameFlags::SuppressFactoryMethodAsInit) && + (result.InitKind == CtorInitializerKind::Factory || + result.InitKind == CtorInitializerKind::ConvenienceFactory)) { + skipCustomName = true; + } else { + // Note that this is an initializer. + isInitializer = true; + } + } + } + + if (!skipCustomName) { + SmallVector argumentNames; + bool isFunctionName; + StringRef baseName = parseDeclName(nameAttr->getName(), argumentNames, + isFunctionName); + if (baseName.empty()) return result; + + result.HasCustomName = true; + result.Imported = formDeclName(baseName, argumentNames, isFunctionName); + + if (method) { + // Get the parameters. + ArrayRef params{ + method->param_begin(), + method->param_end() + }; + + result.ErrorInfo = considerErrorImport(*this, method, baseName, + argumentNames, params, + isInitializer, + /*hasCustomName=*/true); + } - return Identifier(); + + return result; + } } - Identifier result = importDeclName(D->getDeclName(), removePrefix); - if (result.empty()) + // For empty names, there is nothing to do. + if (D->getDeclName().isEmpty()) return result; + + /// Whether the result is a function name. + bool isFunction = false; + bool isInitializer = false; + unsigned initializerPrefixLen; + StringRef baseName; + SmallVector argumentNames; + SmallString<16> selectorSplitScratch; + StringScratchSpace stringScratch; + ArrayRef params; + switch (D->getDeclName().getNameKind()) { + case clang::DeclarationName::CXXConstructorName: + case clang::DeclarationName::CXXConversionFunctionName: + case clang::DeclarationName::CXXDestructorName: + case clang::DeclarationName::CXXLiteralOperatorName: + case clang::DeclarationName::CXXOperatorName: + case clang::DeclarationName::CXXUsingDirective: + // Handling these is part of C++ interoperability. return result; + case clang::DeclarationName::Identifier: + // Map the identifier. + baseName = D->getDeclName().getAsIdentifierInfo()->getName(); + break; + + case clang::DeclarationName::ObjCMultiArgSelector: + case clang::DeclarationName::ObjCOneArgSelector: + case clang::DeclarationName::ObjCZeroArgSelector: { + auto objcMethod = cast(D); + isInitializer = shouldImportAsInitializer(objcMethod, initializerPrefixLen, + result.InitKind); + + // If we would import a factory method as an initializer but were + // asked not to, don't consider this as an initializer. + if (isInitializer && + options.contains(ImportNameFlags::SuppressFactoryMethodAsInit) && + (result.InitKind == CtorInitializerKind::Factory || + result.InitKind == CtorInitializerKind::ConvenienceFactory)) { + isInitializer = false; + } + + // Map the Objective-C selector directly. + auto selector = D->getDeclName().getObjCSelector(); + if (isInitializer) + baseName = "init"; + else + baseName = selector.getNameForSlot(0); + + // Get the parameters. + params = { objcMethod->param_begin(), objcMethod->param_end() }; + + // If we have a variadic method for which we need to drop the last + // selector piece, do so now. + unsigned numArgs = selector.getNumArgs(); + if (objcMethod->isVariadic() && shouldMakeSelectorNonVariadic(selector)) { + --numArgs; + result.DroppedVariadic = true; + params = params.drop_back(1); + } + + for (unsigned index = 0; index != numArgs; ++index) { + if (index == 0) { + argumentNames.push_back(StringRef()); + } else { + StringRef argName = selector.getNameForSlot(index); + + // Swift 2 lowercased all subsequent argument names. + // Swift 3 may handle this as part of omitting needless words, below, + // but don't preempt that here. + if (!OmitNeedlessWords) + argName = camel_case::toLowercaseWord(argName, stringScratch); + + argumentNames.push_back(argName); + } + } + + // For initializers, compute the first argument name. + if (isInitializer) { + // Skip over the prefix. + auto argName = selector.getNameForSlot(0).substr(initializerPrefixLen); + + // Drop "With" if present after the "init". + bool droppedWith = false; + if (argName.startswith("With")) { + argName = argName.substr(4); + droppedWith = true; + } + + // Lowercase the remaining argument name. + argName = camel_case::toLowercaseWord(argName, selectorSplitScratch); + + // If we dropped "with" and ended up with a reserved name, + // put "with" back. + if (droppedWith && isSwiftReservedName(argName)) { + selectorSplitScratch = "with"; + selectorSplitScratch += selector.getNameForSlot(0).substr( + initializerPrefixLen + 4); + argName = selectorSplitScratch; + } + + // Set the first argument name to be the name we computed. If + // there is no first argument, create one for this purpose. + if (argumentNames.empty()) { + if (!argName.empty()) { + // FIXME: Record what happened here for the caller? + argumentNames.push_back(argName); + } + } else { + argumentNames[0] = argName; + } + } + + result.ErrorInfo = considerErrorImport(*this, objcMethod, baseName, + argumentNames, params, isInitializer, + /*hasCustomName=*/false); + + isFunction = true; + break; + } + } + + // Perform automatic name transformations. + + // Enumeration constants may have common prefixes stripped. + if (isa(D)) { + auto enumDecl = cast(D->getDeclContext()); + StringRef removePrefix = getEnumConstantNamePrefix(enumDecl); + if (baseName.startswith(removePrefix)) + baseName = baseName.substr(removePrefix.size()); + } + + // Objective-C protocols may have the suffix "Protocol" appended if + // the non-suffixed name would conflict with another entity in the + // same top-level module. + SmallString<16> baseNameWithProtocolSuffix; + if (auto objcProto = dyn_cast(D)) { + if (objcProto->hasDefinition()) { + // Test to see if there is a value with the same name as the protocol + // in the same module. + // FIXME: This will miss macros. + auto clangModule = getClangSubmoduleForDecl(objcProto); + if (clangModule.hasValue() && clangModule.getValue()) + clangModule = clangModule.getValue()->getTopLevelModule(); + + auto isInSameModule = [&](const clang::Decl *D) -> bool { + auto declModule = getClangSubmoduleForDecl(D); + if (!declModule.hasValue()) + return false; + // Handle the bridging header case. This is pretty nasty since things + // can get added to it *later*, but there's not much we can do. + if (!declModule.getValue()) + return *clangModule == nullptr; + return *clangModule == declModule.getValue()->getTopLevelModule(); + }; + + // Allow this lookup to find hidden names. We don't want the + // decision about whether to rename the protocol to depend on + // what exactly the user has imported. Indeed, if we're being + // asked to resolve a serialization cross-reference, the user + // may not have imported this module at all, which means a + // normal lookup wouldn't even find the protocol! + // + // Meanwhile, we don't need to worry about finding unwanted + // hidden declarations from different modules because we do a + // module check before deciding that there's a conflict. + bool hasConflict = false; + clang::LookupResult lookupResult(getClangSema(), D->getDeclName(), + clang::SourceLocation(), + clang::Sema::LookupOrdinaryName); + lookupResult.setAllowHidden(true); + lookupResult.suppressDiagnostics(); + + if (getClangSema().LookupName(lookupResult, /*scope=*/nullptr)) { + hasConflict = std::any_of(lookupResult.begin(), lookupResult.end(), + isInSameModule); + } + if (!hasConflict) { + lookupResult.clear(clang::Sema::LookupTagName); + if (getClangSema().LookupName(lookupResult, /*scope=*/nullptr)) { + hasConflict = std::any_of(lookupResult.begin(), lookupResult.end(), + isInSameModule); + } + } + + if (hasConflict) { + baseNameWithProtocolSuffix = baseName; + baseNameWithProtocolSuffix += SWIFT_PROTOCOL_SUFFIX; + baseName = baseNameWithProtocolSuffix; + } + } + } + + // Typedef declarations might be CF types that will drop the "Ref" + // suffix. + bool aliasIsFunction = false; + bool aliasIsInitializer = false; + StringRef aliasBaseName; + SmallVector aliasArgumentNames; + if (auto typedefNameDecl = dyn_cast(D)) { + auto swiftName = getCFTypeName(typedefNameDecl, &aliasBaseName); + if (!swiftName.empty()) { + baseName = swiftName; + } + } + + // Local function to determine whether the given declaration is subject to + // a swift_private attribute. auto hasSwiftPrivate = [this](const clang::NamedDecl *D) { if (D->hasAttr()) return true; @@ -1321,41 +2358,34 @@ ClangImporter::Implementation::importName(const clang::NamedDecl *D, if (auto *ECD = dyn_cast(D)) { auto *ED = cast(ECD->getDeclContext()); switch (classifyEnum(ED)) { - case EnumKind::Constants: - case EnumKind::Unknown: - if (ED->hasAttr()) - return true; - if (auto *enumTypedef = ED->getTypedefNameForAnonDecl()) - if (enumTypedef->hasAttr()) + case EnumKind::Constants: + case EnumKind::Unknown: + if (ED->hasAttr()) return true; - break; + if (auto *enumTypedef = ED->getTypedefNameForAnonDecl()) + if (enumTypedef->hasAttr()) + return true; + break; - case EnumKind::Enum: - case EnumKind::Options: - break; + case EnumKind::Enum: + case EnumKind::Options: + break; } } return false; }; - if (hasSwiftPrivate(D) && D->getDeclName().isIdentifier()) { - SmallString<64> name{"__"}; - name += result.str(); - result = SwiftContext.getIdentifier(name.str()); - } - - // Omit needless words from properties. + // Omit needless words. + StringScratchSpace omitNeedlessWordsScratch; if (OmitNeedlessWords) { + // Objective-C properties. if (auto objcProperty = dyn_cast(D)) { auto contextType = getClangDeclContextType(D->getDeclContext()); if (!contextType.isNull()) { auto contextTypeName = getClangTypeNameForOmission(contextType); auto propertyTypeName = getClangTypeNameForOmission( objcProperty->getType()); - StringScratchSpace scratch; - StringRef name = result.str(); - // Find the property names. const InheritedNameSet *allPropertyNames = nullptr; if (!contextType.isNull()) { @@ -1366,192 +2396,106 @@ ClangImporter::Implementation::importName(const clang::NamedDecl *D, /*forInstance=*/true); } - if (omitNeedlessWords(name, { }, "", propertyTypeName, contextTypeName, - { }, /*returnsSelf=*/false, /*isProperty=*/true, - allPropertyNames, scratch)) { - result = SwiftContext.getIdentifier(name); - } + (void)omitNeedlessWords(baseName, { }, "", propertyTypeName, + contextTypeName, { }, /*returnsSelf=*/false, + /*isProperty=*/true, allPropertyNames, + omitNeedlessWordsScratch); } } - } - return result; -} - -/// Import an argument name. -static Identifier importArgName(ASTContext &ctx, StringRef name, - bool dropWith, bool isSwiftPrivate) { - // Simple case: empty name. - if (name.empty()) { - if (isSwiftPrivate) - return ctx.getIdentifier("__"); - return Identifier(); - } - - SmallString<32> scratch; - auto words = camel_case::getWords(name); - auto firstWord = *words.begin(); - StringRef argName = name; - - // If we're dropping "with", handle that now. - if (dropWith) { - // If the first word is "with"... - if (name.size() > 4 && - camel_case::sameWordIgnoreFirstCase(firstWord, "with")) { - // Drop it. - ++NumInitsDroppedWith; - - auto iter = words.begin(); - ++iter; - - argName = name.substr(iter.getPosition()); - // Don't drop "with" if the resulting arg is a reserved name. - if (ClangImporter::Implementation::isSwiftReservedName( - camel_case::toLowercaseWord(argName, scratch))) { - argName = name; - } - } else { - // If we're tracking statistics, check whether the name starts with - // a preposition. - if (llvm::AreStatisticsEnabled()) { - if (getPrepositionKind(firstWord)) - ++NumInitsPrepositionSplit; - else - ++NumInitsNonPrepositionSplit; - } - - argName = name; + // Objective-C methods. + if (auto method = dyn_cast(D)) { + (void)omitNeedlessWordsInFunctionName( + baseName, + argumentNames, + params, + method->getReturnType(), + method->getDeclContext(), + getNonNullArgs(method, params), + getKnownObjCMethod(method), + result.ErrorInfo ? Optional(result.ErrorInfo->ParamIndex) + : None, + method->hasRelatedResultType(), + method->isInstanceMethod(), + omitNeedlessWordsScratch); } } - /// Lowercase the first word to form the argument name. - argName = camel_case::toLowercaseWord(argName, scratch); - if (!isSwiftPrivate) - return ctx.getIdentifier(argName); - - SmallString<32> prefixed{"__"}; - prefixed.append(argName); - return ctx.getIdentifier(prefixed.str()); -} - -/// Map an Objective-C selector name to a Swift method name. -static DeclName mapSelectorName(ASTContext &ctx, - ObjCSelector selector, - bool isInitializer, - bool isSwiftPrivate) { - // Zero-argument selectors. - if (selector.getNumArgs() == 0) { - ++NumNullaryMethodNames; - - auto name = selector.getSelectorPieces()[0]; - StringRef nameText = name.empty()? "" : name.str(); - - if (!isInitializer) { - if (!isSwiftPrivate) - return DeclName(ctx, name, {}); + // If this declaration has the swift_private attribute, prepend "__" to the + // appropriate place. + SmallString<16> swiftPrivateScratch; + SmallString<16> swiftPrivateAliasScratch; + if (hasSwiftPrivate(D)) { + // Make the given name private. + // + // Returns true if this is not possible. + auto makeNamePrivate = [](bool isInitializer, + StringRef &baseName, + SmallVectorImpl &argumentNames, + CtorInitializerKind initKind, + SmallString<16> &scratch) -> bool { + scratch = "__"; + + if (isInitializer) { + // For initializers, prepend "__" to the first argument name. + if (argumentNames.empty()) { + // FIXME: ... unless it was from a factory method, for historical + // reasons. + if (initKind == CtorInitializerKind::Factory || + initKind == CtorInitializerKind::ConvenienceFactory) + return true; - SmallString<32> newName{"__"}; - newName.append(nameText); - return DeclName(ctx, ctx.getIdentifier(newName.str()), {}); - } + // FIXME: Record that we did this. + argumentNames.push_back("__"); + } else { + scratch += argumentNames[0]; + argumentNames[0] = scratch; + } + } else { + // For all other entities, prepend "__" to the base name. + scratch += baseName; + baseName = scratch; + } - // Simple case for initializers. - if (nameText == "init" && !isSwiftPrivate) - return DeclName(ctx, name, { }); + return false; + }; - // This is an initializer with no parameters but a name that - // contains more than 'init', so synthesize an argument to capture - // what follows 'init'. - ++NumNullaryInitMethodsMadeUnary; - assert(camel_case::getFirstWord(nameText).equals("init")); - auto baseName = ctx.Id_init; - auto argName = importArgName(ctx, nameText.substr(4), /*dropWith=*/true, - isSwiftPrivate); - return DeclName(ctx, baseName, argName); - } + // Make the name private. + if (makeNamePrivate(isInitializer, baseName, argumentNames, + result.InitKind, swiftPrivateScratch)) + return result; - // Determine the base name and first argument name. - Identifier baseName; - SmallVector argumentNames; - Identifier firstPiece = selector.getSelectorPieces()[0]; - StringRef firstPieceText = firstPiece.empty()? "" : firstPiece.str(); - if (isInitializer) { - assert(camel_case::getFirstWord(firstPieceText).equals("init")); - baseName = ctx.Id_init; - argumentNames.push_back(importArgName(ctx, firstPieceText.substr(4), - /*dropWith=*/true, isSwiftPrivate)); - } else { - baseName = firstPiece; - if (isSwiftPrivate) { - SmallString<32> newName{"__"}; - newName.append(firstPieceText); - baseName = ctx.getIdentifier(newName); + // If we have an alias name, make it private as well. + if (!aliasBaseName.empty()) { + (void)makeNamePrivate(aliasIsInitializer, aliasBaseName, + aliasArgumentNames, CtorInitializerKind::Designated, + swiftPrivateAliasScratch); } - argumentNames.push_back(Identifier()); } - if (argumentNames[0].empty()) - ++NumMethodsMissingFirstArgName; - - // Determine the remaining argument names. - unsigned n = selector.getNumArgs(); - if (n == 1) - ++NumUnaryMethodNames; - else - ++NumMultiMethodNames; - - for (auto piece : selector.getSelectorPieces().slice(1)) { - if (piece.empty()) - argumentNames.push_back(piece); - else - argumentNames.push_back(importArgName(ctx, piece.str(), - /*dropWith=*/false, - /*isSwiftPrivate=*/false)); - } - return DeclName(ctx, baseName, argumentNames); + result.Imported = formDeclName(baseName, argumentNames, isFunction); + result.Alias = formDeclName(aliasBaseName, aliasArgumentNames, + aliasIsFunction); + return result; } -namespace { - /// Function object used to create Clang selectors from strings. - class CreateSelector { - ASTContext &Ctx; - - public: - CreateSelector(ASTContext &ctx) : Ctx(ctx){ } - - template - ObjCSelector operator()(unsigned numParams, Strings ...strings) const { - Identifier pieces[sizeof...(Strings)] = { - (strings[0]? Ctx.getIdentifier(strings) : Identifier())... - }; - - assert((numParams == 0 && sizeof...(Strings) == 1) || - (numParams > 0 && sizeof...(Strings) == numParams)); - return ObjCSelector(Ctx, numParams, pieces); - } - }; - - /// Function object used to create Swift method names from strings. - class CreateMethodName { - ASTContext &Ctx; - Identifier BaseName; +Identifier +ClangImporter::Implementation::importIdentifier( + const clang::IdentifierInfo *identifier, + StringRef removePrefix) +{ + if (!identifier) return Identifier(); - public: - CreateMethodName(ASTContext &ctx, StringRef baseName) - : Ctx(ctx) - { - BaseName = Ctx.getIdentifier(baseName); + StringRef name = identifier->getName(); + // Remove the prefix, if any. + if (!removePrefix.empty()) { + if (name.startswith(removePrefix)) { + name = name.slice(removePrefix.size(), name.size()); } + } - template - DeclName operator()(Strings ...strings) const { - Identifier pieces[sizeof...(Strings)] = { - (strings[0]? Ctx.getIdentifier(strings) : Identifier())... - }; - - return DeclName(Ctx, BaseName, pieces); - } - }; + // Get the Swift identifier. + return SwiftContext.getIdentifier(name); } ObjCSelector ClangImporter::Implementation::importSelector( @@ -1611,70 +2555,6 @@ ClangImporter::Implementation::exportSelector(ObjCSelector selector) { pieces.data()); } - -DeclName -ClangImporter::Implementation::mapSelectorToDeclName(ObjCSelector selector, - bool isInitializer, - bool isSwiftPrivate) -{ - // Check whether we've already mapped this selector. - auto known = SelectorMappings.find({selector, isInitializer}); - if (known != SelectorMappings.end()) - return known->second; - - // Map the selector. - auto result = mapSelectorName(SwiftContext, selector, isInitializer, - isSwiftPrivate); - - // Cache the result and return. - SelectorMappings[{selector, isInitializer}] = result; - return result; -} - -DeclName ClangImporter::Implementation::mapFactorySelectorToInitializerName( - ObjCSelector selector, - StringRef className, - bool isSwiftPrivate) { - // See if we can match the class name to the beginning of the first selector - // piece. - auto firstPiece = selector.getSelectorPieces()[0]; - StringRef firstArgLabel = matchLeadingTypeName(firstPiece.str(), className); - if (firstArgLabel.size() == firstPiece.str().size()) - return DeclName(); - - // Form the first argument label. - llvm::SmallString<32> scratch; - SmallVector argumentNames; - argumentNames.push_back( - importArgName(SwiftContext, - camel_case::toLowercaseWord(firstArgLabel, scratch), - /*dropWith=*/true, - isSwiftPrivate)); - - // Handle nullary factory methods. - if (selector.getNumArgs() == 0) { - if (argumentNames[0].empty()) - return DeclName(SwiftContext, SwiftContext.Id_init, { }); - - // We don't have a convenience place to put the remaining argument name, - // so leave it as a factory method. - ++NumFactoryMethodsNullary; - return DeclName(); - } - - // Map the remaining selector pieces. - for (auto piece : selector.getSelectorPieces().slice(1)) { - if (piece.empty()) - argumentNames.push_back(piece); - else - argumentNames.push_back(importArgName(SwiftContext, piece.str(), - /*dropWith=*/false, - /*isSwiftPrivate=*/false)); - } - - return DeclName(SwiftContext, SwiftContext.Id_init, argumentNames); -} - /// Translate the "nullability" notion from API notes into an optional type /// kind. OptionalTypeKind ClangImporter::Implementation::translateNullability( @@ -2063,6 +2943,93 @@ ClangImporter::Implementation::isAccessibilityDecl(const clang::Decl *decl) { return false; } +bool ClangImporter::Implementation::isInitMethod( + const clang::ObjCMethodDecl *method) { + // init methods are always instance methods. + if (!method->isInstanceMethod()) return false; + + // init methods must be classified as such by Clang. + if (method->getMethodFamily() != clang::OMF_init) return false; + + // Swift restriction: init methods must start with the word "init". + auto selector = method->getSelector(); + return camel_case::getFirstWord(selector.getNameForSlot(0)) == "init"; +} + +bool ClangImporter::Implementation::shouldImportAsInitializer( + const clang::ObjCMethodDecl *method, + unsigned &prefixLength, + CtorInitializerKind &kind) { + /// Is this an initializer? + if (isInitMethod(method)) { + prefixLength = 4; + kind = CtorInitializerKind::Designated; + return true; + } + + // It must be a class method. + if (!method->isClassMethod()) return false; + + // Said class methods must be in an actual class. + auto objcClass = method->getClassInterface(); + if (!objcClass) return false; + + // Check whether we should try to import this factory method as an + // initializer. + switch (getFactoryAsInit(objcClass, method)) { + case FactoryAsInitKind::AsInitializer: + // Okay; check for the correct result type below. + prefixLength = 0; + break; + + case FactoryAsInitKind::Infer: { + // See if we can match the class name to the beginning of the first + // selector piece. + auto firstPiece = method->getSelector().getNameForSlot(0); + StringRef firstArgLabel = matchLeadingTypeName(firstPiece, + objcClass->getName()); + if (firstArgLabel.size() == firstPiece.size()) + return false; + + // FIXME: Factory methods cannot have dummy parameters added for + // historical reasons. + if (!firstArgLabel.empty() && method->getSelector().getNumArgs() == 0) + return false; + + // Store the prefix length. + prefixLength = firstPiece.size() - firstArgLabel.size(); + + // Continue checking the result type, below. + break; + } + + case FactoryAsInitKind::AsClassMethod: + return false; + } + + // Determine whether we have a suitable return type. + if (method->hasRelatedResultType()) { + // When the factory method has an "instancetype" result type, we + // can import it as a convenience factory method. + kind = CtorInitializerKind::ConvenienceFactory; + } else if (auto objcPtr = method->getReturnType() + ->getAs()) { + if (objcPtr->getInterfaceDecl() != objcClass) { + // FIXME: Could allow a subclass here, but the rest of the compiler + // isn't prepared for that yet. + return false; + } + + // Factory initializer. + kind = CtorInitializerKind::Factory; + } else { + // Not imported as an initializer. + return false; + } + + return true; +} + #pragma mark Name lookup void ClangImporter::lookupValue(Identifier name, VisibleDeclConsumer &consumer){ auto &pp = Impl.Instance->getPreprocessor(); @@ -2610,7 +3577,7 @@ void ClangImporter::lookupVisibleDecls(VisibleDeclConsumer &Consumer) const { for (auto I = ClangPP.macro_begin(), E = ClangPP.macro_end(); I != E; ++I) { if (!I->first->hasMacroDefinition()) continue; - auto Name = Impl.importDeclName(I->first); + auto Name = Impl.importIdentifier(I->first); if (Name.empty()) continue; if (auto *Imported = Impl.importMacro( @@ -3306,3 +4273,11 @@ void ClangImporter::getMangledName(raw_ostream &os, Impl.Mangler->mangleName(clangDecl, os); } + +void ClangImporter::dumpSwiftLookupTables() { + Impl.dumpSwiftLookupTables(); +} + +void ClangImporter::Implementation::dumpSwiftLookupTables() { + BridgingHeaderLookupTable.dump(); +} diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 921b286648ad4..c8acbb3ffbb2f 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -48,8 +48,6 @@ #define DEBUG_TYPE "Clang module importer" STATISTIC(NumTotalImportedEntities, "# of imported clang entities"); -STATISTIC(NumFactoryMethodsWrongResult, - "# of factory methods not mapped due to an incorrect result type"); STATISTIC(NumFactoryMethodsAsInitializers, "# of factory methods mapped to initializers"); @@ -323,101 +321,6 @@ static bool isNSDictionaryMethod(const clang::ObjCMethodDecl *MD, return true; } -/// \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; -} - - /// Build the \c rawValue property trivial getter for an option set or /// unknown enum. /// @@ -1064,12 +967,16 @@ makeBitFieldAccessors(ClangImporter::Implementation &Impl, /// generated name will most likely be unique. static Identifier getClangDeclName(ClangImporter::Implementation &Impl, const clang::TagDecl *decl) { - if (decl->getDeclName() || decl->hasAttr()) - return Impl.importName(decl); - else if (auto *typedefForAnon = decl->getTypedefNameForAnonDecl()) - return Impl.importName(typedefForAnon); - - Identifier name; + // Import the name of this declaration. + Identifier name = Impl.importFullName(decl).Imported.getBaseName(); + if (!name.empty()) return name; + + // If that didn't succeed, check whether this is an anonymous tag declaration + // with a corresponding typedef-name declaration. + if (decl->getDeclName().isEmpty()) { + if (auto *typedefForAnon = decl->getTypedefNameForAnonDecl()) + return Impl.importFullName(typedefForAnon).Imported.getBaseName(); + } if (!decl->isRecord()) return name; @@ -1272,16 +1179,36 @@ static StringRef getImportedCFTypeName(StringRef name) { return name; } -static bool isCFTypeDecl(const clang::TypedefNameDecl *Decl) { - if (auto pointee = CFPointeeInfo::classifyTypedef(Decl)) - return pointee.isValid(); +bool ClangImporter::Implementation::isCFTypeDecl( + const clang::TypedefNameDecl *Decl) { + if (CFPointeeInfo::classifyTypedef(Decl)) + return true; return false; } StringRef ClangImporter::Implementation::getCFTypeName( - const clang::TypedefNameDecl *decl) { - if (isCFTypeDecl(decl)) - return getImportedCFTypeName(decl->getName()); + const clang::TypedefNameDecl *decl, + StringRef *secondaryName) { + if (secondaryName) *secondaryName = ""; + + if (auto pointee = CFPointeeInfo::classifyTypedef(decl)) { + auto name = decl->getName(); + if (pointee.isRecord()) { + auto resultName = getImportedCFTypeName(name); + if (secondaryName && name != resultName) + *secondaryName = name; + + return resultName; + } + + if (pointee.isTypedef() && secondaryName) { + StringRef otherName = getImportedCFTypeName(name); + if (otherName != name) + *secondaryName = otherName; + } + + return name; + } return ""; } @@ -1456,11 +1383,7 @@ namespace { } Type importCFClassType(const clang::TypedefNameDecl *decl, - StringRef name, CFPointeeInfo info) { - // If the name ends in 'Ref', drop that from the imported class name. - StringRef nameWithoutRef = getImportedCFTypeName(name); - Identifier className = Impl.SwiftContext.getIdentifier(nameWithoutRef); - + Identifier className, CFPointeeInfo info) { auto dc = Impl.importDeclContextOf(decl); if (!dc) return Type(); @@ -1518,7 +1441,8 @@ namespace { } Decl *VisitTypedefNameDecl(const clang::TypedefNameDecl *Decl) { - auto Name = Impl.importName(Decl); + auto importedName = Impl.importFullName(Decl); + auto Name = importedName.Imported.getBaseName(); if (Name.empty()) return nullptr; @@ -1540,10 +1464,15 @@ namespace { if (auto pointee = CFPointeeInfo::classifyTypedef(Decl)) { // If the pointee is a record, consider creating a class type. if (pointee.isRecord()) { - SwiftType = importCFClassType(Decl, Name.str(), pointee); + SwiftType = importCFClassType(Decl, Name, pointee); if (!SwiftType) return nullptr; NameMapping = MappedTypeNameKind::DefineOnly; + // If there is an alias (i.e., that doesn't have "Ref"), + // use that as the name of the typedef later. + if (importedName.Alias) + Name = importedName.Alias.getBaseName(); + // If the pointee is another CF typedef, create an extra typealias // for the name without "Ref", but not a separate type. } else if (pointee.isTypedef()) { @@ -1552,7 +1481,6 @@ namespace { if (!underlying) return nullptr; - // Remove one level of "Ref" from the typealias. if (auto typealias = dyn_cast(underlying)) { Type doublyUnderlyingTy = typealias->getUnderlyingType(); if (isa(doublyUnderlyingTy.getPointer())) @@ -1565,20 +1493,24 @@ namespace { if (!DC) return nullptr; - StringRef nameWithoutRef = getImportedCFTypeName(Name.str()); - Identifier idWithoutRef = - Impl.SwiftContext.getIdentifier(nameWithoutRef); - auto aliasWithoutRef = - Impl.createDeclWithClangNode(Decl, - Impl.importSourceLoc(Decl->getLocStart()), - idWithoutRef, - Impl.importSourceLoc(Decl->getLocation()), - TypeLoc::withoutLoc(SwiftType), - DC); - - aliasWithoutRef->computeType(); - SwiftType = aliasWithoutRef->getDeclaredType(); - NameMapping = MappedTypeNameKind::DefineOnly; + // If there is an alias (i.e., that doesn't have "Ref"), + // create that separate typedef. + if (importedName.Alias) { + auto aliasWithoutRef = + Impl.createDeclWithClangNode( + Decl, + Impl.importSourceLoc(Decl->getLocStart()), + importedName.Alias.getBaseName(), + Impl.importSourceLoc(Decl->getLocation()), + TypeLoc::withoutLoc(SwiftType), + DC); + + aliasWithoutRef->computeType(); + SwiftType = aliasWithoutRef->getDeclaredType(); + NameMapping = MappedTypeNameKind::DefineOnly; + } else { + NameMapping = MappedTypeNameKind::DefineAndUse; + } // If the pointee is 'const void', // 'CFTypeRef', bring it in specifically as AnyObject. @@ -1828,131 +1760,12 @@ namespace { return constructor; } - /// Get the Swift name for an enum constant. - static Identifier getEnumConstantName(ClangImporter::Implementation &impl, - const clang::EnumConstantDecl *decl, - const clang::EnumDecl *clangEnum) { - if (auto *nameAttr = decl->getAttr()) { - StringRef customName = nameAttr->getName(); - if (Lexer::isIdentifier(customName)) - return impl.SwiftContext.getIdentifier(customName); - } - - StringRef enumPrefix = impl.EnumConstantNamePrefixes.lookup(clangEnum); - return impl.importName(decl, enumPrefix); - } - - /// Determine the common prefix to remove from the element names of an - /// enum. We'll elide this prefix from then names in - /// the Swift interface because Swift enum cases are naturally namespaced - /// by the enum type. - void computeEnumCommonWordPrefix(const clang::EnumDecl *decl, - Identifier enumName) { - auto ec = decl->enumerator_begin(), ecEnd = decl->enumerator_end(); - if (ec == ecEnd) - return; - - 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; - } - }; - - 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; - } - - StringRef commonPrefix = (*ec)->getName(); - bool followedByNonIdentifier = false; - for (++ec; ec != ecEnd; ++ec) { - 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(); - } - - // Account for the enum being imported using - // __attribute__((swift_private)). This is a little ad hoc, but it's a - // rare case anyway. - StringRef enumNameStr = enumName.str(); - if (enumNameStr.startswith("__") && !checkPrefix.startswith("__")) - enumNameStr = enumNameStr.drop_front(2); - - 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); - } - Impl.EnumConstantNamePrefixes.insert({decl, commonPrefix}); - } - /// Import an NS_ENUM constant as a case of a Swift enum. Decl *importEnumCase(const clang::EnumConstantDecl *decl, const clang::EnumDecl *clangEnum, EnumDecl *theEnum) { auto &context = Impl.SwiftContext; - auto name = getEnumConstantName(Impl, decl, clangEnum); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2005,7 +1818,7 @@ namespace { Decl *importOptionConstant(const clang::EnumConstantDecl *decl, const clang::EnumDecl *clangEnum, NominalTypeDecl *theStruct) { - auto name = getEnumConstantName(Impl, decl, clangEnum); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2030,11 +1843,11 @@ namespace { EnumElementDecl *original, const clang::EnumDecl *clangEnum, NominalTypeDecl *importedEnum) { - auto name = getEnumConstantName(Impl, alias, clangEnum); + auto name = Impl.importFullName(alias).Imported.getBaseName(); if (name.empty()) return nullptr; - // Construct the original constant. Enum constants witbout payloads look + // Construct the original constant. Enum constants without payloads look // like simple values, but actually have type 'MyEnum.Type -> MyEnum'. auto constantRef = new (Impl.SwiftContext) DeclRefExpr(original, SourceLoc(), @@ -2126,7 +1939,6 @@ namespace { structDecl->addMember(labeledValueConstructor); structDecl->addMember(patternBinding); structDecl->addMember(var); - computeEnumCommonWordPrefix(decl, name); return structDecl; } @@ -2294,8 +2106,6 @@ namespace { enumDecl->addMember(rawValueBinding); result = enumDecl; - computeEnumCommonWordPrefix(decl, name); - break; } @@ -2589,7 +2399,7 @@ namespace { Decl *VisitEnumConstantDecl(const clang::EnumConstantDecl *decl) { auto clangEnum = cast(decl->getDeclContext()); - auto name = getEnumConstantName(Impl, decl, clangEnum); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2684,7 +2494,7 @@ namespace { return nullptr; } } - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2715,21 +2525,13 @@ namespace { return nullptr; // Determine the name of the function. - DeclName name; - bool hasCustomName; - if (auto *customNameAttr = decl->getAttr()) { - name = parseDeclName(customNameAttr->getName()); - hasCustomName = true; - } - - if (!name) { - name = Impl.importName(decl); - hasCustomName = false; - } - - if (!name) + auto importedName = Impl.importFullName(decl); + if (!importedName) return nullptr; + DeclName name = importedName.Imported; + bool hasCustomName = importedName.HasCustomName; + // Import the function type. If we have parameters, make sure their names // get into the resulting function type. SmallVector bodyPatterns; @@ -2800,7 +2602,7 @@ namespace { Decl *VisitFieldDecl(const clang::FieldDecl *decl) { // Fields are imported as variables. - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2846,7 +2648,7 @@ namespace { return nullptr; // Variables are imported as... variables. - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2998,117 +2800,28 @@ namespace { return result; } - /// Parse a stringified Swift DeclName, e.g. "init(frame:)". - DeclName parseDeclName(StringRef Name) { - if (Name.back() != ')') - return {}; - - StringRef BaseName, Parameters; - std::tie(BaseName, Parameters) = Name.split('('); - if (!Lexer::isIdentifier(BaseName) || BaseName == "_") - return {}; - - if (Parameters.empty()) - return {}; - Parameters = Parameters.drop_back(); // ')' - - Identifier BaseID = Impl.SwiftContext.getIdentifier(BaseName); - if (Parameters.empty()) - return DeclName(Impl.SwiftContext, BaseID, {}); - - if (Parameters.back() != ':') - return {}; - - SmallVector ParamIDs; - do { - StringRef NextParam; - std::tie(NextParam, Parameters) = Parameters.split(':'); - - if (!Lexer::isIdentifier(NextParam)) - return {}; - Identifier NextParamID; - if (NextParam != "_") - NextParamID = Impl.SwiftContext.getIdentifier(NextParam); - ParamIDs.push_back(NextParamID); - } while (!Parameters.empty()); - - return DeclName(Impl.SwiftContext, BaseID, ParamIDs); - } - /// If the given method is a factory method, import it as a constructor Optional importFactoryMethodAsConstructor(Decl *member, const clang::ObjCMethodDecl *decl, ObjCSelector selector, DeclContext *dc) { - // Only class methods can be mapped to constructors. - if (!decl->isClassMethod()) - return None; - - // Said class methods must be in an actual class. - auto objcClass = decl->getClassInterface(); - if (!objcClass) - return None; - - DeclName initName; - bool hasCustomName; + // Import the full name of the method. + auto importedName = Impl.importFullName(decl); - // Check whether we're allowed to try. - switch (Impl.getFactoryAsInit(objcClass, decl)) { - case FactoryAsInitKind::AsInitializer: - if (auto *customNameAttr = decl->getAttr()) { - initName = parseDeclName(customNameAttr->getName()); - hasCustomName = true; - break; - } - // FIXME: We probably should stop using this codepath. It won't ever - // succeed. - SWIFT_FALLTHROUGH; - - case FactoryAsInitKind::Infer: { - // Check whether the name fits the pattern. - bool isSwiftPrivate = decl->hasAttr(); - initName = - Impl.mapFactorySelectorToInitializerName(selector, - objcClass->getName(), - isSwiftPrivate); - hasCustomName = false; - break; - } - case FactoryAsInitKind::AsClassMethod: - return None; - } + // Check that we imported an initializer name. + DeclName initName = importedName; + if (initName.getBaseName() != Impl.SwiftContext.Id_init) return None; - if (!initName) + // ... that came from a factory method. + if (importedName.InitKind != CtorInitializerKind::Factory && + importedName.InitKind != CtorInitializerKind::ConvenienceFactory) return None; - // Check the result type to determine what kind of initializer we can - // create (if any). - CtorInitializerKind initKind; - if (decl->hasRelatedResultType()) { - // instancetype factory methods become convenience factory initializers. - initKind = CtorInitializerKind::ConvenienceFactory; - } else if (auto objcPtr = decl->getReturnType() - ->getAs()) { - if (objcPtr->getInterfaceDecl() == objcClass) { - initKind = CtorInitializerKind::Factory; - } else { - // FIXME: Could allow a subclass here, but the rest of the compiler - // isn't prepared for that yet. - // Not a factory method. - ++NumFactoryMethodsWrongResult; - return None; - } - } else { - // Not a factory method. - ++NumFactoryMethodsWrongResult; - return None; - } - bool redundant = false; - auto result = importConstructor(decl, dc, false, initKind, - /*required=*/false, selector, initName, - hasCustomName, + auto result = importConstructor(decl, dc, false, importedName.InitKind, + /*required=*/false, selector, + importedName, {decl->param_begin(), decl->param_size()}, decl->isVariadic(), redundant); if (result) @@ -3122,7 +2835,8 @@ namespace { // TODO: Could add a replacement string? llvm::SmallString<64> message; llvm::raw_svector_ostream os(message); - os << "use object construction '" << objcClass->getName() << "("; + os << "use object construction '" + << decl->getClassInterface()->getName() << "("; for (auto arg : initName.getArgumentNames()) { os << arg << ":"; } @@ -3140,8 +2854,7 @@ namespace { DeclContext *dc, bool forceClassMethod = false) { // If we have an init method, import it as an initializer. - if (decl->getMethodFamily() == clang::OMF_init && - isReallyInitMethod(decl)) { + if (Impl.isInitMethod(decl)) { // Cannot force initializers into class methods. if (forceClassMethod) return nullptr; @@ -3166,21 +2879,11 @@ namespace { if (methodAlreadyImported(selector, isInstance, dc)) return nullptr; - DeclName name; - bool hasCustomName; - if (auto *customNameAttr = decl->getAttr()) { - if (!customNameAttr->getName().startswith("init(")) { - name = parseDeclName(customNameAttr->getName()); - hasCustomName = true; - } - } - if (!name) { - hasCustomName = false; - bool isSwiftPrivate = decl->hasAttr(); - name = Impl.mapSelectorToDeclName(selector, /*isInitializer=*/false, - isSwiftPrivate); - } - if (!name) + auto importedName + = Impl.importFullName(decl, + ClangImporter::Implementation::ImportNameFlags + ::SuppressFactoryMethodAsInit); + if (!importedName) return nullptr; assert(dc->getDeclaredTypeOfContext() && "Method in non-type context?"); @@ -3208,6 +2911,7 @@ namespace { kind = SpecialMethodKind::NSDictionarySubscriptGetter; // Import the type that this method will have. + DeclName name = importedName.Imported; Optional errorConvention; auto type = Impl.importMethodType(decl, decl->getReturnType(), @@ -3216,8 +2920,8 @@ namespace { decl->isVariadic(), decl->hasAttr(), isInSystemModule(dc), - hasCustomName, bodyPatterns, + importedName, name, errorConvention, kind); @@ -3331,27 +3035,6 @@ namespace { return result; } - private: - /// Check whether the given name starts with the given word. - static bool startsWithWord(StringRef name, StringRef word) { - if (name.size() < word.size()) return false; - return ((name.size() == word.size() || !islower(name[word.size()])) && - name.startswith(word)); - } - - /// Determine whether the given Objective-C method, which Clang classifies - /// as an init method, is considered an init method in Swift. - static bool isReallyInitMethod(const clang::ObjCMethodDecl *method) { - if (!method->isInstanceMethod()) - return false; - - auto selector = method->getSelector(); - auto first = selector.getIdentifierInfoForSlot(0); - if (!first) return false; - - return startsWithWord(first->getName(), "init"); - } - public: /// \brief Given an imported method, try to import it as some kind of /// special declaration, e.g., a constructor or subscript. @@ -3440,54 +3123,6 @@ namespace { } } - /// Map an init method to a Swift declaration name. - /// - /// Some special cased remappings also change the parameter signature of the - /// imported initializer, such as to drop vararg parameters. - /// - /// All parameters are in/out parameters. - DeclName - mapInitSelectorToDeclName(ObjCSelector &selector, - ArrayRef &args, - bool &variadic, - bool isSwiftPrivate) { - auto &C = Impl.SwiftContext; - - // Map a few initializers to non-variadic versions that drop the - // variadic parameter. - if (variadic && shouldMakeSelectorNonVariadic(selector)) { - selector = ObjCSelector(C, selector.getNumArgs() - 1, - selector.getSelectorPieces().slice(0, - selector.getSelectorPieces().size() - 1)); - args = args.slice(0, args.size() - 1); - variadic = false; - } - - return Impl.mapSelectorToDeclName(selector, /*initializer*/true, - isSwiftPrivate); - } - - static bool shouldMakeSelectorNonVariadic(ObjCSelector selector) { - // This is UIActionSheet's designated initializer. - if (selector.isNonNullarySelector({ "initWithTitle", - "delegate", - "cancelButtonTitle", - "destructiveButtonTitle", - "otherButtonTitles" })) - return true; - - // This is UIAlertView's designated initializer. - if (selector.isNonNullarySelector({ "initWithTitle", - "message", - "delegate", - "cancelButtonTitle", - "otherButtonTitles" })) - return true; - - // Nothing else for now. - return false; - } - /// \brief Given an imported method, try to import it as a constructor. /// /// Objective-C methods in the 'init' family are imported as @@ -3503,9 +3138,7 @@ namespace { Optional kind, bool required) { // Only methods in the 'init' family can become constructors. - assert(objcMethod->getMethodFamily() == clang::OMF_init && - "Not an init method"); - assert(isReallyInitMethod(objcMethod) && "Not a real init method"); + assert(Impl.isInitMethod(objcMethod) && "Not a real init method"); // Check whether we've already created the constructor. auto known = Impl.Constructors.find({objcMethod, dc}); @@ -3522,14 +3155,20 @@ namespace { objcMethod->param_begin(), objcMethod->param_end() }; - bool isSwiftPrivate = objcMethod->hasAttr(); + bool variadic = objcMethod->isVariadic(); - DeclName name = mapInitSelectorToDeclName(selector, params, variadic, - isSwiftPrivate); + auto importedName = Impl.importFullName(objcMethod); + if (!importedName) return nullptr; + + // If we dropped the variadic, handle it now. + if (importedName.DroppedVariadic) { + params = params.drop_back(1); + variadic = false; + } bool redundant; return importConstructor(objcMethod, dc, implicit, kind, required, - selector, name, /*customName=*/false, params, + selector, importedName, params, variadic, redundant); } @@ -3610,6 +3249,8 @@ namespace { return false; } + using ImportedName = ClangImporter::Implementation::ImportedName; + /// \brief Given an imported method, try to import it as a constructor. /// /// Objective-C methods in the 'init' family are imported as @@ -3628,8 +3269,7 @@ namespace { Optional kindIn, bool required, ObjCSelector selector, - DeclName name, - bool hasCustomName, + ImportedName importedName, ArrayRef args, bool variadic, bool &redundant) { @@ -3678,14 +3318,15 @@ namespace { // Import the type that this method will have. Optional errorConvention; + DeclName name = importedName.Imported; auto type = Impl.importMethodType(objcMethod, objcMethod->getReturnType(), args, variadic, objcMethod->hasAttr(), isInSystemModule(dc), - hasCustomName, bodyPatterns, + importedName, name, errorConvention, SpecialMethodKind::Constructor); @@ -4814,8 +4455,7 @@ namespace { continue; // When mirroring an initializer, make it designated and required. - if (objcMethod->getMethodFamily() == clang::OMF_init && - isReallyInitMethod(objcMethod)) { + if (Impl.isInitMethod(objcMethod)) { // Import the constructor. if (auto imported = importConstructor( objcMethod, dc, /*implicit=*/true, @@ -4887,14 +4527,15 @@ namespace { assert(ctor->getInitKind() == CtorInitializerKind::ConvenienceFactory); + ImportedName importedName = Impl.importFullName(objcMethod); + importedName.HasCustomName = true; bool redundant; if (auto newCtor = importConstructor(objcMethod, classDecl, /*implicit=*/true, ctor->getInitKind(), /*required=*/false, ctor->getObjCSelector(), - ctor->getFullName(), - /*customName=*/true, + importedName, objcMethod->parameters(), objcMethod->isVariadic(), redundant)) { @@ -5068,7 +4709,7 @@ namespace { } Decl *VisitObjCProtocolDecl(const clang::ObjCProtocolDecl *decl) { - Identifier name = Impl.importName(decl); + Identifier name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -5087,59 +4728,6 @@ namespace { decl = decl->getDefinition(); - // Test to see if there is a value with the same name as the protocol - // in the same module. - // FIXME: This will miss macros. - auto clangModule = Impl.getClangSubmoduleForDecl(decl); - if (clangModule.hasValue() && clangModule.getValue()) - clangModule = clangModule.getValue()->getTopLevelModule(); - - auto isInSameModule = [&](const clang::Decl *D) -> bool { - auto declModule = Impl.getClangSubmoduleForDecl(D); - if (!declModule.hasValue()) - return false; - // Handle the bridging header case. This is pretty nasty since things - // can get added to it *later*, but there's not much we can do. - if (!declModule.getValue()) - return *clangModule == nullptr; - return *clangModule == declModule.getValue()->getTopLevelModule(); - }; - - // Allow this lookup to find hidden names. We don't want the - // decision about whether to rename the protocol to depend on - // what exactly the user has imported. Indeed, if we're being - // asked to resolve a serialization cross-reference, the user - // may not have imported this module at all, which means a - // normal lookup wouldn't even find the protocol! - // - // Meanwhile, we don't need to worry about finding unwanted - // hidden declarations from different modules because we do a - // module check before deciding that there's a conflict. - bool hasConflict = false; - clang::LookupResult lookupResult(Impl.getClangSema(), decl->getDeclName(), - clang::SourceLocation(), - clang::Sema::LookupOrdinaryName); - lookupResult.setAllowHidden(true); - lookupResult.suppressDiagnostics(); - - if (Impl.getClangSema().LookupName(lookupResult, /*scope=*/nullptr)) { - hasConflict = std::any_of(lookupResult.begin(), lookupResult.end(), - isInSameModule); - } - if (!hasConflict) { - lookupResult.clear(clang::Sema::LookupTagName); - if (Impl.getClangSema().LookupName(lookupResult, /*scope=*/nullptr)) { - hasConflict = std::any_of(lookupResult.begin(), lookupResult.end(), - isInSameModule); - } - } - - if (hasConflict) { - SmallString<64> nameBuf{name.str()}; - nameBuf += SWIFT_PROTOCOL_SUFFIX; - name = Impl.SwiftContext.getIdentifier(nameBuf.str()); - } - auto dc = Impl.importDeclContextOf(decl); if (!dc) return nullptr; @@ -5157,7 +4745,7 @@ namespace { name, None); result->computeType(); - addObjCAttribute(result, Impl.importDeclName(decl->getDeclName())); + addObjCAttribute(result, Impl.importIdentifier(decl->getIdentifier())); if (declaredNative) markMissingSwiftDecl(result); @@ -5222,7 +4810,7 @@ namespace { } Decl *VisitObjCInterfaceDecl(const clang::ObjCInterfaceDecl *decl) { - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -5242,7 +4830,7 @@ namespace { result->setSuperclass(Type()); result->setCheckedInheritanceClause(); result->setAddedImplicitInitializers(); // suppress all initializers - addObjCAttribute(result, Impl.importDeclName(decl->getDeclName())); + addObjCAttribute(result, Impl.importIdentifier(decl->getIdentifier())); Impl.registerExternalDecl(result); return result; }; @@ -5311,7 +4899,7 @@ namespace { Impl.ImportedDecls[decl->getCanonicalDecl()] = result; result->setCircularityCheck(CircularityCheck::Checked); result->setAddedImplicitInitializers(); - addObjCAttribute(result, Impl.importDeclName(decl->getDeclName())); + addObjCAttribute(result, Impl.importIdentifier(decl->getIdentifier())); if (declaredNative) markMissingSwiftDecl(result); @@ -5438,7 +5026,7 @@ namespace { Decl *VisitObjCPropertyDecl(const clang::ObjCPropertyDecl *decl, DeclContext *dc) { - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -6487,7 +6075,6 @@ ClangImporter::Implementation::getSpecialTypedefKind(clang::TypedefNameDecl *dec Identifier ClangImporter::getEnumConstantName(const clang::EnumConstantDecl *enumConstant){ - auto clangEnum = cast(enumConstant->getDeclContext()); - return SwiftDeclConverter::getEnumConstantName(Impl, enumConstant, clangEnum); + return Impl.importFullName(enumConstant).Imported.getBaseName(); } diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 1c3a8e3f527cc..57e44703c7ccb 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -34,7 +34,6 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/TypeVisitor.h" -#include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" @@ -151,6 +150,7 @@ namespace { }; bool canImportAsOptional(ImportHint hint) { + // See also ClangImporter.cpp's canImportAsOptional. switch (hint) { case ImportHint::None: case ImportHint::BOOL: @@ -1245,9 +1245,9 @@ Type ClangImporter::Implementation::importPropertyType( /// Get a bit vector indicating which arguments are non-null for a /// given function or method. -static llvm::SmallBitVector getNonNullArgs( - const clang::Decl *decl, - ArrayRef params) { +llvm::SmallBitVector ClangImporter::Implementation::getNonNullArgs( + const clang::Decl *decl, + ArrayRef params) { llvm::SmallBitVector result; if (!decl) return result; @@ -1425,7 +1425,7 @@ Type ClangImporter::Implementation::importFunctionType( } // Figure out the name for this parameter. - Identifier bodyName = importName(param); + Identifier bodyName = importFullName(param).Imported.getBaseName(); // Retrieve the argument name. Identifier name; @@ -1543,330 +1543,6 @@ namespace { }; } -static bool isBlockParameter(const clang::ParmVarDecl *param) { - return param->getType()->isBlockPointerType(); -} - -static bool isErrorOutParameter(const clang::ParmVarDecl *param, - ForeignErrorConvention::IsOwned_t &isErrorOwned) { - clang::QualType type = param->getType(); - - // Must be a pointer. - auto ptrType = type->getAs(); - if (!ptrType) return false; - type = ptrType->getPointeeType(); - - // For NSError**, take ownership from the qualifier. - if (auto objcPtrType = type->getAs()) { - auto iface = objcPtrType->getInterfaceDecl(); - if (iface && iface->getName() == "NSError") { - switch (type.getObjCLifetime()) { - case clang::Qualifiers::OCL_None: - llvm_unreachable("not in ARC?"); - - case clang::Qualifiers::OCL_ExplicitNone: - case clang::Qualifiers::OCL_Autoreleasing: - isErrorOwned = ForeignErrorConvention::IsNotOwned; - return true; - - case clang::Qualifiers::OCL_Weak: - // We just don't know how to handle this. - return false; - - case clang::Qualifiers::OCL_Strong: - isErrorOwned = ForeignErrorConvention::IsOwned; - return false; - } - llvm_unreachable("bad error ownership"); - } - } - return false; -} - -static bool isBoolType(ClangImporter::Implementation &importer, Type type) { - if (auto nominalType = type->getAs()) { - return nominalType->getDecl() == importer.SwiftContext.getBoolDecl(); - } - return false; -} - -static bool isIntegerType(Type type) { - // Look through arbitrarily many struct abstractions. - while (auto structDecl = type->getStructOrBoundGenericStruct()) { - // Require the struct to have exactly one stored property. - auto properties = structDecl->getStoredProperties(); - auto i = properties.begin(), e = properties.end(); - if (i == e) return false; - - VarDecl *property = *i; - if (++i != e) return false; - type = property->getType(); - } - - return type->is(); -} - -static Optional -classifyMethodErrorHandling(ClangImporter::Implementation &importer, - const clang::ObjCMethodDecl *clangDecl, - Type importedResultType) { - // TODO: opt out any non-standard methods here? - - // Check for an explicit attribute. - if (auto attr = clangDecl->getAttr()) { - switch (attr->getConvention()) { - case clang::SwiftErrorAttr::None: - return None; - - case clang::SwiftErrorAttr::NonNullError: - return ForeignErrorConvention::NonNilError; - - // Only honor null_result if we actually imported as a - // non-optional type. - case clang::SwiftErrorAttr::NullResult: - if (importedResultType->getAnyOptionalObjectType()) - return ForeignErrorConvention::NilResult; - return None; - - // Preserve the original result type on a zero_result unless we - // imported it as Bool. - case clang::SwiftErrorAttr::ZeroResult: - if (isBoolType(importer, importedResultType)) { - return ForeignErrorConvention::ZeroResult; - } else if (isIntegerType(importedResultType)) { - return ForeignErrorConvention::ZeroPreservedResult; - } - return None; - - // There's no reason to do the same for nonzero_result because the - // only meaningful value remaining would be zero. - case clang::SwiftErrorAttr::NonZeroResult: - if (isIntegerType(importedResultType)) - return ForeignErrorConvention::NonZeroResult; - return None; - } - llvm_unreachable("bad swift_error kind"); - } - - // Otherwise, apply the default rules. - - // For bool results, a zero value is an error. - if (isBoolType(importer, importedResultType)) { - return ForeignErrorConvention::ZeroResult; - } - - // For optional reference results, a nil value is normally an error. - if (importedResultType->getAnyOptionalObjectType()) { - return ForeignErrorConvention::NilResult; - } - - return None; -} - -static const char ErrorSuffix[] = "AndReturnError"; -static const char AltErrorSuffix[] = "WithError"; - -/// Look for a method that will import to have the same name as the -/// given method after importing the Nth parameter as an elided error -/// parameter. -static bool hasErrorMethodNameCollision(ClangImporter::Implementation &importer, - const clang::ObjCMethodDecl *method, - unsigned paramIndex, - StringRef suffixToStrip) { - // Copy the existing selector pieces into an array. - auto selector = method->getSelector(); - unsigned numArgs = selector.getNumArgs(); - assert(numArgs > 0); - - SmallVector chunks; - for (unsigned i = 0, e = selector.getNumArgs(); i != e; ++i) { - chunks.push_back(selector.getIdentifierInfoForSlot(i)); - } - - auto &ctx = method->getASTContext(); - if (paramIndex == 0 && !suffixToStrip.empty()) { - StringRef name = chunks[0]->getName(); - assert(name.endswith(suffixToStrip)); - name = name.drop_back(suffixToStrip.size()); - chunks[0] = &ctx.Idents.get(name); - } else if (paramIndex != 0) { - chunks.erase(chunks.begin() + paramIndex); - } - - auto newSelector = ctx.Selectors.getSelector(numArgs - 1, chunks.data()); - const clang::ObjCMethodDecl *conflict; - if (auto iface = method->getClassInterface()) { - conflict = iface->lookupMethod(newSelector, method->isInstanceMethod()); - } else { - auto protocol = cast(method->getDeclContext()); - conflict = protocol->getMethod(newSelector, method->isInstanceMethod()); - } - - if (conflict == nullptr) - return false; - - // Look to see if the conflicting decl is unavailable, either because it's - // been marked NS_SWIFT_UNAVAILABLE, because it's actually marked unavailable, - // or because it was deprecated before our API sunset. We can handle - // "conflicts" where one form is unavailable. - // FIXME: Somewhat duplicated from Implementation::importAttributes. - clang::AvailabilityResult availability = conflict->getAvailability(); - if (availability != clang::AR_Unavailable && - importer.DeprecatedAsUnavailableFilter) { - for (auto *attr : conflict->specific_attrs()) { - if (attr->getPlatform()->getName() == "swift") { - availability = clang::AR_Unavailable; - break; - } - if (importer.PlatformAvailabilityFilter && - !importer.PlatformAvailabilityFilter(attr->getPlatform()->getName())){ - continue; - } - clang::VersionTuple version = attr->getDeprecated(); - if (version.empty()) - continue; - if (importer.DeprecatedAsUnavailableFilter(version.getMajor(), - version.getMinor())) { - availability = clang::AR_Unavailable; - break; - } - } - } - return availability != clang::AR_Unavailable; -} - - -static Optional -considerErrorImport(ClangImporter::Implementation &importer, - const clang::ObjCMethodDecl *clangDecl, - DeclName &methodName, - ArrayRef params, - Type &importedResultType, - SpecialMethodKind methodKind, - bool hasCustomName) { - // If the declaration name isn't parallel to the actual parameter - // list (e.g. if the method has C-style parameter declarations), - // don't try to apply error conventions. - auto paramNames = methodName.getArgumentNames(); - bool expectsToRemoveError = - hasCustomName && paramNames.size() + 1 == params.size(); - if (!expectsToRemoveError && paramNames.size() != params.size()) - return None; - - for (unsigned index = params.size(); index-- != 0; ) { - // Allow an arbitrary number of trailing blocks. - if (isBlockParameter(params[index])) - continue; - - // Otherwise, require the last parameter to be an out-parameter. - auto isErrorOwned = ForeignErrorConvention::IsNotOwned; - if (!isErrorOutParameter(params[index], isErrorOwned)) - break; - - auto errorKind = - classifyMethodErrorHandling(importer, clangDecl, importedResultType); - if (!errorKind) return None; - - // Consider adjusting the imported declaration name to remove the - // parameter. - bool adjustName = !hasCustomName; - - // Never do this if it's the first parameter of a constructor. - if (methodKind == SpecialMethodKind::Constructor && index == 0) { - adjustName = false; - } - - // If the error parameter is the first parameter, try removing the - // standard error suffix from the base name. - StringRef suffixToStrip; - Identifier newBaseName = methodName.getBaseName(); - if (adjustName && index == 0 && paramNames[0].empty()) { - StringRef baseNameStr = newBaseName.str(); - if (baseNameStr.endswith(ErrorSuffix)) - suffixToStrip = ErrorSuffix; - else if (baseNameStr.endswith(AltErrorSuffix)) - suffixToStrip = AltErrorSuffix; - - if (!suffixToStrip.empty()) { - baseNameStr = baseNameStr.drop_back(suffixToStrip.size()); - if (baseNameStr.empty() || importer.isSwiftReservedName(baseNameStr)) { - adjustName = false; - suffixToStrip = {}; - } else { - newBaseName = importer.SwiftContext.getIdentifier(baseNameStr); - } - } - } - - // Also suppress name changes if there's a collision. - // TODO: this logic doesn't really work with init methods - // TODO: this privileges the old API over the new one - if (adjustName && - hasErrorMethodNameCollision(importer, clangDecl, index, - suffixToStrip)) { - // If there was a conflict on the first argument, and this was - // the first argument and we're not stripping error suffixes, just - // give up completely on error import. - if (index == 0 && suffixToStrip.empty()) { - return None; - - // If there was a conflict stripping an error suffix, adjust the - // name but don't change the base name. This avoids creating a - // spurious _: () argument. - } else if (index == 0 && !suffixToStrip.empty()) { - suffixToStrip = {}; - newBaseName = methodName.getBaseName(); - - // Otherwise, give up on adjusting the name. - } else { - adjustName = false; - } - } - - auto replaceWithVoid = ForeignErrorConvention::IsNotReplaced; - if (!adjustName && !expectsToRemoveError) - replaceWithVoid = ForeignErrorConvention::IsReplaced; - ErrorImportInfo errorInfo = { - *errorKind, isErrorOwned, replaceWithVoid, index, CanType(), - importedResultType->getCanonicalType() - }; - - // Adjust the return type. - switch (*errorKind) { - case ForeignErrorConvention::ZeroResult: - case ForeignErrorConvention::NonZeroResult: - importedResultType = TupleType::getEmpty(importer.SwiftContext); - break; - - case ForeignErrorConvention::NilResult: - importedResultType = importedResultType->getAnyOptionalObjectType(); - assert(importedResultType && - "result type of NilResult convention was not imported as optional"); - break; - - case ForeignErrorConvention::ZeroPreservedResult: - case ForeignErrorConvention::NonNilError: - break; - } - - if (!adjustName) - return errorInfo; - - // Build the new declaration name. - SmallVector newParamNames; - newParamNames.append(paramNames.begin(), - paramNames.begin() + index); - newParamNames.append(paramNames.begin() + index + 1, - paramNames.end()); - methodName = DeclName(importer.SwiftContext, newBaseName, newParamNames); - - return errorInfo; - } - - // Didn't find an error parameter. - return None; -} - /// Determine whether this is the name of an Objective-C collection /// with a single element type. static bool isObjCCollectionName(StringRef typeName) { @@ -2028,27 +1704,18 @@ OmissionTypeName ClangImporter::Implementation::getClangTypeNameForOmission( } /// Attempt to omit needless words from the given function name. -DeclName ClangImporter::Implementation::omitNeedlessWordsInFunctionName( - DeclName name, - ArrayRef params, - clang::QualType resultType, - const clang::DeclContext *dc, - const llvm::SmallBitVector &nonNullArgs, - const Optional &knownMethod, - Optional errorParamIndex, - bool returnsSelf, - bool isInstanceMethod) { - ASTContext &ctx = SwiftContext; - - // Collect the argument names. - SmallVector argNames; - for (auto arg : name.getArgumentNames()) { - if (arg.empty()) - argNames.push_back(""); - else - argNames.push_back(arg.str()); - } - +bool ClangImporter::Implementation::omitNeedlessWordsInFunctionName( + StringRef &baseName, + SmallVectorImpl &argumentNames, + ArrayRef params, + clang::QualType resultType, + const clang::DeclContext *dc, + const llvm::SmallBitVector &nonNullArgs, + const Optional &knownMethod, + Optional errorParamIndex, + bool returnsSelf, + bool isInstanceMethod, + StringScratchSpace &scratch) { // Collect the parameter type names. StringRef firstParamName; SmallVector paramTypes; @@ -2075,21 +1742,17 @@ DeclName ClangImporter::Implementation::omitNeedlessWordsInFunctionName( param->getType(), getParamOptionality(param, !nonNullArgs.empty() && nonNullArgs[i], - knownMethod + knownMethod && knownMethod->NullabilityAudited ? Optional( knownMethod->getParamTypeInfo(i)) : None), - name.getBaseName(), numParams, + SwiftContext.getIdentifier(baseName), numParams, isLastParameter); paramTypes.push_back(getClangTypeNameForOmission(param->getType()) .withDefaultArgument(hasDefaultArg)); } - // Omit needless words. - StringRef baseName = name.getBaseName().str(); - StringScratchSpace scratch; - // Find the property names. const InheritedNameSet *allPropertyNames = nullptr; auto contextType = getClangDeclContextType(dc); @@ -2100,35 +1763,12 @@ DeclName ClangImporter::Implementation::omitNeedlessWordsInFunctionName( isInstanceMethod); } - if (!omitNeedlessWords(baseName, argNames, firstParamName, - getClangTypeNameForOmission(resultType), - getClangTypeNameForOmission(contextType), - paramTypes, returnsSelf, /*isProperty=*/false, - allPropertyNames, scratch)) - return name; - - /// Retrieve a replacement identifier. - auto getReplacementIdentifier = [&](StringRef name, - Identifier old) -> Identifier{ - if (name.empty()) - return Identifier(); - - if (!old.empty() && name == old.str()) - return old; - - return ctx.getIdentifier(name); - }; - - Identifier newBaseName = getReplacementIdentifier(baseName, - name.getBaseName()); - SmallVector newArgNames; - auto oldArgNames = name.getArgumentNames(); - for (unsigned i = 0, n = argNames.size(); i != n; ++i) { - newArgNames.push_back(getReplacementIdentifier(argNames[i], - oldArgNames[i])); - } - - return DeclName(ctx, newBaseName, newArgNames); + // Omit needless words. + return omitNeedlessWords(baseName, argumentNames, firstParamName, + getClangTypeNameForOmission(resultType), + getClangTypeNameForOmission(contextType), + paramTypes, returnsSelf, /*isProperty=*/false, + allPropertyNames, scratch); } /// Retrieve the instance type of the given Clang declaration context. @@ -2190,13 +1830,68 @@ bool ClangImporter::Implementation::canInferDefaultArgument( return false; } +/// Adjust the result type of a throwing function based on the +/// imported error information. +static Type adjustResultTypeForThrowingFunction( + const ClangImporter::Implementation::ImportedErrorInfo &errorInfo, + Type resultTy) { + switch (errorInfo.Kind) { + case ForeignErrorConvention::ZeroResult: + case ForeignErrorConvention::NonZeroResult: + return TupleType::getEmpty(resultTy->getASTContext()); + + case ForeignErrorConvention::NilResult: + resultTy = resultTy->getAnyOptionalObjectType(); + assert(resultTy && + "result type of NilResult convention was not imported as optional"); + return resultTy; + + case ForeignErrorConvention::ZeroPreservedResult: + case ForeignErrorConvention::NonNilError: + return resultTy; + } +} + +/// Produce the foreign error convention from the imported error info, +/// error parameter type, and original result type. +static ForeignErrorConvention +getForeignErrorInfo( + const ClangImporter::Implementation::ImportedErrorInfo &errorInfo, + CanType errorParamTy, CanType origResultTy) { + assert(errorParamTy && "not fully initialized!"); + using FEC = ForeignErrorConvention; + auto ReplaceParamWithVoid = errorInfo.ReplaceParamWithVoid + ? FEC::IsReplaced + : FEC::IsNotReplaced; + switch (errorInfo.Kind) { + case FEC::ZeroResult: + return FEC::getZeroResult(errorInfo.ParamIndex, errorInfo.IsOwned, + ReplaceParamWithVoid, errorParamTy, origResultTy); + case FEC::NonZeroResult: + return FEC::getNonZeroResult(errorInfo.ParamIndex, errorInfo.IsOwned, + ReplaceParamWithVoid, errorParamTy, + origResultTy); + case FEC::ZeroPreservedResult: + return FEC::getZeroPreservedResult(errorInfo.ParamIndex, errorInfo.IsOwned, + ReplaceParamWithVoid, errorParamTy); + case FEC::NilResult: + return FEC::getNilResult(errorInfo.ParamIndex, errorInfo.IsOwned, + ReplaceParamWithVoid, errorParamTy); + case FEC::NonNilError: + return FEC::getNonNilError(errorInfo.ParamIndex, errorInfo.IsOwned, + ReplaceParamWithVoid, errorParamTy); + } + llvm_unreachable("bad error convention"); +} + Type ClangImporter::Implementation::importMethodType( const clang::ObjCMethodDecl *clangDecl, clang::QualType resultType, ArrayRef params, bool isVariadic, bool isNoReturn, - bool isFromSystemModule, bool isCustomName, + bool isFromSystemModule, SmallVectorImpl &bodyPatterns, + ImportedName importedName, DeclName &methodName, Optional &foreignErrorInfo, SpecialMethodKind kind) { @@ -2244,7 +1939,9 @@ Type ClangImporter::Implementation::importMethodType( } // Import the result type. + CanType origSwiftResultTy; Type swiftResultTy; + auto errorInfo = importedName.ErrorInfo; if (isPropertyGetter) { swiftResultTy = importPropertyType(property, isFromSystemModule); } else { @@ -2270,6 +1967,12 @@ Type ClangImporter::Implementation::importMethodType( allowNSUIntegerAsIntInResult, /*isFullyBridgeable*/true, OptionalityOfReturn); + // Adjust the result type for a throwing function. + if (swiftResultTy && errorInfo) { + origSwiftResultTy = swiftResultTy->getCanonicalType(); + swiftResultTy = adjustResultTypeForThrowingFunction(*errorInfo, + swiftResultTy); + } if (swiftResultTy && clangDecl->getMethodFamily() == clang::OMF_performSelector) { @@ -2290,25 +1993,10 @@ Type ClangImporter::Implementation::importMethodType( if (!swiftResultTy) return Type(); - auto errorInfo = considerErrorImport(*this, clangDecl, methodName, params, - swiftResultTy, kind, isCustomName); + CanType errorParamType; llvm::SmallBitVector nonNullArgs = getNonNullArgs(clangDecl, params); - // If we should omit needless words and don't have a custom name, do so. - if (OmitNeedlessWords && !isCustomName && clangDecl && - (kind == SpecialMethodKind::Regular || - kind == SpecialMethodKind::Constructor)) { - methodName = omitNeedlessWordsInFunctionName( - methodName, params, resultType, - clangDecl->getDeclContext(), - nonNullArgs, - knownMethod, - errorInfo ? Optional(errorInfo->ParamIndex) : None, - clangDecl->hasRelatedResultType(), - clangDecl->isInstanceMethod()); - } - // Import the parameters. SmallVector swiftArgParams; SmallVector swiftBodyParams; @@ -2406,7 +2094,7 @@ Type ClangImporter::Implementation::importMethodType( // If this is the error parameter, remember it, but don't build it // into the parameter type. if (errorInfo && paramIndex == errorInfo->ParamIndex) { - errorInfo->ParamType = swiftParamTy->getCanonicalType(); + errorParamType = swiftParamTy->getCanonicalType(); // ...unless we're supposed to replace it with (). if (errorInfo->ReplaceParamWithVoid) { @@ -2427,7 +2115,7 @@ Type ClangImporter::Implementation::importMethodType( } // Figure out the name for this parameter. - Identifier bodyName = importName(param); + Identifier bodyName = importFullName(param).Imported.getBaseName(); // Figure out the name for this argument, which comes from the method name. Identifier name; @@ -2492,7 +2180,7 @@ Type ClangImporter::Implementation::importMethodType( addEmptyTupleParameter(argNames[0]); } - if (isCustomName && argNames.size() != swiftBodyParams.size()) { + if (importedName.HasCustomName && argNames.size() != swiftBodyParams.size()) { // Note carefully: we're emitting a warning in the /Clang/ buffer. auto &srcMgr = getClangASTContext().getSourceManager(); auto &rawDiagClient = Instance->getDiagnosticClient(); @@ -2519,7 +2207,8 @@ Type ClangImporter::Implementation::importMethodType( extInfo = extInfo.withIsNoReturn(isNoReturn); if (errorInfo) { - foreignErrorInfo = errorInfo->asForeignErrorConvention(); + foreignErrorInfo = getForeignErrorInfo(*errorInfo, errorParamType, + origSwiftResultTy); // Mark that the function type throws. extInfo = extInfo.withThrows(true); diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 59c800f6a8f23..e453704169f95 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 "SwiftLookupTable.h" #include "swift/ClangImporter/ClangImporter.h" #include "swift/AST/ASTContext.h" #include "swift/AST/LazyResolver.h" @@ -31,6 +32,7 @@ #include "llvm/ADT/APSInt.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/TinyPtrVector.h" #include @@ -251,6 +253,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation const bool ImportForwardDeclarations; const bool OmitNeedlessWords; const bool InferDefaultArguments; + const bool UseSwiftLookupTables; constexpr static const char * const moduleImportBufferName = ""; @@ -289,6 +292,9 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// if type checking has begun. llvm::PointerIntPair typeResolver; + /// The Swift lookup table for the bridging header. + SwiftLookupTable BridgingHeaderLookupTable; + public: /// \brief Mapping of already-imported declarations. llvm::DenseMap ImportedDecls; @@ -305,9 +311,6 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation llvm::SmallDenseMap SpecialTypedefNames; - /// Mapping from Objective-C selectors to method names. - llvm::DenseMap, DeclName> SelectorMappings; - /// Is the given identifier a reserved name in Swift? static bool isSwiftReservedName(StringRef name); @@ -421,6 +424,24 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// imported as methods into Swift. bool isAccessibilityDecl(const clang::Decl *objCMethodOrProp); + /// Determine whether this method is an Objective-C "init" method + /// that will be imported as a Swift initializer. + bool isInitMethod(const clang::ObjCMethodDecl *method); + + /// Determine whether this Objective-C method should be imported as + /// an initializer. + /// + /// \param prefixLength Will be set to the length of the prefix that + /// should be stripped from the first selector piece, e.g., "init" + /// or the restated name of the class in a factory method. + /// + /// \param kind Will be set to the kind of initializer being + /// imported. Note that this does not distinguish designated + /// vs. convenience; both will be classified as "designated". + bool shouldImportAsInitializer(const clang::ObjCMethodDecl *method, + unsigned &prefixLength, + CtorInitializerKind &kind); + private: /// \brief Generation number that is used for crude versioning. /// @@ -498,6 +519,10 @@ 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(const clang::EnumDecl *enumDecl); + public: /// \brief Keep track of enum constant values that have been imported. llvm::DenseMap, @@ -620,6 +645,10 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation void addBridgeHeaderTopLevelDecls(clang::Decl *D); bool shouldIgnoreBridgeHeaderTopLevelDecl(clang::Decl *D); + /// Add the given named declaration as an entry to the given Swift name + /// lookup table, including any of its child entries. + void addEntryToLookupTable(SwiftLookupTable &table, clang::NamedDecl *named); + public: void registerExternalDecl(Decl *D) { RegisteredExternalDecls.push_back(D); @@ -690,47 +719,114 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// or a null type if the DeclContext does not have a correspinding type. clang::QualType getClangDeclContextType(const clang::DeclContext *dc); + /// Determine whether this typedef is a CF type. + static bool isCFTypeDecl(const clang::TypedefNameDecl *Decl); + /// Determine the imported CF type for the given typedef-name, or the empty /// string if this is not an imported CF type name. - StringRef getCFTypeName(const clang::TypedefNameDecl *decl); + StringRef getCFTypeName(const clang::TypedefNameDecl *decl, + StringRef *secondaryName = nullptr); /// Retrieve the type name of a Clang type for the purposes of /// omitting unneeded words. OmissionTypeName getClangTypeNameForOmission(clang::QualType type); /// Omit needless words in a function name. - DeclName omitNeedlessWordsInFunctionName( - DeclName name, - ArrayRef params, - clang::QualType resultType, - const clang::DeclContext *dc, - const llvm::SmallBitVector &nonNullArgs, - const Optional &knownMethod, - Optional errorParamIndex, - bool returnsSelf, - bool isInstanceMethod); + bool omitNeedlessWordsInFunctionName( + StringRef &baseName, + SmallVectorImpl &argumentNames, + ArrayRef params, + clang::QualType resultType, + const clang::DeclContext *dc, + const llvm::SmallBitVector &nonNullArgs, + const Optional &knownMethod, + Optional errorParamIndex, + bool returnsSelf, + bool isInstanceMethod, + StringScratchSpace &scratch); /// \brief Converts the given Swift identifier for Clang. clang::DeclarationName exportName(Identifier name); - /// Imports the name of the given Clang decl into Swift. + /// Information about imported error parameters. + struct ImportedErrorInfo { + ForeignErrorConvention::Kind Kind; + ForeignErrorConvention::IsOwned_t IsOwned; + + /// The index of the error parameter. + unsigned ParamIndex; + + /// Whether the parameter is being replaced with "void" + /// (vs. removed). + bool ReplaceParamWithVoid; + }; + + /// Describes a name that was imported from Clang. + struct ImportedName { + /// The imported name. + DeclName Imported; + + /// An additional alias to the imported name, which should be + /// recorded in name lookup tables as well. + DeclName Alias; + + /// Whether this name was explicitly specified via a Clang + /// swift_name attribute. + bool HasCustomName = false; + + /// Whether this was one of a special class of Objective-C + /// initializers for which we drop the variadic argument rather + /// than refuse to import the initializer. + bool DroppedVariadic = false; + + /// For an initializer, the kind of initializer to import. + CtorInitializerKind InitKind = CtorInitializerKind::Designated; + + /// For names that map Objective-C error handling conventions into + /// throwing Swift methods, describes how the mapping is performed. + Optional ErrorInfo; + + /// Produce just the imported name, for clients that don't care + /// about the details. + operator DeclName() const { return Imported; } + + /// Whether any name was imported. + explicit operator bool() const { return static_cast(Imported); } + }; + + /// Flags that control the import of names in importFullName. + enum class ImportNameFlags { + /// Suppress the factory-method-as-initializer transformation. + SuppressFactoryMethodAsInit = 0x01, + }; + + /// Options that control the import of names in importFullName. + typedef OptionSet ImportNameOptions; + + /// Imports the full name of the given Clang declaration into Swift. /// - /// Note that this may result in a name different from the Clang name, so it - /// should not be used when referencing Clang symbols. (In particular, it - /// should not be put into \c \@objc attributes.) + /// Note that this may result in a name very different from the Clang name, + /// so it should not be used when referencing Clang symbols. /// - /// \sa importName(clang::DeclarationName, StringRef) - Identifier importName(const clang::NamedDecl *D, StringRef removePrefix = ""); - - /// \brief Import the given Clang name into Swift. + /// \param D The Clang declaration whose name should be imported. /// - /// \param name The Clang name to map into Swift. + /// \param effectiveContext If non-null, will be set to the effective + /// Clang declaration context in which the declaration will be imported. + /// This can differ from D's redeclaration context when the Clang importer + /// introduces nesting, e.g., for enumerators within an NS_ENUM. + ImportedName importFullName(const clang::NamedDecl *D, + ImportNameOptions options = None, + clang::DeclContext **effectiveContext = nullptr); + + /// \brief Import the given Clang identifier into Swift. + /// + /// \param identifier The Clang identifier to map into Swift. /// /// \param removePrefix The prefix to remove from the Clang name to produce /// the Swift name. If the Clang name does not start with this prefix, /// nothing is removed. - Identifier importDeclName(clang::DeclarationName name, - StringRef removePrefix = ""); + Identifier importIdentifier(const clang::IdentifierInfo *identifier, + StringRef removePrefix = ""); /// Import an Objective-C selector. ObjCSelector importSelector(clang::Selector selector); @@ -741,34 +837,6 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// Export a Swift Objective-C selector as a Clang Objective-C selector. clang::Selector exportSelector(ObjCSelector selector); - /// Map the given selector to a declaration name. - /// - /// \param selector The selector to map. - /// - /// \param isInitializer Whether this name should be mapped as an - /// initializer. - /// - /// \param isSwiftPrivate Whether this name is for a declaration marked with - /// the 'swift_private' attribute. - DeclName mapSelectorToDeclName(ObjCSelector selector, bool isInitializer, - bool isSwiftPrivate); - - /// Try to map the given selector, which may be the name of a factory method, - /// to the name of an initializer. - /// - /// \param selector The selector to map. - /// - /// \param className The name of the class in which the method occurs. - /// - /// \param isSwiftPrivate Whether this name is for a declaration marked with - /// the 'swift_private' attribute. - /// - /// \returns the initializer name for this factory method, or an empty - /// name if this selector does not fit the pattern. - DeclName mapFactorySelectorToInitializerName(ObjCSelector selector, - StringRef className, - bool isSwiftPrivate); - /// \brief Import the given Swift source location into Clang. clang::SourceLocation exportSourceLoc(SourceLoc loc); @@ -1048,6 +1116,12 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation unsigned numParams, bool isLastParameter); + /// Retrieve a bit vector containing the non-null argument + /// annotations for the given declaration. + llvm::SmallBitVector getNonNullArgs( + const clang::Decl *decl, + ArrayRef params); + /// \brief Import the type of an Objective-C method. /// /// This routine should be preferred when importing function types for @@ -1063,10 +1137,9 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// \param isNoReturn Whether the function is noreturn. /// \param isFromSystemModule Whether to apply special rules that only apply /// to system APIs. - /// \param isCustomName If true, the user has provided this name. /// \param bodyPatterns The patterns visible inside the function body. /// whether the created arg/body patterns are different (selector-style). - /// \param methodName The name of the imported method. + /// \param importedName The name of the imported method. /// \param errorConvention Information about the method's error conventions. /// \param kind Controls whether we're building a type for a method that /// needs special handling. @@ -1077,9 +1150,10 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation clang::QualType resultType, ArrayRef params, bool isVariadic, bool isNoReturn, - bool isFromSystemModule, bool isCustomName, + bool isFromSystemModule, SmallVectorImpl &bodyPatterns, - DeclName &methodName, + ImportedName importedName, + DeclName &name, Optional &errorConvention, SpecialMethodKind kind); @@ -1177,6 +1251,9 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation ASD->setSetterAccessibility(Accessibility::Public); return D; } + + /// Dump the Swift-specific name lookup tables we generate. + void dumpSwiftLookupTables(); }; } diff --git a/lib/ClangImporter/SwiftLookupTable.cpp b/lib/ClangImporter/SwiftLookupTable.cpp new file mode 100644 index 0000000000000..c234ee2cf3d13 --- /dev/null +++ b/lib/ClangImporter/SwiftLookupTable.cpp @@ -0,0 +1,207 @@ +//===--- SwiftLookupTable.cpp - Swift Lookup Table ------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2015 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 implements support for Swift name lookup tables stored in Clang +// modules. +// +//===----------------------------------------------------------------------===// +#include "SwiftLookupTable.h" +#include "swift/Basic/STLExtras.h" +#include "clang/AST/DeclObjC.h" +using namespace swift; + +bool SwiftLookupTable::matchesContext(clang::DeclContext *foundContext, + clang::DeclContext *requestedContext) { + /// If the requested context was null, we match. + if (!requestedContext) + return true; + + // If the contexts match, we match. + if (foundContext == requestedContext) + return true; + + // If we found something in an Objective-C protocol to which a class + // conforms, we match. + if (auto objcProto = dyn_cast(foundContext)) { + if (auto objcClass = dyn_cast(requestedContext)) { + return objcClass->ClassImplementsProtocol(objcProto, + /*lookupCategory=*/true); + } + } + + return false; +} + +/// Determine whether the new declarations matches an existing declaration. +static bool matchesExistingDecl(clang::Decl *decl, clang::Decl *existingDecl) { + // If the canonical declarations are equivalent, we have a match. + if (decl->getCanonicalDecl() == existingDecl->getCanonicalDecl()) { + return true; + } + + return false; +} + +void SwiftLookupTable::addEntry(DeclName name, clang::NamedDecl *decl, + clang::DeclContext *effectiveContext) { + clang::DeclContext *context = effectiveContext->getPrimaryContext(); + + // First, check whether there is already a full name entry. + auto knownFull = FullNameTable.find(name); + if (knownFull == FullNameTable.end()) { + // We didn't have a full name entry, so record that in the base + // name table. + BaseNameTable[name.getBaseName()].push_back(name); + + // Insert the entry into the full name table. We're done. + FullTableEntry newEntry; + newEntry.Context = context; + newEntry.Decls.push_back(decl); + (void)FullNameTable.insert({name, { newEntry }}); + return; + } + + // Check whether there is already an entry with the same context. + auto &fullEntries = knownFull->second; + for (auto &fullEntry : fullEntries) { + if (fullEntry.Context == context) { + // Check whether this entry matches any existing entry. + for (auto existingDecl : fullEntry.Decls) { + if (matchesExistingDecl(decl, existingDecl)) return; + } + + fullEntry.Decls.push_back(decl); + return; + } + } + + // This is a new context for this name. Add it. + FullTableEntry newEntry; + newEntry.Context = context; + newEntry.Decls.push_back(decl); + fullEntries.push_back(newEntry); +} + +static void printName(clang::NamedDecl *named, llvm::raw_ostream &out) { + // If there is a name, print it. + if (!named->getDeclName().isEmpty()) { + // If we have an Objective-C method, print the class name along + // with '+'/'-'. + if (auto objcMethod = dyn_cast(named)) { + out << (objcMethod->isInstanceMethod() ? '-' : '+') << '['; + if (auto classDecl = objcMethod->getClassInterface()) { + classDecl->printName(out); + out << ' '; + } else if (auto proto = dyn_cast( + objcMethod->getDeclContext())) { + proto->printName(out); + out << ' '; + } + named->printName(out); + out << ']'; + return; + } + + // If we have an Objective-C property, print the class name along + // with the property name. + if (auto objcProperty = dyn_cast(named)) { + auto dc = objcProperty->getDeclContext(); + if (auto classDecl = dyn_cast(dc)) { + classDecl->printName(out); + out << '.'; + } else if (auto categoryDecl = dyn_cast(dc)) { + categoryDecl->getClassInterface()->printName(out); + out << '.'; + } else if (auto proto = dyn_cast(dc)) { + proto->printName(out); + out << '.'; + } + named->printName(out); + return; + } + + named->printName(out); + return; + } + + // If this is an anonymous tag declaration with a typedef name, use that. + if (auto tag = dyn_cast(named)) { + if (auto typedefName = tag->getTypedefNameForAnonDecl()) { + printName(typedefName, out); + return; + } + } +} + +void SwiftLookupTable::dump() const { + // Dump the base name -> full name mappings. + SmallVector baseNames; + for (const auto &entry : BaseNameTable) { + baseNames.push_back(entry.first); + } + std::sort(baseNames.begin(), baseNames.end(), + [&](Identifier x, Identifier y) { + return x.compare(y) < 0; + }); + llvm::errs() << "Base -> full name mappings:\n"; + for (auto baseName : baseNames) { + llvm::errs() << " " << baseName.str() << " --> "; + const auto &fullNames = BaseNameTable.find(baseName)->second; + interleave(fullNames.begin(), fullNames.end(), + [](DeclName fullName) { + llvm::errs() << fullName; + }, + [] { + llvm::errs() << ", "; + }); + llvm::errs() << "\n"; + } + llvm::errs() << "\n"; + + // Dump the full name -> full table entry mappings. + SmallVector fullNames; + for (const auto &entry : FullNameTable) { + fullNames.push_back(entry.first); + } + std::sort(fullNames.begin(), fullNames.end(), + [](DeclName x, DeclName y) { + return x.compare(y) < 0; + }); + llvm::errs() << "Full name -> entry mappings:\n"; + for (auto fullName : fullNames) { + llvm::errs() << " " << fullName << ":\n"; + const auto &fullEntries = FullNameTable.find(fullName)->second; + for (const auto &fullEntry : fullEntries) { + llvm::errs() << " "; + if (fullEntry.Context->isTranslationUnit()) { + llvm::errs() << "TU"; + } else if (auto named = dyn_cast(fullEntry.Context)) { + printName(named, llvm::errs()); + } else { + llvm::errs() << ""; + } + llvm::errs() << ": "; + + interleave(fullEntry.Decls.begin(), fullEntry.Decls.end(), + [](clang::NamedDecl *decl) { + if (auto named = dyn_cast(decl)) + printName(named, llvm::errs()); + else + decl->printName(llvm::errs()); + }, + [] { + llvm::errs() << ", "; + }); + llvm::errs() << "\n"; + } + } +} diff --git a/lib/ClangImporter/SwiftLookupTable.h b/lib/ClangImporter/SwiftLookupTable.h new file mode 100644 index 0000000000000..59ec0a120bcb4 --- /dev/null +++ b/lib/ClangImporter/SwiftLookupTable.h @@ -0,0 +1,110 @@ +//===--- SwiftLookupTable.h - Swift Lookup Table ----------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2015 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 implements support for Swift name lookup tables stored in Clang +// modules. +// +//===----------------------------------------------------------------------===// +#ifndef SWIFT_CLANGIMPORTER_SWIFTLOOKUPTABLE_H +#define SWIFT_CLANGIMPORTER_SWIFTLOOKUPTABLE_H + +#include "swift/Basic/LLVM.h" +#include "swift/AST/Identifier.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/TinyPtrVector.h" + +namespace clang { +class NamedDecl; +class DeclContext; +} + +namespace swift { + +/// A lookup table that maps Swift names to the set of Clang +/// declarations with that particular name. +/// +/// The names of C entities can undergo significant transformations +/// when they are mapped into Swift, which makes Clang's name lookup +/// mechanisms useless when searching for the Swift name of +/// entities. This lookup table provides efficient access to the C +/// entities based on their Swift names, and is used by the Clang +/// importer to satisfy the Swift compiler's queries. +class SwiftLookupTable { + /// An entry in the table of C entities indexed by full Swift name. + struct FullTableEntry { + /// The context in which the entities with the given name occur, e.g., + /// a class, struct, translation unit, etc. + /// + /// Many Clang DeclContexts can have redeclarations, so this entry + /// is always the canonical DeclContext for the entity. + clang::DeclContext *Context; + + /// The set of Clang declarations with this name and in this + /// context. + llvm::TinyPtrVector Decls; + }; + + /// A table mapping from the full name of Swift entities to all of + /// the C entities that have that name, in all contexts. + llvm::DenseMap> FullNameTable; + + /// A table mapping from the base name of a Swift name to all of the + /// full Swift names based on that identifier. + llvm::DenseMap> BaseNameTable; + + /// Determine whether the given context we found matches the + /// requested context. + bool matchesContext(clang::DeclContext *foundContext, + clang::DeclContext *requestedContext); +public: + /// Add an entry to the lookup table. + /// + /// \param name The Swift name of the entry. + /// \param decl The Clang declaration to add. + /// \param effectiveContext The effective context in which name lookup occurs. + void addEntry(DeclName name, clang::NamedDecl *decl, + clang::DeclContext *effectiveContext); + + /// Lookup the set of declarations with the given base name. + /// + /// \param baseName The base name to search for. All results will + /// have this base name. + /// + /// \param context The context in which the resulting set of + /// declarations should reside. This may be null to indicate that + /// all results from all contexts should be produced. + ArrayRef + lookup(Identifier baseName, + clang::DeclContext *context, + SmallVectorImpl &scratch); + + /// Lookup the set of declarations with the given full name. + /// + /// \param name The full name to search for. All results will have + /// this full name. + /// + /// \param context The context in which the resulting set of + /// declarations should reside. This may be null to indicate that + /// all results from all contexts should be produced. + ArrayRef + lookup(DeclName name, + clang::DeclContext *context, + SmallVectorImpl &scratch); + + /// Dump the internal representation of this lookup table. + void dump() const; +}; + +} + +#endif // SWIFT_CLANGIMPORTER_SWIFTLOOKUPTABLE_H diff --git a/test/ClangModules/Inputs/SwiftPrivateAttr.txt b/test/ClangModules/Inputs/SwiftPrivateAttr.txt index 813f9ce12afe2..06331fbd4c4c4 100644 --- a/test/ClangModules/Inputs/SwiftPrivateAttr.txt +++ b/test/ClangModules/Inputs/SwiftPrivateAttr.txt @@ -25,7 +25,7 @@ class Foo : NSObject, __PrivProto { init() } class Bar : NSObject { - init!() + init!(__: ()) init!(__noArgs: ()) init!(__oneArg arg: Int32) init!(__twoArgs arg: Int32, other arg2: Int32) diff --git a/test/IDE/Inputs/swift_name.h b/test/IDE/Inputs/swift_name.h new file mode 100644 index 0000000000000..7968e0bb2e146 --- /dev/null +++ b/test/IDE/Inputs/swift_name.h @@ -0,0 +1,41 @@ +#define SWIFT_NAME(X) __attribute__((swift_name(#X))) + +#ifndef SWIFT_ENUM_EXTRA +# define SWIFT_ENUM_EXTRA +#endif + +#ifndef SWIFT_ENUM +# define SWIFT_ENUM(_type, _name) \ + enum _name : _type _name; \ + enum SWIFT_ENUM_EXTRA _name : _type +#endif + +// Renaming global variables. +int SNFoo SWIFT_NAME(Bar); + +// Renaming tags and fields. +struct SWIFT_NAME(SomeStruct) SNSomeStruct { + double X SWIFT_NAME(x); +}; + +// Renaming C functions +struct SNSomeStruct SNMakeSomeStruct(double X, double Y) SWIFT_NAME(makeSomeStruct(x:y:)); + +struct SNSomeStruct SNMakeSomeStructForX(double X) SWIFT_NAME(makeSomeStruct(x:)); + +// Renaming typedefs. +typedef int SNIntegerType SWIFT_NAME(MyInt); + +// Renaming enumerations. +SWIFT_ENUM(unsigned char, SNColorChoice) { + SNColorRed SWIFT_NAME(Rouge), + SNColorGreen, + SNColorBlue +}; + +// swift_private attribute +void SNTransposeInPlace(struct SNSomeStruct *value) __attribute__((swift_private)); + +typedef struct { + double x, y, z; +} SNPoint SWIFT_NAME(Point); diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h new file mode 100644 index 0000000000000..deea6f29a68fc --- /dev/null +++ b/test/IDE/Inputs/swift_name_objc.h @@ -0,0 +1,68 @@ +@import ObjectiveC; + +#define SWIFT_NAME(X) __attribute__((swift_name(#X))) + +#ifndef SWIFT_ENUM_EXTRA +# define SWIFT_ENUM_EXTRA +#endif + +#ifndef SWIFT_ENUM +# define SWIFT_ENUM(_type, _name) \ + enum _name : _type _name; \ + enum SWIFT_ENUM_EXTRA _name : _type +#endif + +// Renaming classes +SWIFT_NAME(SomeClass) +@interface SNSomeClass : NSObject +- (instancetype)initWithFloat:(float)f; +- (instancetype)initWithDefault; +- (void)instanceMethodWithX:(float)x Y:(float)y Z:(float)z; ++ (instancetype)someClassWithDouble:(double)d; ++ (instancetype)someClassWithTry:(BOOL)shouldTry; ++ (NSObject *)buildWithObject:(NSObject *)object SWIFT_NAME(init(object:)); ++ (instancetype)buildWithUnsignedChar:(unsigned char)uint8 SWIFT_NAME(init(uint8:)); +@property (readonly,nonatomic) float floatProperty; +@property (readwrite,nonatomic) double doubleProperty; +@end + +SWIFT_NAME(SomeProtocol) +@protocol SNSomeProtocol +- (void)protoInstanceMethodWithX:(float)x y:(float)y; +@end + +@interface SNSomeClass () +- (void)extensionMethodWithX:(float)x y:(float)y; +@end + +@interface SNSomeClass (Category1) +- (void)categoryMethodWithX:(float)x y:(float)y; +- (void)categoryMethodWithX:(float)x y:(float)y z:(float)z; +@end + +@interface SNCollision +@end + +@protocol SNCollision +@end + +@protocol NSAccessibility +@property (nonatomic) float accessibilityFloat; +@end + +@interface UIActionSheet : NSObject +-(instancetype)initWithTitle:(const char *)title delegate:(id)delegate cancelButtonTitle:(const char *)cancelButtonTitle destructiveButtonTitle:(const char *)destructiveButtonTitle otherButtonTitles:(const char *)otherButtonTitles, ...; +@end + +@interface NSError : NSObject +@end + +@interface NSErrorImports : NSObject +- (nullable NSObject *)methodAndReturnError:(NSError **)error; +- (BOOL)methodWithFloat:(float)value error:(NSError **)error; +- (nullable instancetype)initAndReturnError:(NSError **)error; +- (nullable instancetype)initWithFloat:(float)value error:(NSError **)error; +@end + +typedef const void *CFTypeRef __attribute__((objc_bridge(id))); +typedef const struct __attribute__((objc_bridge(id))) __CCItem *CCItemRef; diff --git a/test/IDE/dump_swift_lookup_tables.swift b/test/IDE/dump_swift_lookup_tables.swift new file mode 100644 index 0000000000000..14144b5d1e961 --- /dev/null +++ b/test/IDE/dump_swift_lookup_tables.swift @@ -0,0 +1,48 @@ +// RUN: %target-swift-ide-test -dump-importer-lookup-table -source-filename %s -import-objc-header %S/Inputs/swift_name.h > %t.log 2>&1 +// RUN: FileCheck %s < %t.log + +// CHECK: Base -> full name mappings: +// CHECK-NEXT: Bar --> Bar +// CHECK-NEXT: Blue --> Blue +// CHECK-NEXT: Green --> Green +// CHECK-NEXT: MyInt --> MyInt +// CHECK-NEXT: Point --> Point +// CHECK-NEXT: Rouge --> Rouge +// CHECK-NEXT: SNColorChoice --> SNColorChoice +// CHECK-NEXT: SomeStruct --> SomeStruct +// CHECK-NEXT: __SNTransposeInPlace --> __SNTransposeInPlace +// CHECK-NEXT: makeSomeStruct --> makeSomeStruct(x:y:), makeSomeStruct(x:) +// CHECK-NEXT: x --> x +// CHECK-NEXT: y --> y +// CHECK-NEXT: z --> z + +// CHECK: Full name -> entry mappings: +// CHECK-NEXT: Bar: +// CHECK-NEXT: TU: SNFoo +// CHECK-NEXT: Blue: +// CHECK-NEXT: SNColorChoice: SNColorBlue +// CHECK-NEXT: Green: +// CHECK-NEXT: SNColorChoice: SNColorGreen +// CHECK-NEXT: MyInt: +// CHECK-NEXT: TU: SNIntegerType +// CHECK-NEXT: Point: +// CHECK-NEXT: TU: SNPoint +// CHECK-NEXT: Rouge: +// CHECK-NEXT: SNColorChoice: SNColorRed +// CHECK-NEXT: SNColorChoice: +// CHECK-NEXT: TU: SNColorChoice, SNColorChoice +// CHECK-NEXT: SomeStruct: +// CHECK-NEXT: TU: SNSomeStruct +// CHECK-NEXT: __SNTransposeInPlace: +// CHECK-NEXT: TU: SNTransposeInPlace +// CHECK-NEXT: makeSomeStruct(x:): +// CHECK-NEXT: TU: SNMakeSomeStructForX +// CHECK-NEXT: makeSomeStruct(x:y:): +// CHECK-NEXT: TU: SNMakeSomeStruct +// CHECK-NEXT: x: +// CHECK-NEXT: SNSomeStruct: X +// CHECK-NEXT: SNPoint: x +// CHECK-NEXT: y: +// CHECK-NEXT: SNPoint: y +// CHECK-NEXT: z: +// CHECK-NEXT: SNPoint: z diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift new file mode 100644 index 0000000000000..cb1160bce5214 --- /dev/null +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -0,0 +1,103 @@ +// RUN: %target-swift-ide-test -dump-importer-lookup-table -source-filename %s -import-objc-header %S/Inputs/swift_name_objc.h > %t.log 2>&1 +// RUN: FileCheck %s < %t.log + +// RUN: %target-swift-ide-test -dump-importer-lookup-table -source-filename %s -import-objc-header %S/Inputs/swift_name_objc.h -enable-omit-needless-words > %t-omit-needless-words.log 2>&1 +// RUN: FileCheck -check-prefix=CHECK-OMIT-NEEDLESS-WORDS %s < %t-omit-needless-words.log + +// REQUIRES: objc_interop + +// CHECK: Base -> full name mappings: +// CHECK-NEXT: CCItem --> CCItem +// CHECK-NEXT: CCItemRef --> CCItemRef +// CHECK-NEXT: CFTypeRef --> CFTypeRef +// CHECK-NEXT: NSAccessibility --> NSAccessibility +// CHECK-NEXT: NSError --> NSError +// CHECK-NEXT: NSErrorImports --> NSErrorImports +// CHECK-NEXT: SNCollision --> SNCollision +// CHECK-NEXT: SNCollisionProtocol --> SNCollisionProtocol +// CHECK-NEXT: SomeClass --> SomeClass +// CHECK-NEXT: SomeProtocol --> SomeProtocol +// CHECK-NEXT: UIActionSheet --> UIActionSheet +// CHECK-NEXT: __CCItem --> __CCItem +// CHECK-NEXT: accessibilityFloat --> accessibilityFloat() +// CHECK-NEXT: categoryMethodWithX --> categoryMethodWithX(_:y:), categoryMethodWithX(_:y:z:) +// CHECK-NEXT: doubleProperty --> doubleProperty{{$}} +// CHECK-NEXT: extensionMethodWithX --> extensionMethodWithX(_:y:) +// CHECK-NEXT: floatProperty --> floatProperty{{$}} +// CHECK-NEXT: init --> init(float:), init(withDefault:), init(double:), init(withTry:), init(uint8:), init(title:delegate:cancelButtonTitle:destructiveButtonTitle:) +// CHECK-NEXT: instanceMethodWithX --> instanceMethodWithX(_:y:z:) +// CHECK-NEXT: method --> method() +// CHECK-NEXT: methodWithFloat --> methodWithFloat(_:) +// CHECK-NEXT: protoInstanceMethodWithX --> protoInstanceMethodWithX(_:y:) +// CHECK-NEXT: setAccessibilityFloat --> setAccessibilityFloat(_:) + +// CHECK: Full name -> entry mappings: +// CHECK-NEXT: CCItem: +// CHECK-NEXT: TU: CCItemRef +// CHECK-NEXT: CCItemRef: +// CHECK-NEXT: TU: CCItemRef +// CHECK-NEXT: CFTypeRef: +// CHECK-NEXT: TU: CFTypeRef +// CHECK-NEXT: NSAccessibility: +// CHECK-NEXT: TU: NSAccessibility{{$}} +// CHECK-NEXT: NSError: +// CHECK-NEXT: TU: NSError +// CHECK-NEXT: NSErrorImports: +// CHECK-NEXT: TU: NSErrorImports +// CHECK-NEXT: SNCollision: +// CHECK-NEXT: TU: SNCollision{{$}} +// CHECK-NEXT: SNCollisionProtocol: +// CHECK-NEXT: TU: SNCollision{{$}} +// CHECK-NEXT: SomeClass: +// CHECK-NEXT: TU: SNSomeClass +// CHECK-NEXT: SomeProtocol: +// CHECK-NEXT: TU: SNSomeProtocol +// CHECK-NEXT: UIActionSheet: +// CHECK-NEXT: TU: UIActionSheet +// CHECK-NEXT: __CCItem: +// CHECK-NEXT: TU: __CCItem +// CHECK-NEXT: accessibilityFloat(): +// CHECK-NEXT: NSAccessibility: -[NSAccessibility accessibilityFloat] +// CHECK-NEXT: categoryMethodWithX(_:y:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass categoryMethodWithX:y:] +// CHECK-NEXT: categoryMethodWithX(_:y:z:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass categoryMethodWithX:y:z:] +// CHECK-NEXT: doubleProperty: +// CHECK-NEXT: SNSomeClass: SNSomeClass.doubleProperty +// CHECK-NEXT: extensionMethodWithX(_:y:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass extensionMethodWithX:y:] +// CHECK-NEXT: floatProperty: +// CHECK-NEXT: SNSomeClass: SNSomeClass.floatProperty +// CHECK-NEXT: init(andReturnError:): +// CHECK-NEXT: NSErrorImports: -[NSErrorImports initAndReturnError:] +// CHECK-NEXT: init(double:): +// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:] +// CHECK-NEXT: init(float:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithFloat:] +// CHECK-NEXT: NSErrorImports: -[NSErrorImports initWithFloat:error:] +// CHECK-NEXT: init(title:delegate:cancelButtonTitle:destructiveButtonTitle:): +// CHECK-NEXT: UIActionSheet: -[UIActionSheet initWithTitle:delegate:cancelButtonTitle:destructiveButtonTitle:otherButtonTitles:] +// CHECK-NEXT: init(uint8:): +// CHECK-NEXT: SNSomeClass: +[SNSomeClass buildWithUnsignedChar:] +// CHECK-NEXT: init(withDefault:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithDefault] +// CHECK-NEXT: init(withTry:): +// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithTry:] +// CHECK-NEXT: instanceMethodWithX(_:y:z:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass instanceMethodWithX:Y:Z:] +// CHECK-NEXT: method(): +// CHECK-NEXT: NSErrorImports: -[NSErrorImports methodAndReturnError:] +// CHECK-NEXT: methodWithFloat(_:): +// CHECK-NEXT: NSErrorImports: -[NSErrorImports methodWithFloat:error:] +// CHECK-NEXT: protoInstanceMethodWithX(_:y:): +// CHECK-NEXT: SNSomeProtocol: -[SNSomeProtocol protoInstanceMethodWithX:y:] +// CHECK-NEXT: setAccessibilityFloat(_:): +// CHECK-NEXT: NSAccessibility: -[NSAccessibility setAccessibilityFloat:] + +// CHECK-OMIT-NEEDLESS-WORDS: Base -> full name mappings: +// CHECK-OMIT-NEEDLESS-WORDS: instanceMethodWithX --> instanceMethodWithX(_:y:z:) +// CHECK-OMIT-NEEDLESS-WORDS: methodWith --> methodWith(_:) + +// CHECK-OMIT-NEEDLESS-WORDS: Full name -> entry mappings: +// CHECK-OMIT-NEEDLESS-WORDS: methodWith(_:): +// CHECK-OMIT-NEEDLESS-WORDS: NSErrorImports: -[NSErrorImports methodWithFloat:error:] diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index 8dd6667b66f3f..bdb89fe0bd0aa 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -69,6 +69,7 @@ enum class ActionType { CodeCompletion, REPLCodeCompletion, DumpCompletionCache, + DumpImporterLookupTable, SyntaxColoring, DumpAPI, DumpComments, @@ -144,6 +145,8 @@ Action(llvm::cl::desc("Mode:"), llvm::cl::init(ActionType::None), "repl-code-completion", "Perform REPL-style code completion"), clEnumValN(ActionType::DumpCompletionCache, "dump-completion-cache", "Dump a code completion cache file"), + clEnumValN(ActionType::DumpImporterLookupTable, + "dump-importer-lookup-table", "Dump the Clang importer's lookup tables"), clEnumValN(ActionType::SyntaxColoring, "syntax-coloring", "Perform syntax coloring"), clEnumValN(ActionType::DumpAPI, @@ -279,6 +282,12 @@ InferDefaultArguments( llvm::cl::desc("Infer default arguments for imported parameters"), llvm::cl::init(false)); +static llvm::cl::opt +UseSwiftLookupTables( + "enable-swift-name-lookup-tables", + llvm::cl::desc("Use Swift-specific name lookup tables in the importer"), + llvm::cl::init(false)); + static llvm::cl::opt OmitNeedlessWords("enable-omit-needless-words", llvm::cl::desc("Omit needless words when importing Objective-C names"), @@ -942,6 +951,37 @@ static int doDumpAPI(const CompilerInvocation &InitInvok, return 0; } +static int doDumpImporterLookupTables(const CompilerInvocation &InitInvok, + StringRef SourceFilename) { + if (options::ImportObjCHeader.empty()) { + llvm::errs() << "implicit header required\n"; + llvm::cl::PrintHelpMessage(); + return 1; + } + + CompilerInvocation Invocation(InitInvok); + Invocation.addInputFilename(SourceFilename); + + // We must use the Swift lookup tables. + Invocation.getClangImporterOptions().UseSwiftLookupTables = true; + + CompilerInstance CI; + + // Display diagnostics to stderr. + PrintingDiagnosticConsumer PrintDiags; + CI.addDiagnosticConsumer(&PrintDiags); + if (CI.setup(Invocation)) + return 1; + CI.performSema(); + + auto &Context = CI.getASTContext(); + auto &Importer = static_cast( + *Context.getClangModuleLoader()); + Importer.dumpSwiftLookupTables(); + + return 0; +} + //============================================================================// // Structure Annotation //============================================================================// @@ -2515,6 +2555,8 @@ int main(int argc, char *argv[]) { options::OmitNeedlessWords; InitInvok.getClangImporterOptions().InferDefaultArguments |= options::InferDefaultArguments; + InitInvok.getClangImporterOptions().UseSwiftLookupTables |= + options::UseSwiftLookupTables; if (!options::ResourceDir.empty()) { InitInvok.setRuntimeResourcePath(options::ResourceDir); @@ -2618,6 +2660,10 @@ int main(int argc, char *argv[]) { ExitCode = doDumpAPI(InitInvok, options::SourceFilename); break; + case ActionType::DumpImporterLookupTable: + ExitCode = doDumpImporterLookupTables(InitInvok, options::SourceFilename); + break; + case ActionType::Structure: ExitCode = doStructureAnnotation(InitInvok, options::SourceFilename); break;