1193 lines · cpp
1//===--- ClangdServer.cpp - Main clangd server code --------------*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===-------------------------------------------------------------------===//8 9#include "ClangdServer.h"10#include "CodeComplete.h"11#include "Config.h"12#include "Diagnostics.h"13#include "DumpAST.h"14#include "FindSymbols.h"15#include "Format.h"16#include "HeaderSourceSwitch.h"17#include "InlayHints.h"18#include "ParsedAST.h"19#include "Preamble.h"20#include "Protocol.h"21#include "SemanticHighlighting.h"22#include "SemanticSelection.h"23#include "SourceCode.h"24#include "TUScheduler.h"25#include "XRefs.h"26#include "clang-include-cleaner/Record.h"27#include "index/FileIndex.h"28#include "index/Merge.h"29#include "index/StdLib.h"30#include "refactor/Rename.h"31#include "refactor/Tweak.h"32#include "support/Cancellation.h"33#include "support/Context.h"34#include "support/Logger.h"35#include "support/MemoryTree.h"36#include "support/ThreadsafeFS.h"37#include "support/Trace.h"38#include "clang/Basic/Stack.h"39#include "clang/Format/Format.h"40#include "clang/Lex/Preprocessor.h"41#include "clang/Tooling/CompilationDatabase.h"42#include "clang/Tooling/Core/Replacement.h"43#include "llvm/ADT/ArrayRef.h"44#include "llvm/ADT/STLExtras.h"45#include "llvm/ADT/StringRef.h"46#include "llvm/Support/Error.h"47#include "llvm/Support/Path.h"48#include "llvm/Support/raw_ostream.h"49#include <algorithm>50#include <chrono>51#include <future>52#include <memory>53#include <mutex>54#include <optional>55#include <string>56#include <type_traits>57#include <utility>58#include <vector>59 60namespace clang {61namespace clangd {62namespace {63 64// Tracks number of times a tweak has been offered.65static constexpr trace::Metric TweakAvailable(66 "tweak_available", trace::Metric::Counter, "tweak_id");67 68// Update the FileIndex with new ASTs and plumb the diagnostics responses.69struct UpdateIndexCallbacks : public ParsingCallbacks {70 UpdateIndexCallbacks(FileIndex *FIndex,71 ClangdServer::Callbacks *ServerCallbacks,72 const ThreadsafeFS &TFS, AsyncTaskRunner *Tasks,73 bool CollectInactiveRegions)74 : FIndex(FIndex), ServerCallbacks(ServerCallbacks), TFS(TFS),75 Stdlib{std::make_shared<StdLibSet>()}, Tasks(Tasks),76 CollectInactiveRegions(CollectInactiveRegions) {}77 78 void onPreambleAST(79 PathRef Path, llvm::StringRef Version, CapturedASTCtx ASTCtx,80 std::shared_ptr<const include_cleaner::PragmaIncludes> PI) override {81 82 if (!FIndex)83 return;84 85 auto &PP = ASTCtx.getPreprocessor();86 auto &CI = ASTCtx.getCompilerInvocation();87 if (auto Loc = Stdlib->add(CI.getLangOpts(), PP.getHeaderSearchInfo()))88 indexStdlib(CI, std::move(*Loc));89 90 // FIndex outlives the UpdateIndexCallbacks.91 auto Task = [FIndex(FIndex), Path(Path.str()), Version(Version.str()),92 ASTCtx(std::move(ASTCtx)), PI(std::move(PI))]() mutable {93 trace::Span Tracer("PreambleIndexing");94 FIndex->updatePreamble(Path, Version, ASTCtx.getASTContext(),95 ASTCtx.getPreprocessor(), *PI);96 };97 98 if (Tasks) {99 Tasks->runAsync("Preamble indexing for:" + Path + Version,100 std::move(Task));101 } else102 Task();103 }104 105 void indexStdlib(const CompilerInvocation &CI, StdLibLocation Loc) {106 // This task is owned by Tasks, which outlives the TUScheduler and107 // therefore the UpdateIndexCallbacks.108 // We must be careful that the references we capture outlive TUScheduler.109 auto Task = [LO(CI.getLangOpts()), Loc(std::move(Loc)),110 CI(std::make_unique<CompilerInvocation>(CI)),111 // External values that outlive ClangdServer112 TFS(&TFS),113 // Index outlives TUScheduler (declared first)114 FIndex(FIndex),115 // shared_ptr extends lifetime116 Stdlib(Stdlib),117 // We have some FS implementations that rely on information in118 // the context.119 Ctx(Context::current().clone())]() mutable {120 // Make sure we install the context into current thread.121 WithContext C(std::move(Ctx));122 clang::noteBottomOfStack();123 IndexFileIn IF;124 IF.Symbols = indexStandardLibrary(std::move(CI), Loc, *TFS);125 if (Stdlib->isBest(LO))126 FIndex->updatePreamble(std::move(IF));127 };128 if (Tasks)129 // This doesn't have a semaphore to enforce -j, but it's rare.130 Tasks->runAsync("IndexStdlib", std::move(Task));131 else132 Task();133 }134 135 void onMainAST(PathRef Path, ParsedAST &AST, PublishFn Publish) override {136 if (FIndex)137 FIndex->updateMain(Path, AST);138 139 if (ServerCallbacks)140 Publish([&]() {141 ServerCallbacks->onDiagnosticsReady(Path, AST.version(),142 AST.getDiagnostics());143 if (CollectInactiveRegions) {144 ServerCallbacks->onInactiveRegionsReady(Path,145 getInactiveRegions(AST));146 }147 });148 }149 150 void onFailedAST(PathRef Path, llvm::StringRef Version,151 std::vector<Diag> Diags, PublishFn Publish) override {152 if (ServerCallbacks)153 Publish(154 [&]() { ServerCallbacks->onDiagnosticsReady(Path, Version, Diags); });155 }156 157 void onFileUpdated(PathRef File, const TUStatus &Status) override {158 if (ServerCallbacks)159 ServerCallbacks->onFileUpdated(File, Status);160 }161 162 void onPreamblePublished(PathRef File) override {163 if (ServerCallbacks)164 ServerCallbacks->onSemanticsMaybeChanged(File);165 }166 167private:168 FileIndex *FIndex;169 ClangdServer::Callbacks *ServerCallbacks;170 const ThreadsafeFS &TFS;171 std::shared_ptr<StdLibSet> Stdlib;172 AsyncTaskRunner *Tasks;173 bool CollectInactiveRegions;174};175 176class DraftStoreFS : public ThreadsafeFS {177public:178 DraftStoreFS(const ThreadsafeFS &Base, const DraftStore &Drafts)179 : Base(Base), DirtyFiles(Drafts) {}180 181private:182 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {183 auto OFS = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(184 Base.view(std::nullopt));185 OFS->pushOverlay(DirtyFiles.asVFS());186 return OFS;187 }188 189 const ThreadsafeFS &Base;190 const DraftStore &DirtyFiles;191};192 193} // namespace194 195ClangdServer::Options ClangdServer::optsForTest() {196 ClangdServer::Options Opts;197 Opts.UpdateDebounce = DebouncePolicy::fixed(/*zero*/ {});198 Opts.StorePreamblesInMemory = true;199 Opts.AsyncThreadsCount = 4; // Consistent!200 return Opts;201}202 203ClangdServer::Options::operator TUScheduler::Options() const {204 TUScheduler::Options Opts;205 Opts.AsyncThreadsCount = AsyncThreadsCount;206 Opts.RetentionPolicy = RetentionPolicy;207 Opts.StorePreamblesInMemory = StorePreamblesInMemory;208 Opts.UpdateDebounce = UpdateDebounce;209 Opts.ContextProvider = ContextProvider;210 Opts.PreambleThrottler = PreambleThrottler;211 return Opts;212}213 214ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,215 const ThreadsafeFS &TFS, const Options &Opts,216 Callbacks *Callbacks)217 : FeatureModules(Opts.FeatureModules), CDB(CDB), TFS(TFS),218 DynamicIdx(Opts.BuildDynamicSymbolIndex219 ? new FileIndex(Opts.EnableOutgoingCalls)220 : nullptr),221 ModulesManager(Opts.ModulesManager),222 ClangTidyProvider(Opts.ClangTidyProvider),223 UseDirtyHeaders(Opts.UseDirtyHeaders),224 LineFoldingOnly(Opts.LineFoldingOnly),225 PreambleParseForwardingFunctions(Opts.PreambleParseForwardingFunctions),226 ImportInsertions(Opts.ImportInsertions),227 PublishInactiveRegions(Opts.PublishInactiveRegions),228 WorkspaceRoot(Opts.WorkspaceRoot),229 Transient(Opts.ImplicitCancellation ? TUScheduler::InvalidateOnUpdate230 : TUScheduler::NoInvalidation),231 DirtyFS(std::make_unique<DraftStoreFS>(TFS, DraftMgr)) {232 if (Opts.AsyncThreadsCount != 0)233 IndexTasks.emplace();234 // Pass a callback into `WorkScheduler` to extract symbols from a newly235 // parsed file and rebuild the file index synchronously each time an AST236 // is parsed.237 WorkScheduler.emplace(CDB, TUScheduler::Options(Opts),238 std::make_unique<UpdateIndexCallbacks>(239 DynamicIdx.get(), Callbacks, TFS,240 IndexTasks ? &*IndexTasks : nullptr,241 PublishInactiveRegions));242 // Adds an index to the stack, at higher priority than existing indexes.243 auto AddIndex = [&](SymbolIndex *Idx) {244 if (this->Index != nullptr) {245 MergedIdx.push_back(std::make_unique<MergedIndex>(Idx, this->Index));246 this->Index = MergedIdx.back().get();247 } else {248 this->Index = Idx;249 }250 };251 if (Opts.StaticIndex)252 AddIndex(Opts.StaticIndex);253 if (Opts.BackgroundIndex) {254 BackgroundIndex::Options BGOpts;255 BGOpts.ThreadPoolSize = std::max(Opts.AsyncThreadsCount, 1u);256 BGOpts.OnProgress = [Callbacks](BackgroundQueue::Stats S) {257 if (Callbacks)258 Callbacks->onBackgroundIndexProgress(S);259 };260 BGOpts.ContextProvider = Opts.ContextProvider;261 BGOpts.SupportContainedRefs = Opts.EnableOutgoingCalls;262 BackgroundIdx = std::make_unique<BackgroundIndex>(263 TFS, CDB,264 BackgroundIndexStorage::createDiskBackedStorageFactory(265 [&CDB](llvm::StringRef File) { return CDB.getProjectInfo(File); }),266 std::move(BGOpts));267 AddIndex(BackgroundIdx.get());268 }269 if (DynamicIdx)270 AddIndex(DynamicIdx.get());271 272 if (Opts.FeatureModules) {273 FeatureModule::Facilities F{274 *this->WorkScheduler,275 this->Index,276 this->TFS,277 };278 for (auto &Mod : *Opts.FeatureModules)279 Mod.initialize(F);280 }281}282 283ClangdServer::~ClangdServer() {284 // Destroying TUScheduler first shuts down request threads that might285 // otherwise access members concurrently.286 // (Nobody can be using TUScheduler because we're on the main thread).287 WorkScheduler.reset();288 // Now requests have stopped, we can shut down feature modules.289 if (FeatureModules) {290 for (auto &Mod : *FeatureModules)291 Mod.stop();292 for (auto &Mod : *FeatureModules)293 Mod.blockUntilIdle(Deadline::infinity());294 }295}296 297void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,298 llvm::StringRef Version,299 WantDiagnostics WantDiags, bool ForceRebuild) {300 std::string ActualVersion = DraftMgr.addDraft(File, Version, Contents);301 ParseOptions Opts;302 Opts.PreambleParseForwardingFunctions = PreambleParseForwardingFunctions;303 Opts.ImportInsertions = ImportInsertions;304 305 // Compile command is set asynchronously during update, as it can be slow.306 ParseInputs Inputs;307 Inputs.TFS = &getHeaderFS();308 Inputs.Contents = std::string(Contents);309 Inputs.Version = std::move(ActualVersion);310 Inputs.ForceRebuild = ForceRebuild;311 Inputs.Opts = std::move(Opts);312 Inputs.Index = Index;313 Inputs.ClangTidyProvider = ClangTidyProvider;314 Inputs.FeatureModules = FeatureModules;315 Inputs.ModulesManager = ModulesManager;316 bool NewFile = WorkScheduler->update(File, Inputs, WantDiags);317 // If we loaded Foo.h, we want to make sure Foo.cpp is indexed.318 if (NewFile && BackgroundIdx)319 BackgroundIdx->boostRelated(File);320}321 322void ClangdServer::reparseOpenFilesIfNeeded(323 llvm::function_ref<bool(llvm::StringRef File)> Filter) {324 // Reparse only opened files that were modified.325 for (const Path &FilePath : DraftMgr.getActiveFiles())326 if (Filter(FilePath))327 if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race?328 addDocument(FilePath, *Draft->Contents, Draft->Version,329 WantDiagnostics::Auto);330}331 332std::shared_ptr<const std::string> ClangdServer::getDraft(PathRef File) const {333 auto Draft = DraftMgr.getDraft(File);334 if (!Draft)335 return nullptr;336 return std::move(Draft->Contents);337}338 339std::function<Context(PathRef)>340ClangdServer::createConfiguredContextProvider(const config::Provider *Provider,341 Callbacks *Publish) {342 if (!Provider)343 return [](llvm::StringRef) { return Context::current().clone(); };344 345 struct Impl {346 const config::Provider *Provider;347 ClangdServer::Callbacks *Publish;348 std::mutex PublishMu;349 350 Impl(const config::Provider *Provider, ClangdServer::Callbacks *Publish)351 : Provider(Provider), Publish(Publish) {}352 353 Context operator()(llvm::StringRef File) {354 config::Params Params;355 // Don't reread config files excessively often.356 // FIXME: when we see a config file change event, use the event timestamp?357 Params.FreshTime =358 std::chrono::steady_clock::now() - std::chrono::seconds(5);359 llvm::SmallString<256> PosixPath;360 if (!File.empty()) {361 assert(llvm::sys::path::is_absolute(File));362 llvm::sys::path::native(File, PosixPath, llvm::sys::path::Style::posix);363 Params.Path = PosixPath.str();364 }365 366 llvm::StringMap<std::vector<Diag>> ReportableDiagnostics;367 Config C = Provider->getConfig(Params, [&](const llvm::SMDiagnostic &D) {368 // Create the map entry even for note diagnostics we don't report.369 // This means that when the file is parsed with no warnings, we370 // publish an empty set of diagnostics, clearing any the client has.371 handleDiagnostic(D, !Publish || D.getFilename().empty()372 ? nullptr373 : &ReportableDiagnostics[D.getFilename()]);374 });375 // Blindly publish diagnostics for the (unopened) parsed config files.376 // We must avoid reporting diagnostics for *the same file* concurrently.377 // Source diags are published elsewhere, but those are different files.378 if (!ReportableDiagnostics.empty()) {379 std::lock_guard<std::mutex> Lock(PublishMu);380 for (auto &Entry : ReportableDiagnostics)381 Publish->onDiagnosticsReady(Entry.first(), /*Version=*/"",382 Entry.second);383 }384 return Context::current().derive(Config::Key, std::move(C));385 }386 387 void handleDiagnostic(const llvm::SMDiagnostic &D,388 std::vector<Diag> *ClientDiagnostics) {389 switch (D.getKind()) {390 case llvm::SourceMgr::DK_Error:391 elog("config error at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),392 D.getColumnNo(), D.getMessage());393 break;394 case llvm::SourceMgr::DK_Warning:395 log("config warning at {0}:{1}:{2}: {3}", D.getFilename(),396 D.getLineNo(), D.getColumnNo(), D.getMessage());397 break;398 case llvm::SourceMgr::DK_Note:399 case llvm::SourceMgr::DK_Remark:400 vlog("config note at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),401 D.getColumnNo(), D.getMessage());402 ClientDiagnostics = nullptr; // Don't emit notes as LSP diagnostics.403 break;404 }405 if (ClientDiagnostics)406 ClientDiagnostics->push_back(toDiag(D, Diag::ClangdConfig));407 }408 };409 410 // Copyable wrapper.411 return [I(std::make_shared<Impl>(Provider, Publish))](llvm::StringRef Path) {412 return (*I)(Path);413 };414}415 416void ClangdServer::removeDocument(PathRef File) {417 DraftMgr.removeDraft(File);418 WorkScheduler->remove(File);419}420 421void ClangdServer::codeComplete(PathRef File, Position Pos,422 const clangd::CodeCompleteOptions &Opts,423 Callback<CodeCompleteResult> CB) {424 // Copy completion options for passing them to async task handler.425 auto CodeCompleteOpts = Opts;426 if (!CodeCompleteOpts.Index) // Respect overridden index.427 CodeCompleteOpts.Index = Index;428 429 auto Task = [Pos, CodeCompleteOpts, File = File.str(), CB = std::move(CB),430 this](llvm::Expected<InputsAndPreamble> IP) mutable {431 if (!IP)432 return CB(IP.takeError());433 if (auto Reason = isCancelled())434 return CB(llvm::make_error<CancelledError>(Reason));435 436 std::optional<SpeculativeFuzzyFind> SpecFuzzyFind;437 if (!IP->Preamble) {438 // No speculation in Fallback mode, as it's supposed to be much faster439 // without compiling.440 vlog("Build for file {0} is not ready. Enter fallback mode.", File);441 } else if (CodeCompleteOpts.Index) {442 SpecFuzzyFind.emplace();443 {444 std::lock_guard<std::mutex> Lock(CachedCompletionFuzzyFindRequestMutex);445 SpecFuzzyFind->CachedReq = CachedCompletionFuzzyFindRequestByFile[File];446 }447 }448 ParseInputs ParseInput{IP->Command, &getHeaderFS(), IP->Contents.str()};449 // FIXME: Add traling new line if there is none at eof, workaround a crash,450 // see https://github.com/clangd/clangd/issues/332451 if (!IP->Contents.ends_with("\n"))452 ParseInput.Contents.append("\n");453 ParseInput.Index = Index;454 455 CodeCompleteOpts.MainFileSignals = IP->Signals;456 CodeCompleteOpts.AllScopes = Config::current().Completion.AllScopes;457 CodeCompleteOpts.ArgumentLists = Config::current().Completion.ArgumentLists;458 CodeCompleteOpts.InsertIncludes =459 Config::current().Completion.HeaderInsertion;460 CodeCompleteOpts.CodePatterns = Config::current().Completion.CodePatterns;461 // FIXME(ibiryukov): even if Preamble is non-null, we may want to check462 // both the old and the new version in case only one of them matches.463 CodeCompleteResult Result = clangd::codeComplete(464 File, Pos, IP->Preamble, ParseInput, CodeCompleteOpts,465 SpecFuzzyFind ? &*SpecFuzzyFind : nullptr);466 // We don't want `codeComplete` to wait for the async call if it doesn't use467 // the result (e.g. non-index completion, speculation fails), so that `CB`468 // is called as soon as results are available.469 {470 clang::clangd::trace::Span Tracer("Completion results callback");471 CB(std::move(Result));472 }473 if (!SpecFuzzyFind)474 return;475 if (SpecFuzzyFind->NewReq) {476 std::lock_guard<std::mutex> Lock(CachedCompletionFuzzyFindRequestMutex);477 CachedCompletionFuzzyFindRequestByFile[File] = *SpecFuzzyFind->NewReq;478 }479 // Explicitly block until async task completes, this is fine as we've480 // already provided reply to the client and running as a preamble task481 // (i.e. won't block other preamble tasks).482 if (SpecFuzzyFind->Result.valid())483 SpecFuzzyFind->Result.wait();484 };485 486 // We use a potentially-stale preamble because latency is critical here.487 WorkScheduler->runWithPreamble(488 "CodeComplete", File,489 (Opts.RunParser == CodeCompleteOptions::AlwaysParse)490 ? TUScheduler::Stale491 : TUScheduler::StaleOrAbsent,492 std::move(Task));493}494 495void ClangdServer::signatureHelp(PathRef File, Position Pos,496 MarkupKind DocumentationFormat,497 Callback<SignatureHelp> CB) {498 499 auto Action = [Pos, File = File.str(), CB = std::move(CB),500 DocumentationFormat,501 this](llvm::Expected<InputsAndPreamble> IP) mutable {502 if (!IP)503 return CB(IP.takeError());504 505 const auto *PreambleData = IP->Preamble;506 if (!PreambleData)507 return CB(error("Failed to parse includes"));508 509 ParseInputs ParseInput{IP->Command, &getHeaderFS(), IP->Contents.str()};510 // FIXME: Add traling new line if there is none at eof, workaround a crash,511 // see https://github.com/clangd/clangd/issues/332512 if (!IP->Contents.ends_with("\n"))513 ParseInput.Contents.append("\n");514 ParseInput.Index = Index;515 CB(clangd::signatureHelp(File, Pos, *PreambleData, ParseInput,516 DocumentationFormat));517 };518 519 // Unlike code completion, we wait for a preamble here.520 WorkScheduler->runWithPreamble("SignatureHelp", File, TUScheduler::Stale,521 std::move(Action));522}523 524void ClangdServer::formatFile(PathRef File, const std::vector<Range> &Rngs,525 Callback<tooling::Replacements> CB) {526 auto Code = getDraft(File);527 if (!Code)528 return CB(llvm::make_error<LSPError>("trying to format non-added document",529 ErrorCode::InvalidParams));530 std::vector<tooling::Range> RequestedRanges;531 if (!Rngs.empty()) {532 RequestedRanges.reserve(Rngs.size());533 for (const auto &Rng : Rngs) {534 llvm::Expected<size_t> Begin = positionToOffset(*Code, Rng.start);535 if (!Begin)536 return CB(Begin.takeError());537 llvm::Expected<size_t> End = positionToOffset(*Code, Rng.end);538 if (!End)539 return CB(End.takeError());540 RequestedRanges.emplace_back(*Begin, *End - *Begin);541 }542 } else {543 RequestedRanges = {tooling::Range(0, Code->size())};544 }545 546 // Call clang-format.547 auto Action = [File = File.str(), Code = std::move(*Code),548 Ranges = std::move(RequestedRanges), CB = std::move(CB),549 this]() mutable {550 format::FormatStyle Style = getFormatStyleForFile(File, Code, TFS, true);551 tooling::Replacements IncludeReplaces =552 format::sortIncludes(Style, Code, Ranges, File);553 auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces);554 if (!Changed)555 return CB(Changed.takeError());556 557 CB(IncludeReplaces.merge(format::reformat(558 Style, *Changed,559 tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges),560 File)));561 };562 WorkScheduler->runQuick("Format", File, std::move(Action));563}564 565void ClangdServer::formatOnType(PathRef File, Position Pos,566 StringRef TriggerText,567 Callback<std::vector<TextEdit>> CB) {568 auto Code = getDraft(File);569 if (!Code)570 return CB(llvm::make_error<LSPError>("trying to format non-added document",571 ErrorCode::InvalidParams));572 llvm::Expected<size_t> CursorPos = positionToOffset(*Code, Pos);573 if (!CursorPos)574 return CB(CursorPos.takeError());575 auto Action = [File = File.str(), Code = std::move(*Code),576 TriggerText = TriggerText.str(), CursorPos = *CursorPos,577 CB = std::move(CB), this]() mutable {578 auto Style = getFormatStyleForFile(File, Code, TFS, false);579 std::vector<TextEdit> Result;580 for (const tooling::Replacement &R :581 formatIncremental(Code, CursorPos, TriggerText, Style))582 Result.push_back(replacementToEdit(Code, R));583 return CB(Result);584 };585 WorkScheduler->runQuick("FormatOnType", File, std::move(Action));586}587 588void ClangdServer::prepareRename(PathRef File, Position Pos,589 std::optional<std::string> NewName,590 const RenameOptions &RenameOpts,591 Callback<RenameResult> CB) {592 auto Action = [Pos, File = File.str(), CB = std::move(CB),593 NewName = std::move(NewName),594 RenameOpts](llvm::Expected<InputsAndAST> InpAST) mutable {595 if (!InpAST)596 return CB(InpAST.takeError());597 // prepareRename is latency-sensitive: we don't query the index, as we598 // only need main-file references599 auto Results =600 clangd::rename({Pos, NewName.value_or("__clangd_rename_placeholder"),601 InpAST->AST, File, /*FS=*/nullptr,602 /*Index=*/nullptr, RenameOpts});603 if (!Results) {604 // LSP says to return null on failure, but that will result in a generic605 // failure message. If we send an LSP error response, clients can surface606 // the message to users (VSCode does).607 return CB(Results.takeError());608 }609 return CB(*Results);610 };611 WorkScheduler->runWithAST("PrepareRename", File, std::move(Action));612}613 614void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,615 const RenameOptions &Opts,616 Callback<RenameResult> CB) {617 auto Action = [File = File.str(), NewName = NewName.str(), Pos, Opts,618 CB = std::move(CB),619 this](llvm::Expected<InputsAndAST> InpAST) mutable {620 // Tracks number of files edited per invocation.621 static constexpr trace::Metric RenameFiles("rename_files",622 trace::Metric::Distribution);623 if (!InpAST)624 return CB(InpAST.takeError());625 auto R = clangd::rename({Pos, NewName, InpAST->AST, File,626 DirtyFS->view(std::nullopt), Index, Opts});627 if (!R)628 return CB(R.takeError());629 630 if (Opts.WantFormat) {631 auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents,632 *InpAST->Inputs.TFS, false);633 llvm::Error Err = llvm::Error::success();634 for (auto &E : R->GlobalChanges)635 Err =636 llvm::joinErrors(reformatEdit(E.getValue(), Style), std::move(Err));637 638 if (Err)639 return CB(std::move(Err));640 }641 RenameFiles.record(R->GlobalChanges.size());642 return CB(*R);643 };644 WorkScheduler->runWithAST("Rename", File, std::move(Action));645}646 647namespace {648// May generate several candidate selections, due to SelectionTree ambiguity.649// vector of pointers because GCC doesn't like non-copyable Selection.650llvm::Expected<std::vector<std::unique_ptr<Tweak::Selection>>>651tweakSelection(const Range &Sel, const InputsAndAST &AST,652 llvm::vfs::FileSystem *FS) {653 auto Begin = positionToOffset(AST.Inputs.Contents, Sel.start);654 if (!Begin)655 return Begin.takeError();656 auto End = positionToOffset(AST.Inputs.Contents, Sel.end);657 if (!End)658 return End.takeError();659 std::vector<std::unique_ptr<Tweak::Selection>> Result;660 SelectionTree::createEach(661 AST.AST.getASTContext(), AST.AST.getTokens(), *Begin, *End,662 [&](SelectionTree T) {663 Result.push_back(std::make_unique<Tweak::Selection>(664 AST.Inputs.Index, AST.AST, *Begin, *End, std::move(T), FS));665 return false;666 });667 assert(!Result.empty() && "Expected at least one SelectionTree");668 return std::move(Result);669}670 671// Some fixes may perform local renaming, we want to convert those to clangd672// rename commands, such that we can leverage the index for more accurate673// results.674std::optional<ClangdServer::CodeActionResult::Rename>675tryConvertToRename(const Diag *Diag, const Fix &Fix) {676 bool IsClangTidyRename = Diag->Source == Diag::ClangTidy &&677 Diag->Name == "readability-identifier-naming" &&678 !Fix.Edits.empty();679 if (IsClangTidyRename && Diag->InsideMainFile) {680 ClangdServer::CodeActionResult::Rename R;681 R.NewName = Fix.Edits.front().newText;682 R.FixMessage = Fix.Message;683 R.Diag = {Diag->Range, Diag->Message};684 return R;685 }686 687 return std::nullopt;688}689 690} // namespace691 692void ClangdServer::codeAction(const CodeActionInputs &Params,693 Callback<CodeActionResult> CB) {694 auto Action = [Params, CB = std::move(CB),695 FeatureModules(this->FeatureModules)](696 Expected<InputsAndAST> InpAST) mutable {697 if (!InpAST)698 return CB(InpAST.takeError());699 auto KindAllowed =700 [Only(Params.RequestedActionKinds)](llvm::StringRef Kind) {701 if (Only.empty())702 return true;703 return llvm::any_of(Only, [&](llvm::StringRef Base) {704 return Kind.consume_front(Base) &&705 (Kind.empty() || Kind.starts_with("."));706 });707 };708 709 CodeActionResult Result;710 Result.Version = InpAST->AST.version().str();711 if (KindAllowed(CodeAction::QUICKFIX_KIND)) {712 auto FindMatchedDiag = [&InpAST](const DiagRef &DR) -> const Diag * {713 for (const auto &Diag : InpAST->AST.getDiagnostics())714 if (Diag.Range == DR.Range && Diag.Message == DR.Message)715 return &Diag;716 return nullptr;717 };718 for (const auto &DiagRef : Params.Diagnostics) {719 if (const auto *Diag = FindMatchedDiag(DiagRef))720 for (const auto &Fix : Diag->Fixes) {721 if (auto Rename = tryConvertToRename(Diag, Fix)) {722 Result.Renames.emplace_back(std::move(*Rename));723 } else {724 Result.QuickFixes.push_back({DiagRef, Fix});725 }726 }727 }728 }729 730 // Collect Tweaks731 auto Selections = tweakSelection(Params.Selection, *InpAST, /*FS=*/nullptr);732 if (!Selections)733 return CB(Selections.takeError());734 // Don't allow a tweak to fire more than once across ambiguous selections.735 llvm::DenseSet<llvm::StringRef> PreparedTweaks;736 auto DeduplicatingFilter = [&](const Tweak &T) {737 return KindAllowed(T.kind()) && Params.TweakFilter(T) &&738 !PreparedTweaks.count(T.id());739 };740 for (const auto &Sel : *Selections) {741 for (auto &T : prepareTweaks(*Sel, DeduplicatingFilter, FeatureModules)) {742 Result.TweakRefs.push_back(TweakRef{T->id(), T->title(), T->kind()});743 PreparedTweaks.insert(T->id());744 TweakAvailable.record(1, T->id());745 }746 }747 CB(std::move(Result));748 };749 750 WorkScheduler->runWithAST("codeAction", Params.File, std::move(Action),751 Transient);752}753 754void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID,755 Callback<Tweak::Effect> CB) {756 // Tracks number of times a tweak has been attempted.757 static constexpr trace::Metric TweakAttempt(758 "tweak_attempt", trace::Metric::Counter, "tweak_id");759 // Tracks number of times a tweak has failed to produce edits.760 static constexpr trace::Metric TweakFailed(761 "tweak_failed", trace::Metric::Counter, "tweak_id");762 TweakAttempt.record(1, TweakID);763 auto Action = [File = File.str(), Sel, TweakID = TweakID.str(),764 CB = std::move(CB),765 this](Expected<InputsAndAST> InpAST) mutable {766 if (!InpAST)767 return CB(InpAST.takeError());768 auto FS = DirtyFS->view(std::nullopt);769 auto Selections = tweakSelection(Sel, *InpAST, FS.get());770 if (!Selections)771 return CB(Selections.takeError());772 std::optional<llvm::Expected<Tweak::Effect>> Effect;773 // Try each selection, take the first one that prepare()s.774 // If they all fail, Effect will hold get the last error.775 for (const auto &Selection : *Selections) {776 auto T = prepareTweak(TweakID, *Selection, FeatureModules);777 if (T) {778 Effect = (*T)->apply(*Selection);779 break;780 }781 Effect = T.takeError();782 }783 assert(Effect && "Expected at least one selection");784 if (*Effect && (*Effect)->FormatEdits) {785 // Format tweaks that require it centrally here.786 for (auto &It : (*Effect)->ApplyEdits) {787 Edit &E = It.second;788 format::FormatStyle Style =789 getFormatStyleForFile(File, E.InitialCode, TFS, false);790 if (llvm::Error Err = reformatEdit(E, Style))791 elog("Failed to format {0}: {1}", It.first(), std::move(Err));792 }793 } else {794 TweakFailed.record(1, TweakID);795 }796 return CB(std::move(*Effect));797 };798 WorkScheduler->runWithAST("ApplyTweak", File, std::move(Action));799}800 801void ClangdServer::locateSymbolAt(PathRef File, Position Pos,802 Callback<std::vector<LocatedSymbol>> CB) {803 auto Action = [Pos, CB = std::move(CB),804 this](llvm::Expected<InputsAndAST> InpAST) mutable {805 if (!InpAST)806 return CB(InpAST.takeError());807 CB(clangd::locateSymbolAt(InpAST->AST, Pos, Index));808 };809 810 WorkScheduler->runWithAST("Definitions", File, std::move(Action));811}812 813void ClangdServer::switchSourceHeader(814 PathRef Path, Callback<std::optional<clangd::Path>> CB) {815 // We want to return the result as fast as possible, strategy is:816 // 1) use the file-only heuristic, it requires some IO but it is much817 // faster than building AST, but it only works when .h/.cc files are in818 // the same directory.819 // 2) if 1) fails, we use the AST&Index approach, it is slower but supports820 // different code layout.821 if (auto CorrespondingFile =822 getCorrespondingHeaderOrSource(Path, TFS.view(std::nullopt)))823 return CB(std::move(CorrespondingFile));824 auto Action = [Path = Path.str(), CB = std::move(CB),825 this](llvm::Expected<InputsAndAST> InpAST) mutable {826 if (!InpAST)827 return CB(InpAST.takeError());828 CB(getCorrespondingHeaderOrSource(Path, InpAST->AST, Index));829 };830 WorkScheduler->runWithAST("SwitchHeaderSource", Path, std::move(Action));831}832 833void ClangdServer::findDocumentHighlights(834 PathRef File, Position Pos, Callback<std::vector<DocumentHighlight>> CB) {835 auto Action =836 [Pos, CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {837 if (!InpAST)838 return CB(InpAST.takeError());839 CB(clangd::findDocumentHighlights(InpAST->AST, Pos));840 };841 842 WorkScheduler->runWithAST("Highlights", File, std::move(Action), Transient);843}844 845void ClangdServer::findHover(PathRef File, Position Pos,846 Callback<std::optional<HoverInfo>> CB) {847 auto Action = [File = File.str(), Pos, CB = std::move(CB),848 this](llvm::Expected<InputsAndAST> InpAST) mutable {849 if (!InpAST)850 return CB(InpAST.takeError());851 format::FormatStyle Style = getFormatStyleForFile(852 File, InpAST->Inputs.Contents, *InpAST->Inputs.TFS, false);853 CB(clangd::getHover(InpAST->AST, Pos, std::move(Style), Index));854 };855 856 WorkScheduler->runWithAST("Hover", File, std::move(Action), Transient);857}858 859void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,860 TypeHierarchyDirection Direction,861 Callback<std::vector<TypeHierarchyItem>> CB) {862 auto Action = [File = File.str(), Pos, Resolve, Direction, CB = std::move(CB),863 this](Expected<InputsAndAST> InpAST) mutable {864 if (!InpAST)865 return CB(InpAST.takeError());866 CB(clangd::getTypeHierarchy(InpAST->AST, Pos, Resolve, Direction, Index,867 File));868 };869 870 WorkScheduler->runWithAST("TypeHierarchy", File, std::move(Action));871}872 873void ClangdServer::superTypes(874 const TypeHierarchyItem &Item,875 Callback<std::optional<std::vector<TypeHierarchyItem>>> CB) {876 WorkScheduler->run("typeHierarchy/superTypes", /*Path=*/"",877 [=, CB = std::move(CB)]() mutable {878 CB(clangd::superTypes(Item, Index));879 });880}881 882void ClangdServer::subTypes(const TypeHierarchyItem &Item,883 Callback<std::vector<TypeHierarchyItem>> CB) {884 WorkScheduler->run(885 "typeHierarchy/subTypes", /*Path=*/"",886 [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });887}888 889void ClangdServer::resolveTypeHierarchy(890 TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,891 Callback<std::optional<TypeHierarchyItem>> CB) {892 WorkScheduler->run(893 "Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable {894 clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index);895 CB(Item);896 });897}898 899void ClangdServer::prepareCallHierarchy(900 PathRef File, Position Pos, Callback<std::vector<CallHierarchyItem>> CB) {901 auto Action = [File = File.str(), Pos,902 CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable {903 if (!InpAST)904 return CB(InpAST.takeError());905 CB(clangd::prepareCallHierarchy(InpAST->AST, Pos, File));906 };907 WorkScheduler->runWithAST("CallHierarchy", File, std::move(Action));908}909 910void ClangdServer::incomingCalls(911 const CallHierarchyItem &Item,912 Callback<std::vector<CallHierarchyIncomingCall>> CB) {913 WorkScheduler->run("Incoming Calls", "",914 [CB = std::move(CB), Item, this]() mutable {915 CB(clangd::incomingCalls(Item, Index));916 });917}918 919void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,920 Callback<std::vector<InlayHint>> CB) {921 auto Action = [RestrictRange(std::move(RestrictRange)),922 CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable {923 if (!InpAST)924 return CB(InpAST.takeError());925 CB(clangd::inlayHints(InpAST->AST, std::move(RestrictRange)));926 };927 WorkScheduler->runWithAST("InlayHints", File, std::move(Action), Transient);928}929 930void ClangdServer::outgoingCalls(931 const CallHierarchyItem &Item,932 Callback<std::vector<CallHierarchyOutgoingCall>> CB) {933 WorkScheduler->run("Outgoing Calls", "",934 [CB = std::move(CB), Item, this]() mutable {935 CB(clangd::outgoingCalls(Item, Index));936 });937}938 939void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {940 // FIXME: Do nothing for now. This will be used for indexing and potentially941 // invalidating other caches.942}943 944void ClangdServer::workspaceSymbols(945 llvm::StringRef Query, int Limit,946 Callback<std::vector<SymbolInformation>> CB) {947 WorkScheduler->run(948 "getWorkspaceSymbols", /*Path=*/"",949 [Query = Query.str(), Limit, CB = std::move(CB), this]() mutable {950 CB(clangd::getWorkspaceSymbols(Query, Limit, Index,951 WorkspaceRoot.value_or("")));952 });953}954 955void ClangdServer::documentSymbols(llvm::StringRef File,956 Callback<std::vector<DocumentSymbol>> CB) {957 auto Action =958 [CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {959 if (!InpAST)960 return CB(InpAST.takeError());961 CB(clangd::getDocumentSymbols(InpAST->AST));962 };963 WorkScheduler->runWithAST("DocumentSymbols", File, std::move(Action),964 Transient);965}966 967void ClangdServer::foldingRanges(llvm::StringRef File,968 Callback<std::vector<FoldingRange>> CB) {969 auto Code = getDraft(File);970 if (!Code)971 return CB(llvm::make_error<LSPError>(972 "trying to compute folding ranges for non-added document",973 ErrorCode::InvalidParams));974 auto Action = [LineFoldingOnly = LineFoldingOnly, CB = std::move(CB),975 Code = std::move(*Code)]() mutable {976 CB(clangd::getFoldingRanges(Code, LineFoldingOnly));977 };978 // We want to make sure folding ranges are always available for all the open979 // files, hence prefer runQuick to not wait for operations on other files.980 WorkScheduler->runQuick("FoldingRanges", File, std::move(Action));981}982 983void ClangdServer::findType(llvm::StringRef File, Position Pos,984 Callback<std::vector<LocatedSymbol>> CB) {985 auto Action = [Pos, CB = std::move(CB),986 this](llvm::Expected<InputsAndAST> InpAST) mutable {987 if (!InpAST)988 return CB(InpAST.takeError());989 CB(clangd::findType(InpAST->AST, Pos, Index));990 };991 WorkScheduler->runWithAST("FindType", File, std::move(Action));992}993 994void ClangdServer::findImplementations(995 PathRef File, Position Pos, Callback<std::vector<LocatedSymbol>> CB) {996 auto Action = [Pos, CB = std::move(CB),997 this](llvm::Expected<InputsAndAST> InpAST) mutable {998 if (!InpAST)999 return CB(InpAST.takeError());1000 CB(clangd::findImplementations(InpAST->AST, Pos, Index));1001 };1002 1003 WorkScheduler->runWithAST("Implementations", File, std::move(Action));1004}1005 1006void ClangdServer::findReferences(PathRef File, Position Pos, uint32_t Limit,1007 bool AddContainer,1008 Callback<ReferencesResult> CB) {1009 auto Action = [Pos, Limit, AddContainer, CB = std::move(CB),1010 this](llvm::Expected<InputsAndAST> InpAST) mutable {1011 if (!InpAST)1012 return CB(InpAST.takeError());1013 CB(clangd::findReferences(InpAST->AST, Pos, Limit, Index, AddContainer));1014 };1015 1016 WorkScheduler->runWithAST("References", File, std::move(Action));1017}1018 1019void ClangdServer::symbolInfo(PathRef File, Position Pos,1020 Callback<std::vector<SymbolDetails>> CB) {1021 auto Action =1022 [Pos, CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {1023 if (!InpAST)1024 return CB(InpAST.takeError());1025 CB(clangd::getSymbolInfo(InpAST->AST, Pos));1026 };1027 1028 WorkScheduler->runWithAST("SymbolInfo", File, std::move(Action));1029}1030 1031void ClangdServer::semanticRanges(PathRef File,1032 const std::vector<Position> &Positions,1033 Callback<std::vector<SelectionRange>> CB) {1034 auto Action = [Positions, CB = std::move(CB)](1035 llvm::Expected<InputsAndAST> InpAST) mutable {1036 if (!InpAST)1037 return CB(InpAST.takeError());1038 std::vector<SelectionRange> Result;1039 for (const auto &Pos : Positions) {1040 if (auto Range = clangd::getSemanticRanges(InpAST->AST, Pos))1041 Result.push_back(std::move(*Range));1042 else1043 return CB(Range.takeError());1044 }1045 CB(std::move(Result));1046 };1047 WorkScheduler->runWithAST("SemanticRanges", File, std::move(Action));1048}1049 1050void ClangdServer::documentLinks(PathRef File,1051 Callback<std::vector<DocumentLink>> CB) {1052 auto Action =1053 [CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {1054 if (!InpAST)1055 return CB(InpAST.takeError());1056 CB(clangd::getDocumentLinks(InpAST->AST));1057 };1058 WorkScheduler->runWithAST("DocumentLinks", File, std::move(Action),1059 Transient);1060}1061 1062void ClangdServer::semanticHighlights(1063 PathRef File, Callback<std::vector<HighlightingToken>> CB) {1064 1065 auto Action = [CB = std::move(CB),1066 PublishInactiveRegions = PublishInactiveRegions](1067 llvm::Expected<InputsAndAST> InpAST) mutable {1068 if (!InpAST)1069 return CB(InpAST.takeError());1070 // Include inactive regions in semantic highlighting tokens only if the1071 // client doesn't support a dedicated protocol for being informed about1072 // them.1073 CB(clangd::getSemanticHighlightings(InpAST->AST, !PublishInactiveRegions));1074 };1075 WorkScheduler->runWithAST("SemanticHighlights", File, std::move(Action),1076 Transient);1077}1078 1079void ClangdServer::getAST(PathRef File, std::optional<Range> R,1080 Callback<std::optional<ASTNode>> CB) {1081 auto Action =1082 [R, CB(std::move(CB))](llvm::Expected<InputsAndAST> Inputs) mutable {1083 if (!Inputs)1084 return CB(Inputs.takeError());1085 if (!R) {1086 // It's safe to pass in the TU, as dumpAST() does not1087 // deserialize the preamble.1088 auto Node = DynTypedNode::create(1089 *Inputs->AST.getASTContext().getTranslationUnitDecl());1090 return CB(dumpAST(Node, Inputs->AST.getTokens(),1091 Inputs->AST.getASTContext()));1092 }1093 unsigned Start, End;1094 if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R->start))1095 Start = *Offset;1096 else1097 return CB(Offset.takeError());1098 if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R->end))1099 End = *Offset;1100 else1101 return CB(Offset.takeError());1102 bool Success = SelectionTree::createEach(1103 Inputs->AST.getASTContext(), Inputs->AST.getTokens(), Start, End,1104 [&](SelectionTree T) {1105 if (const SelectionTree::Node *N = T.commonAncestor()) {1106 CB(dumpAST(N->ASTNode, Inputs->AST.getTokens(),1107 Inputs->AST.getASTContext()));1108 return true;1109 }1110 return false;1111 });1112 if (!Success)1113 CB(std::nullopt);1114 };1115 WorkScheduler->runWithAST("GetAST", File, std::move(Action));1116}1117 1118void ClangdServer::customAction(PathRef File, llvm::StringRef Name,1119 Callback<InputsAndAST> Action) {1120 WorkScheduler->runWithAST(Name, File, std::move(Action));1121}1122 1123void ClangdServer::diagnostics(PathRef File, Callback<std::vector<Diag>> CB) {1124 auto Action =1125 [CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {1126 if (!InpAST)1127 return CB(InpAST.takeError());1128 return CB(InpAST->AST.getDiagnostics());1129 };1130 1131 WorkScheduler->runWithAST("Diagnostics", File, std::move(Action));1132}1133 1134llvm::StringMap<TUScheduler::FileStats> ClangdServer::fileStats() const {1135 return WorkScheduler->fileStats();1136}1137 1138[[nodiscard]] bool1139ClangdServer::blockUntilIdleForTest(std::optional<double> TimeoutSeconds) {1140 // Order is important here: we don't want to block on A and then B,1141 // if B might schedule work on A.1142 1143#if defined(__has_feature) && \1144 (__has_feature(address_sanitizer) || __has_feature(hwaddress_sanitizer) || \1145 __has_feature(memory_sanitizer) || __has_feature(thread_sanitizer))1146 if (TimeoutSeconds.has_value())1147 (*TimeoutSeconds) *= 10;1148#endif1149 1150 // Nothing else can schedule work on TUScheduler, because it's not threadsafe1151 // and we're blocking the main thread.1152 if (!WorkScheduler->blockUntilIdle(timeoutSeconds(TimeoutSeconds)))1153 return false;1154 // TUScheduler is the only thing that starts background indexing work.1155 if (IndexTasks && !IndexTasks->wait(timeoutSeconds(TimeoutSeconds)))1156 return false;1157 1158 // Unfortunately we don't have strict topological order between the rest of1159 // the components. E.g. CDB broadcast triggers backrgound indexing.1160 // This queries the CDB which may discover new work if disk has changed.1161 //1162 // So try each one a few times in a loop.1163 // If there are no tricky interactions then all after the first are no-ops.1164 // Then on the last iteration, verify they're idle without waiting.1165 //1166 // There's a small chance they're juggling work and we didn't catch them :-(1167 for (std::optional<double> Timeout :1168 {TimeoutSeconds, TimeoutSeconds, std::optional<double>(0)}) {1169 if (!CDB.blockUntilIdle(timeoutSeconds(Timeout)))1170 return false;1171 if (BackgroundIdx && !BackgroundIdx->blockUntilIdleForTest(Timeout))1172 return false;1173 if (FeatureModules && llvm::any_of(*FeatureModules, [&](FeatureModule &M) {1174 return !M.blockUntilIdle(timeoutSeconds(Timeout));1175 }))1176 return false;1177 }1178 1179 assert(WorkScheduler->blockUntilIdle(Deadline::zero()) &&1180 "Something scheduled work while we're blocking the main thread!");1181 return true;1182}1183 1184void ClangdServer::profile(MemoryTree &MT) const {1185 if (DynamicIdx)1186 DynamicIdx->profile(MT.child("dynamic_index"));1187 if (BackgroundIdx)1188 BackgroundIdx->profile(MT.child("background_index"));1189 WorkScheduler->profile(MT.child("tuscheduler"));1190}1191} // namespace clangd1192} // namespace clang1193