[Sema] Add visited contexts to CodeCompleteContext
authorHaojian Wu <hokein@google.com>
Wed, 17 Jan 2018 14:29:25 +0000 (14:29 +0000)
committerHaojian Wu <hokein@google.com>
Wed, 17 Jan 2018 14:29:25 +0000 (14:29 +0000)
Summary:
This would allow code completion clients to know which context is visited during Sema code completion.

Also some changes:
 * add `EnteredContext` callback in VisibleDeclConsumer.
 * add a simple unittest for sema code completion (only for visited contexts at the moment).

Reviewers: ilya-biryukov

Reviewed By: ilya-biryukov

Subscribers: mgorny, bkramer, cfe-commits

Differential Revision: https://reviews.llvm.org/D42071

llvm-svn: 322661

clang/include/clang/Sema/CodeCompleteConsumer.h
clang/include/clang/Sema/Lookup.h
clang/lib/Sema/SemaCodeComplete.cpp
clang/lib/Sema/SemaLookup.cpp
clang/unittests/Sema/CMakeLists.txt
clang/unittests/Sema/CodeCompleteTest.cpp [new file with mode: 0644]

index e8db3ec..2fea262 100644 (file)
@@ -268,6 +268,8 @@ public:
     CCC_Recovery
   };
 
+  using VisitedContextSet = llvm::SmallPtrSet<DeclContext*, 8>;
+
 private:
   enum Kind Kind;
 
@@ -285,6 +287,10 @@ private:
   /// "a::b::"
   llvm::Optional<CXXScopeSpec> ScopeSpecifier;
 
+  /// \brief A set of declaration contexts visited by Sema when doing lookup for
+  /// code completion.
+  VisitedContextSet VisitedContexts;
+
 public:
   /// \brief Construct a new code-completion context of the given kind.
   CodeCompletionContext(enum Kind Kind) : Kind(Kind), SelIdents(None) { }
@@ -328,6 +334,16 @@ public:
     this->ScopeSpecifier = std::move(SS);
   }
 
+  /// \brief Adds a visited context.
+  void addVisitedContext(DeclContext* Ctx) {
+    VisitedContexts.insert(Ctx);
+  }
+
+  /// \brief Retrieves all visited contexts.
+  const VisitedContextSet &getVisitedContexts() const {
+    return VisitedContexts;
+  }
+
   llvm::Optional<const CXXScopeSpec *> getCXXScopeSpecifier() {
     if (ScopeSpecifier)
       return ScopeSpecifier.getPointer();
index 546df88..0e8dcad 100644 (file)
@@ -784,6 +784,12 @@ public:
   /// class of the context we searched.
   virtual void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, DeclContext *Ctx,
                          bool InBaseClass) = 0;
+
+  /// \brief Callback to inform the client that Sema entered into a new context
+  /// to find a visible declaration.
+  //
+  /// \param Ctx the context which Sema entered.
+  virtual void EnteredContext(DeclContext *Ctx) {}
 };
 
 /// \brief A class for storing results from argument-dependent lookup.
index f7adaf4..ac318a6 100644 (file)
@@ -318,6 +318,11 @@ namespace {
     /// \brief Ignore this declaration, if it is seen again.
     void Ignore(const Decl *D) { AllDeclsFound.insert(D->getCanonicalDecl()); }
 
+    /// \brief Add a visited context.
+    void addVisitedContext(DeclContext *Ctx) {
+      CompletionContext.addVisitedContext(Ctx);
+    }
+
     /// \name Name lookup predicates
     ///
     /// These predicates can be passed to the name lookup functions to filter the
@@ -1280,7 +1285,7 @@ namespace {
   class CodeCompletionDeclConsumer : public VisibleDeclConsumer {
     ResultBuilder &Results;
     DeclContext *CurContext;
-    
+
   public:
     CodeCompletionDeclConsumer(ResultBuilder &Results, DeclContext *CurContext)
       : Results(Results), CurContext(CurContext) { }
@@ -1295,6 +1300,10 @@ namespace {
                                    false, Accessible);
       Results.AddResult(Result, CurContext, Hiding, InBaseClass);
     }
+
+    void EnteredContext(DeclContext* Ctx) override {
+      Results.addVisitedContext(Ctx);
+    }
   };
 }
 
