[clang-tools-extra] 97d6e8e - [clangd] Helper for getting nested namespace qualification
Kadir Cetinkaya via cfe-commits
cfe-commits at lists.llvm.org
Mon Nov 25 01:42:23 PST 2019
Author: Kadir Cetinkaya
Date: 2019-11-25T10:42:13+01:00
New Revision: 97d6e8e0f3748e6fe2b27983803e2df75874507e
URL: https://github.com/llvm/llvm-project/commit/97d6e8e0f3748e6fe2b27983803e2df75874507e
DIFF: https://github.com/llvm/llvm-project/commit/97d6e8e0f3748e6fe2b27983803e2df75874507e.diff
LOG: [clangd] Helper for getting nested namespace qualification
Summary:
Introduce a new helper for getting minimally required qualifiers
necessary to spell a name at a point in a given DeclContext. Currently takes
using directives and nested namespecifier of DeclContext itself into account.
Initially will be used in define inline and outline actions.
Reviewers: ilya-biryukov
Subscribers: MaskRay, jkorous, arphaman, usaxena95, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D69608
Added:
Modified:
clang-tools-extra/clangd/AST.cpp
clang-tools-extra/clangd/AST.h
clang-tools-extra/clangd/unittests/ASTTests.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 6c69367633bf..cd6a682007f2 100644
--- 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"
@@ -22,10 +23,15 @@
#include "clang/Basic/Specifiers.h"
#include "clang/Index/USRGeneration.h"
#include "clang/Lex/Lexer.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 <string>
+#include <vector>
namespace clang {
namespace clangd {
@@ -67,6 +73,74 @@ bool isTemplateSpecializationKind(const NamedDecl *D,
isTemplateSpecializationKind<VarDecl>(D, Kind);
}
+// Store all UsingDirectiveDecls in parent contexts of DestContext, that were
+// introduced before InsertionPoint.
+llvm::DenseSet<const NamespaceDecl *>
+getUsingNamespaceDirectives(const DeclContext *DestContext,
+ SourceLocation Until) {
+ const auto &SM = DestContext->getParentASTContext().getSourceManager();
+ llvm::DenseSet<const NamespaceDecl *> 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<UsingDirectiveDecl>(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<bool(NestedNameSpecifier *)> IsVisible) {
+ std::vector<const NestedNameSpecifier *> 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<TagDecl>(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<NamespaceDecl>(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) {
@@ -82,9 +156,7 @@ bool isImplementationDetail(const Decl *D) {
D->getASTContext().getSourceManager());
}
-SourceLocation findName(const clang::Decl *D) {
- return D->getLocation();
-}
+SourceLocation findName(const clang::Decl *D) { return D->getLocation(); }
std::string printQualifiedName(const NamedDecl &ND) {
std::string QName;
@@ -229,7 +301,7 @@ std::string shortenNamespace(const llvm::StringRef OriginalName,
unsigned DifferentAt = 0;
while (DifferentAt < MinLength &&
- CurrentParts[DifferentAt] == OriginalParts[DifferentAt]) {
+ CurrentParts[DifferentAt] == OriginalParts[DifferentAt]) {
DifferentAt++;
}
@@ -239,13 +311,11 @@ std::string shortenNamespace(const llvm::StringRef OriginalName,
return join(Result, "::");
}
-std::string printType(const QualType QT, const DeclContext & Context){
+std::string printType(const QualType QT, const DeclContext &Context) {
PrintingPolicy PP(Context.getParentASTContext().getPrintingPolicy());
PP.SuppressUnwrittenScope = 1;
PP.SuppressTagKeyword = 1;
- return shortenNamespace(
- QT.getAsString(PP),
- printNamespaceScope(Context) );
+ return shortenNamespace(QT.getAsString(PP), printNamespaceScope(Context));
}
QualType declaredType(const TypeDecl *D) {
@@ -364,5 +434,42 @@ llvm::Optional<QualType> getDeducedType(ASTContext &ASTCtx,
return V.DeducedType;
}
+std::string getQualification(ASTContext &Context,
+ const DeclContext *DestContext,
+ SourceLocation InsertionPoint,
+ const NamedDecl *ND) {
+ auto VisibleNamespaceDecls =
+ getUsingNamespaceDirectives(DestContext, InsertionPoint);
+ return getQualification(
+ Context, DestContext, ND->getDeclContext(),
+ [&](NestedNameSpecifier *NNS) {
+ if (NNS->getKind() != NestedNameSpecifier::Namespace)
+ return false;
+ const auto *CanonNSD = NNS->getAsNamespace()->getCanonicalDecl();
+ return llvm::any_of(VisibleNamespaceDecls,
+ [CanonNSD](const NamespaceDecl *NSD) {
+ return NSD->getCanonicalDecl() == CanonNSD;
+ });
+ });
+}
+
+std::string getQualification(ASTContext &Context,
+ const DeclContext *DestContext,
+ SourceLocation InsertionPoint, const NamedDecl *ND,
+ llvm::ArrayRef<std::string> VisibleNamespaces) {
+ for (llvm::StringRef NS : VisibleNamespaces)
+ assert(NS.endswith("::"));
+ 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/AST.h b/clang-tools-extra/clangd/AST.h
index 3850ab4f6b4f..bea67a41005a 100644
--- 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 <string>
+#include <vector>
namespace clang {
class SourceManager;
@@ -76,7 +80,7 @@ llvm::Optional<SymbolID> getSymbolID(const llvm::StringRef MacroName,
/// Returns a QualType as string. The result doesn't contain unwritten scopes
/// like annoymous/inline namespace.
-std::string printType(const QualType QT, const DeclContext & Context);
+std::string printType(const QualType QT, const DeclContext &Context);
/// Try to shorten the OriginalName by removing namespaces from the left of
/// the string that are redundant in the CurrentNamespace. This way the type
@@ -121,6 +125,39 @@ QualType declaredType(const TypeDecl *D);
/// It will return the underlying type.
llvm::Optional<QualType> getDeducedType(ASTContext &, SourceLocation Loc);
+/// 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 an empty string 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. Note that the result might be sub-optimal for
+/// classes, e.g. when the \p ND is a member of the base class.
+///
+/// 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 an empty string 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 to figure out if a shorter
+/// qualification is sufficient for \p ND, and ignores any using namespace
+/// directives. It can be useful if there's no AST for the DestContext, but some
+/// pseudo-parsing is done. i.e. if \p ND is ns1::ns2::X and \p DestContext is
+/// ns1::, users can provide `ns2::` as visible to change the result to be
+/// empty.
+/// 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<std::string> VisibleNamespaces);
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp
index b3040e2a7ed7..90ded128d577 100644
--- a/clang-tools-extra/clangd/unittests/ASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp
@@ -9,9 +9,17 @@
#include "AST.h"
#include "Annotations.h"
+#include "ParsedAST.h"
#include "TestTU.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
#include "clang/Basic/SourceManager.h"
+#include "llvm/ADT/StringRef.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include <cstddef>
+#include <string>
+#include <vector>
namespace clang {
namespace clangd {
@@ -64,6 +72,108 @@ TEST(GetDeducedType, KwAutoExpansion) {
}
}
+TEST(ClangdAST, GetQualification) {
+ // Tries to insert the decl `Foo` into position of each decl named `insert`.
+ // This is done to get an appropriate DeclContext for the insertion location.
+ // Qualifications are the required nested name specifier to spell `Foo` at the
+ // `insert`ion location.
+ // VisibleNamespaces are assumed to be visible at every insertion location.
+ const struct {
+ llvm::StringRef Test;
+ std::vector<llvm::StringRef> Qualifications;
+ std::vector<std::string> 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<const Decl *> 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
More information about the cfe-commits
mailing list