From c2bf16c0a91ca45d566efef7154b38bfef255ab7 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 19 Nov 2015 15:02:14 -0800 Subject: [PATCH 01/23] Clang importer: start building a Swift name -> Clang declarations table. When we parse a bridging header, start building a mapping from Swift names (both base names and full names) to the Clang declarations that have those names in particular Clang contexts. For now, just provide the ability to build the table (barely) and dump it out; we'll grow it's contents in time. --- include/swift/ClangImporter/ClangImporter.h | 3 + .../ClangImporter/ClangImporterOptions.h | 4 + lib/ClangImporter/CMakeLists.txt | 1 + lib/ClangImporter/ClangImporter.cpp | 24 ++- lib/ClangImporter/ImporterImpl.h | 8 + lib/ClangImporter/SwiftLookupTable.cpp | 139 ++++++++++++++++++ lib/ClangImporter/SwiftLookupTable.h | 108 ++++++++++++++ test/IDE/Inputs/swift_name.h | 7 + test/IDE/dump_swift_lookup_tables.swift | 12 ++ tools/swift-ide-test/swift-ide-test.cpp | 47 ++++++ 10 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 lib/ClangImporter/SwiftLookupTable.cpp create mode 100644 lib/ClangImporter/SwiftLookupTable.h create mode 100644 test/IDE/Inputs/swift_name.h create mode 100644 test/IDE/dump_swift_lookup_tables.swift 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/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..cff36fbe139a4 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -736,9 +736,18 @@ 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)) { + Identifier name = importName(named); + if (!name.empty()) + BridgingHeaderLookupTable.addEntry(name, named); + } + } } } } @@ -1021,7 +1030,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 @@ -3306,3 +3316,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/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 59c800f6a8f23..d04c25ad92849 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" @@ -251,6 +252,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 +291,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; @@ -1177,6 +1182,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..e5ff1a32fc38c --- /dev/null +++ b/lib/ClangImporter/SwiftLookupTable.cpp @@ -0,0 +1,139 @@ +//===--- 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; +} + +void SwiftLookupTable::addEntry(DeclName name, clang::NamedDecl *decl) { + clang::DeclContext *context + = decl->getDeclContext()->getRedeclContext()->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) { + 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); +} + +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)) { + named->printName(llvm::errs()); + llvm::errs(); + } else { + llvm::errs() << ""; + } + llvm::errs() << ": "; + + interleave(fullEntry.Decls.begin(), fullEntry.Decls.end(), + [](clang::NamedDecl *decl) { + 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..a9c110086caaf --- /dev/null +++ b/lib/ClangImporter/SwiftLookupTable.h @@ -0,0 +1,108 @@ +//===--- 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. + void addEntry(DeclName name, clang::NamedDecl *decl); + + /// 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/IDE/Inputs/swift_name.h b/test/IDE/Inputs/swift_name.h new file mode 100644 index 0000000000000..aecbc331e624a --- /dev/null +++ b/test/IDE/Inputs/swift_name.h @@ -0,0 +1,7 @@ +#define SWIFT_NAME(X) __attribute__((swift_name(#X))) + +int SNFoo SWIFT_NAME(Bar); + +struct SWIFT_NAME(SomeStruct) SNSomeStruct { + double X SWIFT_NAME(x); +}; diff --git a/test/IDE/dump_swift_lookup_tables.swift b/test/IDE/dump_swift_lookup_tables.swift new file mode 100644 index 0000000000000..3d34cbe1fa133 --- /dev/null +++ b/test/IDE/dump_swift_lookup_tables.swift @@ -0,0 +1,12 @@ +// 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: SomeStruct --> SomeStruct + +// CHECK: Full name -> entry mappings: +// CHECK-NEXT: Bar: +// CHECK-NEXT: TU: SNFoo +// CHECK-NEXT: SomeStruct: +// CHECK-NEXT: TU: SNSomeStruct diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index 8dd6667b66f3f..d403a3ca956b5 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,38 @@ static int doDumpAPI(const CompilerInvocation &InitInvok, return 0; } +static int doDumpImporterLookupTables(const CompilerInvocation &InitInvok, + StringRef SourceFilename) { + auto &FEOpts = InitInvok.getFrontendOptions(); + 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 +2556,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 +2661,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; From a861a7345e71553d79811f800366e45f32afa23c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 20 Nov 2015 09:35:11 -0800 Subject: [PATCH 02/23] Clang importer: introduce "importFullName" to centralize name-mapping logic. The Swift lookup tables are the primary client and test vehicle right now. This change adds the capability to use the swift_name attribute to rename C functions when they are imported into Swift, as well as handling the swift_private attribute more uniformly. There are a few obvious places where I've applied this API to eliminate redundancy. Expect it to broaden as the API fills out more. --- lib/ClangImporter/ClangImporter.cpp | 204 +++++++++++++++++++++++- lib/ClangImporter/ImportDecl.cpp | 57 +------ lib/ClangImporter/ImporterImpl.h | 7 + test/IDE/Inputs/swift_name.h | 10 ++ test/IDE/dump_swift_lookup_tables.swift | 8 + 5 files changed, 231 insertions(+), 55 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index cff36fbe139a4..1da4b9e6858e3 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -743,8 +743,8 @@ bool ClangImporter::Implementation::importHeader( if (UseSwiftLookupTables) { if (auto named = dyn_cast(D)) { - Identifier name = importName(named); - if (!name.empty()) + bool hasCustomName; + if (DeclName name = importFullName(named, hasCustomName)) BridgingHeaderLookupTable.addEntry(name, named); } } @@ -1288,6 +1288,206 @@ ClangImporter::Implementation::exportName(Identifier name) { return ident; } +/// Parse a stringified Swift DeclName, e.g. "init(frame:)". +static DeclName parseDeclName(ASTContext &ctx, StringRef Name) { + if (Name.back() != ')') { + if (Lexer::isIdentifier(Name) && Name != "_") + return ctx.getIdentifier(Name); + + 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 = ctx.getIdentifier(BaseName); + if (Parameters.empty()) + return DeclName(ctx, 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 = ctx.getIdentifier(NextParam); + ParamIDs.push_back(NextParamID); + } while (!Parameters.empty()); + + return DeclName(ctx, BaseID, ParamIDs); +} + +DeclName ClangImporter::Implementation::importFullName( + const clang::NamedDecl *D, + bool &hasCustomName) { + // If we have a swift_name attribute, use that. + if (auto *nameAttr = D->getAttr()) { + hasCustomName = true; + return parseDeclName(SwiftContext, nameAttr->getName()); + } + + // We don't have a customized name. + hasCustomName = false; + + // For empty names, there is nothing to do. + if (D->getDeclName().isEmpty()) return { }; + + /// Whether the result is a function name. + bool isFunction = false; + StringRef baseName; + SmallVector argumentNames; + 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 { }; + + 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: { + // Map the Objective-C selector directly. + auto selector = D->getDeclName().getObjCSelector(); + baseName = selector.getNameForSlot(0); + for (unsigned index = 0, numArgs = selector.getNumArgs(); index != numArgs; + ++index) { + if (index == 0) + argumentNames.push_back(""); + else + argumentNames.push_back(selector.getNameForSlot(index)); + } + + isFunction = true; + break; + } + } + + // 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; + + // Enum constants that are not imported as members should be considered + // private if the parent enum is marked private. + 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()) + return true; + break; + + case EnumKind::Enum: + case EnumKind::Options: + break; + } + } + + return false; + }; + + // 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()); + // Find the property names. + const InheritedNameSet *allPropertyNames = nullptr; + if (!contextType.isNull()) { + if (auto objcPtrType = contextType->getAsObjCInterfacePointerType()) + if (auto objcClassDecl = objcPtrType->getInterfaceDecl()) + allPropertyNames = SwiftContext.getAllPropertyNames( + objcClassDecl, + /*forInstance=*/true); + } + + (void)omitNeedlessWords(baseName, { }, "", propertyTypeName, + contextTypeName, { }, /*returnsSelf=*/false, + /*isProperty=*/true, allPropertyNames, + omitNeedlessWordsScratch); + } + } + } + + // If this declaration has the swift_private attribute, prepend "__" to the + // appropriate place. + SmallString<16> swiftPrivateScratch; + if (hasSwiftPrivate(D)) { + swiftPrivateScratch = "__"; + + if (baseName == "init") { + // For initializers, prepend "__" to the first argument name. + if (argumentNames.empty()) { + // swift_private cannot actually do anything here. Just drop the + // declaration. + // FIXME: Diagnose this? + return { }; + } + + swiftPrivateScratch += argumentNames[0]; + argumentNames[0] = swiftPrivateScratch; + } else { + // For all other entities, prepend "__" to the base name. + swiftPrivateScratch += baseName; + baseName = swiftPrivateScratch; + } + } + + // We cannot import when the base name is not an identifier. + if (!Lexer::isIdentifier(baseName)) + return { }; + + // Get the identifier for the base name. + Identifier baseNameId = SwiftContext.getIdentifier(baseName); + + // If we have a non-function name, just return the base name. + if (!isFunction) return baseNameId; + + // 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); +} + Identifier ClangImporter::Implementation::importDeclName(clang::DeclarationName name, StringRef removePrefix) { diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 921b286648ad4..ae34880056854 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2715,18 +2715,8 @@ 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; - } - + DeclName name = Impl.importFullName(decl, hasCustomName); if (!name) return nullptr; @@ -2998,43 +2988,6 @@ 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, @@ -3056,9 +3009,8 @@ namespace { // 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; + if (decl->hasAttr()) { + initName = Impl.importFullName(decl, hasCustomName); break; } // FIXME: We probably should stop using this codepath. It won't ever @@ -3170,8 +3122,7 @@ namespace { bool hasCustomName; if (auto *customNameAttr = decl->getAttr()) { if (!customNameAttr->getName().startswith("init(")) { - name = parseDeclName(customNameAttr->getName()); - hasCustomName = true; + name = Impl.importFullName(decl, hasCustomName); } } if (!name) { diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index d04c25ad92849..44b2a6beb9dbf 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -718,6 +718,13 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// \brief Converts the given Swift identifier for Clang. clang::DeclarationName exportName(Identifier name); + /// Imports the full name of the given Clang declaration into Swift. + /// + /// Note that this may result in a name very different from the Clang name, + /// so it should not be used when referencing Clang symbols. + DeclName importFullName(const clang::NamedDecl *D, + bool &hasCustomName); + /// Imports the name of the given Clang decl into Swift. /// /// Note that this may result in a name different from the Clang name, so it diff --git a/test/IDE/Inputs/swift_name.h b/test/IDE/Inputs/swift_name.h index aecbc331e624a..509bd1953f082 100644 --- a/test/IDE/Inputs/swift_name.h +++ b/test/IDE/Inputs/swift_name.h @@ -1,7 +1,17 @@ #define SWIFT_NAME(X) __attribute__((swift_name(#X))) +// 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:)); + +// swift_private attribute +void SNTransposeInPlace(struct SNSomeStruct *value) __attribute__((swift_private)); diff --git a/test/IDE/dump_swift_lookup_tables.swift b/test/IDE/dump_swift_lookup_tables.swift index 3d34cbe1fa133..4f67f47bf89e6 100644 --- a/test/IDE/dump_swift_lookup_tables.swift +++ b/test/IDE/dump_swift_lookup_tables.swift @@ -4,9 +4,17 @@ // CHECK: Base -> full name mappings: // CHECK-NEXT: Bar --> Bar // CHECK-NEXT: SomeStruct --> SomeStruct +// CHECK-NEXT: __SNTransposeInPlace --> __SNTransposeInPlace +// CHECK-NEXT: makeSomeStruct --> makeSomeStruct(x:y:), makeSomeStruct(x:) // CHECK: Full name -> entry mappings: // CHECK-NEXT: Bar: // CHECK-NEXT: TU: SNFoo // 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 From 3caf703d4783a018841b3151841af47a418352cf Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 20 Nov 2015 09:56:47 -0800 Subject: [PATCH 03/23] Narrow the Clang importer's importDeclName to just importIdentifier. NFC We never used it for non-identifier names, and the former is somewhat ambiguous with the new importFullName. --- lib/ClangImporter/ClangImporter.cpp | 22 +++++++++++----------- lib/ClangImporter/ImportDecl.cpp | 6 +++--- lib/ClangImporter/ImporterImpl.h | 8 ++++---- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 1da4b9e6858e3..989575fe9a18b 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1489,22 +1489,22 @@ DeclName ClangImporter::Implementation::importFullName( } 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(); +ClangImporter::Implementation::importIdentifier( + const clang::IdentifierInfo *identifier, + StringRef removePrefix) +{ + if (!identifier) return Identifier(); - StringRef nameStr = name.getAsIdentifierInfo()->getName(); + StringRef name = identifier->getName(); // Remove the prefix, if any. if (!removePrefix.empty()) { - if (nameStr.startswith(removePrefix)) { - nameStr = nameStr.slice(removePrefix.size(), nameStr.size()); + if (name.startswith(removePrefix)) { + name = name.slice(removePrefix.size(), name.size()); } } // Get the Swift identifier. - return SwiftContext.getIdentifier(nameStr); + return SwiftContext.getIdentifier(name); } Identifier @@ -1518,7 +1518,7 @@ ClangImporter::Implementation::importName(const clang::NamedDecl *D, return Identifier(); } - Identifier result = importDeclName(D->getDeclName(), removePrefix); + Identifier result = importIdentifier(D->getIdentifier(), removePrefix); if (result.empty()) return result; @@ -2820,7 +2820,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( diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index ae34880056854..8d47c2bf72204 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -5108,7 +5108,7 @@ namespace { name, None); result->computeType(); - addObjCAttribute(result, Impl.importDeclName(decl->getDeclName())); + addObjCAttribute(result, Impl.importIdentifier(decl->getIdentifier())); if (declaredNative) markMissingSwiftDecl(result); @@ -5193,7 +5193,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; }; @@ -5262,7 +5262,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); diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 44b2a6beb9dbf..40a99822281b3 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -734,15 +734,15 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// \sa importName(clang::DeclarationName, StringRef) Identifier importName(const clang::NamedDecl *D, StringRef removePrefix = ""); - /// \brief Import the given Clang name into Swift. + /// \brief Import the given Clang identifier into Swift. /// - /// \param name The Clang name to map 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); From f798d53f9258222859cc8209cc569469a06d25f2 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 20 Nov 2015 10:20:48 -0800 Subject: [PATCH 04/23] Clang importer: convert more importName callers over to importFullName. NFC The sole remaining caller to importName is for enumerators, which may have prefixes that need stripping. That refactor will come in a subsequent commit. --- lib/ClangImporter/ClangImporter.cpp | 3 +-- lib/ClangImporter/ImportDecl.cpp | 30 ++++++++++++++----------- lib/ClangImporter/ImportType.cpp | 4 ++-- lib/ClangImporter/ImporterImpl.h | 9 ++++++++ test/IDE/Inputs/swift_name.h | 7 ++++++ test/IDE/dump_swift_lookup_tables.swift | 6 +++++ 6 files changed, 42 insertions(+), 17 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 989575fe9a18b..8c9fda2b6ffb1 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -743,8 +743,7 @@ bool ClangImporter::Implementation::importHeader( if (UseSwiftLookupTables) { if (auto named = dyn_cast(D)) { - bool hasCustomName; - if (DeclName name = importFullName(named, hasCustomName)) + if (DeclName name = importFullName(named)) BridgingHeaderLookupTable.addEntry(name, named); } } diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 8d47c2bf72204..1f649dbcb8393 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -1064,12 +1064,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).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).getBaseName(); + } if (!decl->isRecord()) return name; @@ -1518,7 +1522,7 @@ namespace { } Decl *VisitTypedefNameDecl(const clang::TypedefNameDecl *Decl) { - auto Name = Impl.importName(Decl); + auto Name = Impl.importFullName(Decl).getBaseName(); if (Name.empty()) return nullptr; @@ -2684,7 +2688,7 @@ namespace { return nullptr; } } - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).getBaseName(); if (name.empty()) return nullptr; @@ -2790,7 +2794,7 @@ namespace { Decl *VisitFieldDecl(const clang::FieldDecl *decl) { // Fields are imported as variables. - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).getBaseName(); if (name.empty()) return nullptr; @@ -2836,7 +2840,7 @@ namespace { return nullptr; // Variables are imported as... variables. - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).getBaseName(); if (name.empty()) return nullptr; @@ -5019,7 +5023,7 @@ namespace { } Decl *VisitObjCProtocolDecl(const clang::ObjCProtocolDecl *decl) { - Identifier name = Impl.importName(decl); + Identifier name = Impl.importFullName(decl).getBaseName(); if (name.empty()) return nullptr; @@ -5173,7 +5177,7 @@ namespace { } Decl *VisitObjCInterfaceDecl(const clang::ObjCInterfaceDecl *decl) { - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).getBaseName(); if (name.empty()) return nullptr; @@ -5389,7 +5393,7 @@ namespace { Decl *VisitObjCPropertyDecl(const clang::ObjCPropertyDecl *decl, DeclContext *dc) { - auto name = Impl.importName(decl); + auto name = Impl.importFullName(decl).getBaseName(); if (name.empty()) return nullptr; diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 1c3a8e3f527cc..3f3c7403e30de 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -1425,7 +1425,7 @@ Type ClangImporter::Implementation::importFunctionType( } // Figure out the name for this parameter. - Identifier bodyName = importName(param); + Identifier bodyName = importFullName(param).getBaseName(); // Retrieve the argument name. Identifier name; @@ -2427,7 +2427,7 @@ Type ClangImporter::Implementation::importMethodType( } // Figure out the name for this parameter. - Identifier bodyName = importName(param); + Identifier bodyName = importFullName(param).getBaseName(); // Figure out the name for this argument, which comes from the method name. Identifier name; diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 40a99822281b3..01ec7e02ef967 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -718,6 +718,15 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// \brief Converts the given Swift identifier for Clang. clang::DeclarationName exportName(Identifier name); + /// Imports the full name of the given Clang declaration into Swift. + /// + /// Note that this may result in a name very different from the Clang name, + /// so it should not be used when referencing Clang symbols. + DeclName importFullName(const clang::NamedDecl *D) { + bool hasCustomName; + return importFullName(D, hasCustomName); + } + /// Imports the full name of the given Clang declaration into Swift. /// /// Note that this may result in a name very different from the Clang name, diff --git a/test/IDE/Inputs/swift_name.h b/test/IDE/Inputs/swift_name.h index 509bd1953f082..6e99535972e2c 100644 --- a/test/IDE/Inputs/swift_name.h +++ b/test/IDE/Inputs/swift_name.h @@ -13,5 +13,12 @@ struct SNSomeStruct SNMakeSomeStruct(double X, double Y) SWIFT_NAME(makeSomeStru struct SNSomeStruct SNMakeSomeStructForX(double X) SWIFT_NAME(makeSomeStruct(x:)); +// Renaming typedefs. +typedef int SNIntegerType SWIFT_NAME(MyInt); + // 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/dump_swift_lookup_tables.swift b/test/IDE/dump_swift_lookup_tables.swift index 4f67f47bf89e6..bffff1d0b4d1f 100644 --- a/test/IDE/dump_swift_lookup_tables.swift +++ b/test/IDE/dump_swift_lookup_tables.swift @@ -3,6 +3,8 @@ // CHECK: Base -> full name mappings: // CHECK-NEXT: Bar --> Bar +// CHECK-NEXT: MyInt --> MyInt +// CHECK-NEXT: Point --> Point // CHECK-NEXT: SomeStruct --> SomeStruct // CHECK-NEXT: __SNTransposeInPlace --> __SNTransposeInPlace // CHECK-NEXT: makeSomeStruct --> makeSomeStruct(x:y:), makeSomeStruct(x:) @@ -10,6 +12,10 @@ // CHECK: Full name -> entry mappings: // CHECK-NEXT: Bar: // CHECK-NEXT: TU: SNFoo +// CHECK-NEXT: MyInt: +// CHECK-NEXT: TU: SNIntegerType +// CHECK-NEXT: Point: +// CHECK-NEXT: TU: SNPoint // CHECK-NEXT: SomeStruct: // CHECK-NEXT: TU: SNSomeStruct // CHECK-NEXT: __SNTransposeInPlace: From 2dc262f5b0442aec6f61d104dbcf5a9775bc2ccb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 20 Nov 2015 10:53:18 -0800 Subject: [PATCH 05/23] Clang importer: Don't add duplicate declarations to the Swift lookup tables. --- lib/ClangImporter/SwiftLookupTable.cpp | 15 +++++++++++++++ test/IDE/Inputs/swift_name.h | 17 +++++++++++++++++ test/IDE/dump_swift_lookup_tables.swift | 3 +++ 3 files changed, 35 insertions(+) diff --git a/lib/ClangImporter/SwiftLookupTable.cpp b/lib/ClangImporter/SwiftLookupTable.cpp index e5ff1a32fc38c..ede77efe0e546 100644 --- a/lib/ClangImporter/SwiftLookupTable.cpp +++ b/lib/ClangImporter/SwiftLookupTable.cpp @@ -41,6 +41,16 @@ bool SwiftLookupTable::matchesContext(clang::DeclContext *foundContext, 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 *context = decl->getDeclContext()->getRedeclContext()->getPrimaryContext(); @@ -64,6 +74,11 @@ void SwiftLookupTable::addEntry(DeclName name, clang::NamedDecl *decl) { 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; } diff --git a/test/IDE/Inputs/swift_name.h b/test/IDE/Inputs/swift_name.h index 6e99535972e2c..925bbbe021b79 100644 --- a/test/IDE/Inputs/swift_name.h +++ b/test/IDE/Inputs/swift_name.h @@ -1,5 +1,15 @@ #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); @@ -16,6 +26,13 @@ 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, + SNColorGreen, + SNColorBlue +}; + // swift_private attribute void SNTransposeInPlace(struct SNSomeStruct *value) __attribute__((swift_private)); diff --git a/test/IDE/dump_swift_lookup_tables.swift b/test/IDE/dump_swift_lookup_tables.swift index bffff1d0b4d1f..b1c3dd1e7c1ca 100644 --- a/test/IDE/dump_swift_lookup_tables.swift +++ b/test/IDE/dump_swift_lookup_tables.swift @@ -5,6 +5,7 @@ // CHECK-NEXT: Bar --> Bar // CHECK-NEXT: MyInt --> MyInt // CHECK-NEXT: Point --> Point +// CHECK-NEXT: SNColorChoice --> SNColorChoice // CHECK-NEXT: SomeStruct --> SomeStruct // CHECK-NEXT: __SNTransposeInPlace --> __SNTransposeInPlace // CHECK-NEXT: makeSomeStruct --> makeSomeStruct(x:y:), makeSomeStruct(x:) @@ -16,6 +17,8 @@ // CHECK-NEXT: TU: SNIntegerType // CHECK-NEXT: Point: // CHECK-NEXT: TU: SNPoint +// CHECK-NEXT: SNColorChoice: +// CHECK-NEXT: TU: SNColorChoice, SNColorChoice{{$}} // CHECK-NEXT: SomeStruct: // CHECK-NEXT: TU: SNSomeStruct // CHECK-NEXT: __SNTransposeInPlace: From 28dea3bc27c0b9d784decfd7c0af4da5b9b35174 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 20 Nov 2015 11:21:41 -0800 Subject: [PATCH 06/23] Clang importer: recursively add members to the Swift lookup tables. This is needed for member lookup via the Swift lookup tables, although the lookup part is not yet implemented. Note also that the results are currently wrong for C enumerations mapped into Swift enums or option sets. That will come shortly. --- lib/ClangImporter/ClangImporter.cpp | 27 +++++++++++++++++++++++-- lib/ClangImporter/ImporterImpl.h | 4 ++++ lib/ClangImporter/SwiftLookupTable.cpp | 22 +++++++++++++++++--- lib/ClangImporter/SwiftLookupTable.h | 4 +++- test/IDE/Inputs/swift_name.h | 2 +- test/IDE/dump_swift_lookup_tables.swift | 21 ++++++++++++++++++- 6 files changed, 72 insertions(+), 8 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 8c9fda2b6ffb1..7a827ddf5e895 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -702,6 +702,30 @@ bool ClangImporter::addSearchPath(StringRef newSearchPath, bool isFramework) { return false; } +void ClangImporter::Implementation::addEntryToLookupTable( + SwiftLookupTable &table, clang::NamedDecl *named) +{ + // If we have a name to import as, add this entry to the table. + if (DeclName name = importFullName(named)) { + clang::DeclContext *effectiveContext + = named->getDeclContext()->getRedeclContext() ->getPrimaryContext(); + + table.addEntry(name, 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, @@ -743,8 +767,7 @@ bool ClangImporter::Implementation::importHeader( if (UseSwiftLookupTables) { if (auto named = dyn_cast(D)) { - if (DeclName name = importFullName(named)) - BridgingHeaderLookupTable.addEntry(name, named); + addEntryToLookupTable(BridgingHeaderLookupTable, named); } } } diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 01ec7e02ef967..f671a7a37adc0 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -625,6 +625,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); diff --git a/lib/ClangImporter/SwiftLookupTable.cpp b/lib/ClangImporter/SwiftLookupTable.cpp index ede77efe0e546..1437f0e78b099 100644 --- a/lib/ClangImporter/SwiftLookupTable.cpp +++ b/lib/ClangImporter/SwiftLookupTable.cpp @@ -51,7 +51,8 @@ static bool matchesExistingDecl(clang::Decl *decl, clang::Decl *existingDecl) { return false; } -void SwiftLookupTable::addEntry(DeclName name, clang::NamedDecl *decl) { +void SwiftLookupTable::addEntry(DeclName name, clang::NamedDecl *decl, + clang::DeclContext *effectiveContext) { clang::DeclContext *context = decl->getDeclContext()->getRedeclContext()->getPrimaryContext(); @@ -91,6 +92,22 @@ void SwiftLookupTable::addEntry(DeclName name, clang::NamedDecl *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()) { + 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; @@ -134,8 +151,7 @@ void SwiftLookupTable::dump() const { if (fullEntry.Context->isTranslationUnit()) { llvm::errs() << "TU"; } else if (auto named = dyn_cast(fullEntry.Context)) { - named->printName(llvm::errs()); - llvm::errs(); + printName(named, llvm::errs()); } else { llvm::errs() << ""; } diff --git a/lib/ClangImporter/SwiftLookupTable.h b/lib/ClangImporter/SwiftLookupTable.h index a9c110086caaf..59ec0a120bcb4 100644 --- a/lib/ClangImporter/SwiftLookupTable.h +++ b/lib/ClangImporter/SwiftLookupTable.h @@ -71,7 +71,9 @@ class SwiftLookupTable { /// /// \param name The Swift name of the entry. /// \param decl The Clang declaration to add. - void addEntry(DeclName name, clang::NamedDecl *decl); + /// \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. /// diff --git a/test/IDE/Inputs/swift_name.h b/test/IDE/Inputs/swift_name.h index 925bbbe021b79..7968e0bb2e146 100644 --- a/test/IDE/Inputs/swift_name.h +++ b/test/IDE/Inputs/swift_name.h @@ -28,7 +28,7 @@ typedef int SNIntegerType SWIFT_NAME(MyInt); // Renaming enumerations. SWIFT_ENUM(unsigned char, SNColorChoice) { - SNColorRed, + SNColorRed SWIFT_NAME(Rouge), SNColorGreen, SNColorBlue }; diff --git a/test/IDE/dump_swift_lookup_tables.swift b/test/IDE/dump_swift_lookup_tables.swift index b1c3dd1e7c1ca..7be2bbe8a01cf 100644 --- a/test/IDE/dump_swift_lookup_tables.swift +++ b/test/IDE/dump_swift_lookup_tables.swift @@ -5,10 +5,16 @@ // CHECK-NEXT: Bar --> Bar // CHECK-NEXT: MyInt --> MyInt // CHECK-NEXT: Point --> Point +// CHECK-NEXT: Rouge --> Rouge +// CHECK-NEXT: SNColorBlue --> SNColorBlue // CHECK-NEXT: SNColorChoice --> SNColorChoice +// CHECK-NEXT: SNColorGreen --> SNColorGreen // 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: @@ -17,8 +23,14 @@ // CHECK-NEXT: TU: SNIntegerType // CHECK-NEXT: Point: // CHECK-NEXT: TU: SNPoint +// CHECK-NEXT: Rouge: +// CHECK-NEXT: TU: SNColorRed +// CHECK-NEXT: SNColorBlue: +// CHECK-NEXT: TU: SNColorBlue // CHECK-NEXT: SNColorChoice: -// CHECK-NEXT: TU: SNColorChoice, SNColorChoice{{$}} +// CHECK-NEXT: TU: SNColorChoice, SNColorChoice +// CHECK-NEXT: SNColorGreen: +// CHECK-NEXT: TU: SNColorGreen // CHECK-NEXT: SomeStruct: // CHECK-NEXT: TU: SNSomeStruct // CHECK-NEXT: __SNTransposeInPlace: @@ -27,3 +39,10 @@ // 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 From c41535a7fb05edc57698ee54e869c75b821d802c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 20 Nov 2015 11:43:01 -0800 Subject: [PATCH 07/23] Clang importer: start computing the effective lookup context with the name. This places enumerators that will become either cases of a Swift enum or options in a Swift option into the context of the C enum type for the name lookup table. --- lib/ClangImporter/ClangImporter.cpp | 43 +++++++++++++++++++++---- lib/ClangImporter/ImportDecl.cpp | 6 ++-- lib/ClangImporter/ImporterImpl.h | 20 ++++++------ lib/ClangImporter/SwiftLookupTable.cpp | 3 +- test/IDE/dump_swift_lookup_tables.swift | 6 ++-- tools/swift-ide-test/swift-ide-test.cpp | 1 - 6 files changed, 54 insertions(+), 25 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 7a827ddf5e895..6b9026b03732d 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -706,10 +706,8 @@ void ClangImporter::Implementation::addEntryToLookupTable( SwiftLookupTable &table, clang::NamedDecl *named) { // If we have a name to import as, add this entry to the table. - if (DeclName name = importFullName(named)) { - clang::DeclContext *effectiveContext - = named->getDeclContext()->getRedeclContext() ->getPrimaryContext(); - + clang::DeclContext *effectiveContext; + if (DeclName name = importFullName(named, nullptr, &effectiveContext)) { table.addEntry(name, named, effectiveContext); } @@ -1353,15 +1351,46 @@ static DeclName parseDeclName(ASTContext &ctx, StringRef Name) { DeclName ClangImporter::Implementation::importFullName( const clang::NamedDecl *D, - bool &hasCustomName) { + bool *hasCustomName, + clang::DeclContext **effectiveContext) { + // 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(); + } + } + // If we have a swift_name attribute, use that. if (auto *nameAttr = D->getAttr()) { - hasCustomName = true; + if (hasCustomName) + *hasCustomName = true; return parseDeclName(SwiftContext, nameAttr->getName()); } // We don't have a customized name. - hasCustomName = false; + if (hasCustomName) + *hasCustomName = false; // For empty names, there is nothing to do. if (D->getDeclName().isEmpty()) return { }; diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 1f649dbcb8393..74a23c51a8127 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2720,7 +2720,7 @@ namespace { // Determine the name of the function. bool hasCustomName; - DeclName name = Impl.importFullName(decl, hasCustomName); + DeclName name = Impl.importFullName(decl, &hasCustomName); if (!name) return nullptr; @@ -3014,7 +3014,7 @@ namespace { switch (Impl.getFactoryAsInit(objcClass, decl)) { case FactoryAsInitKind::AsInitializer: if (decl->hasAttr()) { - initName = Impl.importFullName(decl, hasCustomName); + initName = Impl.importFullName(decl, &hasCustomName); break; } // FIXME: We probably should stop using this codepath. It won't ever @@ -3126,7 +3126,7 @@ namespace { bool hasCustomName; if (auto *customNameAttr = decl->getAttr()) { if (!customNameAttr->getName().startswith("init(")) { - name = Impl.importFullName(decl, hasCustomName); + name = Impl.importFullName(decl, &hasCustomName); } } if (!name) { diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index f671a7a37adc0..b0c9cfcc14be2 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -726,17 +726,19 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// /// Note that this may result in a name very different from the Clang name, /// so it should not be used when referencing Clang symbols. - DeclName importFullName(const clang::NamedDecl *D) { - bool hasCustomName; - return importFullName(D, hasCustomName); - } - - /// Imports the full name of the given Clang declaration into Swift. /// - /// Note that this may result in a name very different from the Clang name, - /// so it should not be used when referencing Clang symbols. + /// \param D The Clang declaration whose name should be imported. + /// + /// \param hasCustomName If non-null, will be set to indicate whether the + /// name was provided directly via a C swift_name attribute. + /// + /// \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. DeclName importFullName(const clang::NamedDecl *D, - bool &hasCustomName); + bool *hasCustomName = nullptr, + clang::DeclContext **effectiveContext = nullptr); /// Imports the name of the given Clang decl into Swift. /// diff --git a/lib/ClangImporter/SwiftLookupTable.cpp b/lib/ClangImporter/SwiftLookupTable.cpp index 1437f0e78b099..502dea31aeaca 100644 --- a/lib/ClangImporter/SwiftLookupTable.cpp +++ b/lib/ClangImporter/SwiftLookupTable.cpp @@ -53,8 +53,7 @@ static bool matchesExistingDecl(clang::Decl *decl, clang::Decl *existingDecl) { void SwiftLookupTable::addEntry(DeclName name, clang::NamedDecl *decl, clang::DeclContext *effectiveContext) { - clang::DeclContext *context - = decl->getDeclContext()->getRedeclContext()->getPrimaryContext(); + clang::DeclContext *context = effectiveContext->getPrimaryContext(); // First, check whether there is already a full name entry. auto knownFull = FullNameTable.find(name); diff --git a/test/IDE/dump_swift_lookup_tables.swift b/test/IDE/dump_swift_lookup_tables.swift index 7be2bbe8a01cf..e13e2d7fc3cec 100644 --- a/test/IDE/dump_swift_lookup_tables.swift +++ b/test/IDE/dump_swift_lookup_tables.swift @@ -24,13 +24,13 @@ // CHECK-NEXT: Point: // CHECK-NEXT: TU: SNPoint // CHECK-NEXT: Rouge: -// CHECK-NEXT: TU: SNColorRed +// CHECK-NEXT: SNColorChoice: SNColorRed // CHECK-NEXT: SNColorBlue: -// CHECK-NEXT: TU: SNColorBlue +// CHECK-NEXT: SNColorChoice: SNColorBlue // CHECK-NEXT: SNColorChoice: // CHECK-NEXT: TU: SNColorChoice, SNColorChoice // CHECK-NEXT: SNColorGreen: -// CHECK-NEXT: TU: SNColorGreen +// CHECK-NEXT: SNColorChoice: SNColorGreen // CHECK-NEXT: SomeStruct: // CHECK-NEXT: TU: SNSomeStruct // CHECK-NEXT: __SNTransposeInPlace: diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index d403a3ca956b5..bdb89fe0bd0aa 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -953,7 +953,6 @@ static int doDumpAPI(const CompilerInvocation &InitInvok, static int doDumpImporterLookupTables(const CompilerInvocation &InitInvok, StringRef SourceFilename) { - auto &FEOpts = InitInvok.getFrontendOptions(); if (options::ImportObjCHeader.empty()) { llvm::errs() << "implicit header required\n"; llvm::cl::PrintHelpMessage(); From fa865c7165a0f15148ad8f6b25f448a48a62fcd2 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 20 Nov 2015 13:15:59 -0800 Subject: [PATCH 08/23] Clang importer: handle enumerator prefix stripping in importFullName(). Centralize the mapping of C names to Swift names further by including enumerator prefix stripping, rather than having that as a separate path. The actual logic and code for computing the prefix is unchanged (despite moving from one file to another). This corrects the name computed for the Swift lookup tables, but is an NFC refactoring for everything else. With this, kill off importName(), because it's been entirely superseded by importFullName(). --- lib/ClangImporter/ClangImporter.cpp | 317 ++++++++++++++++++------ lib/ClangImporter/ImportDecl.cpp | 230 +---------------- lib/ClangImporter/ImporterImpl.h | 13 +- test/IDE/dump_swift_lookup_tables.swift | 12 +- 4 files changed, 252 insertions(+), 320 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 6b9026b03732d..1c6f26137f42f 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1349,6 +1349,232 @@ static DeclName parseDeclName(ASTContext &ctx, StringRef Name) { return DeclName(ctx, BaseID, ParamIDs); } +/// \brief Returns the common prefix of two strings at camel-case word +/// granularity. +/// +/// For example, given "NSFooBar" and "NSFooBas", returns "NSFoo" +/// (not "NSFooBa"). The returned StringRef is a slice of the "a" argument. +/// +/// If either string has a non-identifier character immediately after the +/// prefix, \p followedByNonIdentifier will be set to \c true. If both strings +/// have identifier characters after the prefix, \p followedByNonIdentifier will +/// be set to \c false. Otherwise, \p followedByNonIdentifier will not be +/// changed from its initial value. +/// +/// This is used to derive the common prefix of enum constants so we can elide +/// it from the Swift interface. +static StringRef getCommonWordPrefix(StringRef a, StringRef b, + bool &followedByNonIdentifier) { + auto aWords = camel_case::getWords(a), bWords = camel_case::getWords(b); + auto aI = aWords.begin(), aE = aWords.end(), + bI = bWords.begin(), bE = bWords.end(); + + unsigned prevLength = 0; + unsigned prefixLength = 0; + for ( ; aI != aE && bI != bE; ++aI, ++bI) { + if (*aI != *bI) { + followedByNonIdentifier = false; + break; + } + + prevLength = prefixLength; + prefixLength = aI.getPosition() + aI->size(); + } + + // Avoid creating a prefix where the rest of the string starts with a number. + if ((aI != aE && !Lexer::isIdentifier(*aI)) || + (bI != bE && !Lexer::isIdentifier(*bI))) { + followedByNonIdentifier = true; + prefixLength = prevLength; + } + + return a.slice(0, prefixLength); +} + +/// Returns the common word-prefix of two strings, allowing the second string +/// to be a common English plural form of the first. +/// +/// For example, given "NSProperty" and "NSProperties", the full "NSProperty" +/// is returned. Given "NSMagicArmor" and "NSMagicArmory", only +/// "NSMagic" is returned. +/// +/// The "-s", "-es", and "-ies" patterns cover every plural NS_OPTIONS name +/// in Cocoa and Cocoa Touch. +/// +/// \see getCommonWordPrefix +static StringRef getCommonPluralPrefix(StringRef singular, StringRef plural) { + assert(!plural.empty()); + + if (singular.empty()) + return singular; + + bool ignored; + StringRef commonPrefix = getCommonWordPrefix(singular, plural, ignored); + if (commonPrefix.size() == singular.size() || plural.back() != 's') + return commonPrefix; + + StringRef leftover = singular.substr(commonPrefix.size()); + StringRef firstLeftoverWord = camel_case::getFirstWord(leftover); + StringRef commonPrefixPlusWord = + singular.substr(0, commonPrefix.size() + firstLeftoverWord.size()); + + // Is the plural string just "[singular]s"? + plural = plural.drop_back(); + if (plural.endswith(firstLeftoverWord)) + return commonPrefixPlusWord; + + if (plural.empty() || plural.back() != 'e') + return commonPrefix; + + // Is the plural string "[singular]es"? + plural = plural.drop_back(); + if (plural.endswith(firstLeftoverWord)) + return commonPrefixPlusWord; + + if (plural.empty() || !(plural.back() == 'i' && singular.back() == 'y')) + return commonPrefix; + + // Is the plural string "[prefix]ies" and the singular "[prefix]y"? + plural = plural.drop_back(); + firstLeftoverWord = firstLeftoverWord.drop_back(); + if (plural.endswith(firstLeftoverWord)) + return commonPrefixPlusWord; + + return commonPrefix; +} + +StringRef ClangImporter::Implementation::getEnumConstantNamePrefix( + 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).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; +} + DeclName ClangImporter::Implementation::importFullName( const clang::NamedDecl *D, bool *hasCustomName, @@ -1433,6 +1659,16 @@ DeclName ClangImporter::Implementation::importFullName( } } + // 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()); + } + // Local function to determine whether the given declaration is subject to // a swift_private attribute. auto hasSwiftPrivate = [this](const clang::NamedDecl *D) { @@ -1558,87 +1794,6 @@ ClangImporter::Implementation::importIdentifier( return SwiftContext.getIdentifier(name); } -Identifier -ClangImporter::Implementation::importName(const clang::NamedDecl *D, - StringRef removePrefix) { - if (auto *nameAttr = D->getAttr()) { - StringRef customName = nameAttr->getName(); - if (Lexer::isIdentifier(customName)) - return SwiftContext.getIdentifier(customName); - - return Identifier(); - } - - Identifier result = importIdentifier(D->getIdentifier(), removePrefix); - if (result.empty()) - return result; - - auto hasSwiftPrivate = [this](const clang::NamedDecl *D) { - if (D->hasAttr()) - return true; - - // Enum constants that are not imported as members should be considered - // private if the parent enum is marked private. - 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()) - return true; - 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. - if (OmitNeedlessWords) { - 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()) { - if (auto objcPtrType = contextType->getAsObjCInterfacePointerType()) - if (auto objcClassDecl = objcPtrType->getInterfaceDecl()) - allPropertyNames = SwiftContext.getAllPropertyNames( - objcClassDecl, - /*forInstance=*/true); - } - - if (omitNeedlessWords(name, { }, "", propertyTypeName, contextTypeName, - { }, /*returnsSelf=*/false, /*isProperty=*/true, - allPropertyNames, scratch)) { - result = SwiftContext.getIdentifier(name); - } - } - } - } - - return result; -} - /// Import an argument name. static Identifier importArgName(ASTContext &ctx, StringRef name, bool dropWith, bool isSwiftPrivate) { diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 74a23c51a8127..3587a76178e9b 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -323,101 +323,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. /// @@ -1832,131 +1737,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).getBaseName(); if (name.empty()) return nullptr; @@ -2009,7 +1795,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).getBaseName(); if (name.empty()) return nullptr; @@ -2034,11 +1820,11 @@ namespace { EnumElementDecl *original, const clang::EnumDecl *clangEnum, NominalTypeDecl *importedEnum) { - auto name = getEnumConstantName(Impl, alias, clangEnum); + auto name = Impl.importFullName(alias).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(), @@ -2130,7 +1916,6 @@ namespace { structDecl->addMember(labeledValueConstructor); structDecl->addMember(patternBinding); structDecl->addMember(var); - computeEnumCommonWordPrefix(decl, name); return structDecl; } @@ -2298,8 +2083,6 @@ namespace { enumDecl->addMember(rawValueBinding); result = enumDecl; - computeEnumCommonWordPrefix(decl, name); - break; } @@ -2593,7 +2376,7 @@ namespace { Decl *VisitEnumConstantDecl(const clang::EnumConstantDecl *decl) { auto clangEnum = cast(decl->getDeclContext()); - auto name = getEnumConstantName(Impl, decl, clangEnum); + auto name = Impl.importFullName(decl).getBaseName(); if (name.empty()) return nullptr; @@ -6442,7 +6225,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).getBaseName(); } diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index b0c9cfcc14be2..d0f12de71f8f1 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -503,6 +503,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, @@ -740,15 +744,6 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation bool *hasCustomName = nullptr, clang::DeclContext **effectiveContext = nullptr); - /// Imports the name of the given Clang decl 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.) - /// - /// \sa importName(clang::DeclarationName, StringRef) - Identifier importName(const clang::NamedDecl *D, StringRef removePrefix = ""); - /// \brief Import the given Clang identifier into Swift. /// /// \param identifier The Clang identifier to map into Swift. diff --git a/test/IDE/dump_swift_lookup_tables.swift b/test/IDE/dump_swift_lookup_tables.swift index e13e2d7fc3cec..14144b5d1e961 100644 --- a/test/IDE/dump_swift_lookup_tables.swift +++ b/test/IDE/dump_swift_lookup_tables.swift @@ -3,12 +3,12 @@ // 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: SNColorBlue --> SNColorBlue // CHECK-NEXT: SNColorChoice --> SNColorChoice -// CHECK-NEXT: SNColorGreen --> SNColorGreen // CHECK-NEXT: SomeStruct --> SomeStruct // CHECK-NEXT: __SNTransposeInPlace --> __SNTransposeInPlace // CHECK-NEXT: makeSomeStruct --> makeSomeStruct(x:y:), makeSomeStruct(x:) @@ -19,18 +19,18 @@ // 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: SNColorBlue: -// CHECK-NEXT: SNColorChoice: SNColorBlue // CHECK-NEXT: SNColorChoice: // CHECK-NEXT: TU: SNColorChoice, SNColorChoice -// CHECK-NEXT: SNColorGreen: -// CHECK-NEXT: SNColorChoice: SNColorGreen // CHECK-NEXT: SomeStruct: // CHECK-NEXT: TU: SNSomeStruct // CHECK-NEXT: __SNTransposeInPlace: From 2244cb4fc8fd72b63a4cd0c272ae6cf3d2ef27cb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 21 Nov 2015 11:38:05 -0800 Subject: [PATCH 09/23] Swift lookup tables: start testing and cleaning up handling for Objective-C. Start testing the construction of the Swift lookup tables in the Clang importer for Objective-C entities. Fix some obvious issues, e.g., category and extension entries should be associated with the corresponding class, and the categories/extensions shouldn't have entries in the lookup table. --- lib/ClangImporter/ClangImporter.cpp | 10 ++++ lib/ClangImporter/SwiftLookupTable.cpp | 40 +++++++++++++++- test/IDE/Inputs/swift_name_objc.h | 45 ++++++++++++++++++ test/IDE/dump_swift_lookup_tables_objc.swift | 50 ++++++++++++++++++++ 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 test/IDE/Inputs/swift_name_objc.h create mode 100644 test/IDE/dump_swift_lookup_tables_objc.swift diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 1c6f26137f42f..187a44f899e20 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1579,6 +1579,10 @@ DeclName ClangImporter::Implementation::importFullName( const clang::NamedDecl *D, bool *hasCustomName, clang::DeclContext **effectiveContext) { + // Objective-C categories and extensions don't have names. + if (isa(D)) + return { }; + // Compute the effective context, if requested. if (effectiveContext) { auto dc = const_cast(D->getDeclContext()); @@ -1605,6 +1609,12 @@ DeclName ClangImporter::Implementation::importFullName( // 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(); + } } // If we have a swift_name attribute, use that. diff --git a/lib/ClangImporter/SwiftLookupTable.cpp b/lib/ClangImporter/SwiftLookupTable.cpp index 502dea31aeaca..c234ee2cf3d13 100644 --- a/lib/ClangImporter/SwiftLookupTable.cpp +++ b/lib/ClangImporter/SwiftLookupTable.cpp @@ -94,6 +94,41 @@ void SwiftLookupTable::addEntry(DeclName name, clang::NamedDecl *decl, 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; } @@ -158,7 +193,10 @@ void SwiftLookupTable::dump() const { interleave(fullEntry.Decls.begin(), fullEntry.Decls.end(), [](clang::NamedDecl *decl) { - decl->printName(llvm::errs()); + if (auto named = dyn_cast(decl)) + printName(named, llvm::errs()); + else + decl->printName(llvm::errs()); }, [] { llvm::errs() << ", "; diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h new file mode 100644 index 0000000000000..0cea11d913dd5 --- /dev/null +++ b/test/IDE/Inputs/swift_name_objc.h @@ -0,0 +1,45 @@ +@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; +- (void)instanceMethodWithX:(float)x y:(float)y z:(float)z; ++ (instancetype)someClassWithDouble:(double)d; + +@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 + 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..842fe2dd15878 --- /dev/null +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -0,0 +1,50 @@ +// 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 + +// REQUIRES: objc_interop + +// CHECK: Base -> full name mappings: +// CHECK-NEXT: SNCollision --> SNCollision +// CHECK-NEXT: SomeClass --> SomeClass +// CHECK-NEXT: SomeProtocol --> SomeProtocol +// CHECK-NEXT: categoryMethodWithX --> categoryMethodWithX(_:y:), categoryMethodWithX(_:y:z:) +// CHECK-NEXT: doubleProperty --> doubleProperty, doubleProperty() +// CHECK-NEXT: extensionMethodWithX --> extensionMethodWithX(_:y:) +// CHECK-NEXT: floatProperty --> floatProperty, floatProperty() +// CHECK-NEXT: initWithFloat --> initWithFloat(_:) +// CHECK-NEXT: instanceMethodWithX --> instanceMethodWithX(_:y:z:) +// CHECK-NEXT: protoInstanceMethodWithX --> protoInstanceMethodWithX(_:y:) +// CHECK-NEXT: setDoubleProperty --> setDoubleProperty(_:) +// CHECK-NEXT: someClassWithDouble --> someClassWithDouble(_:) + +// CHECK: Full name -> entry mappings: +// CHECK-NEXT: SNCollision: +// CHECK-NEXT: TU: SNCollision, SNCollision +// CHECK-NEXT: SomeClass: +// CHECK-NEXT: TU: SNSomeClass +// CHECK-NEXT: SomeProtocol: +// CHECK-NEXT: TU: SNSomeProtocol +// 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: 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: floatProperty(): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass floatProperty] +// CHECK-NEXT: initWithFloat(_:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithFloat:] +// CHECK-NEXT: instanceMethodWithX(_:y:z:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass instanceMethodWithX:y:z:] +// CHECK-NEXT: protoInstanceMethodWithX(_:y:): +// CHECK-NEXT: SNSomeProtocol: -[SNSomeProtocol protoInstanceMethodWithX:y:] +// CHECK-NEXT: setDoubleProperty(_:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass setDoubleProperty:] +// CHECK-NEXT: someClassWithDouble(_:): +// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:] From 5776fa59c62c97a39d7f7a6aa2e95d910a20c67e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 21 Nov 2015 20:52:52 -0800 Subject: [PATCH 10/23] Clang importer: migrate "Protocol" suffix computation into importFullName. Refactoring that lets the Swift lookup tables get the names right for Objective-C protocols that would conflict with another entity in the same module (or within the bridging header). It's an NFC cleanup everywhere else. --- lib/ClangImporter/ClangImporter.cpp | 61 ++++++++++++++++++++ lib/ClangImporter/ImportDecl.cpp | 53 ----------------- test/IDE/dump_swift_lookup_tables_objc.swift | 5 +- 3 files changed, 65 insertions(+), 54 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 187a44f899e20..1c29653aff37b 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1679,6 +1679,67 @@ DeclName ClangImporter::Implementation::importFullName( 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; + } + } + } + // Local function to determine whether the given declaration is subject to // a swift_private attribute. auto hasSwiftPrivate = [this](const clang::NamedDecl *D) { diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 3587a76178e9b..ba60c9bce45b4 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -4825,59 +4825,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; diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index 842fe2dd15878..eff3de2dd3ad7 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -5,6 +5,7 @@ // CHECK: Base -> full name mappings: // CHECK-NEXT: SNCollision --> SNCollision +// CHECK-NEXT: SNCollisionProtocol --> SNCollisionProtocol // CHECK-NEXT: SomeClass --> SomeClass // CHECK-NEXT: SomeProtocol --> SomeProtocol // CHECK-NEXT: categoryMethodWithX --> categoryMethodWithX(_:y:), categoryMethodWithX(_:y:z:) @@ -19,7 +20,9 @@ // CHECK: Full name -> entry mappings: // CHECK-NEXT: SNCollision: -// CHECK-NEXT: TU: SNCollision, SNCollision +// CHECK-NEXT: TU: SNCollision{{$}} +// CHECK-NEXT: SNCollisionProtocol: +// CHECK-NEXT: TU: SNCollision{{$}} // CHECK-NEXT: SomeClass: // CHECK-NEXT: TU: SNSomeClass // CHECK-NEXT: SomeProtocol: From 9ee502bedeb9ff43cec2a92c75960a55d10dc2d9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 21 Nov 2015 21:08:20 -0800 Subject: [PATCH 11/23] Clang importer: omit non-accessibility property getters/setters from lookup table. The getters and setters for Objective-C @property declarations are never found by name lookup, so don't introduce them into the Swift lookup tables. Note that we exclude some of the accessibility declarations for unrelated reasons, as we do elsewhere in the importer. --- lib/ClangImporter/ClangImporter.cpp | 32 +++++++++++++++++--- test/IDE/Inputs/swift_name_objc.h | 3 ++ test/IDE/dump_swift_lookup_tables_objc.swift | 20 ++++++------ 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 1c29653aff37b..3ec2c85cfc496 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -705,10 +705,31 @@ bool ClangImporter::addSearchPath(StringRef newSearchPath, bool isFramework) { void ClangImporter::Implementation::addEntryToLookupTable( SwiftLookupTable &table, clang::NamedDecl *named) { - // If we have a name to import as, add this entry to the table. - clang::DeclContext *effectiveContext; - if (DeclName name = importFullName(named, nullptr, &effectiveContext)) { - table.addEntry(name, named, effectiveContext); + // 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 (DeclName name = importFullName(named, nullptr, &effectiveContext)) { + table.addEntry(name, named, effectiveContext); + } } // Walk the members of any context that can have nested members. @@ -1579,7 +1600,8 @@ DeclName ClangImporter::Implementation::importFullName( const clang::NamedDecl *D, bool *hasCustomName, clang::DeclContext **effectiveContext) { - // Objective-C categories and extensions don't have names. + // Objective-C categories and extensions don't have names, despite + // being "named" declarations. if (isa(D)) return { }; diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index 0cea11d913dd5..2d36c925219c9 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -43,3 +43,6 @@ SWIFT_NAME(SomeProtocol) @protocol SNCollision @end +@protocol NSAccessibility +@property (nonatomic) float accessibilityFloat; +@end diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index eff3de2dd3ad7..bca501d37eedd 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -4,21 +4,25 @@ // REQUIRES: objc_interop // CHECK: Base -> full name mappings: +// CHECK-NEXT: NSAccessibility --> NSAccessibility // CHECK-NEXT: SNCollision --> SNCollision // CHECK-NEXT: SNCollisionProtocol --> SNCollisionProtocol // CHECK-NEXT: SomeClass --> SomeClass // CHECK-NEXT: SomeProtocol --> SomeProtocol +// CHECK-NEXT: accessibilityFloat --> accessibilityFloat() // CHECK-NEXT: categoryMethodWithX --> categoryMethodWithX(_:y:), categoryMethodWithX(_:y:z:) -// CHECK-NEXT: doubleProperty --> doubleProperty, doubleProperty() +// CHECK-NEXT: doubleProperty --> doubleProperty{{$}} // CHECK-NEXT: extensionMethodWithX --> extensionMethodWithX(_:y:) -// CHECK-NEXT: floatProperty --> floatProperty, floatProperty() +// CHECK-NEXT: floatProperty --> floatProperty{{$}} // CHECK-NEXT: initWithFloat --> initWithFloat(_:) // CHECK-NEXT: instanceMethodWithX --> instanceMethodWithX(_:y:z:) // CHECK-NEXT: protoInstanceMethodWithX --> protoInstanceMethodWithX(_:y:) -// CHECK-NEXT: setDoubleProperty --> setDoubleProperty(_:) +// CHECK-NEXT: setAccessibilityFloat --> setAccessibilityFloat(_:) // CHECK-NEXT: someClassWithDouble --> someClassWithDouble(_:) // CHECK: Full name -> entry mappings: +// CHECK-NEXT: NSAccessibility: +// CHECK-NEXT: TU: NSAccessibility{{$}} // CHECK-NEXT: SNCollision: // CHECK-NEXT: TU: SNCollision{{$}} // CHECK-NEXT: SNCollisionProtocol: @@ -27,27 +31,25 @@ // CHECK-NEXT: TU: SNSomeClass // CHECK-NEXT: SomeProtocol: // CHECK-NEXT: TU: SNSomeProtocol +// 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: 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: floatProperty(): -// CHECK-NEXT: SNSomeClass: -[SNSomeClass floatProperty] // CHECK-NEXT: initWithFloat(_:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithFloat:] // CHECK-NEXT: instanceMethodWithX(_:y:z:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass instanceMethodWithX:y:z:] // CHECK-NEXT: protoInstanceMethodWithX(_:y:): // CHECK-NEXT: SNSomeProtocol: -[SNSomeProtocol protoInstanceMethodWithX:y:] -// CHECK-NEXT: setDoubleProperty(_:): -// CHECK-NEXT: SNSomeClass: -[SNSomeClass setDoubleProperty:] +// CHECK-NEXT: setAccessibilityFloat(_:): +// CHECK-NEXT: NSAccessibility: -[NSAccessibility setAccessibilityFloat:] // CHECK-NEXT: someClassWithDouble(_:): // CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:] From eedf0fc1dbef3eb5122eea0a90067b588d783a54 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 21 Nov 2015 21:59:03 -0800 Subject: [PATCH 12/23] Clang importer: teach importFullName to map init methods to initializers. We're only using this in the Swift lookup tables at the moment. --- lib/ClangImporter/ClangImporter.cpp | 77 +++++++++++++++++--- lib/ClangImporter/ImportDecl.cpp | 31 +------- lib/ClangImporter/ImporterImpl.h | 4 + test/IDE/Inputs/swift_name_objc.h | 1 + test/IDE/dump_swift_lookup_tables_objc.swift | 6 +- 5 files changed, 77 insertions(+), 42 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 3ec2c85cfc496..0ae3f7691af8a 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1655,8 +1655,10 @@ DeclName ClangImporter::Implementation::importFullName( /// Whether the result is a function name. bool isFunction = false; + bool isInitializer = false; StringRef baseName; SmallVector argumentNames; + SmallString<16> selectorSplitScratch; switch (D->getDeclName().getNameKind()) { case clang::DeclarationName::CXXConstructorName: case clang::DeclarationName::CXXConversionFunctionName: @@ -1675,15 +1677,55 @@ DeclName ClangImporter::Implementation::importFullName( case clang::DeclarationName::ObjCMultiArgSelector: case clang::DeclarationName::ObjCOneArgSelector: case clang::DeclarationName::ObjCZeroArgSelector: { + auto objcMethod = cast(D); + isInitializer = isInitMethod(objcMethod); + // Map the Objective-C selector directly. auto selector = D->getDeclName().getObjCSelector(); - baseName = selector.getNameForSlot(0); + if (isInitializer) + baseName = "init"; + else + baseName = selector.getNameForSlot(0); for (unsigned index = 0, numArgs = selector.getNumArgs(); index != numArgs; ++index) { - if (index == 0) - argumentNames.push_back(""); - else + if (index == 0) { + argumentNames.push_back(StringRef()); + } else { argumentNames.push_back(selector.getNameForSlot(index)); + } + } + + // For initializers, compute the first argument name. + if (isInitializer) { + // Skip over the 'init'. + auto argName = selector.getNameForSlot(0).substr(4); + + // 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(8); + 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() && !argName.empty()) { + // FIXME: Record what happened here for the caller? + argumentNames.push_back(argName); + } else { + argumentNames[0] = argName; + } } isFunction = true; @@ -1825,17 +1867,15 @@ DeclName ClangImporter::Implementation::importFullName( if (hasSwiftPrivate(D)) { swiftPrivateScratch = "__"; - if (baseName == "init") { + if (isInitializer) { // For initializers, prepend "__" to the first argument name. if (argumentNames.empty()) { - // swift_private cannot actually do anything here. Just drop the - // declaration. - // FIXME: Diagnose this? - return { }; + // FIXME: Record that we did this. + argumentNames.push_back("__"); + } else { + swiftPrivateScratch += argumentNames[0]; + argumentNames[0] = swiftPrivateScratch; } - - swiftPrivateScratch += argumentNames[0]; - argumentNames[0] = swiftPrivateScratch; } else { // For all other entities, prepend "__" to the base name. swiftPrivateScratch += baseName; @@ -2572,6 +2612,19 @@ 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"; +} + #pragma mark Name lookup void ClangImporter::lookupValue(Identifier name, VisibleDeclConsumer &consumer){ auto &pp = Impl.Instance->getPreprocessor(); diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index ba60c9bce45b4..b5afec5c0bd16 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2879,8 +2879,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; @@ -3069,27 +3068,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. @@ -3241,9 +3219,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}); @@ -4552,8 +4528,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, diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index d0f12de71f8f1..38ab4b8848f5a 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -426,6 +426,10 @@ 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); + private: /// \brief Generation number that is used for crude versioning. /// diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index 2d36c925219c9..b44271c7d94b3 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -16,6 +16,7 @@ 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; diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index bca501d37eedd..dae80d819677f 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -14,7 +14,7 @@ // CHECK-NEXT: doubleProperty --> doubleProperty{{$}} // CHECK-NEXT: extensionMethodWithX --> extensionMethodWithX(_:y:) // CHECK-NEXT: floatProperty --> floatProperty{{$}} -// CHECK-NEXT: initWithFloat --> initWithFloat(_:) +// CHECK-NEXT: init --> init(float:), init(withDefault:) // CHECK-NEXT: instanceMethodWithX --> instanceMethodWithX(_:y:z:) // CHECK-NEXT: protoInstanceMethodWithX --> protoInstanceMethodWithX(_:y:) // CHECK-NEXT: setAccessibilityFloat --> setAccessibilityFloat(_:) @@ -43,8 +43,10 @@ // CHECK-NEXT: SNSomeClass: -[SNSomeClass extensionMethodWithX:y:] // CHECK-NEXT: floatProperty: // CHECK-NEXT: SNSomeClass: SNSomeClass.floatProperty -// CHECK-NEXT: initWithFloat(_:): +// CHECK-NEXT: init(float:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithFloat:] +// CHECK-NEXT: init(withDefault:): +// CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithDefault] // CHECK-NEXT: instanceMethodWithX(_:y:z:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass instanceMethodWithX:y:z:] // CHECK-NEXT: protoInstanceMethodWithX(_:y:): From ec912440e13919cbad5533da9cda5ebf1b9ac780 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 30 Nov 2015 10:29:20 -0800 Subject: [PATCH 13/23] Clang importer: teach importFullName about factory methods as initializers. --- lib/ClangImporter/ClangImporter.cpp | 94 +++++++++++++++++++- lib/ClangImporter/ImporterImpl.h | 14 +++ test/IDE/Inputs/swift_name_objc.h | 2 + test/IDE/dump_swift_lookup_tables_objc.swift | 9 +- 4 files changed, 111 insertions(+), 8 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 0ae3f7691af8a..a9e908a4bf890 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1643,6 +1643,19 @@ DeclName ClangImporter::Implementation::importFullName( if (auto *nameAttr = D->getAttr()) { if (hasCustomName) *hasCustomName = true; + + // 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. + if (auto method = dyn_cast(D)) { + unsigned initPrefixLength; + CtorInitializerKind kind; + if (nameAttr->getName().startswith("init(") && + !shouldImportAsInitializer(method, initPrefixLength, kind)) + return { }; + } + return parseDeclName(SwiftContext, nameAttr->getName()); } @@ -1656,6 +1669,8 @@ DeclName ClangImporter::Implementation::importFullName( /// Whether the result is a function name. bool isFunction = false; bool isInitializer = false; + unsigned initializerPrefixLen; + CtorInitializerKind initKind; StringRef baseName; SmallVector argumentNames; SmallString<16> selectorSplitScratch; @@ -1678,7 +1693,8 @@ DeclName ClangImporter::Implementation::importFullName( case clang::DeclarationName::ObjCOneArgSelector: case clang::DeclarationName::ObjCZeroArgSelector: { auto objcMethod = cast(D); - isInitializer = isInitMethod(objcMethod); + isInitializer = shouldImportAsInitializer(objcMethod, initializerPrefixLen, + initKind); // Map the Objective-C selector directly. auto selector = D->getDeclName().getObjCSelector(); @@ -1697,8 +1713,8 @@ DeclName ClangImporter::Implementation::importFullName( // For initializers, compute the first argument name. if (isInitializer) { - // Skip over the 'init'. - auto argName = selector.getNameForSlot(0).substr(4); + // Skip over the prefix. + auto argName = selector.getNameForSlot(0).substr(initializerPrefixLen); // Drop "With" if present after the "init". bool droppedWith = false; @@ -1714,7 +1730,8 @@ DeclName ClangImporter::Implementation::importFullName( // put "with" back. if (droppedWith && isSwiftReservedName(argName)) { selectorSplitScratch = "with"; - selectorSplitScratch += selector.getNameForSlot(0).substr(8); + selectorSplitScratch += selector.getNameForSlot(0).substr( + initializerPrefixLen + 4); argName = selectorSplitScratch; } @@ -2625,6 +2642,75 @@ bool ClangImporter::Implementation::isInitMethod( 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; + + // 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(); diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 38ab4b8848f5a..0f3a58e103f66 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -430,6 +430,20 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// 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. /// diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index b44271c7d94b3..5256df494fed7 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -19,6 +19,8 @@ SWIFT_NAME(SomeClass) - (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:)); @property (readonly,nonatomic) float floatProperty; @property (readwrite,nonatomic) double doubleProperty; diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index dae80d819677f..c633983bf50b8 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -14,11 +14,10 @@ // CHECK-NEXT: doubleProperty --> doubleProperty{{$}} // CHECK-NEXT: extensionMethodWithX --> extensionMethodWithX(_:y:) // CHECK-NEXT: floatProperty --> floatProperty{{$}} -// CHECK-NEXT: init --> init(float:), init(withDefault:) +// CHECK-NEXT: init --> init(float:), init(withDefault:), init(double:), init(withTry:){{$}} // CHECK-NEXT: instanceMethodWithX --> instanceMethodWithX(_:y:z:) // CHECK-NEXT: protoInstanceMethodWithX --> protoInstanceMethodWithX(_:y:) // CHECK-NEXT: setAccessibilityFloat --> setAccessibilityFloat(_:) -// CHECK-NEXT: someClassWithDouble --> someClassWithDouble(_:) // CHECK: Full name -> entry mappings: // CHECK-NEXT: NSAccessibility: @@ -43,15 +42,17 @@ // CHECK-NEXT: SNSomeClass: -[SNSomeClass extensionMethodWithX:y:] // CHECK-NEXT: floatProperty: // CHECK-NEXT: SNSomeClass: SNSomeClass.floatProperty +// CHECK-NEXT: init(double:): +// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:] // CHECK-NEXT: init(float:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithFloat:] // 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: protoInstanceMethodWithX(_:y:): // CHECK-NEXT: SNSomeProtocol: -[SNSomeProtocol protoInstanceMethodWithX:y:] // CHECK-NEXT: setAccessibilityFloat(_:): // CHECK-NEXT: NSAccessibility: -[NSAccessibility setAccessibilityFloat:] -// CHECK-NEXT: someClassWithDouble(_:): -// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:] From fedb6a8b8a3672a100cb085984ab2f9822e4c464 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 30 Nov 2015 11:29:33 -0800 Subject: [PATCH 14/23] Clang importer: give importFullName a richer result type. Capture the imported name, as well as some information about it, in the result of importFullName. Some of the clients need this information. --- lib/ClangImporter/ClangImporter.cpp | 65 ++++++++++---------- lib/ClangImporter/ImportDecl.cpp | 44 +++++++------ lib/ClangImporter/ImportType.cpp | 4 +- lib/ClangImporter/ImporterImpl.h | 28 +++++++-- test/IDE/Inputs/swift_name_objc.h | 2 +- test/IDE/dump_swift_lookup_tables_objc.swift | 4 +- 6 files changed, 85 insertions(+), 62 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index a9e908a4bf890..05655e72cc71c 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -727,7 +727,7 @@ void ClangImporter::Implementation::addEntryToLookupTable( if (!suppressDecl) { // If we have a name to import as, add this entry to the table. clang::DeclContext *effectiveContext; - if (DeclName name = importFullName(named, nullptr, &effectiveContext)) { + if (DeclName name = importFullName(named, &effectiveContext)) { table.addEntry(name, named, effectiveContext); } } @@ -1573,7 +1573,7 @@ StringRef ClangImporter::Implementation::getEnumConstantNamePrefix( // 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).getBaseName(); + Identifier enumName = importFullName(decl).Imported.getBaseName(); StringRef enumNameStr = enumName.str(); if (enumNameStr.startswith("__") && !checkPrefix.startswith("__")) enumNameStr = enumNameStr.drop_front(2); @@ -1596,14 +1596,15 @@ StringRef ClangImporter::Implementation::getEnumConstantNamePrefix( return commonPrefix; } -DeclName ClangImporter::Implementation::importFullName( - const clang::NamedDecl *D, - bool *hasCustomName, - clang::DeclContext **effectiveContext) { +auto ClangImporter::Implementation::importFullName( + const clang::NamedDecl *D, + clang::DeclContext **effectiveContext) -> ImportedName { + ImportedName result; + // Objective-C categories and extensions don't have names, despite // being "named" declarations. if (isa(D)) - return { }; + return result; // Compute the effective context, if requested. if (effectiveContext) { @@ -1641,36 +1642,29 @@ DeclName ClangImporter::Implementation::importFullName( // If we have a swift_name attribute, use that. if (auto *nameAttr = D->getAttr()) { - if (hasCustomName) - *hasCustomName = true; - // 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. if (auto method = dyn_cast(D)) { unsigned initPrefixLength; - CtorInitializerKind kind; if (nameAttr->getName().startswith("init(") && - !shouldImportAsInitializer(method, initPrefixLength, kind)) + !shouldImportAsInitializer(method, initPrefixLength, result.InitKind)) return { }; } - return parseDeclName(SwiftContext, nameAttr->getName()); + result.HasCustomName = true; + result.Imported = parseDeclName(SwiftContext, nameAttr->getName()); + return result; } - // We don't have a customized name. - if (hasCustomName) - *hasCustomName = false; - // For empty names, there is nothing to do. - if (D->getDeclName().isEmpty()) return { }; + if (D->getDeclName().isEmpty()) return result; /// Whether the result is a function name. bool isFunction = false; bool isInitializer = false; unsigned initializerPrefixLen; - CtorInitializerKind initKind; StringRef baseName; SmallVector argumentNames; SmallString<16> selectorSplitScratch; @@ -1682,7 +1676,7 @@ DeclName ClangImporter::Implementation::importFullName( case clang::DeclarationName::CXXOperatorName: case clang::DeclarationName::CXXUsingDirective: // Handling these is part of C++ interoperability. - return { }; + return result; case clang::DeclarationName::Identifier: // Map the identifier. @@ -1694,7 +1688,7 @@ DeclName ClangImporter::Implementation::importFullName( case clang::DeclarationName::ObjCZeroArgSelector: { auto objcMethod = cast(D); isInitializer = shouldImportAsInitializer(objcMethod, initializerPrefixLen, - initKind); + result.InitKind); // Map the Objective-C selector directly. auto selector = D->getDeclName().getObjCSelector(); @@ -1902,27 +1896,32 @@ DeclName ClangImporter::Implementation::importFullName( // We cannot import when the base name is not an identifier. if (!Lexer::isIdentifier(baseName)) - return { }; + return result; // Get the identifier for the base name. Identifier baseNameId = SwiftContext.getIdentifier(baseName); - // If we have a non-function name, just return the base name. - if (!isFunction) return baseNameId; + // For functions, we need to form a complete name. + if (isFunction) { + // Convert the argument names. + SmallVector argumentNameIds; + for (auto argName : argumentNames) { + if (argumentNames.empty() || !Lexer::isIdentifier(argName)) { + argumentNameIds.push_back(Identifier()); + continue; + } - // 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)); } - argumentNameIds.push_back(SwiftContext.getIdentifier(argName)); + // Build the result. + result.Imported = DeclName(SwiftContext, baseNameId, argumentNameIds); + } else { + // For non-functions, just use the base name. + result.Imported = baseNameId; } - // Build the result. - return DeclName(SwiftContext, baseNameId, argumentNameIds); + return result; } Identifier diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index b5afec5c0bd16..4407544adea92 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -970,14 +970,14 @@ makeBitFieldAccessors(ClangImporter::Implementation &Impl, static Identifier getClangDeclName(ClangImporter::Implementation &Impl, const clang::TagDecl *decl) { // Import the name of this declaration. - Identifier name = Impl.importFullName(decl).getBaseName(); + 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).getBaseName(); + return Impl.importFullName(typedefForAnon).Imported.getBaseName(); } if (!decl->isRecord()) @@ -1427,7 +1427,7 @@ namespace { } Decl *VisitTypedefNameDecl(const clang::TypedefNameDecl *Decl) { - auto Name = Impl.importFullName(Decl).getBaseName(); + auto Name = Impl.importFullName(Decl).Imported.getBaseName(); if (Name.empty()) return nullptr; @@ -1742,7 +1742,7 @@ namespace { const clang::EnumDecl *clangEnum, EnumDecl *theEnum) { auto &context = Impl.SwiftContext; - auto name = Impl.importFullName(decl).getBaseName(); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -1795,7 +1795,7 @@ namespace { Decl *importOptionConstant(const clang::EnumConstantDecl *decl, const clang::EnumDecl *clangEnum, NominalTypeDecl *theStruct) { - auto name = Impl.importFullName(decl).getBaseName(); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -1820,7 +1820,7 @@ namespace { EnumElementDecl *original, const clang::EnumDecl *clangEnum, NominalTypeDecl *importedEnum) { - auto name = Impl.importFullName(alias).getBaseName(); + auto name = Impl.importFullName(alias).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2376,7 +2376,7 @@ namespace { Decl *VisitEnumConstantDecl(const clang::EnumConstantDecl *decl) { auto clangEnum = cast(decl->getDeclContext()); - auto name = Impl.importFullName(decl).getBaseName(); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2471,7 +2471,7 @@ namespace { return nullptr; } } - auto name = Impl.importFullName(decl).getBaseName(); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2502,11 +2502,13 @@ namespace { return nullptr; // Determine the name of the function. - bool hasCustomName; - DeclName name = Impl.importFullName(decl, &hasCustomName); - 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; @@ -2577,7 +2579,7 @@ namespace { Decl *VisitFieldDecl(const clang::FieldDecl *decl) { // Fields are imported as variables. - auto name = Impl.importFullName(decl).getBaseName(); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2623,7 +2625,7 @@ namespace { return nullptr; // Variables are imported as... variables. - auto name = Impl.importFullName(decl).getBaseName(); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -2797,7 +2799,9 @@ namespace { switch (Impl.getFactoryAsInit(objcClass, decl)) { case FactoryAsInitKind::AsInitializer: if (decl->hasAttr()) { - initName = Impl.importFullName(decl, &hasCustomName); + auto importedName = Impl.importFullName(decl); + initName = importedName.Imported; + hasCustomName = importedName.HasCustomName; break; } // FIXME: We probably should stop using this codepath. It won't ever @@ -2908,7 +2912,9 @@ namespace { bool hasCustomName; if (auto *customNameAttr = decl->getAttr()) { if (!customNameAttr->getName().startswith("init(")) { - name = Impl.importFullName(decl, &hasCustomName); + auto importedName = Impl.importFullName(decl); + name = importedName.Imported; + hasCustomName = importedName.HasCustomName; } } if (!name) { @@ -4781,7 +4787,7 @@ namespace { } Decl *VisitObjCProtocolDecl(const clang::ObjCProtocolDecl *decl) { - Identifier name = Impl.importFullName(decl).getBaseName(); + Identifier name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -4882,7 +4888,7 @@ namespace { } Decl *VisitObjCInterfaceDecl(const clang::ObjCInterfaceDecl *decl) { - auto name = Impl.importFullName(decl).getBaseName(); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -5098,7 +5104,7 @@ namespace { Decl *VisitObjCPropertyDecl(const clang::ObjCPropertyDecl *decl, DeclContext *dc) { - auto name = Impl.importFullName(decl).getBaseName(); + auto name = Impl.importFullName(decl).Imported.getBaseName(); if (name.empty()) return nullptr; @@ -6147,6 +6153,6 @@ ClangImporter::Implementation::getSpecialTypedefKind(clang::TypedefNameDecl *dec Identifier ClangImporter::getEnumConstantName(const clang::EnumConstantDecl *enumConstant){ - return Impl.importFullName(enumConstant).getBaseName(); + return Impl.importFullName(enumConstant).Imported.getBaseName(); } diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 3f3c7403e30de..d4f07e30ad5b8 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -1425,7 +1425,7 @@ Type ClangImporter::Implementation::importFunctionType( } // Figure out the name for this parameter. - Identifier bodyName = importFullName(param).getBaseName(); + Identifier bodyName = importFullName(param).Imported.getBaseName(); // Retrieve the argument name. Identifier name; @@ -2427,7 +2427,7 @@ Type ClangImporter::Implementation::importMethodType( } // Figure out the name for this parameter. - Identifier bodyName = importFullName(param).getBaseName(); + Identifier bodyName = importFullName(param).Imported.getBaseName(); // Figure out the name for this argument, which comes from the method name. Identifier name; diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 0f3a58e103f66..abab2cf4dfe71 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -744,6 +744,26 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// \brief Converts the given Swift identifier for Clang. clang::DeclarationName exportName(Identifier name); + /// Describes a name that was imported from Clang. + struct ImportedName { + /// The imported name. + DeclName Imported; + + /// Whether this name was explicitly specified via a Clang + /// swift_name attribute. + bool HasCustomName = false; + + /// For an initializer, the kind of initializer to import. + CtorInitializerKind InitKind; + + /// 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); } + }; + /// Imports the full name of the given Clang declaration into Swift. /// /// Note that this may result in a name very different from the Clang name, @@ -751,16 +771,12 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// /// \param D The Clang declaration whose name should be imported. /// - /// \param hasCustomName If non-null, will be set to indicate whether the - /// name was provided directly via a C swift_name attribute. - /// /// \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. - DeclName importFullName(const clang::NamedDecl *D, - bool *hasCustomName = nullptr, - clang::DeclContext **effectiveContext = nullptr); + ImportedName importFullName(const clang::NamedDecl *D, + clang::DeclContext **effectiveContext = nullptr); /// \brief Import the given Clang identifier into Swift. /// diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index 5256df494fed7..5baf6191f4f11 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -21,7 +21,7 @@ SWIFT_NAME(SomeClass) + (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 diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index c633983bf50b8..2039769c65625 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -14,7 +14,7 @@ // CHECK-NEXT: doubleProperty --> doubleProperty{{$}} // CHECK-NEXT: extensionMethodWithX --> extensionMethodWithX(_:y:) // CHECK-NEXT: floatProperty --> floatProperty{{$}} -// CHECK-NEXT: init --> init(float:), init(withDefault:), init(double:), init(withTry:){{$}} +// CHECK-NEXT: init --> init(float:), init(withDefault:), init(double:), init(withTry:), init(uint8:){{$}} // CHECK-NEXT: instanceMethodWithX --> instanceMethodWithX(_:y:z:) // CHECK-NEXT: protoInstanceMethodWithX --> protoInstanceMethodWithX(_:y:) // CHECK-NEXT: setAccessibilityFloat --> setAccessibilityFloat(_:) @@ -46,6 +46,8 @@ // CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:] // CHECK-NEXT: init(float:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithFloat:] +// CHECK-NEXT: init(uint8:): +// CHECK-NEXT: SNSomeClass: +[SNSomeClass buildWithUnsignedChar:] // CHECK-NEXT: init(withDefault:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithDefault] // CHECK-NEXT: init(withTry:): From 91d1c3b21a0dd00f8539c3d95f46e0cc153e19fe Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 30 Nov 2015 12:49:59 -0800 Subject: [PATCH 15/23] Clang importer: use importFullName for factory methods as initializers. Eliminates one of the redundant places where we map Objective-C selectors over to Swift names. --- lib/ClangImporter/ClangImporter.cpp | 65 ++++++------------------ lib/ClangImporter/ImportDecl.cpp | 78 +++++------------------------ lib/ClangImporter/ImporterImpl.h | 16 ------ 3 files changed, 28 insertions(+), 131 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 05655e72cc71c..5e161f9117abd 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -74,8 +74,6 @@ 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, @@ -1731,9 +1729,11 @@ auto ClangImporter::Implementation::importFullName( // 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() && !argName.empty()) { - // FIXME: Record what happened here for the caller? - argumentNames.push_back(argName); + if (argumentNames.empty()) { + if (!argName.empty()) { + // FIXME: Record what happened here for the caller? + argumentNames.push_back(argName); + } } else { argumentNames[0] = argName; } @@ -1881,6 +1881,12 @@ auto ClangImporter::Implementation::importFullName( 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 (result.InitKind == CtorInitializerKind::Factory || + result.InitKind == CtorInitializerKind::ConvenienceFactory) + return result; + // FIXME: Record that we did this. argumentNames.push_back("__"); } else { @@ -2196,50 +2202,6 @@ ClangImporter::Implementation::mapSelectorToDeclName(ObjCSelector selector, 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( @@ -2676,6 +2638,11 @@ bool ClangImporter::Implementation::shouldImportAsInitializer( 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(); diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 4407544adea92..aa55c88dbd77e 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"); @@ -2783,75 +2781,22 @@ namespace { 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; - - // Check whether we're allowed to try. - switch (Impl.getFactoryAsInit(objcClass, decl)) { - case FactoryAsInitKind::AsInitializer: - if (decl->hasAttr()) { - auto importedName = Impl.importFullName(decl); - initName = importedName.Imported; - hasCustomName = importedName.HasCustomName; - 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; - } + // Import the full name of the method. + auto importedName = Impl.importFullName(decl); - if (!initName) - return None; + // Check that we imported an initializer name. + DeclName initName = importedName; + if (initName.getBaseName() != Impl.SwiftContext.Id_init) 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; + // ... that came from a factory method. + if (importedName.InitKind != CtorInitializerKind::Factory && + importedName.InitKind != CtorInitializerKind::ConvenienceFactory) return None; - } bool redundant = false; - auto result = importConstructor(decl, dc, false, initKind, + auto result = importConstructor(decl, dc, false, importedName.InitKind, /*required=*/false, selector, initName, - hasCustomName, + importedName.HasCustomName, {decl->param_begin(), decl->param_size()}, decl->isVariadic(), redundant); if (result) @@ -2865,7 +2810,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 << ":"; } diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index abab2cf4dfe71..5be7f93bcf421 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -809,22 +809,6 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation 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); From 13c1805b9092f7e46edfb8461e6ab700028524b2 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 30 Nov 2015 13:33:16 -0800 Subject: [PATCH 16/23] Clang importer: teach importFullName about dropping certain variadics. This moves the hack used for UIActionSheet and UIAlertView's variadic designated initializers over into importFullName(). --- lib/ClangImporter/ClangImporter.cpp | 52 +++++++++++++++++++- lib/ClangImporter/ImporterImpl.h | 5 ++ test/IDE/Inputs/swift_name_objc.h | 4 ++ test/IDE/dump_swift_lookup_tables_objc.swift | 7 ++- 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 5e161f9117abd..cebca9af6a100 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1594,6 +1594,46 @@ StringRef ClangImporter::Implementation::getEnumConstantNamePrefix( 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; +} + auto ClangImporter::Implementation::importFullName( const clang::NamedDecl *D, clang::DeclContext **effectiveContext) -> ImportedName { @@ -1694,8 +1734,16 @@ auto ClangImporter::Implementation::importFullName( baseName = "init"; else baseName = selector.getNameForSlot(0); - for (unsigned index = 0, numArgs = selector.getNumArgs(); index != numArgs; - ++index) { + + // 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; + } + + for (unsigned index = 0; index != numArgs; ++index) { if (index == 0) { argumentNames.push_back(StringRef()); } else { diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 5be7f93bcf421..d0583956e1f3a 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -753,6 +753,11 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// 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; diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index 5baf6191f4f11..ee46925579911 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -49,3 +49,7 @@ SWIFT_NAME(SomeProtocol) @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 diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index 2039769c65625..22df4e156da8e 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -9,12 +9,13 @@ // CHECK-NEXT: SNCollisionProtocol --> SNCollisionProtocol // CHECK-NEXT: SomeClass --> SomeClass // CHECK-NEXT: SomeProtocol --> SomeProtocol +// CHECK-NEXT: UIActionSheet --> UIActionSheet // 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:){{$}} +// 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: protoInstanceMethodWithX --> protoInstanceMethodWithX(_:y:) // CHECK-NEXT: setAccessibilityFloat --> setAccessibilityFloat(_:) @@ -30,6 +31,8 @@ // CHECK-NEXT: TU: SNSomeClass // CHECK-NEXT: SomeProtocol: // CHECK-NEXT: TU: SNSomeProtocol +// CHECK-NEXT: UIActionSheet: +// CHECK-NEXT: TU: UIActionSheet // CHECK-NEXT: accessibilityFloat(): // CHECK-NEXT: NSAccessibility: -[NSAccessibility accessibilityFloat] // CHECK-NEXT: categoryMethodWithX(_:y:): @@ -46,6 +49,8 @@ // CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:] // CHECK-NEXT: init(float:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithFloat:] +// 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:): From 3102e2314c0f4f0afb2824ea3fcd7e7f25b7334e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 30 Nov 2015 13:56:44 -0800 Subject: [PATCH 17/23] Clang importer: switch init method importing over to importFullName. Yet more name-importing refactoring toward eliminating some redundant code. As a drive-by, happens to fix swift_private imports for no-parameter init methods. --- lib/ClangImporter/ImportDecl.cpp | 63 ++++--------------- test/ClangModules/Inputs/SwiftPrivateAttr.txt | 2 +- 2 files changed, 12 insertions(+), 53 deletions(-) diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index aa55c88dbd77e..da2d854f01b87 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -3108,54 +3108,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 @@ -3188,14 +3140,21 @@ 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.slice(1); + variadic = false; + } bool redundant; return importConstructor(objcMethod, dc, implicit, kind, required, - selector, name, /*customName=*/false, params, + selector, importedName, + importedName.HasCustomName, params, variadic, redundant); } 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) From 4c50598916ce002f215ad36e71517804d2085a2b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 30 Nov 2015 14:48:05 -0800 Subject: [PATCH 18/23] Clang importer: switch method name import to importFullName(). This lets us kill the redundant Objective-C selector to DeclName mapping used only for methods. --- lib/ClangImporter/ClangImporter.cpp | 215 +++++----------------------- lib/ClangImporter/ImportDecl.cpp | 24 +--- lib/ClangImporter/ImporterImpl.h | 25 ++-- 3 files changed, 50 insertions(+), 214 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index cebca9af6a100..347ede5beda76 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,27 +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(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; @@ -725,7 +703,7 @@ void ClangImporter::Implementation::addEntryToLookupTable( if (!suppressDecl) { // If we have a name to import as, add this entry to the table. clang::DeclContext *effectiveContext; - if (DeclName name = importFullName(named, &effectiveContext)) { + if (DeclName name = importFullName(named, None, &effectiveContext)) { table.addEntry(name, named, effectiveContext); } } @@ -1636,6 +1614,7 @@ static bool shouldMakeSelectorNonVariadic(clang::Selector selector) { auto ClangImporter::Implementation::importFullName( const clang::NamedDecl *D, + ImportNameOptions options, clang::DeclContext **effectiveContext) -> ImportedName { ImportedName result; @@ -1680,20 +1659,36 @@ auto ClangImporter::Implementation::importFullName( // If we have a swift_name attribute, use that. if (auto *nameAttr = D->getAttr()) { + 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. if (auto method = dyn_cast(D)) { unsigned initPrefixLength; - if (nameAttr->getName().startswith("init(") && - !shouldImportAsInitializer(method, initPrefixLength, result.InitKind)) - return { }; + 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; + } } - result.HasCustomName = true; - result.Imported = parseDeclName(SwiftContext, nameAttr->getName()); - return result; + if (!skipCustomName) { + result.HasCustomName = true; + result.Imported = parseDeclName(SwiftContext, nameAttr->getName()); + return result; + } } // For empty names, there is nothing to do. @@ -1728,6 +1723,15 @@ auto ClangImporter::Implementation::importFullName( 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) @@ -1997,139 +2001,6 @@ ClangImporter::Implementation::importIdentifier( return SwiftContext.getIdentifier(name); } -/// 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; - } - } - - /// 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, {}); - - SmallString<32> newName{"__"}; - newName.append(nameText); - return DeclName(ctx, ctx.getIdentifier(newName.str()), {}); - } - - // Simple case for initializers. - if (nameText == "init" && !isSwiftPrivate) - return DeclName(ctx, name, { }); - - // 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); - } - - // 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); - } - 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); -} - namespace { /// Function object used to create Clang selectors from strings. class CreateSelector { @@ -2230,26 +2101,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; -} - /// Translate the "nullability" notion from API notes into an optional type /// kind. OptionalTypeKind ClangImporter::Implementation::translateNullability( diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index da2d854f01b87..17669f000406c 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2854,22 +2854,11 @@ namespace { if (methodAlreadyImported(selector, isInstance, dc)) return nullptr; - DeclName name; - bool hasCustomName; - if (auto *customNameAttr = decl->getAttr()) { - if (!customNameAttr->getName().startswith("init(")) { - auto importedName = Impl.importFullName(decl); - name = importedName.Imported; - hasCustomName = importedName.HasCustomName; - } - } - 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?"); @@ -2897,6 +2886,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(), @@ -2905,7 +2895,7 @@ namespace { decl->isVariadic(), decl->hasAttr(), isInSystemModule(dc), - hasCustomName, + importedName.HasCustomName, bodyPatterns, name, errorConvention, diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index d0583956e1f3a..928d2042fb519 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -310,9 +310,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); @@ -769,6 +766,15 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation 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 very different from the Clang name, @@ -781,6 +787,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// 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. @@ -802,18 +809,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); - /// \brief Import the given Swift source location into Clang. clang::SourceLocation exportSourceLoc(SourceLoc loc); From 2d7044c024fb08f5463abf30bb4463b36b2e9eea Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 1 Dec 2015 23:55:24 -0800 Subject: [PATCH 19/23] Clang importer: move name translation for throwing methods into importFullName. The translation from the Objective-C NSError** convention into Swift throwing methods alters the names of methods. Move that computation into importFullName. This should be NFC refactoring for everything except the Swift name lookup tables, which will now correctly reflect this name translation. --- include/swift/AST/Identifier.h | 28 - lib/ClangImporter/ClangImporter.cpp | 563 ++++++++++++++++--- lib/ClangImporter/ImportDecl.cpp | 25 +- lib/ClangImporter/ImportType.cpp | 403 +++---------- lib/ClangImporter/ImporterImpl.h | 28 +- test/IDE/Inputs/swift_name_objc.h | 10 + test/IDE/dump_swift_lookup_tables_objc.swift | 15 + 7 files changed, 610 insertions(+), 462 deletions(-) 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/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 347ede5beda76..9a9616b21fe09 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1305,45 +1305,49 @@ ClangImporter::Implementation::exportName(Identifier name) { return ident; } -/// Parse a stringified Swift DeclName, e.g. "init(frame:)". -static DeclName parseDeclName(ASTContext &ctx, StringRef Name) { +/// 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 ctx.getIdentifier(Name); + return Name; - return {}; + return ""; } + IsFunctionName = true; + StringRef BaseName, Parameters; std::tie(BaseName, Parameters) = Name.split('('); if (!Lexer::isIdentifier(BaseName) || BaseName == "_") - return {}; + return ""; if (Parameters.empty()) - return {}; + return ""; Parameters = Parameters.drop_back(); // ')' - Identifier BaseID = ctx.getIdentifier(BaseName); if (Parameters.empty()) - return DeclName(ctx, BaseID, {}); + return BaseName; if (Parameters.back() != ':') - return {}; + return ""; - SmallVector ParamIDs; do { StringRef NextParam; std::tie(NextParam, Parameters) = Parameters.split(':'); if (!Lexer::isIdentifier(NextParam)) - return {}; + return ""; Identifier NextParamID; - if (NextParam != "_") - NextParamID = ctx.getIdentifier(NextParam); - ParamIDs.push_back(NextParamID); + if (NextParam == "_") + ArgNames.push_back(""); + else + ArgNames.push_back(NextParam); } while (!Parameters.empty()); - return DeclName(ctx, BaseID, ParamIDs); + return BaseName; } /// \brief Returns the common prefix of two strings at camel-case word @@ -1612,6 +1616,389 @@ static bool shouldMakeSelectorNonVariadic(clang::Selector selector) { 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, @@ -1657,6 +2044,37 @@ auto ClangImporter::Implementation::importFullName( } } + // 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()) { bool skipCustomName = false; @@ -1665,7 +2083,9 @@ auto ClangImporter::Implementation::importFullName( // 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. - if (auto method = dyn_cast(D)) { + bool isInitializer = false; + auto method = dyn_cast(D); + if (method) { unsigned initPrefixLength; if (nameAttr->getName().startswith("init(")) { if (!shouldImportAsInitializer(method, initPrefixLength, @@ -1679,14 +2099,39 @@ auto ClangImporter::Implementation::importFullName( // custom name. if (options.contains(ImportNameFlags::SuppressFactoryMethodAsInit) && (result.InitKind == CtorInitializerKind::Factory || - result.InitKind == CtorInitializerKind::ConvenienceFactory)) + 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 = parseDeclName(SwiftContext, nameAttr->getName()); + 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 result; } } @@ -1739,12 +2184,19 @@ auto ClangImporter::Implementation::importFullName( else baseName = selector.getNameForSlot(0); + // Get the parameters. + ArrayRef 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.slice(1); } for (unsigned index = 0; index != numArgs; ++index) { @@ -1791,6 +2243,10 @@ auto ClangImporter::Implementation::importFullName( } } + result.ErrorInfo = considerErrorImport(*this, objcMethod, baseName, + argumentNames, params, isInitializer, + /*hasCustomName=*/false); + isFunction = true; break; } @@ -1952,33 +2408,7 @@ auto ClangImporter::Implementation::importFullName( } } - // We cannot import when the base name is not an identifier. - if (!Lexer::isIdentifier(baseName)) - return result; - - // Get the identifier for the base name. - Identifier baseNameId = SwiftContext.getIdentifier(baseName); - - // For functions, we need to form a complete name. - if (isFunction) { - // 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. - result.Imported = DeclName(SwiftContext, baseNameId, argumentNameIds); - } else { - // For non-functions, just use the base name. - result.Imported = baseNameId; - } - + result.Imported = formDeclName(baseName, argumentNames, isFunction); return result; } @@ -2001,49 +2431,6 @@ ClangImporter::Implementation::importIdentifier( return SwiftContext.getIdentifier(name); } -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; - - public: - CreateMethodName(ASTContext &ctx, StringRef baseName) - : Ctx(ctx) - { - BaseName = Ctx.getIdentifier(baseName); - } - - template - DeclName operator()(Strings ...strings) const { - Identifier pieces[sizeof...(Strings)] = { - (strings[0]? Ctx.getIdentifier(strings) : Identifier())... - }; - - return DeclName(Ctx, BaseName, pieces); - } - }; -} - ObjCSelector ClangImporter::Implementation::importSelector( clang::Selector selector) { auto &ctx = SwiftContext; diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 17669f000406c..849aa6240b2b7 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -1179,7 +1179,8 @@ static StringRef getImportedCFTypeName(StringRef name) { return name; } -static bool isCFTypeDecl(const clang::TypedefNameDecl *Decl) { +bool ClangImporter::Implementation::isCFTypeDecl( + const clang::TypedefNameDecl *Decl) { if (auto pointee = CFPointeeInfo::classifyTypedef(Decl)) return pointee.isValid(); return false; @@ -2795,8 +2796,8 @@ namespace { bool redundant = false; auto result = importConstructor(decl, dc, false, importedName.InitKind, - /*required=*/false, selector, initName, - importedName.HasCustomName, + /*required=*/false, selector, + importedName, {decl->param_begin(), decl->param_size()}, decl->isVariadic(), redundant); if (result) @@ -2895,8 +2896,8 @@ namespace { decl->isVariadic(), decl->hasAttr(), isInSystemModule(dc), - importedName.HasCustomName, bodyPatterns, + importedName, name, errorConvention, kind); @@ -3143,8 +3144,7 @@ namespace { bool redundant; return importConstructor(objcMethod, dc, implicit, kind, required, - selector, importedName, - importedName.HasCustomName, params, + selector, importedName, params, variadic, redundant); } @@ -3225,6 +3225,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 @@ -3243,8 +3245,7 @@ namespace { Optional kindIn, bool required, ObjCSelector selector, - DeclName name, - bool hasCustomName, + ImportedName importedName, ArrayRef args, bool variadic, bool &redundant) { @@ -3293,14 +3294,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); @@ -4501,14 +4503,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)) { diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index d4f07e30ad5b8..2b2194cf89393 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -151,6 +151,7 @@ namespace { }; bool canImportAsOptional(ImportHint hint) { + // See also ClangImporter.cpp's canImportAsOptional. switch (hint) { case ImportHint::None: case ImportHint::BOOL: @@ -1543,330 +1544,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) { @@ -2190,13 +1867,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 +1976,9 @@ Type ClangImporter::Implementation::importMethodType( } // Import the result type. + CanType origSwiftResultTy; Type swiftResultTy; + auto errorInfo = importedName.ErrorInfo; if (isPropertyGetter) { swiftResultTy = importPropertyType(property, isFromSystemModule); } else { @@ -2271,6 +2005,13 @@ Type ClangImporter::Implementation::importMethodType( /*isFullyBridgeable*/true, OptionalityOfReturn); + // Adjust the result type for a throwing function. + if (errorInfo) { + origSwiftResultTy = swiftResultTy->getCanonicalType(); + swiftResultTy = adjustResultTypeForThrowingFunction(*errorInfo, + swiftResultTy); + } + if (swiftResultTy && clangDecl->getMethodFamily() == clang::OMF_performSelector) { // performSelector methods that return 'id' should be imported into Swift @@ -2290,13 +2031,12 @@ 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 && + if (OmitNeedlessWords && !importedName.HasCustomName && clangDecl && (kind == SpecialMethodKind::Regular || kind == SpecialMethodKind::Constructor)) { methodName = omitNeedlessWordsInFunctionName( @@ -2406,7 +2146,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) { @@ -2492,7 +2232,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 +2259,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 928d2042fb519..cf627a432a957 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -718,6 +718,9 @@ 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); @@ -741,6 +744,19 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// \brief Converts the given Swift identifier for Clang. clang::DeclarationName exportName(Identifier name); + /// 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. @@ -758,6 +774,10 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// For an initializer, the kind of initializer to import. CtorInitializerKind InitKind; + /// 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; } @@ -1103,10 +1123,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. @@ -1117,9 +1136,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); diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index ee46925579911..b342fdcd5efe3 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -53,3 +53,13 @@ SWIFT_NAME(SomeProtocol) @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 diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index 22df4e156da8e..eec5d2f0beb87 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -5,6 +5,8 @@ // CHECK: Base -> full name mappings: // CHECK-NEXT: NSAccessibility --> NSAccessibility +// CHECK-NEXT: NSError --> NSError +// CHECK-NEXT: NSErrorImports --> NSErrorImports // CHECK-NEXT: SNCollision --> SNCollision // CHECK-NEXT: SNCollisionProtocol --> SNCollisionProtocol // CHECK-NEXT: SomeClass --> SomeClass @@ -17,12 +19,18 @@ // 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: 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: @@ -45,10 +53,13 @@ // 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:): @@ -59,6 +70,10 @@ // 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(_:): From 50eea9cdbcd2c96031c0a02cef196cc9e1925d06 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 2 Dec 2015 09:43:29 -0800 Subject: [PATCH 20/23] Clang importer: handle omit-needless-words for methods in importFullName. --- lib/ClangImporter/ClangImporter.cpp | 23 ++++- lib/ClangImporter/ImportType.cpp | 97 +++++--------------- lib/ClangImporter/ImporterImpl.h | 29 ++++-- test/IDE/dump_swift_lookup_tables_objc.swift | 10 ++ 4 files changed, 71 insertions(+), 88 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 9a9616b21fe09..c47b32e151d29 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -2146,6 +2146,7 @@ auto ClangImporter::Implementation::importFullName( StringRef baseName; SmallVector argumentNames; SmallString<16> selectorSplitScratch; + ArrayRef params; switch (D->getDeclName().getNameKind()) { case clang::DeclarationName::CXXConstructorName: case clang::DeclarationName::CXXConversionFunctionName: @@ -2185,10 +2186,7 @@ auto ClangImporter::Implementation::importFullName( baseName = selector.getNameForSlot(0); // Get the parameters. - ArrayRef params{ - objcMethod->param_begin(), - objcMethod->param_end() - }; + 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. @@ -2378,6 +2376,23 @@ auto ClangImporter::Implementation::importFullName( omitNeedlessWordsScratch); } } + + // 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); + } } // If this declaration has the swift_private attribute, prepend "__" to the diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 2b2194cf89393..f9056bcaeb700 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" @@ -1246,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; @@ -1705,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; @@ -1752,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); @@ -1777,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. @@ -2035,20 +1998,6 @@ Type ClangImporter::Implementation::importMethodType( llvm::SmallBitVector nonNullArgs = getNonNullArgs(clangDecl, params); - // If we should omit needless words and don't have a custom name, do so. - if (OmitNeedlessWords && !importedName.HasCustomName && 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; diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index cf627a432a957..2c18452390b06 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -32,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 @@ -730,16 +731,18 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation 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); @@ -1108,6 +1111,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 diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index eec5d2f0beb87..57b88ca3df193 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -1,6 +1,9 @@ // 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: @@ -78,3 +81,10 @@ // 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: methodWith --> methodWith(_:) + +// CHECK-OMIT-NEEDLESS-WORDS: Full name -> entry mappings: +// CHECK-OMIT-NEEDLESS-WORDS: methodWith(_:): +// CHECK-OMIT-NEEDLESS-WORDS: NSErrorImports: -[NSErrorImports methodWithFloat:error:] From 2ea58f93b04cc39683ad3fcf8d5e99969c69f009 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 2 Dec 2015 12:42:00 -0800 Subject: [PATCH 21/23] Clang importer: handle CF type renaming in importFullName. Ensures that the Swift lookup tables get transformed name for imported CF types, including original name (which is still available). Otherwise, this is an NFC refactoring that gets the last of the naming tricks into importFullName. --- lib/Basic/StringExtras.cpp | 10 ++- lib/ClangImporter/ClangImporter.cpp | 83 +++++++++++++++----- lib/ClangImporter/ImportDecl.cpp | 78 +++++++++++------- lib/ClangImporter/ImportType.cpp | 3 +- lib/ClangImporter/ImporterImpl.h | 9 ++- test/IDE/Inputs/swift_name_objc.h | 3 + test/IDE/dump_swift_lookup_tables_objc.swift | 12 +++ 7 files changed, 145 insertions(+), 53 deletions(-) diff --git a/lib/Basic/StringExtras.cpp b/lib/Basic/StringExtras.cpp index a1d51488e53b3..79b6e506e1db5 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; diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index c47b32e151d29..6f11a9636b67d 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -703,8 +703,12 @@ void ClangImporter::Implementation::addEntryToLookupTable( if (!suppressDecl) { // If we have a name to import as, add this entry to the table. clang::DeclContext *effectiveContext; - if (DeclName name = importFullName(named, None, &effectiveContext)) { - table.addEntry(name, named, 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); } } @@ -2321,6 +2325,19 @@ auto ClangImporter::Implementation::importFullName( } } + // 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) { @@ -2398,32 +2415,58 @@ auto ClangImporter::Implementation::importFullName( // If this declaration has the swift_private attribute, prepend "__" to the // appropriate place. SmallString<16> swiftPrivateScratch; + SmallString<16> swiftPrivateAliasScratch; if (hasSwiftPrivate(D)) { - swiftPrivateScratch = "__"; + // 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; - 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 (result.InitKind == CtorInitializerKind::Factory || - result.InitKind == CtorInitializerKind::ConvenienceFactory) - return result; - - // FIXME: Record that we did this. - argumentNames.push_back("__"); + // FIXME: Record that we did this. + argumentNames.push_back("__"); + } else { + scratch += argumentNames[0]; + argumentNames[0] = scratch; + } } else { - swiftPrivateScratch += argumentNames[0]; - argumentNames[0] = swiftPrivateScratch; + // For all other entities, prepend "__" to the base name. + scratch += baseName; + baseName = scratch; } - } else { - // For all other entities, prepend "__" to the base name. - swiftPrivateScratch += baseName; - baseName = swiftPrivateScratch; + + return false; + }; + + // Make the name private. + if (makeNamePrivate(isInitializer, baseName, argumentNames, + result.InitKind, swiftPrivateScratch)) + return result; + + // If we have an alias name, make it private as well. + if (!aliasBaseName.empty()) { + (void)makeNamePrivate(aliasIsInitializer, aliasBaseName, + aliasArgumentNames, CtorInitializerKind::Designated, + swiftPrivateAliasScratch); } } result.Imported = formDeclName(baseName, argumentNames, isFunction); + result.Alias = formDeclName(aliasBaseName, aliasArgumentNames, + aliasIsFunction); return result; } diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 849aa6240b2b7..b61aeca2de2c5 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -1181,15 +1181,34 @@ static StringRef getImportedCFTypeName(StringRef name) { bool ClangImporter::Implementation::isCFTypeDecl( const clang::TypedefNameDecl *Decl) { - if (auto pointee = CFPointeeInfo::classifyTypedef(Decl)) - return pointee.isValid(); + 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 ""; } @@ -1364,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(); @@ -1426,7 +1441,8 @@ namespace { } Decl *VisitTypedefNameDecl(const clang::TypedefNameDecl *Decl) { - auto Name = Impl.importFullName(Decl).Imported.getBaseName(); + auto importedName = Impl.importFullName(Decl); + auto Name = importedName.Imported.getBaseName(); if (Name.empty()) return nullptr; @@ -1448,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()) { @@ -1460,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())) @@ -1473,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. diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index f9056bcaeb700..57e44703c7ccb 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -1967,9 +1967,8 @@ Type ClangImporter::Implementation::importMethodType( allowNSUIntegerAsIntInResult, /*isFullyBridgeable*/true, OptionalityOfReturn); - // Adjust the result type for a throwing function. - if (errorInfo) { + if (swiftResultTy && errorInfo) { origSwiftResultTy = swiftResultTy->getCanonicalType(); swiftResultTy = adjustResultTypeForThrowingFunction(*errorInfo, swiftResultTy); diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 2c18452390b06..e453704169f95 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -724,7 +724,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// 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. @@ -765,6 +766,10 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// 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; @@ -775,7 +780,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation bool DroppedVariadic = false; /// For an initializer, the kind of initializer to import. - CtorInitializerKind InitKind; + CtorInitializerKind InitKind = CtorInitializerKind::Designated; /// For names that map Objective-C error handling conventions into /// throwing Swift methods, describes how the mapping is performed. diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index b342fdcd5efe3..ab82e08976635 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -63,3 +63,6 @@ SWIFT_NAME(SomeProtocol) - (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_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index 57b88ca3df193..60e16d2cd948a 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -7,6 +7,9 @@ // 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 @@ -15,6 +18,7 @@ // 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{{$}} @@ -28,6 +32,12 @@ // 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: @@ -44,6 +54,8 @@ // 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:): From d9ce07fa419a99b8fed7530a2eb83021d6ea1ce6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 2 Dec 2015 13:21:54 -0800 Subject: [PATCH 22/23] Clang importer: when dropping the variadic parameter, drop the variadic parameter. Fixes iOS build. --- lib/ClangImporter/ClangImporter.cpp | 2 +- lib/ClangImporter/ImportDecl.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 6f11a9636b67d..6930ed590ccc8 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -2198,7 +2198,7 @@ auto ClangImporter::Implementation::importFullName( if (objcMethod->isVariadic() && shouldMakeSelectorNonVariadic(selector)) { --numArgs; result.DroppedVariadic = true; - params = params.slice(1); + params = params.drop_back(1); } for (unsigned index = 0; index != numArgs; ++index) { diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index b61aeca2de2c5..c8acbb3ffbb2f 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -3162,7 +3162,7 @@ namespace { // If we dropped the variadic, handle it now. if (importedName.DroppedVariadic) { - params = params.slice(1); + params = params.drop_back(1); variadic = false; } From 1e432568bc7ce4520c453fb541a7acb8d273f89f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 3 Dec 2015 10:19:58 -0800 Subject: [PATCH 23/23] Clang importer: Swift 2 lowercased subsequent argument names coming from Objective-C selectors. Mimic this. --- include/swift/Basic/StringExtras.h | 26 ++++++++++++++------ lib/Basic/StringExtras.cpp | 5 ++-- lib/ClangImporter/ClangImporter.cpp | 11 ++++++++- test/IDE/Inputs/swift_name_objc.h | 2 +- test/IDE/dump_swift_lookup_tables_objc.swift | 3 ++- 5 files changed, 34 insertions(+), 13 deletions(-) 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/lib/Basic/StringExtras.cpp b/lib/Basic/StringExtras.cpp index 79b6e506e1db5..0716959bf4c2c 100644 --- a/lib/Basic/StringExtras.cpp +++ b/lib/Basic/StringExtras.cpp @@ -442,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/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 6930ed590ccc8..95e900def22a2 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -2150,6 +2150,7 @@ auto ClangImporter::Implementation::importFullName( StringRef baseName; SmallVector argumentNames; SmallString<16> selectorSplitScratch; + StringScratchSpace stringScratch; ArrayRef params; switch (D->getDeclName().getNameKind()) { case clang::DeclarationName::CXXConstructorName: @@ -2205,7 +2206,15 @@ auto ClangImporter::Implementation::importFullName( if (index == 0) { argumentNames.push_back(StringRef()); } else { - argumentNames.push_back(selector.getNameForSlot(index)); + 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); } } diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index ab82e08976635..deea6f29a68fc 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -17,7 +17,7 @@ SWIFT_NAME(SomeClass) @interface SNSomeClass : NSObject - (instancetype)initWithFloat:(float)f; - (instancetype)initWithDefault; -- (void)instanceMethodWithX:(float)x y:(float)y z:(float)z; +- (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:)); diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index 60e16d2cd948a..cb1160bce5214 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -84,7 +84,7 @@ // CHECK-NEXT: init(withTry:): // CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithTry:] // CHECK-NEXT: instanceMethodWithX(_:y:z:): -// CHECK-NEXT: SNSomeClass: -[SNSomeClass instanceMethodWithX:y:z:] +// CHECK-NEXT: SNSomeClass: -[SNSomeClass instanceMethodWithX:Y:Z:] // CHECK-NEXT: method(): // CHECK-NEXT: NSErrorImports: -[NSErrorImports methodAndReturnError:] // CHECK-NEXT: methodWithFloat(_:): @@ -95,6 +95,7 @@ // 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: