Skip to content

Commit 60cd75a

Browse files
committed
[clangd] Inject context provider rather than config into ClangdServer. NFC
This is a step towards allowing CDB behavior to being configurable. Previously ClangdServer itself created the configs and installed them into contexts. This was natural as it knows how to deal with resulting diagnostics. However this prevents config being used in CDB, which must be created before ClangdServer. So we extract the context provider (config loader) as a separate object, which publishes diagnostics to a ClangdServer::Callbacks itself. Now initialization looks like: - First create the config::Provider - Then create the ClangdLSPServer, passing config provider - Next, create the context provider, passing config provider + diagnostic callbacks - now create the CDB, passing context provider - finally create ClangdServer, passing CDB, context provider, and diagnostic callbacks Differential Revision: https://reviews.llvm.org/D95087
1 parent 7388c34 commit 60cd75a

File tree

5 files changed

+107
-83
lines changed

5 files changed

+107
-83
lines changed

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,12 @@ ClangdLSPServer::ClangdLSPServer(class Transport &Transp,
14691469
MsgHandler(new MessageHandler(*this)), TFS(TFS),
14701470
SupportedSymbolKinds(defaultSymbolKinds()),
14711471
SupportedCompletionItemKinds(defaultCompletionItemKinds()), Opts(Opts) {
1472+
if (Opts.ConfigProvider) {
1473+
assert(!Opts.ContextProvider &&
1474+
"Only one of ConfigProvider and ContextProvider allowed!");
1475+
this->Opts.ContextProvider = ClangdServer::createConfiguredContextProvider(
1476+
Opts.ConfigProvider, this);
1477+
}
14721478

14731479
// clang-format off
14741480
MsgHandler->bind("initialize", &ClangdLSPServer::onInitialize);

clang-tools-extra/clangd/ClangdLSPServer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class SymbolIndex;
4141
class ClangdLSPServer : private ClangdServer::Callbacks {
4242
public:
4343
struct Options : ClangdServer::Options {
44+
/// Supplies configuration (overrides ClangdServer::ContextProvider).
45+
config::Provider *ConfigProvider = nullptr;
4446
/// Look for compilation databases, rather than using compile commands
4547
/// set via LSP (extensions) only.
4648
bool UseDirBasedCDB = true;

clang-tools-extra/clangd/ClangdServer.cpp

Lines changed: 81 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,14 @@ ClangdServer::Options::operator TUScheduler::Options() const {
133133
Opts.StorePreamblesInMemory = StorePreamblesInMemory;
134134
Opts.UpdateDebounce = UpdateDebounce;
135135
Opts.AsyncPreambleBuilds = AsyncPreambleBuilds;
136+
Opts.ContextProvider = ContextProvider;
136137
return Opts;
137138
}
138139

139140
ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
140141
const ThreadsafeFS &TFS, const Options &Opts,
141142
Callbacks *Callbacks)
142-
: ConfigProvider(Opts.ConfigProvider), CDB(CDB), TFS(TFS),
143-
ServerCallbacks(Callbacks),
143+
: CDB(CDB), TFS(TFS),
144144
DynamicIdx(Opts.BuildDynamicSymbolIndex
145145
? new FileIndex(Opts.HeavyweightDynamicSymbolIndex,
146146
Opts.CollectMainFileRefs)
@@ -153,14 +153,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
153153
// FIXME(ioeric): this can be slow and we may be able to index on less
154154
// critical paths.
155155
WorkScheduler(
156-
CDB,
157-
[&, this] {
158-
TUScheduler::Options O(Opts);
159-
O.ContextProvider = [this](PathRef P) {
160-
return createProcessingContext(P);
161-
};
162-
return O;
163-
}(),
156+
CDB, TUScheduler::Options(Opts),
164157
std::make_unique<UpdateIndexCallbacks>(
165158
DynamicIdx.get(), Callbacks, Opts.TheiaSemanticHighlighting)) {
166159
// Adds an index to the stack, at higher priority than existing indexes.
@@ -181,9 +174,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
181174
if (Callbacks)
182175
Callbacks->onBackgroundIndexProgress(S);
183176
};
184-
BGOpts.ContextProvider = [this](PathRef P) {
185-
return createProcessingContext(P);
186-
};
177+
BGOpts.ContextProvider = Opts.ContextProvider;
187178
BGOpts.CollectMainFileRefs = Opts.CollectMainFileRefs;
188179
BackgroundIdx = std::make_unique<BackgroundIndex>(
189180
TFS, CDB,
@@ -216,6 +207,83 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
216207
BackgroundIdx->boostRelated(File);
217208
}
218209

210+
std::function<Context(PathRef)>
211+
ClangdServer::createConfiguredContextProvider(const config::Provider *Provider,
212+
Callbacks *Publish) {
213+
if (!Provider)
214+
return [](llvm::StringRef) { return Context::current().clone(); };
215+
216+
struct Impl {
217+
const config::Provider *Provider;
218+
ClangdServer::Callbacks *Publish;
219+
std::mutex PublishMu;
220+
221+
Impl(const config::Provider *Provider, ClangdServer::Callbacks *Publish)
222+
: Provider(Provider), Publish(Publish) {}
223+
224+
Context operator()(llvm::StringRef File) {
225+
config::Params Params;
226+
// Don't reread config files excessively often.
227+
// FIXME: when we see a config file change event, use the event timestamp?
228+
Params.FreshTime =
229+
std::chrono::steady_clock::now() - std::chrono::seconds(5);
230+
llvm::SmallString<256> PosixPath;
231+
if (!File.empty()) {
232+
assert(llvm::sys::path::is_absolute(File));
233+
llvm::sys::path::native(File, PosixPath, llvm::sys::path::Style::posix);
234+
Params.Path = PosixPath.str();
235+
}
236+
237+
llvm::StringMap<std::vector<Diag>> ReportableDiagnostics;
238+
Config C = Provider->getConfig(Params, [&](const llvm::SMDiagnostic &D) {
239+
// Create the map entry even for note diagnostics we don't report.
240+
// This means that when the file is parsed with no warnings, we
241+
// publish an empty set of diagnostics, clearing any the client has.
242+
handleDiagnostic(D, !Publish || D.getFilename().empty()
243+
? nullptr
244+
: &ReportableDiagnostics[D.getFilename()]);
245+
});
246+
// Blindly publish diagnostics for the (unopened) parsed config files.
247+
// We must avoid reporting diagnostics for *the same file* concurrently.
248+
// Source diags are published elsewhere, but those are different files.
249+
if (!ReportableDiagnostics.empty()) {
250+
std::lock_guard<std::mutex> Lock(PublishMu);
251+
for (auto &Entry : ReportableDiagnostics)
252+
Publish->onDiagnosticsReady(Entry.first(), /*Version=*/"",
253+
std::move(Entry.second));
254+
}
255+
return Context::current().derive(Config::Key, std::move(C));
256+
}
257+
258+
void handleDiagnostic(const llvm::SMDiagnostic &D,
259+
std::vector<Diag> *ClientDiagnostics) {
260+
switch (D.getKind()) {
261+
case llvm::SourceMgr::DK_Error:
262+
elog("config error at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
263+
D.getColumnNo(), D.getMessage());
264+
break;
265+
case llvm::SourceMgr::DK_Warning:
266+
log("config warning at {0}:{1}:{2}: {3}", D.getFilename(),
267+
D.getLineNo(), D.getColumnNo(), D.getMessage());
268+
break;
269+
case llvm::SourceMgr::DK_Note:
270+
case llvm::SourceMgr::DK_Remark:
271+
vlog("config note at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
272+
D.getColumnNo(), D.getMessage());
273+
ClientDiagnostics = nullptr; // Don't emit notes as LSP diagnostics.
274+
break;
275+
}
276+
if (ClientDiagnostics)
277+
ClientDiagnostics->push_back(toDiag(D, Diag::ClangdConfig));
278+
}
279+
};
280+
281+
// Copyable wrapper.
282+
return [I(std::make_shared<Impl>(Provider, Publish))](llvm::StringRef Path) {
283+
return (*I)(Path);
284+
};
285+
}
286+
219287
void ClangdServer::removeDocument(PathRef File) { WorkScheduler.remove(File); }
220288

221289
void ClangdServer::codeComplete(PathRef File, Position Pos,
@@ -802,62 +870,6 @@ llvm::StringMap<TUScheduler::FileStats> ClangdServer::fileStats() const {
802870
return WorkScheduler.fileStats();
803871
}
804872

805-
Context ClangdServer::createProcessingContext(PathRef File) const {
806-
if (!ConfigProvider)
807-
return Context::current().clone();
808-
809-
config::Params Params;
810-
// Don't reread config files excessively often.
811-
// FIXME: when we see a config file change event, use the event timestamp.
812-
Params.FreshTime = std::chrono::steady_clock::now() - std::chrono::seconds(5);
813-
llvm::SmallString<256> PosixPath;
814-
if (!File.empty()) {
815-
assert(llvm::sys::path::is_absolute(File));
816-
llvm::sys::path::native(File, PosixPath, llvm::sys::path::Style::posix);
817-
Params.Path = PosixPath.str();
818-
}
819-
820-
llvm::StringMap<std::vector<Diag>> ReportableDiagnostics;
821-
auto ConfigDiagnosticHandler = [&](const llvm::SMDiagnostic &D) {
822-
// Ensure we create the map entry even for note diagnostics we don't report.
823-
// This means that when the file is parsed with no warnings, we'll
824-
// publish an empty set of diagnostics, clearing any the client has.
825-
auto *Reportable = D.getFilename().empty()
826-
? nullptr
827-
: &ReportableDiagnostics[D.getFilename()];
828-
switch (D.getKind()) {
829-
case llvm::SourceMgr::DK_Error:
830-
elog("config error at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
831-
D.getColumnNo(), D.getMessage());
832-
if (Reportable)
833-
Reportable->push_back(toDiag(D, Diag::ClangdConfig));
834-
break;
835-
case llvm::SourceMgr::DK_Warning:
836-
log("config warning at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
837-
D.getColumnNo(), D.getMessage());
838-
if (Reportable)
839-
Reportable->push_back(toDiag(D, Diag::ClangdConfig));
840-
break;
841-
case llvm::SourceMgr::DK_Note:
842-
case llvm::SourceMgr::DK_Remark:
843-
vlog("config note at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
844-
D.getColumnNo(), D.getMessage());
845-
break;
846-
}
847-
};
848-
Config C = ConfigProvider->getConfig(Params, ConfigDiagnosticHandler);
849-
// Blindly publish diagnostics for the (unopened) parsed config files.
850-
// We must avoid reporting diagnostics for *the same file* concurrently.
851-
// Source file diags are published elsewhere, but those are different files.
852-
if (!ReportableDiagnostics.empty()) {
853-
std::lock_guard<std::mutex> Lock(ConfigDiagnosticsMu);
854-
for (auto &Entry : ReportableDiagnostics)
855-
ServerCallbacks->onDiagnosticsReady(Entry.first(), /*Version=*/"",
856-
std::move(Entry.second));
857-
}
858-
return Context::current().derive(Config::Key, std::move(C));
859-
}
860-
861873
LLVM_NODISCARD bool
862874
ClangdServer::blockUntilIdleForTest(llvm::Optional<double> TimeoutSeconds) {
863875
return WorkScheduler.blockUntilIdle(timeoutSeconds(TimeoutSeconds)) &&

clang-tools-extra/clangd/ClangdServer.h

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ class ClangdServer {
8080
virtual void
8181
onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) {}
8282
};
83+
/// Creates a context provider that loads and installs config.
84+
/// Errors in loading config are reported as diagnostics via Callbacks.
85+
/// (This is typically used as ClangdServer::Options::ContextProvider).
86+
static std::function<Context(PathRef)>
87+
createConfiguredContextProvider(const config::Provider *Provider,
88+
ClangdServer::Callbacks *);
8389

8490
struct Options {
8591
/// To process requests asynchronously, ClangdServer spawns worker threads.
@@ -111,8 +117,16 @@ class ClangdServer {
111117
/// If set, use this index to augment code completion results.
112118
SymbolIndex *StaticIndex = nullptr;
113119

114-
/// If set, queried to obtain the configuration to handle each request.
115-
config::Provider *ConfigProvider = nullptr;
120+
/// If set, queried to derive a processing context for some work.
121+
/// Usually used to inject Config (see createConfiguredContextProvider).
122+
///
123+
/// When the provider is called, the active context will be that inherited
124+
/// from the request (e.g. addDocument()), or from the ClangdServer
125+
/// constructor if there is no such request (e.g. background indexing).
126+
///
127+
/// The path is an absolute path of the file being processed.
128+
/// If there is no particular file (e.g. project loading) then it is empty.
129+
std::function<Context(PathRef)> ContextProvider;
116130

117131
/// The Options provider to use when running clang-tidy. If null, clang-tidy
118132
/// checks will be disabled.
@@ -343,19 +357,8 @@ class ClangdServer {
343357
ArrayRef<tooling::Range> Ranges,
344358
Callback<tooling::Replacements> CB);
345359

346-
/// Derives a context for a task processing the specified source file.
347-
/// This includes the current configuration (see Options::ConfigProvider).
348-
/// The empty string means no particular file is the target.
349-
/// Rather than called by each feature, this is exposed to the components
350-
/// that control worker threads, like TUScheduler and BackgroundIndex.
351-
/// This means it's OK to do some IO here, and it cuts across all features.
352-
Context createProcessingContext(PathRef) const;
353-
config::Provider *ConfigProvider = nullptr;
354-
355360
const GlobalCompilationDatabase &CDB;
356361
const ThreadsafeFS &TFS;
357-
Callbacks *ServerCallbacks = nullptr;
358-
mutable std::mutex ConfigDiagnosticsMu;
359362

360363
Path ResourceDir;
361364
// The index used to look up symbols. This could be:

clang-tools-extra/clangd/unittests/ClangdTests.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,8 @@ TEST(ClangdServerTest, RespectsConfig) {
348348
} CfgProvider;
349349

350350
auto Opts = ClangdServer::optsForTest();
351-
Opts.ConfigProvider = &CfgProvider;
351+
Opts.ContextProvider =
352+
ClangdServer::createConfiguredContextProvider(&CfgProvider, nullptr);
352353
OverlayCDB CDB(/*Base=*/nullptr, /*FallbackFlags=*/{},
353354
tooling::ArgumentsAdjuster(CommandMangler::forTests()));
354355
MockFS FS;

0 commit comments

Comments
 (0)