kadircet created this revision.
kadircet added a reviewer: ilya-biryukov.
Herald added subscribers: cfe-commits, usaxena95, arphaman, jkorous, MaskRay.
Herald added a project: clang.

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.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D69608

Files:
  clang-tools-extra/clangd/AST.cpp
  clang-tools-extra/clangd/AST.h
  clang-tools-extra/clangd/unittests/ASTTests.cpp

Index: clang-tools-extra/clangd/unittests/ASTTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/ASTTests.cpp
+++ clang-tools-extra/clangd/unittests/ASTTests.cpp
@@ -7,7 +7,16 @@
 //===----------------------------------------------------------------------===//
 
 #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 <cstddef>
+#include <vector>
 
 namespace clang {
 namespace clangd {
@@ -36,6 +45,66 @@
                 "testns1::TestClass<testns1::OtherClass>", "testns1"));
 }
 
+TEST(ClangdAST, GetQualification) {
+  struct {
+    llvm::StringRef Test;
+    std::vector<llvm::StringRef> Qualifications;
+  } 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::", ""},
+      },
+  };
+  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];
+      auto Qual =
+          getQualification(AST.getASTContext(), D->getLexicalDeclContext(),
+                           D->getBeginLoc(), TargetDecl);
+      if (!Qual)
+        EXPECT_TRUE(Case.Qualifications[I].empty());
+      else {
+        std::string Qualifier;
+        llvm::raw_string_ostream OS(Qualifier);
+        Qual->print(OS, AST.getASTContext().getPrintingPolicy(), true);
+        EXPECT_EQ(OS.str(), Case.Qualifications[I]);
+      }
+    }
+  }
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clang-tools-extra/clangd/AST.h
===================================================================
--- clang-tools-extra/clangd/AST.h
+++ clang-tools-extra/clangd/AST.h
@@ -15,8 +15,10 @@
 
 #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"
 
 namespace clang {
 class SourceManager;
@@ -110,6 +112,20 @@
 ///     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, considering all the using
+/// directives before \p InsertionPoint.
+/// 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. Also if you have `using namespace clang::clangd::bar` before \p
+/// InsertionPoint this function will return null, since no qualification is
+/// necessary in that case.
+NestedNameSpecifier *getQualification(ASTContext &Context,
+                                      const DeclContext *DestContext,
+                                      SourceLocation InsertionPoint,
+                                      const NamedDecl *ND);
+
 } // namespace clangd
 } // namespace clang
 
Index: clang-tools-extra/clangd/AST.cpp
===================================================================
--- clang-tools-extra/clangd/AST.cpp
+++ clang-tools-extra/clangd/AST.cpp
@@ -244,6 +244,59 @@
       printNamespaceScope(Context) );
 }
 
+NestedNameSpecifier *getQualification(ASTContext &Context,
+                                      const DeclContext *DestContext,
+                                      SourceLocation InsertionPoint,
+                                      const NamedDecl *ND) {
+  const auto &SM = Context.getSourceManager();
+  // Store all UsingDirectiveDecls in parent contexts of DestContext, that were
+  // introduced before InsertionPoint.
+  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(), InsertionPoint) ||
+          !SM.isBeforeInTranslationUnit(D->getLocation(), InsertionPoint))
+        continue;
+      if (auto *UDD = llvm::dyn_cast<UsingDirectiveDecl>(D)) {
+        VisibleNamespaceDecls.insert(
+            UDD->getNominatedNamespace()->getCanonicalDecl());
+      }
+    }
+  }
+
+  // Goes over all parents of ND until we find a comman ancestor for DestContext
+  // and ND. Any qualifier including and above common ancestor is redundant,
+  // therefore we stop at lowest common ancestor.
+  std::vector<const NamespaceDecl *> SourceParents;
+  for (const DeclContext *Context = ND->getLexicalDeclContext(); Context;
+       Context = Context->getLookupParent()) {
+    // Stop once we reach a common ancestor.
+    if (Context->Encloses(DestContext))
+      break;
+    // Inline namespace names are not spelled while qualifying a name, so skip
+    // those.
+    if (Context->isInlineNamespace())
+      continue;
+
+    auto *NSD = llvm::dyn_cast<NamespaceDecl>(Context);
+    assert(NSD && "Non-namespace decl context found.");
+    // Stop if this namespace is already visible at DestContext.
+    if (VisibleNamespaceDecls.count(NSD->getCanonicalDecl()))
+      break;
+    // Again, ananoymous namespaces are not spelled while qualifying a name.
+    if (NSD->isAnonymousNamespace())
+      continue;
+
+    SourceParents.push_back(NSD);
+  }
+
+  // Go over name-specifiers in reverse order to create necessary qualification,
+  // since we stored inner-most parent first.
+  NestedNameSpecifier *Result = nullptr;
+  for (const auto *Parent : llvm::reverse(SourceParents))
+    Result = NestedNameSpecifier::Create(Context, Result, Parent);
+  return Result;
+}
 
 } // namespace clangd
 } // namespace clang
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to