Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace llvm { class raw_ostream; @@ -58,17 +59,22 @@ std::vector Diags; }; +using IncludeReferenceMap = std::unordered_map; + /// Stores and provides access to parsed AST. class ParsedAST { public: /// Attempts to run Clang and store parsed AST. If \p Preamble is non-null /// it is reused during parsing. static llvm::Optional - Build(const Context &Ctx, std::unique_ptr CI, - std::shared_ptr Preamble, - std::unique_ptr Buffer, - std::shared_ptr PCHs, - IntrusiveRefCntPtr VFS); + Build(const Context &Ctx, + std::unique_ptr CI, + std::shared_ptr Preamble, + std::unique_ptr Buffer, + std::shared_ptr PCHs, + IntrusiveRefCntPtr VFS, + IncludeReferenceMap IRM); + ParsedAST(ParsedAST &&Other); ParsedAST &operator=(ParsedAST &&Other); @@ -88,12 +94,14 @@ const std::vector &getDiagnostics() const; + const IncludeReferenceMap &getIRM() const { return IRM; }; + private: ParsedAST(std::shared_ptr Preamble, std::unique_ptr Clang, std::unique_ptr Action, std::vector TopLevelDecls, - std::vector Diags); + std::vector Diags, IncludeReferenceMap IRM); private: void ensurePreambleDeclsDeserialized(); @@ -113,6 +121,7 @@ std::vector Diags; std::vector TopLevelDecls; bool PreambleDeclsDeserialized; + IncludeReferenceMap IRM; }; // Provides thread-safe access to ParsedAST. @@ -266,6 +275,17 @@ SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos, const FileEntry *FE); +/// Get definition of symbol at a specified \p Pos. +std::vector findDefinitions(const Context &Ctx, ParsedAST &AST, + Position Pos); + +std::vector +findDocumentHighlights(const Context &Ctx, ParsedAST &AST, Position Pos); + +std::vector +findDefinitions(const Context &Ctx, ParsedAST &AST, Position Pos); + + /// For testing/debugging purposes. Note that this method deserializes all /// unserialized Decls, so use with care. void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS); Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -27,8 +27,6 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/Support/CrashRecoveryContext.h" #include "llvm/Support/Format.h" -#include -#include using namespace clang::clangd; using namespace clang; @@ -71,12 +69,52 @@ std::vector TopLevelDecls; }; +class IncludeRefsCollector : public PPCallbacks { +public: + IncludeRefsCollector(SourceManager &SourceMgr, IncludeReferenceMap &IRM) + : SourceMgr(SourceMgr), IRM(IRM) {} + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override { + auto SR = FilenameRange.getAsRange(); + if (SR.isInvalid() || !File || File->tryGetRealPathName().empty()) + return; + + if (SourceMgr.isInMainFile(FilenameRange.getAsRange().getBegin())) { + // Only inclusion directives in the main file make sense. The user cannot + // select directives not in the main file. + IRM.insert( + {getRange(FilenameRange.getAsRange()), File->tryGetRealPathName()}); + } + } + + Range getRange(SourceRange SR) { + Position Begin; + Begin.line = SourceMgr.getSpellingLineNumber(SR.getBegin()); + Begin.character = SourceMgr.getSpellingColumnNumber(SR.getBegin()); + Position End; + End.line = SourceMgr.getSpellingLineNumber(SR.getEnd()); + End.character = SourceMgr.getSpellingColumnNumber(SR.getEnd()); + return {Begin, End}; + } + +private: + SourceManager &SourceMgr; + IncludeReferenceMap &IRM; +}; + class CppFilePreambleCallbacks : public PreambleCallbacks { public: std::vector takeTopLevelDeclIDs() { return std::move(TopLevelDeclIDs); } + CppFilePreambleCallbacks(IncludeReferenceMap &IRM) + : SourceMgr(nullptr), IRM(IRM) {} + void AfterPCHEmitted(ASTWriter &Writer) override { TopLevelDeclIDs.reserve(TopLevelDecls.size()); for (Decl *D : TopLevelDecls) { @@ -95,9 +133,20 @@ } } + void BeforeExecute(CompilerInstance &CI) override { + SourceMgr = &CI.getSourceManager(); + } + + std::unique_ptr createPPCallbacks() { + return llvm::make_unique(*SourceMgr, IRM); + } + private: std::vector TopLevelDecls; std::vector TopLevelDeclIDs; + SourceManager *SourceMgr; + IncludeReferenceMap &IRM; + std::vector> TempPreambleIncludeLocations; }; /// Convert from clang diagnostic level to LSP severity. @@ -224,13 +273,12 @@ AST.getASTContext().getTranslationUnitDecl()->dump(OS, true); } -llvm::Optional -ParsedAST::Build(const Context &Ctx, - std::unique_ptr CI, - std::shared_ptr Preamble, - std::unique_ptr Buffer, - std::shared_ptr PCHs, - IntrusiveRefCntPtr VFS) { +llvm::Optional ParsedAST::Build( + const Context &Ctx, std::unique_ptr CI, + std::shared_ptr Preamble, + std::unique_ptr Buffer, + std::shared_ptr PCHs, + IntrusiveRefCntPtr VFS, IncludeReferenceMap IRM) { std::vector ASTDiags; StoreDiagsConsumer UnitDiagsConsumer(/*ref*/ ASTDiags); @@ -252,16 +300,19 @@ MainInput.getFile()); return llvm::None; } + + Clang->getPreprocessor().addPPCallbacks( + llvm::make_unique(Clang->getSourceManager(), IRM)); + if (!Action->Execute()) log(Ctx, "Execute() failed when building AST for " + MainInput.getFile()); - // UnitDiagsConsumer is local, we can not store it in CompilerInstance that // has a longer lifetime. Clang->getDiagnostics().setClient(new IgnoreDiagnostics); std::vector ParsedDecls = Action->takeTopLevelDecls(); return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action), - std::move(ParsedDecls), std::move(ASTDiags)); + std::move(ParsedDecls), std::move(ASTDiags), std::move(IRM)); } namespace { @@ -272,8 +323,6 @@ Mgr.translateFileLineCol(FE, Pos.line + 1, Pos.character + 1); return Mgr.getMacroArgExpandedLocation(InputLoc); } - - } // namespace void ParsedAST::ensurePreambleDeclsDeserialized() { @@ -339,11 +388,11 @@ std::unique_ptr Clang, std::unique_ptr Action, std::vector TopLevelDecls, - std::vector Diags) + std::vector Diags, IncludeReferenceMap IRM) : Preamble(std::move(Preamble)), Clang(std::move(Clang)), Action(std::move(Action)), Diags(std::move(Diags)), - TopLevelDecls(std::move(TopLevelDecls)), - PreambleDeclsDeserialized(false) { + TopLevelDecls(std::move(TopLevelDecls)), PreambleDeclsDeserialized(false), + IRM(std::move(IRM)) { assert(this->Clang); assert(this->Action); } @@ -501,6 +550,7 @@ } assert(CI && "Couldn't create CompilerInvocation"); + IncludeReferenceMap IRM; std::unique_ptr ContentsBuffer = llvm::MemoryBuffer::getMemBufferCopy(NewContents, That->FileName); @@ -526,7 +576,9 @@ IntrusiveRefCntPtr PreambleDiagsEngine = CompilerInstance::createDiagnostics( &CI->getDiagnosticOpts(), &PreambleDiagnosticsConsumer, false); - CppFilePreambleCallbacks SerializedDeclsCollector; + + CppFilePreambleCallbacks SerializedDeclsCollector(IRM); + auto BuiltPreamble = PrecompiledPreamble::Build( *CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, VFS, PCHs, /*StoreInMemory=*/That->StorePreamblesInMemory, @@ -568,9 +620,8 @@ trace::Span Tracer(Ctx, "Build"); SPAN_ATTACH(Tracer, "File", That->FileName); NewAST = ParsedAST::Build(Ctx, std::move(CI), std::move(NewPreamble), - std::move(ContentsBuffer), PCHs, VFS); + std::move(ContentsBuffer), PCHs, VFS, IRM); } - if (NewAST) { Diagnostics.insert(Diagnostics.end(), NewAST->getDiagnostics().begin(), NewAST->getDiagnostics().end()); Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -325,8 +325,8 @@ void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, CodeCompletionResult *Results, unsigned NumResults) override final { - if (auto SS = Context.getCXXScopeSpecifier()) - CompletedName.SSInfo = extraCompletionScope(S, **SS); +// if (auto SS = Context.getCXXScopeSpecifier()) +// CompletedName.SSInfo = extraCompletionScope(S, **SS); CompletedName.Filter = S.getPreprocessor().getCodeCompletionFilter(); std::priority_queue Candidates; @@ -804,7 +804,7 @@ Result.IncludeBriefComments = IncludeBriefComments; // Enable index-based code completion when Index is provided. - Result.IncludeNamespaceLevelDecls = !Index; + // Result.IncludeNamespaceLevelDecls = !Index; return Result; } Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -108,6 +108,14 @@ bool fromJSON(const json::Expr &, Range &); json::Expr toJSON(const Range &); +class RangeHash { +public: + std::size_t operator()(const Range &R) const { + return ((R.start.line & 0x18) << 3) | ((R.start.character & 0x18) << 1) | + ((R.end.line & 0x18) >> 1) | ((R.end.character & 0x18) >> 3); + } +}; + struct Location { /// The text document's URI. URI uri; Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -18,6 +18,7 @@ class DeclarationAndMacrosFinder : public index::IndexDataConsumer { std::vector Decls; std::vector MacroInfos; + std::vector DeclarationLocations; const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; @@ -37,6 +38,16 @@ return std::move(Decls); } + std::vector takeLocations() { + // Don't keep the same location multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(DeclarationLocations.begin(), DeclarationLocations.end()); + auto Last = + std::unique(DeclarationLocations.begin(), DeclarationLocations.end()); + DeclarationLocations.erase(Last, DeclarationLocations.end()); + return std::move(DeclarationLocations); + } + std::vector takeMacroInfos() { // Don't keep the same Macro info multiple times. std::sort(MacroInfos.begin(), MacroInfos.end()); @@ -45,6 +56,8 @@ return std::move(MacroInfos); } + const SourceLocation &getSearchedLocation() { return SearchedLocation; }; + bool handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, ArrayRef Relations, FileID FID, @@ -62,6 +75,25 @@ SourceMgr.getFileID(SearchedLocation) == FID; } + void addDeclarationLocation(const SourceRange &ValSourceRange) { + const SourceManager &SourceMgr = AST.getSourceManager(); + const LangOptions &LangOpts = AST.getLangOpts(); + SourceLocation LocStart = ValSourceRange.getBegin(); + SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), + 0, SourceMgr, LangOpts); + Position Begin; + Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1; + Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1; + Position End; + End.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1; + End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1; + Range R = {Begin, End}; + DeclarationLocations.push_back( + Location{URI::fromFile( + SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart))), + R}); + } + void finish() override { // Also handle possible macro at the searched location. Token Result; @@ -142,6 +174,26 @@ indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), DeclMacrosFinder, IndexOpts); + std::vector IRMResult; + if (!AST.getIRM().empty()) { + for (auto &IncludeLoc : AST.getIRM()) { + Range R = IncludeLoc.first; + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + unsigned CharNumber = SourceMgr.getSpellingColumnNumber( + DeclMacrosFinder->getSearchedLocation()); + + if ((unsigned)R.start.line == + SourceMgr.getSpellingLineNumber( + DeclMacrosFinder->getSearchedLocation()) && + ((unsigned)R.start.character >= CharNumber && + CharNumber <= (unsigned)R.end.character)) { + IRMResult.push_back(Location{URI::fromFile(IncludeLoc.second), + Range{Position{0, 0}, Position{0, 0}}}); + return IRMResult; + } + } + } + std::vector Decls = DeclMacrosFinder->takeDecls(); std::vector MacroInfos = DeclMacrosFinder->takeMacroInfos(); @@ -160,7 +212,10 @@ Result.push_back(*L); } - return Result; + if (Result.empty()) + return DeclMacrosFinder->takeLocations(); + else + return Result; } namespace { Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -7,7 +7,6 @@ // //===----------------------------------------------------------------------===// -#include "ClangdLSPServer.h" #include "ClangdServer.h" #include "Context.h" #include "TestFS.h" @@ -751,6 +750,75 @@ EXPECT_FALSE(PathResult.hasValue()); } +TEST_F(ClangdVFSTest, CheckDefinitionIncludes) { + MockFSProvider FS; + ErrorCheckingDiagConsumer DiagConsumer; + MockCompilationDatabase CDB; + + ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), + /*StorePreamblesInMemory=*/true, + EmptyLogger::getInstance()); + + auto FooCpp = getVirtualTestFilePath("foo.cpp"); + const auto SourceContents = R"cpp( + #include "foo.h" + #include "invalid.h" + int b = a; + // test + int foo; + #include "foo.h" + )cpp"; + FS.Files[FooCpp] = SourceContents; + auto FooH = getVirtualTestFilePath("foo.h"); + const auto HeaderContents = "int a;"; + + FS.Files[FooCpp] = SourceContents; + FS.Files[FooH] = HeaderContents; + + Server.addDocument(FooH, HeaderContents); + Server.addDocument(FooCpp, SourceContents); + + Position P = Position{1, 11}; + + auto ExpectedLocations = Server.findDefinitions(FooCpp, P); + ASSERT_TRUE(!!ExpectedLocations); + std::vector Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); + std::string s("file:///"); + std::string check = Locations[0].uri.uri; + check = check.erase(0, s.size() - 1); + check = check.substr(0, check.size()); + ASSERT_EQ(check, FooH); + ASSERT_EQ(Locations[0].range.start.line, 0); + ASSERT_EQ(Locations[0].range.start.character, 0); + ASSERT_EQ(Locations[0].range.end.line, 0); + ASSERT_EQ(Locations[0].range.end.character, 0); + + // Test ctrl-clicking on the #include part on the statement + Position P2 = Position{1, 3}; + + ExpectedLocations = Server.findDefinitions(FooCpp, P2); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); + + // Test invalid include + Position P3 = Position{2, 11}; + + ExpectedLocations = Server.findDefinitions(FooCpp, P3); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(Locations.empty()); + + // Test include outside of Preamble + Position P4 = Position{6, 5}; + + ExpectedLocations = Server.findDefinitions(FooCpp, P4); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); +} + TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) { class NoConcurrentAccessDiagConsumer : public DiagnosticsConsumer { public: