kadircet updated this revision to Diff 230211.
kadircet marked 4 inline comments as done.
kadircet added a comment.

- Add support for non-namespace decl contexts.
- Add an overload for "textual namespace" handling.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D69608/new/

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,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 <cstddef>
+#include <string>
+#include <vector>
 
 namespace clang {
 namespace clangd {
@@ -36,6 +46,103 @@
                 "testns1::TestClass<testns1::OtherClass>", "testns1"));
 }
 
+TEST(ClangdAST, GetQualification) {
+  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
Index: clang-tools-extra/clangd/AST.h
===================================================================
--- clang-tools-extra/clangd/AST.h
+++ 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;
@@ -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<std::string> VisibleNamespaces);
+
 } // namespace clangd
 } // namespace clang
 
Index: clang-tools-extra/clangd/AST.cpp
===================================================================
--- clang-tools-extra/clangd/AST.cpp
+++ 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 <string>
+#include <vector>
 
 namespace clang {
 namespace clangd {
@@ -65,6 +71,75 @@
          isTemplateSpecializationKind<VarDecl>(D, Kind);
 }
 
+// Store all UsingDirectiveDecls in parent contexts of DestContext, that were
+// introduced before InsertionPoint.
+llvm::DenseSet<const NamespaceDecl *>
+getUsingNamespceDirectives(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) {
@@ -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<std::string> 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
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to