Skip to content

Commit 7e6d39b

Browse files
committed
Call into the BuildSystemManager from ClangLanguageServerShim to get build settings
The same kind of change that we did for `SwiftLanguageServer`. Instead of caching build settings inside `ClangLanguageServerShim`, always call into `BuildSystemManager` to get the build settings.
1 parent 33cf8cc commit 7e6d39b

File tree

1 file changed

+45
-35
lines changed

1 file changed

+45
-35
lines changed

Sources/SourceKitLSP/Clang/ClangLanguageServer.swift

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,6 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
6464

6565
let clangdOptions: [String]
6666

67-
/// Resolved build settings by file. Must be accessed with the `lock`.
68-
private var buildSettingsByFile: [DocumentURI: ClangBuildSettings] = [:]
69-
70-
/// Lock protecting `buildSettingsByFile`.
71-
private var lock: NSLock = NSLock()
72-
7367
/// The current state of the `clangd` language server.
7468
/// Changing the property automatically notified the state change handlers.
7569
private var state: LanguageServerState {
@@ -102,6 +96,10 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
10296
/// A callback with which `ClangLanguageServer` can request its owner to reopen all documents in case it has crashed.
10397
private let reopenDocuments: (ToolchainLanguageServer) -> Void
10498

99+
/// The documents that have been opened and which language they have been
100+
/// opened with.
101+
private var openDocuments: [DocumentURI: Language] = [:]
102+
105103
/// While `clangd` is running, its PID.
106104
#if os(Windows)
107105
private var hClangd: HANDLE = INVALID_HANDLE_VALUE
@@ -132,6 +130,16 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
132130
try startClangdProcesss()
133131
}
134132

