Skip to content

Commit fc3c85c

Browse files
authored
Merge pull request swiftlang#242 from DavidGoldman/dependencyhandling
Improve handling/testing of `filesDependenciesUpdated` for clangd
2 parents 67e6b6c + 453d773 commit fc3c85c

File tree

7 files changed

+71
-4
lines changed

7 files changed

+71
-4
lines changed

Sources/LanguageServerProtocol/Notifications/TextSynchronizationNotifications.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,19 @@ public struct DidChangeTextDocumentNotification: NotificationType, Hashable {
7474
/// Edits to the document.
7575
public var contentChanges: [TextDocumentContentChangeEvent]
7676

77+
/// Force the LSP to rebuild its AST for the given file. This is useful for clangd to workaround clangd's assumption that
78+
/// missing header files will stay missing.
79+
/// **LSP Extension from clangd**.
80+
public var forceRebuild: Bool? = nil
81+
7782
public init(
7883
textDocument: VersionedTextDocumentIdentifier,
79-
contentChanges: [TextDocumentContentChangeEvent])
84+
contentChanges: [TextDocumentContentChangeEvent],
85+
forceRebuild: Bool? = nil)
8086
{
8187
self.textDocument = textDocument
8288
self.contentChanges = contentChanges
89+
self.forceRebuild = forceRebuild
8390
}
8491
}
8592

Sources/SourceKit/clangd/ClangLanguageServer.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,13 @@ extension ClangLanguageServerShim {
129129
}
130130

131131
public func documentDependenciesUpdated(_ uri: DocumentURI, language: Language) {
132-
// In order to tell clangd to reload an AST, we send it an empty `didChangeTextDocument`. This
133-
// works well for us as the moment since clangd ignores the document version.
132+
// In order to tell clangd to reload an AST, we send it an empty `didChangeTextDocument`
133+
// with `forceRebuild` set in case any missing header files have been added.
134+
// This works well for us as the moment since clangd ignores the document version.
134135
let note = DidChangeTextDocumentNotification(
135136
textDocument: VersionedTextDocumentIdentifier(uri, version: nil),
136-
contentChanges: [])
137+
contentChanges: [],
138+
forceRebuild: true)
137139
clangd.send(note)
138140
}
139141

Tests/INPUTS/GeneratedHeader/lib.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int /*libX:def*/libX(int value) {
2+
return value ? 22 : 0;
3+
}

Tests/INPUTS/GeneratedHeader/main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include "lib-generated.h"
2+
3+
int main(int argc, const char *argv[]) {
4+
return /*libX:call:main*/libX(argc);
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "sources": ["main.c", "lib.c"] }

Tests/SourceKitTests/SourceKitTests.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,52 @@ final class SKTests: XCTestCase {
235235
fatalError("error \(finished) waiting for post-build diagnostics notification")
236236
}
237237
}
238+
239+
func testDependenciesUpdatedCXXTibs() throws {
240+
guard let ws = try mutableSourceKitTibsTestWorkspace(name: "GeneratedHeader") else { return }
241+
guard let server = ws.testServer.server else {
242+
XCTFail("Unable to fetch SourceKitServer to notify for build system events.")
243+
return
244+
}
245+
246+
let moduleRef = ws.testLoc("libX:call:main")
247+
let startExpectation = XCTestExpectation(description: "initial diagnostics")
248+
ws.sk.handleNextNotification { (note: Notification<PublishDiagnosticsNotification>) in
249+
// Expect one error:
250+
// - Implicit declaration of function invalid
251+
XCTAssertEqual(note.params.diagnostics.count, 1)
252+
startExpectation.fulfill()
253+
}
254+
255+
let generatedHeaderURL = moduleRef.url.deletingLastPathComponent()
256+
.appendingPathComponent("lib-generated.h", isDirectory: false)
257+
258+
// Write an empty header file first since clangd doesn't handle missing header
259+
// files without a recently upstreamed extension.
260+
try "".write(to: generatedHeaderURL, atomically: true, encoding: .utf8)
261+
try ws.openDocument(moduleRef.url, language: .c)
262+
let started = XCTWaiter.wait(for: [startExpectation], timeout: 3)
263+
if started != .completed {
264+
fatalError("error \(started) waiting for initial diagnostics notification")
265+
}
266+
267+
// Update the header file to have the proper contents for our code to build.
268+
let contents = "int libX(int value);"
269+
try contents.write(to: generatedHeaderURL, atomically: true, encoding: .utf8)
270+
try ws.buildAndIndex()
271+
272+
let finishExpectation = XCTestExpectation(description: "post-build diagnostics")
273+
ws.sk.handleNextNotification { (note: Notification<PublishDiagnosticsNotification>) in
274+
// No more errors expected, import should resolve since we the generated header file
275+
// now has the proper contents.
276+
XCTAssertEqual(note.params.diagnostics.count, 0)
277+
finishExpectation.fulfill()
278+
}
279+
server.filesDependenciesUpdated([DocumentURI(moduleRef.url)])
280+
281+
let finished = XCTWaiter.wait(for: [finishExpectation], timeout: 3)
282+
if finished != .completed {
283+
fatalError("error \(finished) waiting for post-build diagnostics notification")
284+
}
285+
}
238286
}

Tests/SourceKitTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ extension SKTests {
140140
// to regenerate.
141141
static let __allTests__SKTests = [
142142
("testCodeCompleteSwiftTibs", testCodeCompleteSwiftTibs),
143+
("testDependenciesUpdatedCXXTibs", testDependenciesUpdatedCXXTibs),
143144
("testDependenciesUpdatedSwiftTibs", testDependenciesUpdatedSwiftTibs),
144145
("testIndexShutdown", testIndexShutdown),
145146
("testIndexSwiftModules", testIndexSwiftModules),

0 commit comments

Comments
 (0)