index 157d090..7e7eac3 100644 (file)
@@ -3507,6 +3507,8 @@ static void LookupVisibleDecls(DeclContext *Ctx, LookupResult &Result,
   if (Visited.visitedContext(Ctx->getPrimaryContext()))
     return;
 
+  Consumer.EnteredContext(Ctx);
+
   // Outside C++, lookup results for the TU live on identifiers.
   if (isa<TranslationUnitDecl>(Ctx) &&
       !Result.getSema().getLangOpts().CPlusPlus) {
index 16fae82..45460f1 100644 (file)
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
 
 add_clang_unittest(SemaTests
   ExternalSemaSourceTest.cpp
+  CodeCompleteTest.cpp
   )
 
 target_link_libraries(SemaTests
diff --git a/clang/unittests/Sema/CodeCompleteTest.cpp b/clang/unittests/Sema/CodeCompleteTest.cpp
new file mode 100644 (file)
index 0000000..8e888cb
--- /dev/null
@@ -0,0 +1,134 @@
+//=== unittests/Sema/CodeCompleteTest.cpp - Code Complete tests ==============//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Parse/ParseAST.h"
+#include "clang/Sema/Sema.h"
+#include "clang/Sema/SemaDiagnostic.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+namespace {
+
+using namespace clang;
+using namespace clang::tooling;
+using ::testing::UnorderedElementsAre;
+
+const char TestCCName[] = "test.cc";
+using VisitedContextResults = std::vector<std::string>;
+
+class VisitedContextFinder: public CodeCompleteConsumer {
+public:
+  VisitedContextFinder(VisitedContextResults &Results)
+      : CodeCompleteConsumer(/*CodeCompleteOpts=*/{},
+                             /*CodeCompleteConsumer*/ false),
+        VCResults(Results),
+        CCTUInfo(std::make_shared<GlobalCodeCompletionAllocator>()) {}
+
+  void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
+                                  CodeCompletionResult *Results,
+                                  unsigned NumResults) override {
+    VisitedContexts = Context.getVisitedContexts();
+    VCResults = getVisitedNamespace();
+  }
+
+  CodeCompletionAllocator &getAllocator() override {
+    return CCTUInfo.getAllocator();
+  }
+
+  CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
+
+  std::vector<std::string> getVisitedNamespace() const {
+    std::vector<std::string> NSNames;
+    for (const auto *Context : VisitedContexts)
+      if (const auto *NS = llvm::dyn_cast<NamespaceDecl>(Context))
+        NSNames.push_back(NS->getQualifiedNameAsString());
+    return NSNames;
+  }
+
+private:
+  VisitedContextResults& VCResults;
+  CodeCompletionTUInfo CCTUInfo;
+  CodeCompletionContext::VisitedContextSet VisitedContexts;
+};
+
+class CodeCompleteAction : public SyntaxOnlyAction {
+public:
+  CodeCompleteAction(ParsedSourceLocation P, VisitedContextResults &Results)
+      : CompletePosition(std::move(P)), VCResults(Results) {}
+
+  bool BeginInvocation(CompilerInstance &CI) override {
+    CI.getFrontendOpts().CodeCompletionAt = CompletePosition;
+    CI.setCodeCompletionConsumer(new VisitedContextFinder(VCResults));
+    return true;
+  }
+
+private:
+  // 1-based code complete position <Line, Col>;
+  ParsedSourceLocation CompletePosition;
+  VisitedContextResults& VCResults;
+};
+
+ParsedSourceLocation offsetToPosition(llvm::StringRef Code, size_t Offset) {
+  Offset = std::min(Code.size(), Offset);
+  StringRef Before = Code.substr(0, Offset);
+  int Lines = Before.count('\n');
+  size_t PrevNL = Before.rfind('\n');
+  size_t StartOfLine = (PrevNL == StringRef::npos) ? 0 : (PrevNL + 1);
+  return {TestCCName, static_cast<unsigned>(Lines + 1),
+          static_cast<unsigned>(Offset - StartOfLine + 1)};
+}
+
+VisitedContextResults runCodeCompleteOnCode(StringRef Code) {
+  VisitedContextResults Results;
+  auto TokenOffset = Code.find('^');
+  assert(TokenOffset != StringRef::npos &&
+         "Completion token ^ wasn't found in Code.");
+  std::string WithoutToken = Code.take_front(TokenOffset);
+  WithoutToken += Code.drop_front(WithoutToken.size() + 1);
+  assert(StringRef(WithoutToken).find('^') == StringRef::npos &&
+         "expected exactly one completion token ^ inside the code");
+
+  auto Action = llvm::make_unique<CodeCompleteAction>(
+      offsetToPosition(WithoutToken, TokenOffset), Results);
+  clang::tooling::runToolOnCodeWithArgs(Action.release(), Code, {"-std=c++11"},
+                                        TestCCName);
+  return Results;
+}
+
+TEST(SemaCodeCompleteTest, VisitedNSForValidQualifiedId) {
+  auto VisitedNS = runCodeCompleteOnCode(R"cpp(
+     namespace ns1 {}
+     namespace ns2 {}
+     namespace ns3 {}
+     namespace ns3 { namespace nns3 {} }
+
+     namespace foo {
+     using namespace ns1;
+     namespace ns4 {} // not visited
+     namespace { using namespace ns2; }
+     inline namespace bar { using namespace ns3::nns3; }
+     } // foo
+     namespace ns { foo::^ }
+  )cpp");
+  EXPECT_THAT(VisitedNS, UnorderedElementsAre("foo", "ns1", "ns2", "ns3::nns3",
+                                              "foo::(anonymous)"));
+}
+
+TEST(SemaCodeCompleteTest, VisitedNSForInvalideQualifiedId) {
+  auto VisitedNS = runCodeCompleteOnCode(R"cpp(
+     namespace ns { foo::^ }
+  )cpp");
+  EXPECT_TRUE(VisitedNS.empty());
+}
+
+} // namespace