Index: clang-tidy/readability/CMakeLists.txt =================================================================== --- clang-tidy/readability/CMakeLists.txt +++ clang-tidy/readability/CMakeLists.txt @@ -24,6 +24,7 @@ RedundantStringCStrCheck.cpp RedundantSmartptrGetCheck.cpp RedundantStringInitCheck.cpp + SIMDIntrinsicsCheck.cpp SimplifyBooleanExprCheck.cpp StaticAccessedThroughInstanceCheck.cpp StaticDefinitionInAnonymousNamespaceCheck.cpp Index: clang-tidy/readability/ReadabilityTidyModule.cpp =================================================================== --- clang-tidy/readability/ReadabilityTidyModule.cpp +++ clang-tidy/readability/ReadabilityTidyModule.cpp @@ -31,6 +31,7 @@ #include "RedundantSmartptrGetCheck.h" #include "RedundantStringCStrCheck.h" #include "RedundantStringInitCheck.h" +#include "SIMDIntrinsicsCheck.h" #include "SimplifyBooleanExprCheck.h" #include "StaticAccessedThroughInstanceCheck.h" #include "StaticDefinitionInAnonymousNamespaceCheck.h" @@ -92,6 +93,8 @@ "readability-redundant-string-cstr"); CheckFactories.registerCheck( "readability-redundant-string-init"); + CheckFactories.registerCheck( + "readability-simd-intrinsics"); CheckFactories.registerCheck( "readability-simplify-boolean-expr"); CheckFactories.registerCheck( Index: clang-tidy/readability/SIMDIntrinsicsCheck.h =================================================================== --- /dev/null +++ clang-tidy/readability/SIMDIntrinsicsCheck.h @@ -0,0 +1,40 @@ +//===--- SIMDIntrinsicsCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find SIMD intrinsics calls and suggest std::experimental::simd alternatives. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-simd-intrinsics.html +class SIMDIntrinsicsCheck : public ClangTidyCheck { +public: + SIMDIntrinsicsCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + + private: + const bool Suggest; + StringRef Std; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H Index: clang-tidy/readability/SIMDIntrinsicsCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/readability/SIMDIntrinsicsCheck.cpp @@ -0,0 +1,152 @@ +//===--- SIMDIntrinsicsCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SIMDIntrinsicsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Support/Regex.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +// If the callee has parameter of VectorType or pointer to VectorType, +// or the return type is VectorType, we consider it a vector function +// and a candidate for checking. +AST_MATCHER(FunctionDecl, isVectorFunction) { + bool IsVector = Node.getReturnType()->isVectorType(); + for (const ParmVarDecl *Parm : Node.parameters()) { + QualType Type = Parm->getType(); + if (Type->isPointerType()) + Type = Type->getPointeeType(); + if (Type->isVectorType()) + IsVector = true; + } + return IsVector; +} + +} // namespace + +static StringRef TrySuggestPPC(StringRef Name) { + if (!Name.consume_front("vec_")) + return {}; + + static const llvm::StringMap Mapping{ + // [simd.alg] + {"max", "$std::max"}, + {"min", "$std::min"}, + + // [simd.binary] + {"add", "operator+ on $simd objects"}, + {"sub", "operator- on $simd objects"}, + {"mul", "operator* on $simd objects"}, + }; + + auto It = Mapping.find(Name); + if (It != Mapping.end()) + return It->second; + return {}; +} + +static StringRef TrySuggestX86(StringRef Name) { + if (!(Name.consume_front("_mm_") || Name.consume_front("_mm256_") || + Name.consume_front("_mm512_"))) + return {}; + + // [simd.alg] + if (Name.startswith("max_")) + return "$simd::max"; + if (Name.startswith("min_")) + return "$simd::min"; + + // [simd.binary] + if (Name.startswith("add_")) + return "operator+ on $simd objects"; + if (Name.startswith("sub_")) + return "operator- on $simd objects"; + if (Name.startswith("mul_")) + return "operator* on $simd objects"; + + return {}; +} + +SIMDIntrinsicsCheck::SIMDIntrinsicsCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), Suggest(Options.get("Suggest", 0) != 0) {} + +void SIMDIntrinsicsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Suggest", 0); +} + +void SIMDIntrinsicsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + // libcxx implementation backports it to C++11 std::experimental::simd. + Std = getLangOpts().CPlusPlus2a ? "std" : "std::experimental"; + + Finder->addMatcher(callExpr(callee(functionDecl(allOf( + matchesName("^::(_mm_|_mm256_|_mm512_|vec_)"), + isVectorFunction()))), + unless(isExpansionInSystemHeader())) + .bind("call"), + this); +} + +void SIMDIntrinsicsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + assert(Call != nullptr); + const FunctionDecl *Callee = Call->getDirectCallee(); + if (!Callee) + return; + + StringRef Old = Callee->getName(); + StringRef New; + llvm::Triple::ArchType Arch = + Result.Context->getTargetInfo().getTriple().getArch(); + + switch (Arch) { + default: + break; + case llvm::Triple::ppc: + case llvm::Triple::ppc64: + case llvm::Triple::ppc64le: + New = TrySuggestPPC(Old); + break; + case llvm::Triple::x86: + case llvm::Triple::x86_64: + New = TrySuggestX86(Old); + break; + } + + if (!New.empty()) { + std::string Message; + // If Suggest is true, give a P0214 alternative, otherwise point it out it + // is non-portable. + if (Suggest) { + Message = (Twine("'") + Old + "' can be replaced by " + New).str(); + Message = llvm::Regex("\\$std").sub(Std, Message); + Message = llvm::Regex("\\$simd").sub(Std.str() + "::simd", Message); + } else { + Message = (Twine("'") + Old + "' is a non-portable " + + llvm::Triple::getArchTypeName(Arch) + " intrinsic function") + .str(); + } + diag(Call->getExprLoc(), Message); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -14,6 +14,7 @@ DraftStore.cpp FuzzyMatch.cpp GlobalCompilationDatabase.cpp + Headers.cpp JSONExpr.cpp JSONRPCDispatcher.cpp Logger.cpp @@ -25,6 +26,7 @@ TUScheduler.cpp URI.cpp XRefs.cpp + index/CanonicalIncludes.cpp index/FileIndex.cpp index/Index.cpp index/MemIndex.cpp Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -75,6 +75,7 @@ void onFileEvent(DidChangeWatchedFilesParams &Params) override; void onCommand(ExecuteCommandParams &Params) override; void onRename(RenameParams &Parames) override; + void onHover(TextDocumentPositionParams &Params) override; std::vector getFixIts(StringRef File, const clangd::Diagnostic &D); Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -87,8 +87,8 @@ } // namespace void ClangdLSPServer::onInitialize(InitializeParams &Params) { - if (Params.rootUri && !Params.rootUri->file.empty()) - Server.setRootPath(Params.rootUri->file); + if (Params.rootUri && *Params.rootUri) + Server.setRootPath(Params.rootUri->file()); else if (Params.rootPath && !Params.rootPath->empty()) Server.setRootPath(*Params.rootPath); @@ -118,10 +118,13 @@ }}, {"definitionProvider", true}, {"documentHighlightProvider", true}, + {"hoverProvider", true}, {"renameProvider", true}, {"executeCommandProvider", json::obj{ - {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, + {"commands", + {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE}}, }}, }}}}); } @@ -136,9 +139,9 @@ void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) { if (Params.metadata && !Params.metadata->extraFlags.empty()) - CDB.setExtraFlagsForFile(Params.textDocument.uri.file, + CDB.setExtraFlagsForFile(Params.textDocument.uri.file(), std::move(Params.metadata->extraFlags)); - Server.addDocument(Params.textDocument.uri.file, Params.textDocument.text); + Server.addDocument(Params.textDocument.uri.file(), Params.textDocument.text); } void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) { @@ -146,7 +149,7 @@ return replyError(ErrorCode::InvalidParams, "can only apply one change at a time"); // We only support full syncing right now. - Server.addDocument(Params.textDocument.uri.file, + Server.addDocument(Params.textDocument.uri.file(), Params.contentChanges[0].text); } @@ -155,6 +158,14 @@ } void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) { + auto ApplyEdit = [](WorkspaceEdit WE) { + ApplyWorkspaceEditParams Edit; + Edit.edit = std::move(WE); + // We don't need the response so id == 1 is OK. + // Ideally, we would wait for the response and if there is no error, we + // would reply success/failure to the original RPC. + call("workspace/applyEdit", Edit); + }; if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND && Params.workspaceEdit) { // The flow for "apply-fix" : @@ -166,13 +177,35 @@ // 6. The editor applies the changes (applyEdit), and sends us a reply (but // we ignore it) - ApplyWorkspaceEditParams ApplyEdit; - ApplyEdit.edit = *Params.workspaceEdit; reply("Fix applied."); - // We don't need the response so id == 1 is OK. - // Ideally, we would wait for the response and if there is no error, we - // would reply success/failure to the original RPC. - call("workspace/applyEdit", ApplyEdit); + ApplyEdit(*Params.workspaceEdit); + } else if (Params.command == + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) { + auto &FileURI = Params.includeInsertion->textDocument.uri; + auto Code = Server.getDocument(FileURI.file()); + if (!Code) + return replyError(ErrorCode::InvalidParams, + ("command " + + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE + + " called on non-added file " + FileURI.file()) + .str()); + auto Replaces = Server.insertInclude(FileURI.file(), *Code, + Params.includeInsertion->header); + if (!Replaces) { + std::string ErrMsg = + ("Failed to generate include insertion edits for adding " + + Params.includeInsertion->header + " into " + FileURI.file()) + .str(); + log(ErrMsg + ":" + llvm::toString(Replaces.takeError())); + replyError(ErrorCode::InternalError, ErrMsg); + return; + } + auto Edits = replacementsToEdits(*Code, *Replaces); + WorkspaceEdit WE; + WE.changes = {{FileURI.uri(), Edits}}; + + reply("Inserted header " + Params.includeInsertion->header); + ApplyEdit(std::move(WE)); } else { // We should not get here because ExecuteCommandParams would not have // parsed in the first place and this handler should not be called. But if @@ -184,7 +217,7 @@ } void ClangdLSPServer::onRename(RenameParams &Params) { - Path File = Params.textDocument.uri.file; + Path File = Params.textDocument.uri.file(); llvm::Optional Code = Server.getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, @@ -206,12 +239,12 @@ } void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) { - Server.removeDocument(Params.textDocument.uri.file); + Server.removeDocument(Params.textDocument.uri.file()); } void ClangdLSPServer::onDocumentOnTypeFormatting( DocumentOnTypeFormattingParams &Params) { - auto File = Params.textDocument.uri.file; + auto File = Params.textDocument.uri.file(); auto Code = Server.getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, @@ -227,7 +260,7 @@ void ClangdLSPServer::onDocumentRangeFormatting( DocumentRangeFormattingParams &Params) { - auto File = Params.textDocument.uri.file; + auto File = Params.textDocument.uri.file(); auto Code = Server.getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, @@ -242,7 +275,7 @@ } void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) { - auto File = Params.textDocument.uri.file; + auto File = Params.textDocument.uri.file(); auto Code = Server.getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, @@ -259,14 +292,14 @@ void ClangdLSPServer::onCodeAction(CodeActionParams &Params) { // We provide a code action for each diagnostic at the requested location // which has FixIts available. - auto Code = Server.getDocument(Params.textDocument.uri.file); + auto Code = Server.getDocument(Params.textDocument.uri.file()); if (!Code) return replyError(ErrorCode::InvalidParams, "onCodeAction called for non-added file"); json::ary Commands; for (Diagnostic &D : Params.context.diagnostics) { - auto Edits = getFixIts(Params.textDocument.uri.file, D); + auto Edits = getFixIts(Params.textDocument.uri.file(), D); if (!Edits.empty()) { WorkspaceEdit WE; WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}}; @@ -281,12 +314,12 @@ } void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) { - Server.codeComplete(Params.textDocument.uri.file, Params.position, CCOpts, + Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts, [](Tagged List) { reply(List.Value); }); } void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) { - Server.signatureHelp(Params.textDocument.uri.file, Params.position, + Server.signatureHelp(Params.textDocument.uri.file(), Params.position, [](llvm::Expected> SignatureHelp) { if (!SignatureHelp) return replyError( @@ -298,7 +331,7 @@ void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) { Server.findDefinitions( - Params.textDocument.uri.file, Params.position, + Params.textDocument.uri.file(), Params.position, [](llvm::Expected>> Items) { if (!Items) return replyError(ErrorCode::InvalidParams, @@ -308,13 +341,13 @@ } void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) { - llvm::Optional Result = Server.switchSourceHeader(Params.uri.file); + llvm::Optional Result = Server.switchSourceHeader(Params.uri.file()); reply(Result ? URI::createFile(*Result).toString() : ""); } void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) { Server.findDocumentHighlights( - Params.textDocument.uri.file, Params.position, + Params.textDocument.uri.file(), Params.position, [](llvm::Expected>> Highlights) { if (!Highlights) return replyError(ErrorCode::InternalError, @@ -323,6 +356,19 @@ }); } +void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) { + Server.findHover(Params.textDocument.uri.file(), Params.position, + [](llvm::Expected> H) { + if (!H) { + replyError(ErrorCode::InternalError, + llvm::toString(H.takeError())); + return; + } + + reply(H->Value); + }); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory, const clangd::CodeCompleteOptions &CCOpts, Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -212,6 +212,10 @@ void(llvm::Expected>>)> Callback); + /// Get code hover for a given position. + void findHover(PathRef File, Position Pos, + UniqueFunction>)> Callback); + /// Run formatting for \p Rng inside \p File with content \p Code. llvm::Expected formatRange(StringRef Code, PathRef File, Range Rng); @@ -231,6 +235,13 @@ UniqueFunction>)> Callback); + /// Inserts a new #include of \p Header into \p File, if it's not present. + /// \p Header is either an URI that can be resolved to an #include path that + /// is suitable to be inserted or a literal string quoted with <> or "" that + /// can be #included directly. + Expected insertInclude(PathRef File, StringRef Code, + StringRef Header); + /// Gets current document contents for \p File. Returns None if \p File is not /// currently tracked. /// FIXME(ibiryukov): This function is here to allow offset-to-Position Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -9,6 +9,7 @@ #include "ClangdServer.h" #include "CodeComplete.h" +#include "Headers.h" #include "SourceCode.h" #include "XRefs.h" #include "index/Merge.h" @@ -310,6 +311,47 @@ BindWithForward(Action, File.str(), NewName.str(), std::move(Callback))); } +Expected +ClangdServer::insertInclude(PathRef File, StringRef Code, + llvm::StringRef Header) { + std::string ToInclude; + if (Header.startswith("<") || Header.startswith("\"")) { + ToInclude = Header; + } else { + auto U = URI::parse(Header); + if (!U) + return U.takeError(); + auto Resolved = URI::resolve(*U, /*HintPath=*/File); + if (!Resolved) + return Resolved.takeError(); + + auto FS = FSProvider.getTaggedFileSystem(File).Value; + tooling::CompileCommand CompileCommand = + CompileArgs.getCompileCommand(File); + FS->setCurrentWorkingDirectory(CompileCommand.Directory); + + auto Include = + shortenIncludePath(File, Code, *Resolved, CompileCommand, FS); + if (!Include) + return Include.takeError(); + if (Include->empty()) + return tooling::Replacements(); + ToInclude = std::move(*Include); + } + + auto Style = format::getStyle("file", File, "llvm"); + if (!Style) { + llvm::consumeError(Style.takeError()); + // FIXME(ioeric): needs more consistent style support in clangd server. + Style = format::getLLVMStyle(); + } + // Replacement with offset UINT_MAX and length 0 will be treated as include + // insertion. + tooling::Replacement R(File, /*Offset=*/UINT_MAX, 0, "#include " + ToInclude); + return format::cleanupAroundReplacements(Code, tooling::Replacements(R), + *Style); +} + llvm::Optional ClangdServer::getDocument(PathRef File) { auto Latest = DraftMgr.getDraft(File); if (!Latest.Draft) @@ -449,6 +491,30 @@ WorkScheduler.runWithAST(File, BindWithForward(Action, std::move(Callback))); } +void ClangdServer::findHover( + PathRef File, Position Pos, + UniqueFunction>)> Callback) { + Hover FinalHover; + auto FileContents = DraftMgr.getDraft(File); + if (!FileContents.Draft) + return Callback(llvm::make_error( + "findHover called on non-added file", + llvm::errc::invalid_argument)); + + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + + auto Action = [Pos, TaggedFS](decltype(Callback) Callback, + llvm::Expected InpAST) { + if (!InpAST) + return Callback(InpAST.takeError()); + + Hover Result = clangd::getHover(InpAST->AST, Pos); + Callback(make_tagged(std::move(Result), TaggedFS.Tag)); + }; + + WorkScheduler.runWithAST(File, BindWithForward(Action, std::move(Callback))); +} + void ClangdServer::scheduleReparseAndDiags( PathRef File, VersionedDraft Contents, Tagged> TaggedFS) { Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -439,8 +439,10 @@ Diagnostics.insert(Diagnostics.end(), NewAST->getDiagnostics().begin(), NewAST->getDiagnostics().end()); } - if (ASTCallback && NewAST) + if (ASTCallback && NewAST) { + trace::Span Tracer("Running ASTCallback"); ASTCallback(FileName, NewAST.getPointer()); + } // Write the results of rebuild into class fields. Preamble = std::move(NewPreamble); Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -19,13 +19,16 @@ #include "Compiler.h" #include "FuzzyMatch.h" #include "Logger.h" +#include "SourceCode.h" #include "Trace.h" #include "index/Index.h" +#include "clang/Format/Format.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Index/USRGeneration.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "clang/Sema/Sema.h" +#include "clang/Tooling/Core/Replacement.h" #include "llvm/Support/Format.h" #include @@ -249,7 +252,8 @@ } // Builds an LSP completion item. - CompletionItem build(const CompletionItemScores &Scores, + CompletionItem build(llvm::StringRef FileName, + const CompletionItemScores &Scores, const CodeCompleteOptions &Opts, CodeCompletionString *SemaCCS) const { assert(bool(SemaResult) == bool(SemaCCS)); @@ -282,6 +286,28 @@ I.documentation = D->Documentation; if (I.detail.empty()) I.detail = D->CompletionDetail; + // We only insert #include for items with details, since we can't tell + // whether the file URI of the canonical declaration would be the + // canonical #include without checking IncludeHeader in the detail. + // FIXME: delay creating include insertion command to + // "completionItem/resolve", when it is supported + if (!D->IncludeHeader.empty() || + !IndexResult->CanonicalDeclaration.FileURI.empty()) { + // LSP favors additionalTextEdits over command. But we are still using + // command here because it would be expensive to calculate #include + // insertion edits for all candidates, and the include insertion edit + // is unlikely to conflict with the code completion edits. + Command Cmd; + // Command title is not added since this is not a user-facing command. + Cmd.command = ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE; + IncludeInsertion Insertion; + Insertion.header = D->IncludeHeader.empty() + ? IndexResult->CanonicalDeclaration.FileURI + : D->IncludeHeader; + Insertion.textDocument.uri = URIForFile(FileName); + Cmd.includeInsertion = std::move(Insertion); + I.command = std::move(Cmd); + } } } I.scoreInfo = Scores; @@ -806,6 +832,7 @@ // This score is combined with the result quality score for the final score. // - TopN determines the results with the best score. class CodeCompleteFlow { + PathRef FileName; const CodeCompleteOptions &Opts; // Sema takes ownership of Recorder. Recorder is valid until Sema cleanup. std::unique_ptr RecorderOwner; @@ -816,9 +843,9 @@ public: // A CodeCompleteFlow object is only useful for calling run() exactly once. - CodeCompleteFlow(const CodeCompleteOptions &Opts) - : Opts(Opts), RecorderOwner(new CompletionRecorder(Opts)), - Recorder(*RecorderOwner) {} + CodeCompleteFlow(PathRef FileName, const CodeCompleteOptions &Opts) + : FileName(FileName), Opts(Opts), + RecorderOwner(new CompletionRecorder(Opts)), Recorder(*RecorderOwner) {} CompletionList run(const SemaCompleteInput &SemaCCInput) && { trace::Span Tracer("CodeCompleteFlow"); @@ -956,7 +983,7 @@ CodeCompletionString *SemaCCS = nullptr; if (auto *SR = Candidate.SemaResult) SemaCCS = Recorder.codeCompletionString(*SR, Opts.IncludeBriefComments); - return Candidate.build(Scores, Opts, SemaCCS); + return Candidate.build(FileName, Scores, Opts, SemaCCS); } }; @@ -967,8 +994,8 @@ IntrusiveRefCntPtr VFS, std::shared_ptr PCHs, CodeCompleteOptions Opts) { - return CodeCompleteFlow(Opts).run( - {FileName, Command, Preamble, Contents, Pos, VFS, PCHs}); + return CodeCompleteFlow(FileName, Opts) + .run({FileName, Command, Preamble, Contents, Pos, VFS, PCHs}); } SignatureHelp signatureHelp(PathRef FileName, Index: clangd/Headers.h =================================================================== --- /dev/null +++ clangd/Headers.h @@ -0,0 +1,37 @@ +//===--- Headers.h - Include headers -----------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H + +#include "Path.h" +#include "clang/Basic/VirtualFileSystem.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { +/// Determines the preferred way to #include a file, taking into account the +/// search path. Usually this will prefer a shorter representation like +/// 'Foo/Bar.h' over a longer one like 'Baz/include/Foo/Bar.h'. +/// +/// \param Header is an absolute file path. +/// \return A quoted "path" or . If \p Header is already (directly) +/// included in the file (including those included via different paths), this +/// returns an empty string. +llvm::Expected +shortenIncludePath(PathRef File, llvm::StringRef Code, llvm::StringRef Header, + const tooling::CompileCommand &CompileCommand, + IntrusiveRefCntPtr FS); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H Index: clangd/Headers.cpp =================================================================== --- /dev/null +++ clangd/Headers.cpp @@ -0,0 +1,117 @@ +//===--- Headers.cpp - Include headers ---------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Headers.h" +#include "Compiler.h" +#include "Logger.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/HeaderSearch.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "clang/Tooling/CompilationDatabase.h" + +namespace clang { +namespace clangd { +namespace { + +class RecordHeaders : public PPCallbacks { +public: + RecordHeaders(std::set &Headers) : Headers(Headers) {} + + void InclusionDirective(SourceLocation /*HashLoc*/, + const Token & /*IncludeTok*/, + llvm::StringRef /*FileName*/, bool /*IsAngled*/, + CharSourceRange /*FilenameRange*/, + const FileEntry *File, llvm::StringRef /*SearchPath*/, + llvm::StringRef /*RelativePath*/, + const Module * /*Imported*/) override { + if (File != nullptr && !File->tryGetRealPathName().empty()) + Headers.insert(File->tryGetRealPathName()); + } + +private: + std::set &Headers; +}; + +} // namespace + +/// FIXME(ioeric): we might not want to insert an absolute include path if the +/// path is not shortened. +llvm::Expected +shortenIncludePath(llvm::StringRef File, llvm::StringRef Code, + llvm::StringRef Header, + const tooling::CompileCommand &CompileCommand, + IntrusiveRefCntPtr FS) { + // Set up a CompilerInstance and create a preprocessor to collect existing + // #include headers in \p Code. Preprocesor also provides HeaderSearch with + // which we can calculate the shortest include path for \p Header. + std::vector Argv; + for (const auto &S : CompileCommand.CommandLine) + Argv.push_back(S.c_str()); + IgnoringDiagConsumer IgnoreDiags; + auto CI = clang::createInvocationFromCommandLine( + Argv, + CompilerInstance::createDiagnostics(new DiagnosticOptions(), &IgnoreDiags, + false), + FS); + if (!CI) + return llvm::make_error( + "Failed to create a compiler instance for " + File, + llvm::inconvertibleErrorCode()); + CI->getFrontendOpts().DisableFree = false; + // Parse the main file to get all existing #includes in the file, and then we + // can make sure the same header (even with different include path) is not + // added more than once. + CI->getPreprocessorOpts().SingleFileParseMode = true; + + auto Clang = prepareCompilerInstance( + std::move(CI), /*Preamble=*/nullptr, + llvm::MemoryBuffer::getMemBuffer(Code, File), + std::make_shared(), FS, IgnoreDiags); + auto &DiagOpts = Clang->getDiagnosticOpts(); + DiagOpts.IgnoreWarnings = true; + + if (Clang->getFrontendOpts().Inputs.empty()) + return llvm::make_error( + "Empty frontend action inputs empty for file " + File, + llvm::inconvertibleErrorCode()); + PreprocessOnlyAction Action; + if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) + return llvm::make_error( + "Failed to begin preprocessor only action for file " + File, + llvm::inconvertibleErrorCode()); + std::set ExistingHeaders; + Clang->getPreprocessor().addPPCallbacks( + llvm::make_unique(ExistingHeaders)); + if (!Action.Execute()) + return llvm::make_error( + "Failed to execute preprocessor only action for file " + File, + llvm::inconvertibleErrorCode()); + if (ExistingHeaders.find(Header) != ExistingHeaders.end()) { + return llvm::make_error( + Header + " is already included in " + File, + llvm::inconvertibleErrorCode()); + } + + auto &HeaderSearchInfo = Clang->getPreprocessor().getHeaderSearchInfo(); + bool IsSystem = false; + std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics( + Header, CompileCommand.Directory, &IsSystem); + if (IsSystem) + Suggested = "<" + Suggested + ">"; + else + Suggested = "\"" + Suggested + "\""; + + log("Suggested #include for " + Header + " is: " + Suggested); + return Suggested; +} + +} // namespace clangd +} // namespace clang Index: clangd/JSONRPCDispatcher.cpp =================================================================== --- clangd/JSONRPCDispatcher.cpp +++ clangd/JSONRPCDispatcher.cpp @@ -13,6 +13,7 @@ #include "Trace.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Chrono.h" #include "llvm/Support/SourceMgr.h" #include @@ -60,20 +61,19 @@ OS << Message; OS.flush(); - std::lock_guard Guard(StreamMutex); - // Log without headers. - Logs << "--> " << S << '\n'; - Logs.flush(); - - // Emit message with header. - Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S; - Outs.flush(); + { + std::lock_guard Guard(StreamMutex); + Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S; + Outs.flush(); + } + log(llvm::Twine("--> ") + S); } void JSONOutput::log(const Twine &Message) { + llvm::sys::TimePoint<> Timestamp = std::chrono::system_clock::now(); trace::log(Message); std::lock_guard Guard(StreamMutex); - Logs << Message << '\n'; + Logs << llvm::formatv("[{0:%H:%M:%S.%L}] {1}\n", Timestamp, Message); Logs.flush(); } Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -49,12 +49,17 @@ }; struct URIForFile { - std::string file; + URIForFile() = default; + explicit URIForFile(std::string AbsPath); - std::string uri() const { return URI::createFile(file).toString(); } + /// Retrieves absolute path to the file. + llvm::StringRef file() const { return File; } + + explicit operator bool() const { return !File.empty(); } + std::string uri() const { return URI::createFile(File).toString(); } friend bool operator==(const URIForFile &LHS, const URIForFile &RHS) { - return LHS.file == RHS.file; + return LHS.File == RHS.File; } friend bool operator!=(const URIForFile &LHS, const URIForFile &RHS) { @@ -62,8 +67,11 @@ } friend bool operator<(const URIForFile &LHS, const URIForFile &RHS) { - return LHS.file < RHS.file; + return LHS.File < RHS.File; } + +private: + std::string File; }; /// Serialize/deserialize \p URIForFile to/from a string URI. @@ -74,6 +82,7 @@ /// The text document's URI. URIForFile uri; }; +json::Expr toJSON(const TextDocumentIdentifier &); bool fromJSON(const json::Expr &, TextDocumentIdentifier &); struct Position { @@ -416,6 +425,17 @@ bool fromJSON(const json::Expr &, WorkspaceEdit &); json::Expr toJSON(const WorkspaceEdit &WE); +struct IncludeInsertion { + /// The document in which the command was invoked. + TextDocumentIdentifier textDocument; + + /// The header to be inserted. This could be either a URI ir a literal string + /// quoted with <> or "" that can be #included directly. + std::string header; +}; +bool fromJSON(const json::Expr &, IncludeInsertion &); +json::Expr toJSON(const IncludeInsertion &II); + /// Exact commands are not specified in the protocol so we define the /// ones supported by Clangd here. The protocol specifies the command arguments /// to be "any[]" but to make this safer and more manageable, each command we @@ -427,15 +447,25 @@ struct ExecuteCommandParams { // Command to apply fix-its. Uses WorkspaceEdit as argument. const static llvm::StringLiteral CLANGD_APPLY_FIX_COMMAND; + // Command to insert an #include into code. + const static llvm::StringLiteral CLANGD_INSERT_HEADER_INCLUDE; /// The command identifier, e.g. CLANGD_APPLY_FIX_COMMAND std::string command; // Arguments llvm::Optional workspaceEdit; + + llvm::Optional includeInsertion; }; bool fromJSON(const json::Expr &, ExecuteCommandParams &); +struct Command : public ExecuteCommandParams { + std::string title; +}; + +json::Expr toJSON(const Command &C); + struct ApplyWorkspaceEditParams { WorkspaceEdit edit; }; @@ -450,6 +480,27 @@ }; bool fromJSON(const json::Expr &, TextDocumentPositionParams &); +enum class MarkupKind { + PlainText, + Markdown, +}; + +struct MarkupContent { + MarkupKind Kind = MarkupKind::PlainText; + std::string Value; +}; +json::Expr toJSON(const MarkupContent &MC); + +struct Hover { + /// The hover's content + MarkupContent Contents; + + /// An optional range is a range inside a text document + /// that is used to visualize a hover, e.g. by changing the background color. + llvm::Optional Range; +}; +json::Expr toJSON(const Hover &H); + /// The kind of a completion entry. enum class CompletionItemKind { Missing = 0, @@ -552,12 +603,10 @@ /// themselves. std::vector additionalTextEdits; + llvm::Optional command; // TODO(krasimir): The following optional fields defined by the language // server protocol are unsupported: // - // command?: Command - An optional command that is executed *after* inserting - // this completion. - // // data?: any - A data entry field that is preserved on a completion item // between a completion and a completion resolve request. }; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -12,8 +12,8 @@ //===----------------------------------------------------------------------===// #include "Protocol.h" -#include "URI.h" #include "Logger.h" +#include "URI.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Format.h" @@ -24,6 +24,11 @@ namespace clang { namespace clangd { +URIForFile::URIForFile(std::string AbsPath) { + assert(llvm::sys::path::is_absolute(AbsPath) && "the path is relative"); + File = std::move(AbsPath); +} + bool fromJSON(const json::Expr &E, URIForFile &R) { if (auto S = E.asString()) { auto U = URI::parse(*S); @@ -40,20 +45,22 @@ log(llvm::toString(Path.takeError())); return false; } - R.file = *Path; + R = URIForFile(*Path); return true; } return false; } -json::Expr toJSON(const URIForFile &U) { - return U.uri(); -} +json::Expr toJSON(const URIForFile &U) { return U.uri(); } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const URIForFile &U) { return OS << U.uri(); } +json::Expr toJSON(const TextDocumentIdentifier &R) { + return json::obj{{"uri", R.uri}}; +} + bool fromJSON(const json::Expr &Params, TextDocumentIdentifier &R) { json::ObjectMapper O(Params); return O && O.map("uri", R.uri); @@ -323,6 +330,8 @@ const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND = "clangd.applyFix"; +const llvm::StringLiteral ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE = + "clangd.insertInclude"; bool fromJSON(const json::Expr &Params, ExecuteCommandParams &R) { json::ObjectMapper O(Params); @@ -333,10 +342,22 @@ if (R.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) { return Args && Args->size() == 1 && fromJSON(Args->front(), R.workspaceEdit); + } else if (R.command == ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) { + return Args && Args->size() == 1 && + fromJSON(Args->front(), R.includeInsertion); } return false; // Unrecognized command. } +json::Expr toJSON(const Command &C) { + auto Cmd = json::obj{{"title", C.title}, {"command", C.command}}; + if (C.workspaceEdit) + Cmd["arguments"] = {*C.workspaceEdit}; + else if (C.includeInsertion) + Cmd["arguments"] = {*C.includeInsertion}; + return std::move(Cmd); +} + json::Expr toJSON(const WorkspaceEdit &WE) { if (!WE.changes) return json::obj{}; @@ -346,6 +367,15 @@ return json::obj{{"changes", std::move(FileChanges)}}; } +bool fromJSON(const json::Expr &II, IncludeInsertion &R) { + json::ObjectMapper O(II); + return O && O.map("textDocument", R.textDocument) && + O.map("header", R.header); +} +json::Expr toJSON(const IncludeInsertion &II) { + return json::obj{{"textDocument", II.textDocument}, {"header", II.header}}; +} + json::Expr toJSON(const ApplyWorkspaceEditParams &Params) { return json::obj{{"edit", Params.edit}}; } @@ -356,6 +386,36 @@ O.map("position", R.position); } +static StringRef toTextKind(MarkupKind Kind) +{ + switch (Kind) { + case MarkupKind::PlainText: + return "plaintext"; + case MarkupKind::Markdown: + return "markdown"; + } + llvm_unreachable("Invalid MarkupKind"); +} + +json::Expr toJSON(const MarkupContent &MC) { + if (MC.Value.empty()) + return nullptr; + + return json::obj{ + {"kind", toTextKind(MC.Kind)}, + {"value", MC.Value}, + }; +} + +json::Expr toJSON(const Hover &H) { + json::obj Result{{"contents", toJSON(H.Contents)}}; + + if (H.Range.hasValue()) + Result["range"] = toJSON(*H.Range); + + return Result; +} + json::Expr toJSON(const CompletionItem &CI) { assert(!CI.label.empty() && "completion item label is required"); json::obj Result{{"label", CI.label}}; @@ -377,6 +437,8 @@ Result["textEdit"] = *CI.textEdit; if (!CI.additionalTextEdits.empty()) Result["additionalTextEdits"] = json::ary(CI.additionalTextEdits); + if (CI.command) + Result["command"] = *CI.command; return std::move(Result); } Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -51,6 +51,7 @@ virtual void onCommand(ExecuteCommandParams &Params) = 0; virtual void onRename(RenameParams &Parames) = 0; virtual void onDocumentHighlight(TextDocumentPositionParams &Params) = 0; + virtual void onHover(TextDocumentPositionParams &Params) = 0; }; void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -67,6 +67,7 @@ Register("textDocument/switchSourceHeader", &ProtocolCallbacks::onSwitchSourceHeader); Register("textDocument/rename", &ProtocolCallbacks::onRename); + Register("textDocument/hover", &ProtocolCallbacks::onHover); Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); Register("workspace/executeCommand", &ProtocolCallbacks::onCommand); Register("textDocument/documentHighlight", Index: clangd/XRefs.h =================================================================== --- clangd/XRefs.h +++ clangd/XRefs.h @@ -27,6 +27,9 @@ std::vector findDocumentHighlights(ParsedAST &AST, Position Pos); +/// Get the hover information when hovering at \p Pos. +Hover getHover(ParsedAST &AST, Position Pos); + } // namespace clangd } // namespace clang #endif Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -9,6 +9,7 @@ #include "XRefs.h" #include "Logger.h" #include "URI.h" +#include "clang/AST/DeclTemplate.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexingAction.h" #include "llvm/Support/Path.h" @@ -31,10 +32,15 @@ return nullptr; } +struct MacroDecl { + StringRef Name; + const MacroInfo *Info; +}; + /// Finds declarations locations that a given source location refers to. class DeclarationAndMacrosFinder : public index::IndexDataConsumer { std::vector Decls; - std::vector MacroInfos; + std::vector MacroInfos; const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; @@ -54,10 +60,17 @@ return std::move(Decls); } - std::vector takeMacroInfos() { + std::vector takeMacroInfos() { // Don't keep the same Macro info multiple times. - std::sort(MacroInfos.begin(), MacroInfos.end()); - auto Last = std::unique(MacroInfos.begin(), MacroInfos.end()); + std::sort(MacroInfos.begin(), MacroInfos.end(), + [](const MacroDecl &Left, const MacroDecl &Right) { + return Left.Info < Right.Info; + }); + + auto Last = std::unique(MacroInfos.begin(), MacroInfos.end(), + [](const MacroDecl &Left, const MacroDecl &Right) { + return Left.Info == Right.Info; + }); MacroInfos.erase(Last, MacroInfos.end()); return std::move(MacroInfos); } @@ -111,7 +124,7 @@ PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); MacroInfo *MacroInf = MacroDef.getMacroInfo(); if (MacroInf) { - MacroInfos.push_back(MacroInf); + MacroInfos.push_back(MacroDecl{IdentifierInfo->getName(), MacroInf}); } } } @@ -149,7 +162,7 @@ } } - L.uri.file = FilePath.str(); + L.uri = URIForFile(FilePath.str()); L.range = R; return L; } @@ -176,8 +189,7 @@ DeclMacrosFinder, IndexOpts); std::vector Decls = DeclMacrosFinder->takeDecls(); - std::vector MacroInfos = - DeclMacrosFinder->takeMacroInfos(); + std::vector MacroInfos = DeclMacrosFinder->takeMacroInfos(); std::vector Result; for (auto Item : Decls) { @@ -187,7 +199,8 @@ } for (auto Item : MacroInfos) { - SourceRange SR(Item->getDefinitionLoc(), Item->getDefinitionEndLoc()); + SourceRange SR(Item.Info->getDefinitionLoc(), + Item.Info->getDefinitionEndLoc()); auto L = getDeclarationLocation(AST, SR); if (L) Result.push_back(*L); @@ -299,5 +312,138 @@ return DocHighlightsFinder->takeHighlights(); } +static PrintingPolicy PrintingPolicyForDecls(PrintingPolicy Base) { + PrintingPolicy Policy(Base); + + Policy.AnonymousTagLocations = false; + Policy.TerseOutput = true; + Policy.PolishForDeclaration = true; + Policy.ConstantsAsWritten = true; + Policy.SuppressTagKeyword = false; + + return Policy; +} + +/// Return a string representation (e.g. "class MyNamespace::MyClass") of +/// the type declaration \p TD. +static std::string TypeDeclToString(const TypeDecl *TD) { + QualType Type = TD->getASTContext().getTypeDeclType(TD); + + PrintingPolicy Policy = + PrintingPolicyForDecls(TD->getASTContext().getPrintingPolicy()); + + std::string Name; + llvm::raw_string_ostream Stream(Name); + Type.print(Stream, Policy); + + return Stream.str(); +} + +/// Return a string representation (e.g. "namespace ns1::ns2") of +/// the named declaration \p ND. +static std::string NamedDeclQualifiedName(const NamedDecl *ND, + StringRef Prefix) { + PrintingPolicy Policy = + PrintingPolicyForDecls(ND->getASTContext().getPrintingPolicy()); + + std::string Name; + llvm::raw_string_ostream Stream(Name); + Stream << Prefix << ' '; + ND->printQualifiedName(Stream, Policy); + + return Stream.str(); +} + +/// Given a declaration \p D, return a human-readable string representing the +/// scope in which it is declared. If the declaration is in the global scope, +/// return the string "global namespace". +static llvm::Optional getScopeName(const Decl *D) { + const DeclContext *DC = D->getDeclContext(); + + if (const TranslationUnitDecl *TUD = dyn_cast(DC)) + return std::string("global namespace"); + + if (const TypeDecl *TD = dyn_cast(DC)) + return TypeDeclToString(TD); + else if (const NamespaceDecl *ND = dyn_cast(DC)) + return NamedDeclQualifiedName(ND, "namespace"); + else if (const FunctionDecl *FD = dyn_cast(DC)) + return NamedDeclQualifiedName(FD, "function"); + + return llvm::None; +} + +/// Generate a \p Hover object given the declaration \p D. +static Hover getHoverContents(const Decl *D) { + Hover H; + llvm::Optional NamedScope = getScopeName(D); + + // Generate the "Declared in" section. + if (NamedScope) { + assert(!NamedScope->empty()); + + H.Contents.Value += "Declared in "; + H.Contents.Value += *NamedScope; + H.Contents.Value += "\n\n"; + } + + // We want to include the template in the Hover. + if (TemplateDecl *TD = D->getDescribedTemplate()) + D = TD; + + std::string DeclText; + llvm::raw_string_ostream OS(DeclText); + + PrintingPolicy Policy = + PrintingPolicyForDecls(D->getASTContext().getPrintingPolicy()); + + D->print(OS, Policy); + + OS.flush(); + + H.Contents.Value += DeclText; + return H; +} + +/// Generate a \p Hover object given the macro \p MacroInf. +static Hover getHoverContents(StringRef MacroName) { + Hover H; + + H.Contents.Value = "#define "; + H.Contents.Value += MacroName; + + return H; +} + +Hover getHover(ParsedAST &AST, Position Pos) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); + if (FE == nullptr) + return Hover(); + + SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE); + auto DeclMacrosFinder = std::make_shared( + llvm::errs(), SourceLocationBeg, AST.getASTContext(), + AST.getPreprocessor()); + + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DeclMacrosFinder, IndexOpts); + + std::vector Macros = DeclMacrosFinder->takeMacroInfos(); + if (!Macros.empty()) + return getHoverContents(Macros[0].Name); + + std::vector Decls = DeclMacrosFinder->takeDecls(); + if (!Decls.empty()) + return getHoverContents(Decls[0]); + + return Hover(); +} + } // namespace clangd } // namespace clang Index: clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp =================================================================== --- clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp +++ clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp @@ -13,10 +13,12 @@ // //===---------------------------------------------------------------------===// +#include "index/CanonicalIncludes.h" #include "index/Index.h" #include "index/Merge.h" #include "index/SymbolCollector.h" #include "index/SymbolYAML.h" +#include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Index/IndexDataConsumer.h" @@ -57,11 +59,19 @@ class WrappedIndexAction : public WrapperFrontendAction { public: WrappedIndexAction(std::shared_ptr C, + std::unique_ptr Includes, const index::IndexingOptions &Opts, tooling::ExecutionContext *Ctx) : WrapperFrontendAction( index::createIndexingAction(C, Opts, nullptr)), - Ctx(Ctx), Collector(C) {} + Ctx(Ctx), Collector(C), Includes(std::move(Includes)), + PragmaHandler(collectIWYUHeaderMaps(this->Includes.get())) {} + + std::unique_ptr + CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { + CI.getPreprocessor().addCommentHandler(PragmaHandler.get()); + return WrapperFrontendAction::CreateASTConsumer(CI, InFile); + } void EndSourceFileAction() override { WrapperFrontendAction::EndSourceFileAction(); @@ -78,6 +88,8 @@ private: tooling::ExecutionContext *Ctx; std::shared_ptr Collector; + std::unique_ptr Includes; + std::unique_ptr PragmaHandler; }; index::IndexingOptions IndexOpts; @@ -86,9 +98,13 @@ IndexOpts.IndexFunctionLocals = false; auto CollectorOpts = SymbolCollector::Options(); CollectorOpts.FallbackDir = AssumedHeaderDir; + CollectorOpts.CollectIncludePath = true; + auto Includes = llvm::make_unique(); + addSystemHeadersMapping(Includes.get()); + CollectorOpts.Includes = Includes.get(); return new WrappedIndexAction( - std::make_shared(std::move(CollectorOpts)), IndexOpts, - Ctx); + std::make_shared(std::move(CollectorOpts)), + std::move(Includes), IndexOpts, Ctx); } tooling::ExecutionContext *Ctx; Index: clangd/index/CanonicalIncludes.h =================================================================== --- /dev/null +++ clangd/index/CanonicalIncludes.h @@ -0,0 +1,79 @@ +//===-- CanonicalIncludes.h - remap #include header -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// At indexing time, we decide which file to #included for a symbol. +// Usually this is the file with the canonical decl, but there are exceptions: +// - private headers may have pragmas pointing to the matching public header. +// (These are "IWYU" pragmas, named after the include-what-you-use tool). +// - the standard library is implemented in many files, without any pragmas. +// We have a lookup table for common standard library implementations. +// libstdc++ puts char_traits in bits/char_traits.h, but we #include . +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_CANONICALINCLUDES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_CANONICALINCLUDES_H + +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Regex.h" +#include +#include + +namespace clang { +namespace clangd { + +/// Maps a definition location onto an #include file, based on a set of filename +/// rules. +class CanonicalIncludes { +public: + CanonicalIncludes() = default; + + /// Adds a string-to-string mapping from \p Path to \p CanonicalPath. + void addMapping(llvm::StringRef Path, llvm::StringRef CanonicalPath); + + /// Maps all files matching \p RE to \p CanonicalPath + void addRegexMapping(llvm::StringRef RE, llvm::StringRef CanonicalPath); + + /// \return \p Header itself if there is no mapping for it; otherwise, return + /// a canonical header name. + llvm::StringRef mapHeader(llvm::StringRef Header) const; + +private: + // A map from header patterns to header names. This needs to be mutable so + // that we can match again a Regex in a const function member. + // FIXME(ioeric): All the regexes we have so far are suffix matches. The + // performance could be improved by allowing only suffix matches instead of + // arbitrary regexes. + mutable std::vector> + RegexHeaderMappingTable; +}; + +/// Returns a CommentHandler that parses pragma comment on include files to +/// determine when we should include a different header from the header that +/// directly defines a symbol. Mappinps are registered with \p Includes. +/// +/// Currently it only supports IWYU private pragma: +/// https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md#iwyu-pragma-private +std::unique_ptr +collectIWYUHeaderMaps(CanonicalIncludes *Includes); + +/// Adds mapping for system headers. Approximately, the following system headers +/// are handled: +/// - C++ standard library e.g. bits/basic_string.h$ -> +/// - Posix library e.g. bits/pthreadtypes.h$ -> +/// - Compiler extensions, e.g. include/avx512bwintrin.h$ -> +/// The mapping is hardcoded and hand-maintained, so it might not cover all +/// headers. +void addSystemHeadersMapping(CanonicalIncludes *Includes); + +} // namespace clangd +} // namespace clang + +#endif //LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_HEADERMAPCOLLECTOR_H Index: clangd/index/CanonicalIncludes.cpp =================================================================== --- /dev/null +++ clangd/index/CanonicalIncludes.cpp @@ -0,0 +1,704 @@ +//===-- CanonicalIncludes.h - remap #inclue headers--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CanonicalIncludes.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace clangd { +namespace { +const char IWYUPragma[] = "// IWYU pragma: private, include "; +} // namespace + +void CanonicalIncludes::addMapping(llvm::StringRef Path, + llvm::StringRef CanonicalPath) { + addRegexMapping((llvm::Twine("^") + Path + "$").str(), CanonicalPath); +} + +void CanonicalIncludes::addRegexMapping(llvm::StringRef RE, + llvm::StringRef CanonicalPath) { + this->RegexHeaderMappingTable.emplace_back(llvm::Regex(RE), CanonicalPath); +} + +llvm::StringRef CanonicalIncludes::mapHeader(llvm::StringRef Header) const { + for (auto &Entry : RegexHeaderMappingTable) { +#ifndef NDEBUG + std::string Dummy; + assert(Entry.first.isValid(Dummy) && "Regex should never be invalid!"); +#endif + if (Entry.first.match(Header)) + return Entry.second; + } + return Header; +} + +std::unique_ptr +collectIWYUHeaderMaps(CanonicalIncludes *Includes) { + class PragmaCommentHandler : public clang::CommentHandler { + public: + PragmaCommentHandler(CanonicalIncludes *Includes) : Includes(Includes) {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getCharRange(Range), + PP.getSourceManager(), PP.getLangOpts()); + if (!Text.consume_front(IWYUPragma)) + return false; + // FIXME(ioeric): resolve the header and store actual file path. For now, + // we simply assume the written header is suitable to be #included. + Includes->addMapping(PP.getSourceManager().getFilename(Range.getBegin()), + Text.startswith("<") ? Text.str() + : ("\"" + Text + "\"").str()); + return false; + } + + private: + CanonicalIncludes *const Includes; + }; + return llvm::make_unique(Includes); +} + +void addSystemHeadersMapping(CanonicalIncludes *Includes) { + static const std::vector> + SystemHeaderMap = { + {"include/__stddef_max_align_t.h$", ""}, + {"include/__wmmintrin_aes.h$", ""}, + {"include/__wmmintrin_pclmul.h$", ""}, + {"include/adxintrin.h$", ""}, + {"include/ammintrin.h$", ""}, + {"include/avx2intrin.h$", ""}, + {"include/avx512bwintrin.h$", ""}, + {"include/avx512cdintrin.h$", ""}, + {"include/avx512dqintrin.h$", ""}, + {"include/avx512erintrin.h$", ""}, + {"include/avx512fintrin.h$", ""}, + {"include/avx512ifmaintrin.h$", ""}, + {"include/avx512ifmavlintrin.h$", ""}, + {"include/avx512pfintrin.h$", ""}, + {"include/avx512vbmiintrin.h$", ""}, + {"include/avx512vbmivlintrin.h$", ""}, + {"include/avx512vlbwintrin.h$", ""}, + {"include/avx512vlcdintrin.h$", ""}, + {"include/avx512vldqintrin.h$", ""}, + {"include/avx512vlintrin.h$", ""}, + {"include/avxintrin.h$", ""}, + {"include/bmi2intrin.h$", ""}, + {"include/bmiintrin.h$", ""}, + {"include/emmintrin.h$", ""}, + {"include/f16cintrin.h$", ""}, + {"include/float.h$", ""}, + {"include/fma4intrin.h$", ""}, + {"include/fmaintrin.h$", ""}, + {"include/fxsrintrin.h$", ""}, + {"include/ia32intrin.h$", ""}, + {"include/immintrin.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/limits.h$", ""}, + {"include/lzcntintrin.h$", ""}, + {"include/mm3dnow.h$", ""}, + {"include/mm_malloc.h$", ""}, + {"include/mmintrin.h$", ""}, + {"include/mwaitxintrin.h$", ""}, + {"include/pkuintrin.h$", ""}, + {"include/pmmintrin.h$", ""}, + {"include/popcntintrin.h$", ""}, + {"include/prfchwintrin.h$", ""}, + {"include/rdseedintrin.h$", ""}, + {"include/rtmintrin.h$", ""}, + {"include/shaintrin.h$", ""}, + {"include/smmintrin.h$", ""}, + {"include/stdalign.h$", ""}, + {"include/stdarg.h$", ""}, + {"include/stdbool.h$", ""}, + {"include/stddef.h$", ""}, + {"include/stdint.h$", ""}, + {"include/tbmintrin.h$", ""}, + {"include/tmmintrin.h$", ""}, + {"include/wmmintrin.h$", ""}, + {"include/x86intrin.h$", ""}, + {"include/xmmintrin.h$", ""}, + {"include/xopintrin.h$", ""}, + {"include/xsavecintrin.h$", ""}, + {"include/xsaveintrin.h$", ""}, + {"include/xsaveoptintrin.h$", ""}, + {"include/xsavesintrin.h$", ""}, + {"include/xtestintrin.h$", ""}, + {"include/_G_config.h$", ""}, + {"include/assert.h$", ""}, + {"algorithm$", ""}, + {"array$", ""}, + {"atomic$", ""}, + {"backward/auto_ptr.h$", ""}, + {"backward/binders.h$", ""}, + {"bits/algorithmfwd.h$", ""}, + {"bits/alloc_traits.h$", ""}, + {"bits/allocator.h$", ""}, + {"bits/atomic_base.h$", ""}, + {"bits/atomic_lockfree_defines.h$", ""}, + {"bits/basic_ios.h$", ""}, + {"bits/basic_ios.tcc$", ""}, + {"bits/basic_string.h$", ""}, + {"bits/basic_string.tcc$", ""}, + {"bits/char_traits.h$", ""}, + {"bits/codecvt.h$", ""}, + {"bits/concept_check.h$", ""}, + {"bits/cpp_type_traits.h$", ""}, + {"bits/cxxabi_forced.h$", ""}, + {"bits/deque.tcc$", ""}, + {"bits/exception_defines.h$", ""}, + {"bits/exception_ptr.h$", ""}, + {"bits/forward_list.h$", ""}, + {"bits/forward_list.tcc$", ""}, + {"bits/fstream.tcc$", ""}, + {"bits/functexcept.h$", ""}, + {"bits/functional_hash.h$", ""}, + {"bits/gslice.h$", ""}, + {"bits/gslice_array.h$", ""}, + {"bits/hash_bytes.h$", ""}, + {"bits/hashtable.h$", ""}, + {"bits/hashtable_policy.h$", ""}, + {"bits/indirect_array.h$", ""}, + {"bits/ios_base.h$", ""}, + {"bits/istream.tcc$", ""}, + {"bits/list.tcc$", ""}, + {"bits/locale_classes.h$", ""}, + {"bits/locale_classes.tcc$", ""}, + {"bits/locale_facets.h$", ""}, + {"bits/locale_facets.tcc$", ""}, + {"bits/locale_facets_nonio.h$", ""}, + {"bits/locale_facets_nonio.tcc$", ""}, + {"bits/localefwd.h$", ""}, + {"bits/mask_array.h$", ""}, + {"bits/memoryfwd.h$", ""}, + {"bits/move.h$", ""}, + {"bits/nested_exception.h$", ""}, + {"bits/ostream.tcc$", ""}, + {"bits/ostream_insert.h$", ""}, + {"bits/postypes.h$", ""}, + {"bits/ptr_traits.h$", ""}, + {"bits/random.h$", ""}, + {"bits/random.tcc$", ""}, + {"bits/range_access.h$", ""}, + {"bits/regex.h$", ""}, + {"bits/regex_compiler.h$", ""}, + {"bits/regex_constants.h$", ""}, + {"bits/regex_cursor.h$", ""}, + {"bits/regex_error.h$", ""}, + {"bits/regex_grep_matcher.h$", ""}, + {"bits/regex_grep_matcher.tcc$", ""}, + {"bits/regex_nfa.h$", ""}, + {"bits/shared_ptr.h$", ""}, + {"bits/shared_ptr_base.h$", ""}, + {"bits/slice_array.h$", ""}, + {"bits/sstream.tcc$", ""}, + {"bits/stl_algo.h$", ""}, + {"bits/stl_algobase.h$", ""}, + {"bits/stl_bvector.h$", ""}, + {"bits/stl_construct.h$", ""}, + {"bits/stl_deque.h$", ""}, + {"bits/stl_function.h$", ""}, + {"bits/stl_heap.h$", ""}, + {"bits/stl_iterator.h$", ""}, + {"bits/stl_iterator_base_funcs.h$", ""}, + {"bits/stl_iterator_base_types.h$", ""}, + {"bits/stl_list.h$", ""}, + {"bits/stl_map.h$", ""}, + {"bits/stl_multimap.h$", ""}, + {"bits/stl_multiset.h$", ""}, + {"bits/stl_numeric.h$", ""}, + {"bits/stl_pair.h$", ""}, + {"bits/stl_queue.h$", ""}, + {"bits/stl_raw_storage_iter.h$", ""}, + {"bits/stl_relops.h$", ""}, + {"bits/stl_set.h$", ""}, + {"bits/stl_stack.h$", ""}, + {"bits/stl_tempbuf.h$", ""}, + {"bits/stl_tree.h$", ""}, + {"bits/stl_uninitialized.h$", ""}, + {"bits/stl_vector.h$", ""}, + {"bits/stream_iterator.h$", ""}, + {"bits/streambuf.tcc$", ""}, + {"bits/streambuf_iterator.h$", ""}, + {"bits/stringfwd.h$", ""}, + {"bits/unique_ptr.h$", ""}, + {"bits/unordered_map.h$", ""}, + {"bits/unordered_set.h$", ""}, + {"bits/uses_allocator.h$", ""}, + {"bits/valarray_after.h$", ""}, + {"bits/valarray_array.h$", ""}, + {"bits/valarray_array.tcc$", ""}, + {"bits/valarray_before.h$", ""}, + {"bits/vector.tcc$", ""}, + {"bitset$", ""}, + {"ccomplex$", ""}, + {"cctype$", ""}, + {"cerrno$", ""}, + {"cfenv$", ""}, + {"cfloat$", ""}, + {"chrono$", ""}, + {"cinttypes$", ""}, + {"climits$", ""}, + {"clocale$", ""}, + {"cmath$", ""}, + {"complex$", ""}, + {"complex.h$", ""}, + {"condition_variable$", ""}, + {"csetjmp$", ""}, + {"csignal$", ""}, + {"cstdalign$", ""}, + {"cstdarg$", ""}, + {"cstdbool$", ""}, + {"cstdint$", ""}, + {"cstdio$", ""}, + {"cstdlib$", ""}, + {"cstring$", ""}, + {"ctgmath$", ""}, + {"ctime$", ""}, + {"cwchar$", ""}, + {"cwctype$", ""}, + {"cxxabi.h$", ""}, + {"debug/debug.h$", ""}, + {"deque$", ""}, + {"exception$", ""}, + {"ext/alloc_traits.h$", ""}, + {"ext/atomicity.h$", ""}, + {"ext/concurrence.h$", ""}, + {"ext/new_allocator.h$", ""}, + {"ext/numeric_traits.h$", ""}, + {"ext/string_conversions.h$", ""}, + {"ext/type_traits.h$", ""}, + {"fenv.h$", ""}, + {"forward_list$", ""}, + {"fstream$", ""}, + {"functional$", ""}, + {"future$", ""}, + {"initializer_list$", ""}, + {"iomanip$", ""}, + {"ios$", ""}, + {"iosfwd$", ""}, + {"iostream$", ""}, + {"istream$", ""}, + {"iterator$", ""}, + {"limits$", ""}, + {"list$", ""}, + {"locale$", ""}, + {"map$", ""}, + {"memory$", ""}, + {"mutex$", ""}, + {"new$", ""}, + {"numeric$", ""}, + {"ostream$", ""}, + {"queue$", ""}, + {"random$", ""}, + {"ratio$", ""}, + {"regex$", ""}, + {"scoped_allocator$", ""}, + {"set$", ""}, + {"sstream$", ""}, + {"stack$", ""}, + {"stdexcept$", ""}, + {"streambuf$", ""}, + {"string$", ""}, + {"system_error$", ""}, + {"tgmath.h$", ""}, + {"thread$", ""}, + {"tuple$", ""}, + {"type_traits$", ""}, + {"typeindex$", ""}, + {"typeinfo$", ""}, + {"unordered_map$", ""}, + {"unordered_set$", ""}, + {"utility$", ""}, + {"valarray$", ""}, + {"vector$", ""}, + {"include/complex.h$", ""}, + {"include/ctype.h$", ""}, + {"include/errno.h$", ""}, + {"include/fenv.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/libio.h$", ""}, + {"include/limits.h$", ""}, + {"include/locale.h$", ""}, + {"include/math.h$", ""}, + {"include/setjmp.h$", ""}, + {"include/signal.h$", ""}, + {"include/stdint.h$", ""}, + {"include/stdio.h$", ""}, + {"include/stdlib.h$", ""}, + {"include/string.h$", ""}, + {"include/time.h$", ""}, + {"include/wchar.h$", ""}, + {"include/wctype.h$", ""}, + {"bits/cmathcalls.h$", ""}, + {"bits/errno.h$", ""}, + {"bits/fenv.h$", ""}, + {"bits/huge_val.h$", ""}, + {"bits/huge_valf.h$", ""}, + {"bits/huge_vall.h$", ""}, + {"bits/inf.h$", ""}, + {"bits/local_lim.h$", ""}, + {"bits/locale.h$", ""}, + {"bits/mathcalls.h$", ""}, + {"bits/mathdef.h$", ""}, + {"bits/nan.h$", ""}, + {"bits/posix1_lim.h$", ""}, + {"bits/posix2_lim.h$", ""}, + {"bits/setjmp.h$", ""}, + {"bits/sigaction.h$", ""}, + {"bits/sigcontext.h$", ""}, + {"bits/siginfo.h$", ""}, + {"bits/signum.h$", ""}, + {"bits/sigset.h$", ""}, + {"bits/sigstack.h$", ""}, + {"bits/stdio_lim.h$", ""}, + {"bits/sys_errlist.h$", ""}, + {"bits/time.h$", ""}, + {"bits/timex.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/wchar.h$", ""}, + {"bits/wordsize.h$", ""}, + {"bits/xopen_lim.h$", ""}, + {"include/xlocale.h$", ""}, + {"bits/atomic_word.h$", ""}, + {"bits/basic_file.h$", ""}, + {"bits/c\\+\\+allocator.h$", ""}, + {"bits/c\\+\\+config.h$", ""}, + {"bits/c\\+\\+io.h$", ""}, + {"bits/c\\+\\+locale.h$", ""}, + {"bits/cpu_defines.h$", ""}, + {"bits/ctype_base.h$", ""}, + {"bits/cxxabi_tweaks.h$", ""}, + {"bits/error_constants.h$", ""}, + {"bits/gthr-default.h$", ""}, + {"bits/gthr.h$", ""}, + {"bits/opt_random.h$", ""}, + {"bits/os_defines.h$", ""}, + // GNU C headers + {"include/aio.h$", ""}, + {"include/aliases.h$", ""}, + {"include/alloca.h$", ""}, + {"include/ar.h$", ""}, + {"include/argp.h$", ""}, + {"include/argz.h$", ""}, + {"include/arpa/nameser.h$", ""}, + {"include/arpa/nameser_compat.h$", ""}, + {"include/byteswap.h$", ""}, + {"include/cpio.h$", ""}, + {"include/crypt.h$", ""}, + {"include/dirent.h$", ""}, + {"include/dlfcn.h$", ""}, + {"include/elf.h$", ""}, + {"include/endian.h$", ""}, + {"include/envz.h$", ""}, + {"include/err.h$", ""}, + {"include/error.h$", ""}, + {"include/execinfo.h$", ""}, + {"include/fcntl.h$", ""}, + {"include/features.h$", ""}, + {"include/fenv.h$", ""}, + {"include/fmtmsg.h$", ""}, + {"include/fnmatch.h$", ""}, + {"include/fstab.h$", ""}, + {"include/fts.h$", ""}, + {"include/ftw.h$", ""}, + {"include/gconv.h$", ""}, + {"include/getopt.h$", ""}, + {"include/glob.h$", ""}, + {"include/grp.h$", ""}, + {"include/gshadow.h$", ""}, + {"include/iconv.h$", ""}, + {"include/ifaddrs.h$", ""}, + {"include/kdb.h$", ""}, + {"include/langinfo.h$", ""}, + {"include/libgen.h$", ""}, + {"include/libintl.h$", ""}, + {"include/link.h$", ""}, + {"include/malloc.h$", ""}, + {"include/mcheck.h$", ""}, + {"include/memory.h$", ""}, + {"include/mntent.h$", ""}, + {"include/monetary.h$", ""}, + {"include/mqueue.h$", ""}, + {"include/netdb.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/nl_types.h$", ""}, + {"include/nss.h$", ""}, + {"include/obstack.h$", ""}, + {"include/panel.h$", ""}, + {"include/paths.h$", ""}, + {"include/printf.h$", ""}, + {"include/profile.h$", ""}, + {"include/pthread.h$", ""}, + {"include/pty.h$", ""}, + {"include/pwd.h$", ""}, + {"include/re_comp.h$", ""}, + {"include/regex.h$", ""}, + {"include/regexp.h$", ""}, + {"include/resolv.h$", ""}, + {"include/rpc/netdb.h$", ""}, + {"include/sched.h$", ""}, + {"include/search.h$", ""}, + {"include/semaphore.h$", ""}, + {"include/sgtty.h$", ""}, + {"include/shadow.h$", ""}, + {"include/spawn.h$", ""}, + {"include/stab.h$", ""}, + {"include/stdc-predef.h$", ""}, + {"include/stdio_ext.h$", ""}, + {"include/strings.h$", ""}, + {"include/stropts.h$", ""}, + {"include/sudo_plugin.h$", ""}, + {"include/sysexits.h$", ""}, + {"include/tar.h$", ""}, + {"include/tcpd.h$", ""}, + {"include/term.h$", ""}, + {"include/term_entry.h$", ""}, + {"include/termcap.h$", ""}, + {"include/termios.h$", ""}, + {"include/thread_db.h$", ""}, + {"include/tic.h$", ""}, + {"include/ttyent.h$", ""}, + {"include/uchar.h$", ""}, + {"include/ucontext.h$", ""}, + {"include/ulimit.h$", ""}, + {"include/unctrl.h$", ""}, + {"include/unistd.h$", ""}, + {"include/utime.h$", ""}, + {"include/utmp.h$", ""}, + {"include/utmpx.h$", ""}, + {"include/values.h$", ""}, + {"include/wordexp.h$", ""}, + {"fpu_control.h$", ""}, + {"ieee754.h$", ""}, + {"include/xlocale.h$", ""}, + {"gnu/lib-names.h$", ""}, + {"gnu/libc-version.h$", ""}, + {"gnu/option-groups.h$", ""}, + {"gnu/stubs-32.h$", ""}, + {"gnu/stubs-64.h$", ""}, + {"gnu/stubs-x32.h$", ""}, + {"include/rpc/auth_des.h$", ""}, + {"include/rpc/rpc_msg.h$", ""}, + {"include/rpc/pmap_clnt.h$", ""}, + {"include/rpc/rpc.h$", ""}, + {"include/rpc/types.h$", ""}, + {"include/rpc/auth_unix.h$", ""}, + {"include/rpc/key_prot.h$", ""}, + {"include/rpc/pmap_prot.h$", ""}, + {"include/rpc/auth.h$", ""}, + {"include/rpc/svc_auth.h$", ""}, + {"include/rpc/xdr.h$", ""}, + {"include/rpc/pmap_rmt.h$", ""}, + {"include/rpc/des_crypt.h$", ""}, + {"include/rpc/svc.h$", ""}, + {"include/rpc/rpc_des.h$", ""}, + {"include/rpc/clnt.h$", ""}, + {"include/scsi/scsi.h$", ""}, + {"include/scsi/sg.h$", ""}, + {"include/scsi/scsi_ioctl.h$", ""}, + {"include/netrose/rose.h$", ""}, + {"include/nfs/nfs.h$", ""}, + {"include/netatalk/at.h$", ""}, + {"include/netinet/ether.h$", ""}, + {"include/netinet/icmp6.h$", ""}, + {"include/netinet/if_ether.h$", ""}, + {"include/netinet/if_fddi.h$", ""}, + {"include/netinet/if_tr.h$", ""}, + {"include/netinet/igmp.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/netinet/in_systm.h$", ""}, + {"include/netinet/ip.h$", ""}, + {"include/netinet/ip6.h$", ""}, + {"include/netinet/ip_icmp.h$", ""}, + {"include/netinet/tcp.h$", ""}, + {"include/netinet/udp.h$", ""}, + {"include/netrom/netrom.h$", ""}, + {"include/protocols/routed.h$", ""}, + {"include/protocols/rwhod.h$", ""}, + {"include/protocols/talkd.h$", ""}, + {"include/protocols/timed.h$", ""}, + {"include/rpcsvc/klm_prot.x$", ""}, + {"include/rpcsvc/rstat.h$", ""}, + {"include/rpcsvc/spray.x$", ""}, + {"include/rpcsvc/nlm_prot.x$", ""}, + {"include/rpcsvc/nis_callback.x$", ""}, + {"include/rpcsvc/yp.h$", ""}, + {"include/rpcsvc/yp.x$", ""}, + {"include/rpcsvc/nfs_prot.h$", ""}, + {"include/rpcsvc/rex.h$", ""}, + {"include/rpcsvc/yppasswd.h$", ""}, + {"include/rpcsvc/rex.x$", ""}, + {"include/rpcsvc/nis_tags.h$", ""}, + {"include/rpcsvc/nis_callback.h$", ""}, + {"include/rpcsvc/nfs_prot.x$", ""}, + {"include/rpcsvc/bootparam_prot.x$", ""}, + {"include/rpcsvc/rusers.x$", ""}, + {"include/rpcsvc/rquota.x$", ""}, + {"include/rpcsvc/nis.h$", ""}, + {"include/rpcsvc/nislib.h$", ""}, + {"include/rpcsvc/ypupd.h$", ""}, + {"include/rpcsvc/bootparam.h$", ""}, + {"include/rpcsvc/spray.h$", ""}, + {"include/rpcsvc/key_prot.h$", ""}, + {"include/rpcsvc/klm_prot.h$", ""}, + {"include/rpcsvc/sm_inter.h$", ""}, + {"include/rpcsvc/nlm_prot.h$", ""}, + {"include/rpcsvc/yp_prot.h$", ""}, + {"include/rpcsvc/ypclnt.h$", ""}, + {"include/rpcsvc/rstat.x$", ""}, + {"include/rpcsvc/rusers.h$", ""}, + {"include/rpcsvc/key_prot.x$", ""}, + {"include/rpcsvc/sm_inter.x$", ""}, + {"include/rpcsvc/rquota.h$", ""}, + {"include/rpcsvc/nis.x$", ""}, + {"include/rpcsvc/bootparam_prot.h$", ""}, + {"include/rpcsvc/mount.h$", ""}, + {"include/rpcsvc/mount.x$", ""}, + {"include/rpcsvc/nis_object.x$", ""}, + {"include/rpcsvc/yppasswd.x$", ""}, + {"sys/acct.h$", ""}, + {"sys/auxv.h$", ""}, + {"sys/cdefs.h$", ""}, + {"sys/debugreg.h$", ""}, + {"sys/dir.h$", ""}, + {"sys/elf.h$", ""}, + {"sys/epoll.h$", ""}, + {"sys/eventfd.h$", ""}, + {"sys/fanotify.h$", ""}, + {"sys/file.h$", ""}, + {"sys/fsuid.h$", ""}, + {"sys/gmon.h$", ""}, + {"sys/gmon_out.h$", ""}, + {"sys/inotify.h$", ""}, + {"sys/io.h$", ""}, + {"sys/ioctl.h$", ""}, + {"sys/ipc.h$", ""}, + {"sys/kd.h$", ""}, + {"sys/kdaemon.h$", ""}, + {"sys/klog.h$", ""}, + {"sys/mman.h$", ""}, + {"sys/mount.h$", ""}, + {"sys/msg.h$", ""}, + {"sys/mtio.h$", ""}, + {"sys/param.h$", ""}, + {"sys/pci.h$", ""}, + {"sys/perm.h$", ""}, + {"sys/personality.h$", ""}, + {"sys/poll.h$", ""}, + {"sys/prctl.h$", ""}, + {"sys/procfs.h$", ""}, + {"sys/profil.h$", ""}, + {"sys/ptrace.h$", ""}, + {"sys/queue.h$", ""}, + {"sys/quota.h$", ""}, + {"sys/raw.h$", ""}, + {"sys/reboot.h$", ""}, + {"sys/reg.h$", ""}, + {"sys/resource.h$", ""}, + {"sys/select.h$", ""}, + {"sys/sem.h$", ""}, + {"sys/sendfile.h$", ""}, + {"sys/shm.h$", ""}, + {"sys/signalfd.h$", ""}, + {"sys/socket.h$", ""}, + {"sys/stat.h$", ""}, + {"sys/statfs.h$", ""}, + {"sys/statvfs.h$", ""}, + {"sys/swap.h$", ""}, + {"sys/syscall.h$", ""}, + {"sys/sysctl.h$", ""}, + {"sys/sysinfo.h$", ""}, + {"sys/syslog.h$", ""}, + {"sys/sysmacros.h$", ""}, + {"sys/termios.h$", ""}, + {"sys/time.h$", ""}, + {"sys/timeb.h$", ""}, + {"sys/timerfd.h$", ""}, + {"sys/times.h$", ""}, + {"sys/timex.h$", ""}, + {"sys/ttychars.h$", ""}, + {"sys/ttydefaults.h$", ""}, + {"sys/types.h$", ""}, + {"sys/ucontext.h$", ""}, + {"sys/uio.h$", ""}, + {"sys/un.h$", ""}, + {"sys/user.h$", ""}, + {"sys/ustat.h$", ""}, + {"sys/utsname.h$", ""}, + {"sys/vlimit.h$", ""}, + {"sys/vm86.h$", ""}, + {"sys/vtimes.h$", ""}, + {"sys/wait.h$", ""}, + {"sys/xattr.h$", ""}, + {"bits/epoll.h$", ""}, + {"bits/eventfd.h$", ""}, + {"bits/inotify.h$", ""}, + {"bits/ipc.h$", ""}, + {"bits/ipctypes.h$", ""}, + {"bits/mman-linux.h$", ""}, + {"bits/mman.h$", ""}, + {"bits/msq.h$", ""}, + {"bits/resource.h$", ""}, + {"bits/sem.h$", ""}, + {"bits/shm.h$", ""}, + {"bits/signalfd.h$", ""}, + {"bits/statfs.h$", ""}, + {"bits/statvfs.h$", ""}, + {"bits/timerfd.h$", ""}, + {"bits/utsname.h$", ""}, + {"bits/auxv.h$", ""}, + {"bits/byteswap-16.h$", ""}, + {"bits/byteswap.h$", ""}, + {"bits/confname.h$", ""}, + {"bits/dirent.h$", ""}, + {"bits/dlfcn.h$", ""}, + {"bits/elfclass.h$", ""}, + {"bits/endian.h$", ""}, + {"bits/environments.h$", ""}, + {"bits/fcntl-linux.h$", ""}, + {"bits/fcntl.h$", ""}, + {"bits/in.h$", ""}, + {"bits/ioctl-types.h$", ""}, + {"bits/ioctls.h$", ""}, + {"bits/link.h$", ""}, + {"bits/mqueue.h$", ""}, + {"bits/netdb.h$", ""}, + {"bits/param.h$", ""}, + {"bits/poll.h$", ""}, + {"bits/posix_opt.h$", ""}, + {"bits/pthreadtypes.h$", ""}, + {"bits/sched.h$", ""}, + {"bits/select.h$", ""}, + {"bits/semaphore.h$", ""}, + {"bits/sigthread.h$", ""}, + {"bits/sockaddr.h$", ""}, + {"bits/socket.h$", ""}, + {"bits/socket_type.h$", ""}, + {"bits/stab.def$", ""}, + {"bits/stat.h$", ""}, + {"bits/stropts.h$", ""}, + {"bits/syscall.h$", ""}, + {"bits/syslog-path.h$", ""}, + {"bits/termios.h$", ""}, + {"bits/types.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/uio.h$", ""}, + {"bits/ustat.h$", ""}, + {"bits/utmp.h$", ""}, + {"bits/utmpx.h$", ""}, + {"bits/waitflags.h$", ""}, + {"bits/waitstatus.h$", ""}, + {"bits/xtitypes.h$", ""}, + }; + for (const auto &Pair : SystemHeaderMap) + Includes->addRegexMapping(Pair.first, Pair.second); +} + +} // namespace clangd +} // namespace clang Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -152,11 +152,19 @@ /// and have clients resolve full symbol information for a specific candidate /// if needed. struct Details { - // Documentation including comment for the symbol declaration. + /// Documentation including comment for the symbol declaration. llvm::StringRef Documentation; - // This is what goes into the LSP detail field in a completion item. For - // example, the result type of a function. + /// This is what goes into the LSP detail field in a completion item. For + /// example, the result type of a function. llvm::StringRef CompletionDetail; + /// This can be either a URI of the header to be #include'd for this symbol, + /// or a literal header quoted with <> or "" that is suitable to be included + /// directly. When this is a URI, the exact #include path needs to be + /// calculated according to the URI scheme. + /// + /// If empty, FileURI in CanonicalDeclaration should be used to calculate + /// the #include path. + llvm::StringRef IncludeHeader; }; // Optional details of the symbol. Index: clangd/index/Index.cpp =================================================================== --- clangd/index/Index.cpp +++ clangd/index/Index.cpp @@ -80,6 +80,7 @@ // Intern the actual strings. Intern(Detail->Documentation); Intern(Detail->CompletionDetail); + Intern(Detail->IncludeHeader); // Replace the detail pointer with our copy. S.Detail = Detail; } Index: clangd/index/Merge.cpp =================================================================== --- clangd/index/Merge.cpp +++ clangd/index/Merge.cpp @@ -90,6 +90,8 @@ Scratch->Documentation = O.Detail->Documentation; if (Scratch->CompletionDetail == "") Scratch->CompletionDetail = O.Detail->CompletionDetail; + if (Scratch->IncludeHeader == "") + Scratch->IncludeHeader = O.Detail->IncludeHeader; S.Detail = Scratch; } else S.Detail = O.Detail; Index: clangd/index/SymbolCollector.h =================================================================== --- clangd/index/SymbolCollector.h +++ clangd/index/SymbolCollector.h @@ -7,6 +7,7 @@ // //===----------------------------------------------------------------------===// +#include "CanonicalIncludes.h" #include "Index.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" @@ -39,6 +40,10 @@ /// in symbols. The list of schemes will be tried in order until a working /// scheme is found. If no scheme works, symbol location will be dropped. std::vector URISchemes = {"file"}; + bool CollectIncludePath = false; + /// If set, this is used to map symbol #include path to a potentially + /// different #include path. + const CanonicalIncludes *Includes = nullptr; }; SymbolCollector(Options Opts); Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -11,6 +11,7 @@ #include "../CodeCompletionStrings.h" #include "../Logger.h" #include "../URI.h" +#include "CanonicalIncludes.h" #include "clang/AST/DeclCXX.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/SourceManager.h" @@ -127,6 +128,51 @@ return false; } +// We only collect #include paths for symbols that are suitable for global code +// completion, except for namespaces since #include path for a namespace is hard +// to define. +bool shouldCollectIncludePath(index::SymbolKind Kind) { + using SK = index::SymbolKind; + switch (Kind) { + case SK::Macro: + case SK::Enum: + case SK::Struct: + case SK::Class: + case SK::Union: + case SK::TypeAlias: + case SK::Using: + case SK::Function: + case SK::Variable: + case SK::EnumConstant: + return true; + default: + return false; + } +} + +/// Gets a canonical include (
or "header") for header of \p Loc. +/// Returns None if the header has no canonical include. +/// FIXME: we should handle .inc files whose symbols are expected be exported by +/// their containing headers. +llvm::Optional +getIncludeHeader(const SourceManager &SM, SourceLocation Loc, + const SymbolCollector::Options &Opts) { + llvm::StringRef FilePath = SM.getFilename(Loc); + if (FilePath.empty()) + return llvm::None; + if (Opts.Includes) { + llvm::StringRef Mapped = Opts.Includes->mapHeader(FilePath); + if (Mapped != FilePath) + return (Mapped.startswith("<") || Mapped.startswith("\"")) + ? Mapped.str() + : ("\"" + Mapped + "\"").str(); + } + // If the header path is the same as the file path of the declaration, we skip + // storing the #include path; users can use the URI in declaration location to + // calculate the #include path. + return llvm::None; +} + // Return the symbol location of the given declaration `D`. // // For symbols defined inside macros: @@ -252,6 +298,14 @@ std::string Documentation = getDocumentation(*CCS); std::string CompletionDetail = getDetail(*CCS); + std::string Include; + if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { + // Use the expansion location to get the #include header since this is + // where the symbol is exposed. + if (auto Header = + getIncludeHeader(SM, SM.getExpansionLoc(ND.getLocation()), Opts)) + Include = std::move(*Header); + } S.CompletionFilterText = FilterText; S.CompletionLabel = Label; S.CompletionPlainInsertText = PlainInsertText; @@ -259,6 +313,7 @@ Symbol::Details Detail; Detail.Documentation = Documentation; Detail.CompletionDetail = CompletionDetail; + Detail.IncludeHeader = Include; S.Detail = &Detail; Symbols.insert(S); Index: clangd/index/SymbolYAML.cpp =================================================================== --- clangd/index/SymbolYAML.cpp +++ clangd/index/SymbolYAML.cpp @@ -64,6 +64,7 @@ static void mapping(IO &io, Symbol::Details &Detail) { io.mapOptional("Documentation", Detail.Documentation); io.mapOptional("CompletionDetail", Detail.CompletionDetail); + io.mapOptional("IncludeHeader", Detail.IncludeHeader); } }; Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -16,6 +16,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" #include #include Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -88,6 +88,12 @@ Functions that have trailing returns are disallowed, except for those using decltype specifiers and lambda with otherwise unutterable return types. + +- New `readability-simd-intrinsics + `_ check + + Warns if SIMD intrinsics are used which can be replaced by + ``std::experimental::simd`` operations. - New alias `hicpp-avoid-goto `_ to Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -217,6 +217,7 @@ readability-redundant-smartptr-get readability-redundant-string-cstr readability-redundant-string-init + readability-simd-intrinsics readability-simplify-boolean-expr readability-static-accessed-through-instance readability-static-definition-in-anonymous-namespace Index: docs/clang-tidy/checks/readability-simd-intrinsics.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/readability-simd-intrinsics.rst @@ -0,0 +1,44 @@ +.. title:: clang-tidy - readability-simd-intrinsics + +readability-simd-intrinsics +=========================== + +Finds SIMD intrinsics calls and suggests ``std::experimental::simd`` (`P0214`_) +alternatives. + +If the option ``Suggest`` is set to non-zero, for + +.. code-block:: c++ + + _mm_add_epi32(a, b); // x86 + vec_add(a, b); // Power + +the check suggests an alternative: ``operator+`` on ``std::experimental::simd`` +objects. + +Otherwise, it just complains the intrinsics are non-portable (and there are +`P0214`_ alternatives). + +Many architectures provide SIMD operations (e.g. x86 SSE/AVX, Power AltiVec/VSX, +ARM NEON). It is common that SIMD code implementing the same algorithm, is +written in multiple target-dispatching pieces to optimize for different +architectures or micro-architectures. + +The C++ standard proposal `P0214`_ and its extensions cover many common SIMD +operations. By migrating from target-dependent intrinsics to `P0214`_ +operations, the SIMD code can be simplified and pieces for different targets can +be unified. + +Refer to `P0214`_ for introduction and motivation for the data-parallel standard +library. + +Options +------- + +.. option:: Suggest + + If this option is set to non-zero (default is `0`), the check will suggest + `P0214`_ alternatives, otherwise it only points out the intrinsic function is + non-portable. + +.. _P0214: http://wg21.link/p0214 Index: include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp =================================================================== --- include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp +++ include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp @@ -211,6 +211,10 @@ {"cwctype$", ""}, {"cxxabi.h$", ""}, {"debug/debug.h$", ""}, + {"debug/map.h$", ""}, + {"debug/multimap.h$", ""}, + {"debug/multiset.h$", ""}, + {"debug/set.h$", ""}, {"deque$", ""}, {"exception$", ""}, {"ext/alloc_traits.h$", ""}, Index: test/clang-tidy/readability-simd-intrinsics-ppc.cpp =================================================================== --- /dev/null +++ test/clang-tidy/readability-simd-intrinsics-ppc.cpp @@ -0,0 +1,13 @@ +// RUN: %check_clang_tidy %s readability-simd-intrinsics %t -- \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: readability-simd-intrinsics.Suggest, value: 1} \ +// RUN: ]}' -- -target ppc64le -maltivec -std=c++11 + +vector int vec_add(vector int, vector int); + +void PPC() { + vector int i0, i1; + + vec_add(i0, i1); +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'vec_add' can be replaced by operator+ on std::experimental::simd objects [readability-simd-intrinsics] +} Index: test/clang-tidy/readability-simd-intrinsics-x86.cpp =================================================================== --- /dev/null +++ test/clang-tidy/readability-simd-intrinsics-x86.cpp @@ -0,0 +1,25 @@ +// RUN: %check_clang_tidy %s readability-simd-intrinsics %t -- \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: readability-simd-intrinsics.Suggest, value: 1} \ +// RUN: ]}' -- -target x86_64 -std=c++11 + +typedef long long __m128i __attribute__((vector_size(16))); +typedef double __m256 __attribute__((vector_size(32))); + +__m128i _mm_add_epi32(__m128i, __m128i); +__m256 _mm256_load_pd(double const *); +void _mm256_store_pd(double *, __m256); + +int _mm_add_fake(int, int); + +void X86() { + __m128i i0, i1; + __m256 d0; + + _mm_add_epi32(i0, i1); +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: '_mm_add_epi32' can be replaced by operator+ on std::experimental::simd objects [readability-simd-intrinsics] + d0 = _mm256_load_pd(0); + _mm256_store_pd(0, d0); + + _mm_add_fake(0, 1); +} Index: test/clangd/completion-snippets.test =================================================================== --- test/clangd/completion-snippets.test +++ test/clangd/completion-snippets.test @@ -28,9 +28,7 @@ # CHECK-NEXT: "result": { # CHECK-NEXT: "isIncomplete": {{.*}} # CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "func_with_args", +# CHECK: "filterText": "func_with_args", # CHECK-NEXT: "insertText": "func_with_args(${1:int a}, ${2:int b})", # CHECK-NEXT: "insertTextFormat": 2, # CHECK-NEXT: "kind": 3, Index: test/clangd/hover.test =================================================================== --- /dev/null +++ test/clangd/hover.test @@ -0,0 +1,19 @@ +# RUN: clangd -lit-test < %s | FileCheck %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"void foo(); int main() { foo(); }\n"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":0,"character":27}}} +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "contents": { +# CHECK-NEXT: "kind": "plaintext", +# CHECK-NEXT: "value": "Declared in global namespace\n\nvoid foo()" +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT:} +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/initialize-params-invalid.test =================================================================== --- test/clangd/initialize-params-invalid.test +++ test/clangd/initialize-params-invalid.test @@ -24,9 +24,11 @@ # CHECK-NEXT: "documentRangeFormattingProvider": true, # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ -# CHECK-NEXT: "clangd.applyFix" +# CHECK-NEXT: "clangd.applyFix", +# CHECK-NEXT: "clangd.insertInclude" # CHECK-NEXT: ] # CHECK-NEXT: }, +# CHECK-NEXT: "hoverProvider": true, # CHECK-NEXT: "renameProvider": true, # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [ Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -24,9 +24,11 @@ # CHECK-NEXT: "documentRangeFormattingProvider": true, # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ -# CHECK-NEXT: "clangd.applyFix" +# CHECK-NEXT: "clangd.applyFix", +# CHECK-NEXT: "clangd.insertInclude" # CHECK-NEXT: ] # CHECK-NEXT: }, +# CHECK-NEXT: "hoverProvider": true, # CHECK-NEXT: "renameProvider": true, # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [ Index: test/clangd/insert-include.test =================================================================== --- /dev/null +++ test/clangd/insert-include.test @@ -0,0 +1,36 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +# RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"void f() {}"}}} +--- +{"jsonrpc":"2.0","id":3,"method":"workspace/executeCommand","params":{"arguments":[{"header":"","textDocument":{"uri":"test:///main.cpp"}}],"command":"clangd.insertInclude"}} +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": "Inserted header " +# CHECK-NEXT: } +# CHECK: "method": "workspace/applyEdit", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "edit": { +# CHECK-NEXT: "changes": { +# CHECK-NEXT: "file:///clangd-test/main.cpp": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "#include \n", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":4,"method":"shutdown"} Index: test/clangd/trace.test =================================================================== --- test/clangd/trace.test +++ test/clangd/trace.test @@ -1,4 +1,4 @@ -# RUN: CLANGD_TRACE=%t clangd -lit-test < %s && FileCheck %s < %t +# RUN: env CLANGD_TRACE=%t clangd -lit-test < %s && FileCheck %s < %t {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} --- {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {}"}}} Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -17,6 +17,7 @@ ContextTests.cpp FileIndexTests.cpp FuzzyMatchTests.cpp + HeadersTests.cpp IndexTests.cpp JSONExprTests.cpp SourceCodeTests.cpp Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -12,6 +12,7 @@ #include "Matchers.h" #include "SyncAPI.h" #include "TestFS.h" +#include "URI.h" #include "clang/Config/config.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" @@ -100,8 +101,6 @@ return replacePtrsInDump(DumpWithMemLocs); } -} // namespace - class ClangdVFSTest : public ::testing::Test { protected: std::string parseSourceAndDumpAST( @@ -114,13 +113,10 @@ ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); for (const auto &FileWithContents : ExtraFiles) - FS.Files[getVirtualTestFilePath(FileWithContents.first)] = - FileWithContents.second; - - auto SourceFilename = getVirtualTestFilePath(SourceFileRelPath); + FS.Files[testPath(FileWithContents.first)] = FileWithContents.second; + auto SourceFilename = testPath(SourceFileRelPath); FS.ExpectedFile = SourceFilename; - Server.addDocument(SourceFilename, SourceContents); auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename); EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; @@ -176,10 +172,9 @@ int b = a; )cpp"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - auto FooH = getVirtualTestFilePath("foo.h"); + auto FooCpp = testPath("foo.cpp"); - FS.Files[FooH] = "int a;"; + FS.Files[testPath("foo.h")] = "int a;"; FS.Files[FooCpp] = SourceContents; FS.ExpectedFile = FooCpp; @@ -215,8 +210,8 @@ int b = a; )cpp"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - auto FooH = getVirtualTestFilePath("foo.h"); + auto FooCpp = testPath("foo.cpp"); + auto FooH = testPath("foo.h"); FS.Files[FooH] = "int a;"; FS.Files[FooCpp] = SourceContents; @@ -252,7 +247,7 @@ /*AsyncThreadsCount=*/0, /*StorePreamblesInMemory=*/true); - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); const auto SourceContents = "int a;"; FS.Files[FooCpp] = SourceContents; FS.ExpectedFile = FooCpp; @@ -309,7 +304,7 @@ llvm::sys::path::append(StringPath, IncludeDir, "string"); FS.Files[StringPath] = "class mock_string {};"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); const auto SourceContents = R"cpp( #include mock_string x; @@ -340,7 +335,7 @@ // No need to sync reparses, because reparses are performed on the calling // thread. - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); const auto SourceContents1 = R"cpp( template struct foo { T x; }; @@ -388,13 +383,13 @@ // No need to sync reparses, because reparses are performed on the calling // thread. - Path FooCpp = getVirtualTestFilePath("foo.cpp").str(); + Path FooCpp = testPath("foo.cpp"); const auto SourceContents = R"cpp( struct Something { int method(); }; )cpp"; - Path BarCpp = getVirtualTestFilePath("bar.cpp").str(); + Path BarCpp = testPath("bar.cpp"); FS.Files[FooCpp] = ""; FS.Files[BarCpp] = ""; @@ -423,11 +418,11 @@ /*AsyncThreadsCount=*/0, /*StorePreamblesInMemory=*/true); - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); // clang cannot create CompilerInvocation if we pass two files in the // CompileCommand. We pass the file in ExtraFlags once and CDB adds another // one in getCompileCommand(). - CDB.ExtraClangFlags.push_back(FooCpp.str()); + CDB.ExtraClangFlags.push_back(FooCpp); // Clang can't parse command args in that case, but we shouldn't crash. Server.addDocument(FooCpp, "int main() {}"); @@ -478,16 +473,13 @@ unsigned MaxLineForFileRequests = 7; unsigned MaxColumnForFileRequests = 10; - std::vector> FilePaths; - FilePaths.reserve(FilesCount); - for (unsigned I = 0; I < FilesCount; ++I) - FilePaths.push_back(getVirtualTestFilePath(std::string("Foo") + - std::to_string(I) + ".cpp")); - // Mark all of those files as existing. + std::vector FilePaths; MockFSProvider FS; - for (auto &&FilePath : FilePaths) - FS.Files[FilePath] = ""; - + for (unsigned I = 0; I < FilesCount; ++I) { + std::string Name = std::string("Foo") + std::to_string(I) + ".cpp"; + FS.Files[Name] = ""; + FilePaths.push_back(testPath(Name)); + } struct FileStat { unsigned HitsWithoutErrors = 0; @@ -698,9 +690,9 @@ int b = a; )cpp"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - auto FooH = getVirtualTestFilePath("foo.h"); - auto Invalid = getVirtualTestFilePath("main.cpp"); + auto FooCpp = testPath("foo.cpp"); + auto FooH = testPath("foo.h"); + auto Invalid = testPath("main.cpp"); FS.Files[FooCpp] = SourceContents; FS.Files[FooH] = "int a;"; @@ -721,8 +713,8 @@ // Test with header file in capital letters and different extension, source // file with different extension - auto FooC = getVirtualTestFilePath("bar.c"); - auto FooHH = getVirtualTestFilePath("bar.HH"); + auto FooC = testPath("bar.c"); + auto FooHH = testPath("bar.HH"); FS.Files[FooC] = SourceContents; FS.Files[FooHH] = "int a;"; @@ -732,8 +724,8 @@ ASSERT_EQ(PathResult.getValue(), FooHH); // Test with both capital letters - auto Foo2C = getVirtualTestFilePath("foo2.C"); - auto Foo2HH = getVirtualTestFilePath("foo2.HH"); + auto Foo2C = testPath("foo2.C"); + auto Foo2HH = testPath("foo2.HH"); FS.Files[Foo2C] = SourceContents; FS.Files[Foo2HH] = "int a;"; @@ -742,8 +734,8 @@ ASSERT_EQ(PathResult.getValue(), Foo2HH); // Test with source file as capital letter and .hxx header file - auto Foo3C = getVirtualTestFilePath("foo3.C"); - auto Foo3HXX = getVirtualTestFilePath("foo3.hxx"); + auto Foo3C = testPath("foo3.C"); + auto Foo3HXX = testPath("foo3.hxx"); SourceContents = R"c( #include "foo3.hxx" @@ -808,7 +800,7 @@ int d; )cpp"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); MockFSProvider FS; FS.Files[FooCpp] = ""; @@ -826,5 +818,38 @@ ASSERT_EQ(DiagConsumer.Count, 2); // Sanity check - we actually ran both? } +TEST_F(ClangdVFSTest, InsertIncludes) { + MockFSProvider FS; + ErrorCheckingDiagConsumer DiagConsumer; + MockCompilationDatabase CDB; + ClangdServer Server(CDB, DiagConsumer, FS, + /*AsyncThreadsCount=*/0, + /*StorePreamblesInMemory=*/true); + + // No need to sync reparses, because reparses are performed on the calling + // thread. + auto FooCpp = testPath("foo.cpp"); + const auto Code = R"cpp( +#include "x.h" + +void f() {} +)cpp"; + FS.Files[FooCpp] = Code; + Server.addDocument(FooCpp, Code); + + auto Inserted = [&](llvm::StringRef Header, llvm::StringRef Expected) { + auto Replaces = Server.insertInclude(FooCpp, Code, Header); + EXPECT_TRUE(static_cast(Replaces)); + auto Changed = tooling::applyAllReplacements(Code, *Replaces); + EXPECT_TRUE(static_cast(Changed)); + return llvm::StringRef(*Changed).contains( + (llvm::Twine("#include ") + Expected + "").str()); + }; + + EXPECT_TRUE(Inserted("\"y.h\"", "\"y.h\"")); + EXPECT_TRUE(Inserted("", "")); +} + +} // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -119,7 +119,7 @@ IgnoreDiagnostics DiagConsumer; ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); - auto File = getVirtualTestFilePath("foo.cpp"); + auto File = testPath("foo.cpp"); Annotations Test(Text); Server.addDocument(File, Test.code()); EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; @@ -344,7 +344,7 @@ MockCompilationDatabase CDB; ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); - auto File = getVirtualTestFilePath("foo.cpp"); + auto File = testPath("foo.cpp"); Server.addDocument(File, "ignored text!"); Annotations Example("int cbc; int b = ^;"); @@ -543,9 +543,9 @@ ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); - FS.Files[getVirtualTestFilePath("bar.h")] = + FS.Files[testPath("bar.h")] = R"cpp(namespace ns { struct preamble { int member; }; })cpp"; - auto File = getVirtualTestFilePath("foo.cpp"); + auto File = testPath("foo.cpp"); Annotations Test(R"cpp( #include "bar.h" namespace ns { int local; } @@ -580,12 +580,12 @@ /*StorePreamblesInMemory=*/true, /*BuildDynamicSymbolIndex=*/true); - Server.addDocument(getVirtualTestFilePath("foo.cpp"), R"cpp( + Server.addDocument(testPath("foo.cpp"), R"cpp( namespace ns { class XYZ {}; void foo(int x) {} } )cpp"); ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; - auto File = getVirtualTestFilePath("bar.cpp"); + auto File = testPath("bar.cpp"); Annotations Test(R"cpp( namespace ns { class XXX {}; @@ -622,7 +622,7 @@ IgnoreDiagnostics DiagConsumer; ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); - auto File = getVirtualTestFilePath("foo.cpp"); + auto File = testPath("foo.cpp"); Annotations Test(Text); Server.addDocument(File, Test.code()); EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; Index: unittests/clangd/HeadersTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/HeadersTests.cpp @@ -0,0 +1,98 @@ +//===-- HeadersTests.cpp - Include headers unit tests -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Headers.h" +#include "TestFS.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +class HeadersTest : public ::testing::Test { +public: + HeadersTest() { + CDB.ExtraClangFlags = {SearchDirArg.c_str()}; + FS.Files[MainFile] = ""; + } + +protected: + // Adds \p File with content \p Code to the sub directory and returns the + // absolute path. +// std::string addToSubdir(PathRef File, llvm::StringRef Code = "") { +// assert(llvm::sys::path::is_relative(File) && "FileName should be relative"); +// llvm::SmallString<32> Path; +// llvm::sys::path::append(Path, Subdir, File); +// FS.Files[Path] = Code; +// return Path.str(); +// }; + + // Shortens the header and returns "" on error. + std::string shorten(PathRef Header) { + auto VFS = FS.getTaggedFileSystem(MainFile).Value; + auto Cmd = CDB.getCompileCommand(MainFile); + assert(static_cast(Cmd)); + VFS->setCurrentWorkingDirectory(Cmd->Directory); + auto Path = + shortenIncludePath(MainFile, FS.Files[MainFile], Header, *Cmd, VFS); + if (!Path) { + llvm::consumeError(Path.takeError()); + return std::string(); + } + return std::move(*Path); + } + MockFSProvider FS; + MockCompilationDatabase CDB; + std::string MainFile = testPath("main.cpp"); + std::string Subdir = testPath("sub"); + std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str(); +}; + +TEST_F(HeadersTest, InsertInclude) { + std::string Path = testPath("sub/bar.h"); + FS.Files[Path] = ""; + EXPECT_EQ(shorten(Path), "\"bar.h\""); +} + +TEST_F(HeadersTest, DontInsertDuplicateSameName) { + FS.Files[MainFile] = R"cpp( +#include "bar.h" +)cpp"; + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = ""; + EXPECT_EQ(shorten(BarHeader), ""); +} + +TEST_F(HeadersTest, DontInsertDuplicateDifferentName) { + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = ""; + FS.Files[MainFile] = R"cpp( +#include "sub/bar.h" // not shortest +)cpp"; + EXPECT_EQ(shorten(BarHeader), ""); +} + +TEST_F(HeadersTest, StillInsertIfTrasitivelyIncluded) { + std::string BazHeader = testPath("sub/baz.h"); + FS.Files[BazHeader] = ""; + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = R"cpp( +#include "baz.h" +)cpp"; + FS.Files[MainFile] = R"cpp( +#include "bar.h" +)cpp"; + EXPECT_EQ(shorten(BazHeader), "\"baz.h\""); +} + +} // namespace +} // namespace clangd +} // namespace clang + Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- unittests/clangd/SymbolCollectorTests.cpp +++ unittests/clangd/SymbolCollectorTests.cpp @@ -47,6 +47,9 @@ } MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; } MATCHER_P(DeclURI, P, "") { return arg.CanonicalDeclaration.FileURI == P; } +MATCHER_P(IncludeHeader, P, "") { + return arg.Detail && arg.Detail->IncludeHeader == P; +} MATCHER_P(DeclRange, Offsets, "") { return arg.CanonicalDeclaration.StartOffset == Offsets.first && arg.CanonicalDeclaration.EndOffset == Offsets.second; @@ -62,41 +65,62 @@ namespace { class SymbolIndexActionFactory : public tooling::FrontendActionFactory { public: - SymbolIndexActionFactory(SymbolCollector::Options COpts) - : COpts(std::move(COpts)) {} + SymbolIndexActionFactory(SymbolCollector::Options COpts, + CommentHandler *PragmaHandler) + : COpts(std::move(COpts)), PragmaHandler(PragmaHandler) {} clang::FrontendAction *create() override { + class WrappedIndexAction : public WrapperFrontendAction { + public: + WrappedIndexAction(std::shared_ptr C, + const index::IndexingOptions &Opts, + CommentHandler *PragmaHandler) + : WrapperFrontendAction( + index::createIndexingAction(C, Opts, nullptr)), + PragmaHandler(PragmaHandler) {} + + std::unique_ptr + CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { + if (PragmaHandler) + CI.getPreprocessor().addCommentHandler(PragmaHandler); + return WrapperFrontendAction::CreateASTConsumer(CI, InFile); + } + + private: + index::IndexingOptions IndexOpts; + CommentHandler *PragmaHandler; + }; index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = false; Collector = std::make_shared(COpts); - FrontendAction *Action = - index::createIndexingAction(Collector, IndexOpts, nullptr).release(); - return Action; + return new WrappedIndexAction(Collector, std::move(IndexOpts), + PragmaHandler); } std::shared_ptr Collector; SymbolCollector::Options COpts; + CommentHandler *PragmaHandler; }; class SymbolCollectorTest : public ::testing::Test { public: SymbolCollectorTest() - : TestHeaderName(getVirtualTestFilePath("symbol.h").str()), - TestFileName(getVirtualTestFilePath("symbol.cc").str()) { + : InMemoryFileSystem(new vfs::InMemoryFileSystem), + TestHeaderName(testPath("symbol.h")), + TestFileName(testPath("symbol.cc")) { TestHeaderURI = URI::createFile(TestHeaderName).toString(); TestFileURI = URI::createFile(TestFileName).toString(); } bool runSymbolCollector(StringRef HeaderCode, StringRef MainCode, const std::vector &ExtraArgs = {}) { - llvm::IntrusiveRefCntPtr InMemoryFileSystem( - new vfs::InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), InMemoryFileSystem)); - auto Factory = llvm::make_unique(CollectorOpts); + auto Factory = llvm::make_unique( + CollectorOpts, PragmaHandler.get()); std::vector Args = {"symbol_collector", "-fsyntax-only", "-std=c++11", "-include", @@ -117,12 +141,14 @@ } protected: + llvm::IntrusiveRefCntPtr InMemoryFileSystem; std::string TestHeaderName; std::string TestHeaderURI; std::string TestFileName; std::string TestFileURI; SymbolSlab Symbols; SymbolCollector::Options CollectorOpts; + std::unique_ptr PragmaHandler; }; TEST_F(SymbolCollectorTest, CollectSymbols) { @@ -218,9 +244,8 @@ CollectorOpts.IndexMainFiles = false; TestHeaderName = "x.h"; TestFileName = "x.cpp"; - TestHeaderURI = - URI::createFile(getVirtualTestFilePath(TestHeaderName)).toString(); - CollectorOpts.FallbackDir = getVirtualTestRoot(); + TestHeaderURI = URI::createFile(testPath(TestHeaderName)).toString(); + CollectorOpts.FallbackDir = testRoot(); runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); @@ -231,8 +256,8 @@ CollectorOpts.IndexMainFiles = false; // Use test URI scheme from URITests.cpp CollectorOpts.URISchemes.insert(CollectorOpts.URISchemes.begin(), "unittest"); - TestHeaderName = getVirtualTestFilePath("test-root/x.h").str(); - TestFileName = getVirtualTestFilePath("test-root/x.cpp").str(); + TestHeaderName = testPath("test-root/x.h"); + TestFileName = testPath("test-root/x.cpp"); runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI("unittest:x.h")))); @@ -555,6 +580,46 @@ QName("clang::Foo2"))); } +TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) { + CollectorOpts.CollectIncludePath = true; + runSymbolCollector("class Foo {};", /*Main=*/""); + EXPECT_THAT(Symbols, + UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI), + IncludeHeader("")))); +} + +#ifndef LLVM_ON_WIN32 +TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { + CollectorOpts.CollectIncludePath = true; + CanonicalIncludes Includes; + addSystemHeadersMapping(&Includes); + CollectorOpts.Includes = &Includes; + // bits/basic_string.h$ should be mapped to + TestHeaderName = "/nasty/bits/basic_string.h"; + TestFileName = "/nasty/bits/basic_string.cpp"; + TestHeaderURI = URI::createFile(TestHeaderName).toString(); + runSymbolCollector("class string {};", /*Main=*/""); + EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("string"), + DeclURI(TestHeaderURI), + IncludeHeader("")))); +} +#endif + +TEST_F(SymbolCollectorTest, IWYUPragma) { + CollectorOpts.CollectIncludePath = true; + CanonicalIncludes Includes; + PragmaHandler = collectIWYUHeaderMaps(&Includes); + CollectorOpts.Includes = &Includes; + const std::string Header = R"( + // IWYU pragma: private, include the/good/header.h + class Foo {}; + )"; + runSymbolCollector(Header, /*Main=*/""); + EXPECT_THAT(Symbols, UnorderedElementsAre( + AllOf(QName("Foo"), DeclURI(TestHeaderURI), + IncludeHeader("\"the/good/header.h\"")))); +} + } // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/SyncAPI.cpp =================================================================== --- unittests/clangd/SyncAPI.cpp +++ unittests/clangd/SyncAPI.cpp @@ -32,9 +32,11 @@ operator UniqueFunction() && { assert(!Future.valid() && "conversion to callback called multiple times"); Future = Promise.get_future(); - return BindWithForward([](std::promise Promise, - T Value) { Promise.set_value(std::move(Value)); }, - std::move(Promise)); + return BindWithForward( + [](std::promise> Promise, T Value) { + Promise.set_value(std::make_shared(std::move(Value))); + }, + std::move(Promise)); } ~CaptureProxy() { @@ -42,13 +44,16 @@ return; assert(Future.valid() && "conversion to callback was not called"); assert(!Target->hasValue()); - Target->emplace(Future.get()); + Target->emplace(std::move(*Future.get())); } private: llvm::Optional *Target; - std::promise Promise; - std::future Future; + // Using shared_ptr to workaround compilation errors with MSVC. + // MSVC only allows default-construcitble and copyable objects as future<> + // arguments. + std::promise> Promise; + std::future> Future; }; template CaptureProxy capture(llvm::Optional &Target) { Index: unittests/clangd/TUSchedulerTests.cpp =================================================================== --- unittests/clangd/TUSchedulerTests.cpp +++ unittests/clangd/TUSchedulerTests.cpp @@ -33,12 +33,9 @@ std::move(Contents)}; } - void changeFile(PathRef File, std::string Contents) { - Files[File] = Contents; - } + llvm::StringMap Files; private: - llvm::StringMap Files; MockCompilationDatabase CDB; }; @@ -47,11 +44,11 @@ /*StorePreamblesInMemory=*/true, /*ASTParsedCallback=*/nullptr); - auto Added = getVirtualTestFilePath("added.cpp"); - changeFile(Added, ""); + auto Added = testPath("added.cpp"); + Files[Added] = ""; - auto Missing = getVirtualTestFilePath("missing.cpp"); - changeFile(Missing, ""); + auto Missing = testPath("missing.cpp"); + Files[Missing] = ""; S.update(Added, getInputs(Added, ""), ignoreUpdate); @@ -106,9 +103,9 @@ std::vector Files; for (int I = 0; I < FilesCount; ++I) { - Files.push_back( - getVirtualTestFilePath("foo" + std::to_string(I) + ".cpp").str()); - changeFile(Files.back(), ""); + std::string Name = "foo" + std::to_string(I) + ".cpp"; + Files.push_back(testPath(Name)); + this->Files[Files.back()] = ""; } llvm::StringRef Contents1 = R"cpp(int a;)cpp"; Index: unittests/clangd/TestFS.h =================================================================== --- unittests/clangd/TestFS.h +++ unittests/clangd/TestFS.h @@ -29,7 +29,8 @@ Tagged> getTaggedFileSystem(PathRef File) override; - llvm::Optional> ExpectedFile; + llvm::Optional ExpectedFile; + // If relative paths are used, they are resolved with testPath(). llvm::StringMap Files; VFSTag Tag = VFSTag(); }; @@ -49,10 +50,10 @@ }; // Returns an absolute (fake) test directory for this OS. -const char *getVirtualTestRoot(); +const char *testRoot(); // Returns a suitable absolute path for this OS. -llvm::SmallString<32> getVirtualTestFilePath(PathRef File); +std::string testPath(PathRef File); } // namespace clangd } // namespace clang Index: unittests/clangd/TestFS.cpp =================================================================== --- unittests/clangd/TestFS.cpp +++ unittests/clangd/TestFS.cpp @@ -12,14 +12,17 @@ namespace clang { namespace clangd { +using namespace llvm; + IntrusiveRefCntPtr -buildTestFS(llvm::StringMap const &Files) { +buildTestFS(StringMap const &Files) { IntrusiveRefCntPtr MemFS( new vfs::InMemoryFileSystem); - for (auto &FileAndContents : Files) + for (auto &FileAndContents : Files) { MemFS->addFile(FileAndContents.first(), time_t(), - llvm::MemoryBuffer::getMemBuffer(FileAndContents.second, - FileAndContents.first())); + MemoryBuffer::getMemBuffer(FileAndContents.second, + FileAndContents.first())); + } return MemFS; } @@ -38,20 +41,20 @@ // -ffreestanding avoids implicit stdc-predef.h. } -llvm::Optional +Optional MockCompilationDatabase::getCompileCommand(PathRef File) const { if (ExtraClangFlags.empty()) - return llvm::None; + return None; auto CommandLine = ExtraClangFlags; - auto FileName = llvm::sys::path::filename(File); + auto FileName = sys::path::filename(File); CommandLine.insert(CommandLine.begin(), "clang"); CommandLine.insert(CommandLine.end(), UseRelPaths ? FileName : File); - return {tooling::CompileCommand(llvm::sys::path::parent_path(File), FileName, + return {tooling::CompileCommand(sys::path::parent_path(File), FileName, std::move(CommandLine), "")}; } -const char *getVirtualTestRoot() { +const char *testRoot() { #ifdef LLVM_ON_WIN32 return "C:\\clangd-test"; #else @@ -59,12 +62,12 @@ #endif } -llvm::SmallString<32> getVirtualTestFilePath(PathRef File) { - assert(llvm::sys::path::is_relative(File) && "FileName should be relative"); +std::string testPath(PathRef File) { + assert(sys::path::is_relative(File) && "FileName should be relative"); - llvm::SmallString<32> Path; - llvm::sys::path::append(Path, getVirtualTestRoot(), File); - return Path; + SmallString<32> Path; + sys::path::append(Path, testRoot(), File); + return Path.str(); } } // namespace clangd Index: unittests/clangd/URITests.cpp =================================================================== --- unittests/clangd/URITests.cpp +++ unittests/clangd/URITests.cpp @@ -176,10 +176,10 @@ } TEST(URITest, Platform) { - auto Path = getVirtualTestFilePath("x"); + auto Path = testPath("x"); auto U = URI::create(Path, "file"); EXPECT_TRUE(static_cast(U)); - EXPECT_THAT(resolveOrDie(*U), Path.str()); + EXPECT_THAT(resolveOrDie(*U), Path); } TEST(URITest, ResolveFailed) { Index: unittests/clangd/XRefsTests.cpp =================================================================== --- unittests/clangd/XRefsTests.cpp +++ unittests/clangd/XRefsTests.cpp @@ -46,9 +46,8 @@ // FIXME: this is duplicated with FileIndexTests. Share it. ParsedAST build(StringRef Code) { - auto TestFile = getVirtualTestFilePath("Foo.cpp"); - auto CI = - createInvocationFromCommandLine({"clang", "-xc++", TestFile.c_str()}); + auto CI = createInvocationFromCommandLine( + {"clang", "-xc++", testPath("Foo.cpp").c_str()}); auto Buf = MemoryBuffer::getMemBuffer(Code); auto AST = ParsedAST::Build(std::move(CI), nullptr, std::move(Buf), std::make_shared(), @@ -246,7 +245,7 @@ ClangdServer Server(CDB, DiagConsumer, FS, /*AsyncThreadsCount=*/0, /*StorePreambleInMemory=*/true); - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); FS.Files[FooCpp] = ""; Server.addDocument(FooCpp, SourceAnnotations.code()); @@ -254,9 +253,314 @@ runFindDefinitions(Server, FooCpp, SourceAnnotations.point()); EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; - EXPECT_THAT(Locations->Value, - ElementsAre(Location{URIForFile{FooCpp.str()}, - SourceAnnotations.range()})); + EXPECT_THAT( + Locations->Value, + ElementsAre(Location{URIForFile{FooCpp}, SourceAnnotations.range()})); +} + +TEST(Hover, All) { + struct OneTest { + StringRef Input; + StringRef ExpectedHover; + }; + + OneTest Tests[] = { + { + R"cpp(// Local variable + int main() { + int bonjour; + ^bonjour = 2; + int test1 = bonjour; + } + )cpp", + "Declared in function main\n\nint bonjour", + }, + { + R"cpp(// Local variable in method + struct s { + void method() { + int bonjour; + ^bonjour = 2; + } + }; + )cpp", + "Declared in function s::method\n\nint bonjour", + }, + { + R"cpp(// Struct + namespace ns1 { + struct MyClass {}; + } // namespace ns1 + int main() { + ns1::My^Class* Params; + } + )cpp", + "Declared in namespace ns1\n\nstruct MyClass {}", + }, + { + R"cpp(// Class + namespace ns1 { + class MyClass {}; + } // namespace ns1 + int main() { + ns1::My^Class* Params; + } + )cpp", + "Declared in namespace ns1\n\nclass MyClass {}", + }, + { + R"cpp(// Union + namespace ns1 { + union MyUnion { int x; int y; }; + } // namespace ns1 + int main() { + ns1::My^Union Params; + } + )cpp", + "Declared in namespace ns1\n\nunion MyUnion {}", + }, + { + R"cpp(// Function definition via pointer + int foo(int) {} + int main() { + auto *X = &^foo; + } + )cpp", + "Declared in global namespace\n\nint foo(int)", + }, + { + R"cpp(// Function declaration via call + int foo(int); + int main() { + return ^foo(42); + } + )cpp", + "Declared in global namespace\n\nint foo(int)", + }, + { + R"cpp(// Field + struct Foo { int x; }; + int main() { + Foo bar; + bar.^x; + } + )cpp", + "Declared in struct Foo\n\nint x", + }, + { + R"cpp(// Field with initialization + struct Foo { int x = 5; }; + int main() { + Foo bar; + bar.^x; + } + )cpp", + "Declared in struct Foo\n\nint x = 5", + }, + { + R"cpp(// Static field + struct Foo { static int x; }; + int main() { + Foo::^x; + } + )cpp", + "Declared in struct Foo\n\nstatic int x", + }, + { + R"cpp(// Field, member initializer + struct Foo { + int x; + Foo() : ^x(0) {} + }; + )cpp", + "Declared in struct Foo\n\nint x", + }, + { + R"cpp(// Field, GNU old-style field designator + struct Foo { int x; }; + int main() { + Foo bar = { ^x : 1 }; + } + )cpp", + "Declared in struct Foo\n\nint x", + }, + { + R"cpp(// Field, field designator + struct Foo { int x; }; + int main() { + Foo bar = { .^x = 2 }; + } + )cpp", + "Declared in struct Foo\n\nint x", + }, + { + R"cpp(// Method call + struct Foo { int x(); }; + int main() { + Foo bar; + bar.^x(); + } + )cpp", + "Declared in struct Foo\n\nint x()", + }, + { + R"cpp(// Static method call + struct Foo { static int x(); }; + int main() { + Foo::^x(); + } + )cpp", + "Declared in struct Foo\n\nstatic int x()", + }, + { + R"cpp(// Typedef + typedef int Foo; + int main() { + ^Foo bar; + } + )cpp", + "Declared in global namespace\n\ntypedef int Foo", + }, + { + R"cpp(// Namespace + namespace ns { + struct Foo { static void bar(); } + } // namespace ns + int main() { ^ns::Foo::bar(); } + )cpp", + "Declared in global namespace\n\nnamespace ns {\n}", + }, + { + R"cpp(// Anonymous namespace + namespace ns { + namespace { + int foo; + } // anonymous namespace + } // namespace ns + int main() { ns::f^oo++; } + )cpp", + "Declared in namespace ns::(anonymous)\n\nint foo", + }, + { + R"cpp(// Macro + #define MACRO 0 + #define MACRO 1 + int main() { return ^MACRO; } + #define MACRO 2 + #undef macro + )cpp", + "#define MACRO", + }, + { + R"cpp(// Forward class declaration + class Foo; + class Foo {}; + F^oo* foo(); + )cpp", + "Declared in global namespace\n\nclass Foo {}", + }, + { + R"cpp(// Function declaration + void foo(); + void g() { f^oo(); } + void foo() {} + )cpp", + "Declared in global namespace\n\nvoid foo()", + }, + { + R"cpp(// Enum declaration + enum Hello { + ONE, TWO, THREE, + }; + void foo() { + Hel^lo hello = ONE; + } + )cpp", + "Declared in global namespace\n\nenum Hello {\n}", + }, + { + R"cpp(// Enumerator + enum Hello { + ONE, TWO, THREE, + }; + void foo() { + Hello hello = O^NE; + } + )cpp", + "Declared in enum Hello\n\nONE", + }, + { + R"cpp(// Enumerator in anonymous enum + enum { + ONE, TWO, THREE, + }; + void foo() { + int hello = O^NE; + } + )cpp", + "Declared in enum (anonymous)\n\nONE", + }, + { + R"cpp(// Global variable + static int hey = 10; + void foo() { + he^y++; + } + )cpp", + "Declared in global namespace\n\nstatic int hey = 10", + }, + { + R"cpp(// Global variable in namespace + namespace ns1 { + static int hey = 10; + } + void foo() { + ns1::he^y++; + } + )cpp", + "Declared in namespace ns1\n\nstatic int hey = 10", + }, + { + R"cpp(// Field in anonymous struct + static struct { + int hello; + } s; + void foo() { + s.he^llo++; + } + )cpp", + "Declared in struct (anonymous)\n\nint hello", + }, + { + R"cpp(// Templated function + template + T foo() { + return 17; + } + void g() { auto x = f^oo(); } + )cpp", + "Declared in global namespace\n\ntemplate T foo()", + }, + { + R"cpp(// Anonymous union + struct outer { + union { + int abc, def; + } v; + }; + void g() { struct outer o; o.v.d^ef++; } + )cpp", + "Declared in union outer::(anonymous)\n\nint def", + }, + }; + + for (const OneTest &Test : Tests) { + Annotations T(Test.Input); + auto AST = build(T.code()); + Hover H = getHover(AST, T.point()); + + EXPECT_EQ(H.Contents.Value, Test.ExpectedHover) << Test.Input; + } } } // namespace