Index: include/clang/Sema/CodeCompleteConsumer.h =================================================================== --- include/clang/Sema/CodeCompleteConsumer.h +++ include/clang/Sema/CodeCompleteConsumer.h @@ -268,6 +268,8 @@ CCC_Recovery }; + using VisitedContextSet = llvm::SmallPtrSet; + private: enum Kind Kind; @@ -285,6 +287,10 @@ /// "a::b::" llvm::Optional ScopeSpecifier; + /// \breif The declaration contexts Sema has already visited during 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 @@ 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(); Index: include/clang/Sema/Lookup.h =================================================================== --- include/clang/Sema/Lookup.h +++ include/clang/Sema/Lookup.h @@ -784,6 +784,12 @@ /// 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: lib/Sema/SemaCodeComplete.cpp =================================================================== --- lib/Sema/SemaCodeComplete.cpp +++ lib/Sema/SemaCodeComplete.cpp @@ -318,6 +318,11 @@ /// \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 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer { ResultBuilder &Results; DeclContext *CurContext; - + public: CodeCompletionDeclConsumer(ResultBuilder &Results, DeclContext *CurContext) : Results(Results), CurContext(CurContext) { } @@ -1295,6 +1300,10 @@ false, Accessible); Results.AddResult(Result, CurContext, Hiding, InBaseClass); } + + void EnteredContext(DeclContext* Ctx) override { + Results.addVisitedContext(Ctx); + } }; } Index: lib/Sema/SemaLookup.cpp =================================================================== --- lib/Sema/SemaLookup.cpp +++ lib/Sema/SemaLookup.cpp @@ -3507,6 +3507,8 @@ 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) { Index: unittests/Sema/CMakeLists.txt =================================================================== --- unittests/Sema/CMakeLists.txt +++ unittests/Sema/CMakeLists.txt @@ -4,6 +4,7 @@ add_clang_unittest(SemaTests ExternalSemaSourceTest.cpp + CodeCompleteTest.cpp ) target_link_libraries(SemaTests Index: unittests/Sema/CodeCompleteTest.cpp =================================================================== --- /dev/null +++ unittests/Sema/CodeCompleteTest.cpp @@ -0,0 +1,135 @@ +//=== 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 { + auto& opts = CI.getFrontendOpts(); + opts.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