[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