133+
private func buildSettings(for document: DocumentURI) async -> ClangBuildSettings? {
134+
guard let workspace = workspace.value, let language = openDocuments[document] else {
135+
return nil
136+
}
137+
guard let settings = await workspace.buildSystemManager.buildSettings(for: document, language: language) else {
138+
return nil
139+
}
140+
return ClangBuildSettings(settings.buildSettings, clangPath: clangdPath, isFallback: settings.isFallback)
141+
}
142+
135143
nonisolated func canHandle(workspace: Workspace) -> Bool {
136144
// We launch different clangd instance for each workspace because clangd doesn't have multi-root workspace support.
137145
return workspace === self.workspace.value
@@ -160,8 +168,8 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
160168

161169
/// Start the `clangd` process, either on creation of the `ClangLanguageServerShim` or after `clangd` has crashed.
162170
private func startClangdProcesss() throws {
163-
// Since we are starting a new clangd process, reset the build settings we have transmitted to clangd
164-
buildSettingsByFile = [:]
171+
// Since we are starting a new clangd process, reset the list of open document
172+
openDocuments = [:]
165173

166174
let usToClangd: Pipe = Pipe()
167175
let clangdToUs: Pipe = Pipe()
@@ -249,9 +257,9 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
249257
/// sending a notification that's intended for the editor.
250258
///
251259
/// We should either handle it ourselves or forward it to the client.
252-
func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) {
260+
func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) async {
253261
if let publishDiags = params as? PublishDiagnosticsNotification {
254-
self.publishDiagnostics(Notification(publishDiags, clientID: clientID))
262+
await self.publishDiagnostics(Notification(publishDiags, clientID: clientID))
255263
} else if clientID == ObjectIdentifier(self.clangd) {
256264
self.client.send(params)
257265
}
@@ -332,19 +340,27 @@ extension ClangLanguageServerShim {
332340

333341
/// Intercept clangd's `PublishDiagnosticsNotification` to withold it if we're using fallback
334342
/// build settings.
335-
func publishDiagnostics(_ note: Notification<PublishDiagnosticsNotification>) {
343+
func publishDiagnostics(_ note: Notification<PublishDiagnosticsNotification>) async {
336344
let params = note.params
337-
let buildSettings = self.lock.withLock {
338-
return self.buildSettingsByFile[params.uri]
339-
}
340-
let isFallback = buildSettings?.isFallback ?? true
341-
guard isFallback else {
345+
// Technically, the publish diagnostics notification could still originate
346+
// from when we opened the file with fallback build settings and we could
347+
// have received real build settings since, which haven't been acknowledged
348+
// by clangd yet.
349+
//
350+
// Since there is no way to tell which build settings clangd used to generate
351+
// the diagnostics, there's no good way to resolve this race. For now, this
352+
// should be good enough since the time in which the race may occur is pretty
353+
// short and we expect clangd to send us new diagnostics with the updated
354+
// non-fallback settings very shortly after, which will override the
355+
// incorrect result, making it very temporary.
356+
let buildSettings = await self.buildSettings(for: params.uri)
357+
if buildSettings?.isFallback ?? true {
358+
// Fallback: send empty publish notification instead.
359+
client.send(PublishDiagnosticsNotification(
360+
uri: params.uri, version: params.version, diagnostics: []))
361+
} else {
342362
client.send(note.params)
343-
return
344363
}
345-
// Fallback: send empty publish notification instead.
346-
client.send(PublishDiagnosticsNotification(
347-
uri: params.uri, version: params.version, diagnostics: []))
348364
}
349365

350366
}
@@ -383,16 +399,18 @@ extension ClangLanguageServerShim {
383399

384400
// MARK: - Text synchronization
385401

386-
public func openDocument(_ note: DidOpenTextDocumentNotification) {
402+
public func openDocument(_ note: DidOpenTextDocumentNotification) async {
403+
openDocuments[note.textDocument.uri] = note.textDocument.language
404+
// Send clangd the build settings for the new file. We need to do this before
405+
// sending the open notification, so that the initial diagnostics already
406+
// have build settings.
407+
await documentUpdatedBuildSettings(note.textDocument.uri, change: .removedOrUnavailable)
387408
clangd.send(note)
388409
}
389410

390411
public func closeDocument(_ note: DidCloseTextDocumentNotification) {
412+
openDocuments[note.textDocument.uri] = nil
391413
clangd.send(note)
392-
393-
// Don't clear cached build settings since we've already informed clangd of the settings for the
394-
// file; if we clear the build settings here we should give clangd dummy build settings to make
395-
// sure we're in sync.
396414
}
397415

398416
public func changeDocument(_ note: DidChangeTextDocumentNotification) {
@@ -409,26 +427,18 @@ extension ClangLanguageServerShim {
409427

410428
// MARK: - Build System Integration
411429

412-
public func documentUpdatedBuildSettings(_ uri: DocumentURI, change: FileBuildSettingsChange) {
430+
public func documentUpdatedBuildSettings(_ uri: DocumentURI, change: FileBuildSettingsChange) async {
413431
guard let url = uri.fileURL else {
414432
// FIXME: The clang workspace can probably be reworked to support non-file URIs.
415433
log("Received updated build settings for non-file URI '\(uri)'. Ignoring the update.")
416434
return
417435
}
418-
let clangBuildSettings = ClangBuildSettings(change: change, clangPath: self.clangPath)
436+
let clangBuildSettings = await self.buildSettings(for: uri)
419437
logAsync(level: clangBuildSettings == nil ? .warning : .debug) { _ in
420438
let settingsStr = clangBuildSettings == nil ? "nil" : clangBuildSettings!.compilerArgs.description
421439
return "settings for \(uri): \(settingsStr)"
422440
}
423441

424-
let changed = lock.withLock { () -> Bool in
425-
let prevBuildSettings = self.buildSettingsByFile[uri]
426-
guard clangBuildSettings != prevBuildSettings else { return false }
427-
self.buildSettingsByFile[uri] = clangBuildSettings
428-
return true
429-
}
430-
guard changed else { return }
431-
432442
// The compile command changed, send over the new one.
433443
// FIXME: what should we do if we no longer have valid build settings?
434444
if

0 commit comments

Comments
 (0)