diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -15,9 +15,13 @@ #include "index/SymbolID.h" #include "clang/AST/Decl.h" +#include "clang/AST/NestedNameSpecifier.h" #include "clang/Basic/SourceLocation.h" #include "clang/Lex/MacroInfo.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringRef.h" +#include +#include namespace clang { class SourceManager; @@ -111,6 +115,35 @@ /// void foo() -> returns null NestedNameSpecifierLoc getQualifierLoc(const NamedDecl &ND); +/// Gets the nested name specifier necessary for spelling \p ND in \p +/// DestContext, at \p InsertionPoint. It selects the shortest suffix of \p ND +/// such that it is visible in \p DestContext. +/// Returns null if no qualification is necessary. For example, if you want to +/// qualify clang::clangd::bar::foo in clang::clangd::x, this function will +/// return bar. + +/// This version considers all the using namespace directives before \p +/// InsertionPoint. i.e, if you have `using namespace +/// clang::clangd::bar`, this function will return null, for the example above +/// since no qualification is necessary in that case. +std::string getQualification(ASTContext &Context, + const DeclContext *DestContext, + SourceLocation InsertionPoint, + const NamedDecl *ND); + +/// This function uses the \p VisibleNamespaces as an early exit mechanism +/// instead of semantic using namespace directives. It can be useful if there's +/// no AST for the DestContext, but some pseudo-parsing is available. +/// Elements in VisibleNamespaces should be in the form: `ns::`, with trailing +/// "::". +/// Note that this is just textual and might be incorrect. e.g. when there are +/// two namespaces ns1::a and ns2::a, the function will early exit if "a::" is +/// present in \p VisibleNamespaces, no matter whether it is from ns1:: or ns2:: +std::string getQualification(ASTContext &Context, + const DeclContext *DestContext, + SourceLocation InsertionPoint, const NamedDecl *ND, + llvm::ArrayRef VisibleNamespaces); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -11,6 +11,7 @@ #include "SourceCode.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/NestedNameSpecifier.h" @@ -20,10 +21,15 @@ #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" #include "clang/Index/USRGeneration.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/raw_ostream.h" +#include +#include namespace clang { namespace clangd { @@ -65,6 +71,75 @@ isTemplateSpecializationKind(D, Kind); } +// Store all UsingDirectiveDecls in parent contexts of DestContext, that were +// introduced before InsertionPoint. +llvm::DenseSet +getUsingNamespceDirectives(const DeclContext *DestContext, + SourceLocation Until) { + const auto &SM = DestContext->getParentASTContext().getSourceManager(); + llvm::DenseSet VisibleNamespaceDecls; + for (const auto *DC = DestContext; DC; DC = DC->getLookupParent()) { + for (const auto *D : DC->decls()) { + if (!SM.isWrittenInSameFile(D->getLocation(), Until) || + !SM.isBeforeInTranslationUnit(D->getLocation(), Until)) + continue; + if (auto *UDD = llvm::dyn_cast(D)) { + VisibleNamespaceDecls.insert( + UDD->getNominatedNamespace()->getCanonicalDecl()); + } + } + } + return VisibleNamespaceDecls; +} + +// Goes over all parents of SourceContext until we find a comman ancestor for +// DestContext and SourceContext. Any qualifier including and above common +// ancestor is redundant, therefore we stop at lowest common ancestor. +// In addition to that stops early whenever IsVisible returns true. This can be +// used to implement support for "using namespace" decls. +std::string +getQualification(ASTContext &Context, const DeclContext *DestContext, + const DeclContext *SourceContext, + llvm::function_ref IsVisible) { + std::vector Parents; + bool ReachedNS = false; + for (const DeclContext *CurContext = SourceContext; CurContext; + CurContext = CurContext->getLookupParent()) { + // Stop once we reach a common ancestor. + if (CurContext->Encloses(DestContext)) + break; + + NestedNameSpecifier *NNS = nullptr; + if (auto *TD = llvm::dyn_cast(CurContext)) { + // There can't be any more tag parents after hitting a namespace. + assert(!ReachedNS); + NNS = NestedNameSpecifier::Create(Context, nullptr, false, + TD->getTypeForDecl()); + } else { + ReachedNS = true; + auto *NSD = llvm::cast(CurContext); + NNS = NestedNameSpecifier::Create(Context, nullptr, NSD); + // Anonymous and inline namespace names are not spelled while qualifying a + // name, so skip those. + if (NSD->isAnonymousNamespace() || NSD->isInlineNamespace()) + continue; + } + // Stop if this namespace is already visible at DestContext. + if (IsVisible(NNS)) + break; + + Parents.push_back(NNS); + } + + // Go over name-specifiers in reverse order to create necessary qualification, + // since we stored inner-most parent first. + std::string Result; + llvm::raw_string_ostream OS(Result); + for (const auto *Parent : llvm::reverse(Parents)) + Parent->print(OS, Context.getPrintingPolicy()); + return OS.str(); +} + } // namespace bool isImplicitTemplateInstantiation(const NamedDecl *D) { @@ -244,6 +319,37 @@ printNamespaceScope(Context) ); } +std::string getQualification(ASTContext &Context, + const DeclContext *DestContext, + SourceLocation InsertionPoint, + const NamedDecl *ND) { + auto VisibleNamespaceDecls = + getUsingNamespceDirectives(DestContext, InsertionPoint); + return getQualification(Context, DestContext, ND->getDeclContext(), + [&](NestedNameSpecifier *NNS) { + return llvm::any_of( + VisibleNamespaceDecls, + [NNS](const NamespaceDecl *NSD) { + return NSD == NNS->getAsNamespace(); + }); + }); +} + +std::string getQualification(ASTContext &Context, + const DeclContext *DestContext, + SourceLocation InsertionPoint, const NamedDecl *ND, + llvm::ArrayRef VisibleNamespaces) { + return getQualification( + Context, DestContext, ND->getDeclContext(), + [&](NestedNameSpecifier *NNS) { + return llvm::any_of(VisibleNamespaces, [&](llvm::StringRef Namespace) { + std::string NS; + llvm::raw_string_ostream OS(NS); + NNS->print(OS, Context.getPrintingPolicy()); + return OS.str() == Namespace; + }); + }); +} } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp --- a/clang-tools-extra/clangd/unittests/ASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp @@ -7,7 +7,17 @@ //===----------------------------------------------------------------------===// #include "AST.h" +#include "Annotations.h" +#include "ParsedAST.h" +#include "TestTU.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "llvm/ADT/StringRef.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include +#include +#include namespace clang { namespace clangd { @@ -36,6 +46,103 @@ "testns1::TestClass", "testns1")); } +TEST(ClangdAST, GetQualification) { + const struct { + llvm::StringRef Test; + std::vector Qualifications; + std::vector VisibleNamespaces; + } Cases[] = { + { + R"cpp( + namespace ns1 { namespace ns2 { class Foo {}; } } + void insert(); // ns1::ns2::Foo + namespace ns1 { + void insert(); // ns2::Foo + namespace ns2 { + void insert(); // Foo + } + using namespace ns2; + void insert(); // Foo + } + using namespace ns1; + void insert(); // ns2::Foo + using namespace ns2; + void insert(); // Foo + )cpp", + {"ns1::ns2::", "ns2::", "", "", "ns2::", ""}, + {}, + }, + { + R"cpp( + namespace ns1 { namespace ns2 { class Bar { void Foo(); }; } } + void insert(); // ns1::ns2::Bar::Foo + namespace ns1 { + void insert(); // ns2::Bar::Foo + namespace ns2 { + void insert(); // Bar::Foo + } + using namespace ns2; + void insert(); // Bar::Foo + } + using namespace ns1; + void insert(); // ns2::Bar::Foo + using namespace ns2; + void insert(); // Bar::Foo + )cpp", + {"ns1::ns2::Bar::", "ns2::Bar::", "Bar::", "Bar::", "ns2::Bar::", + "Bar::"}, + {}, + }, + { + R"cpp( + namespace ns1 { namespace ns2 { void Foo(); } } + void insert(); // ns2::Foo + namespace ns1 { + void insert(); // ns2::Foo + namespace ns2 { + void insert(); // Foo + } + } + )cpp", + {"ns2::", "ns2::", ""}, + {"ns1::"}, + }, + }; + for (const auto &Case : Cases) { + Annotations Test(Case.Test); + TestTU TU = TestTU::withCode(Test.code()); + ParsedAST AST = TU.build(); + std::vector InsertionPoints; + const NamedDecl *TargetDecl; + findDecl(AST, [&](const NamedDecl &ND) { + if (ND.getNameAsString() == "Foo") { + TargetDecl = &ND; + return true; + } + + if (ND.getNameAsString() == "insert") + InsertionPoints.push_back(&ND); + return false; + }); + + ASSERT_EQ(InsertionPoints.size(), Case.Qualifications.size()); + for (size_t I = 0, E = InsertionPoints.size(); I != E; ++I) { + const Decl *D = InsertionPoints[I]; + if (Case.VisibleNamespaces.empty()) { + EXPECT_EQ(getQualification(AST.getASTContext(), + D->getLexicalDeclContext(), D->getBeginLoc(), + TargetDecl), + Case.Qualifications[I]); + } else { + EXPECT_EQ(getQualification(AST.getASTContext(), + D->getLexicalDeclContext(), D->getBeginLoc(), + TargetDecl, Case.VisibleNamespaces), + Case.Qualifications[I]); + } + } + } +} + } // namespace } // namespace clangd } // namespace clang