From: Haojian Wu Date: Wed, 17 Jan 2018 14:29:25 +0000 (+0000) Subject: [Sema] Add visited contexts to CodeCompleteContext X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2ac973d583e7355d705239310c720fb03593bcdd;p=clang [Sema] Add visited contexts to CodeCompleteContext 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 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@322661 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Sema/CodeCompleteConsumer.h b/include/clang/Sema/CodeCompleteConsumer.h index e8db3ec237..2fea262a44 100644 --- a/include/clang/Sema/CodeCompleteConsumer.h +++ b/include/clang/Sema/CodeCompleteConsumer.h @@ -268,6 +268,8 @@ public: CCC_Recovery }; + using VisitedContextSet = llvm::SmallPtrSet; + private: enum Kind Kind; @@ -285,6 +287,10 @@ private: /// "a::b::" llvm::Optional 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 getCXXScopeSpecifier() { if (ScopeSpecifier) return ScopeSpecifier.getPointer(); diff --git a/include/clang/Sema/Lookup.h b/include/clang/Sema/Lookup.h index 546df8842a..0e8dcada74 100644 --- a/include/clang/Sema/Lookup.h +++ b/include/clang/Sema/Lookup.h @@ -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. diff --git a/lib/Sema/SemaCodeComplete.cpp b/lib/Sema/SemaCodeComplete.cpp index f7adaf4dcd..ac318a65fc 100644 --- a/lib/Sema/SemaCodeComplete.cpp +++ b/lib/Sema/SemaCodeComplete.cpp @@ -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); + } }; } diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp index 157d090490..7e7eac38c1 100644 --- a/lib/Sema/SemaLookup.cpp +++ b/lib/Sema/SemaLookup.cpp @@ -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(Ctx) && !Result.getSema().getLangOpts().CPlusPlus) { diff --git a/unittests/Sema/CMakeLists.txt b/unittests/Sema/CMakeLists.txt index 16fae820df..45460f1e0f 100644 --- a/unittests/Sema/CMakeLists.txt +++ b/unittests/Sema/CMakeLists.txt @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS add_clang_unittest(SemaTests ExternalSemaSourceTest.cpp + CodeCompleteTest.cpp ) target_link_libraries(SemaTests diff --git a/unittests/Sema/CodeCompleteTest.cpp b/unittests/Sema/CodeCompleteTest.cpp new file mode 100644 index 0000000000..8e888cbe52 --- /dev/null +++ b/unittests/Sema/CodeCompleteTest.cpp @@ -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; + +class VisitedContextFinder: public CodeCompleteConsumer { +public: + VisitedContextFinder(VisitedContextResults &Results) + : CodeCompleteConsumer(/*CodeCompleteOpts=*/{}, + /*CodeCompleteConsumer*/ false), + VCResults(Results), + CCTUInfo(std::make_shared()) {} + + 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 getVisitedNamespace() const { + std::vector NSNames; + for (const auto *Context : VisitedContexts) + if (const auto *NS = llvm::dyn_cast(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 ; + 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(Lines + 1), + static_cast(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( + 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