Skip to content

Commit 0f44f03

Browse files
authored
Merge pull request #1526 from ahoppen/6.0/re-index
[6.0] Add a request to re-index all files in SourceKit-LSP
2 parents 970a755 + 0f8b6a5 commit 0f44f03

File tree

8 files changed

+185
-12
lines changed

8 files changed

+185
-12
lines changed

Documentation/LSP Extensions.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,3 +458,17 @@ New request that returns symbols for all the test classes and test methods withi
458458
```ts
459459
export interface WorkspaceTestsParams {}
460460
```
461+
462+
## `workspace/triggerReindex`
463+
464+
New request to re-index all files open in the SourceKit-LSP server.
465+
466+
Users should not need to rely on this request. The index should always be updated automatically in the background. Having to invoke this request means there is a bug in SourceKit-LSP's automatic re-indexing. It does, however, offer a workaround to re-index files when such a bug occurs where otherwise there would be no workaround.
467+
468+
469+
- params: `TriggerReindexParams`
470+
- result: `void`
471+
472+
```ts
473+
export interface TriggerReindexParams {}
474+
```

Sources/LanguageServerProtocol/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ add_library(LanguageServerProtocol STATIC
7777
Requests/ShutdownRequest.swift
7878
Requests/SignatureHelpRequest.swift
7979
Requests/SymbolInfoRequest.swift
80+
Requests/TriggerReindexRequest.swift
8081
Requests/TypeDefinitionRequest.swift
8182
Requests/TypeHierarchyPrepareRequest.swift
8283
Requests/TypeHierarchySubtypesRequest.swift
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Re-index all files open in the SourceKit-LSP server.
14+
///
15+
/// Users should not need to rely on this request. The index should always be updated automatically in the background.
16+
/// Having to invoke this request means there is a bug in SourceKit-LSP's automatic re-indexing. It does, however, offer
17+
/// a workaround to re-index files when such a bug occurs where otherwise there would be no workaround.
18+
///
19+
/// **LSP Extension**
20+
public struct TriggerReindexRequest: RequestType {
21+
public static let method: String = "workspace/triggerReindex"
22+
public typealias Response = VoidResponse
23+
24+
public init() {}
25+
}

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,15 +238,15 @@ public final actor SemanticIndexManager {
238238
/// Returns immediately after scheduling that task.
239239
///
240240
/// Indexing is being performed with a low priority.
241-
private func scheduleBackgroundIndex(files: some Collection<DocumentURI>) async {
242-
_ = await self.scheduleIndexing(of: files, priority: .low)
241+
private func scheduleBackgroundIndex(files: some Collection<DocumentURI>, indexFilesWithUpToDateUnit: Bool) async {
242+
_ = await self.scheduleIndexing(of: files, indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit, priority: .low)
243243
}
244244

245245
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
246246
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
247247
///
248248
/// This method is intended to initially update the index of a project after it is opened.
249-
public func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles() async {
249+
public func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: Bool = false) async {
250250
generateBuildGraphTask = Task(priority: .low) {
251251
await withLoggingSubsystemAndScope(subsystem: indexLoggingSubsystem, scope: "build-graph-generation") {
252252
logger.log(
@@ -270,16 +270,26 @@ public final actor SemanticIndexManager {
270270
// potentially not knowing about unit files, which causes the corresponding source files to be re-indexed.
271271
index.pollForUnitChangesAndWait()
272272
await testHooks.buildGraphGenerationDidFinish?()
273-
let index = index.checked(for: .modifiedFiles)
274-
let filesToIndex = await self.buildSystemManager.sourceFiles().lazy.map(\.uri)
275-
.filter { !index.hasUpToDateUnit(for: $0) }
276-
await scheduleBackgroundIndex(files: filesToIndex)
273+
var filesToIndex: any Collection<DocumentURI> = await self.buildSystemManager.sourceFiles().lazy.map(\.uri)
274+
if !indexFilesWithUpToDateUnit {
275+
let index = index.checked(for: .modifiedFiles)
276+
filesToIndex = filesToIndex.filter { !index.hasUpToDateUnit(for: $0) }
277+
}
278+
await scheduleBackgroundIndex(files: filesToIndex, indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit)
277279
generateBuildGraphTask = nil
278280
}
279281
}
280282
indexProgressStatusDidChange()
281283
}
282284

285+
/// Causes all files to be re-indexed even if the unit file for the source file is up to date.
286+
/// See `TriggerReindexRequest`.
287+
public func scheduleReindex() async {
288+
await indexStoreUpToDateTracker.markAllKnownOutOfDate()
289+
await preparationUpToDateTracker.markAllKnownOutOfDate()
290+
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: true)
291+
}
292+
283293
/// Wait for all in-progress index tasks to finish.
284294
public func waitForUpToDateIndex() async {
285295
logger.info("Waiting for up-to-date index")
@@ -319,7 +329,7 @@ public final actor SemanticIndexManager {
319329
// Create a new index task for the files that aren't up-to-date. The newly scheduled index tasks will
320330
// - Wait for the existing index operations to finish if they have the same number of files.
321331
// - Reschedule the background index task in favor of an index task with fewer source files.
322-
await self.scheduleIndexing(of: uris, priority: nil).value
332+
await self.scheduleIndexing(of: uris, indexFilesWithUpToDateUnit: false, priority: nil).value
323333
index.pollForUnitChangesAndWait()
324334
logger.debug("Done waiting for up-to-date index")
325335
}
@@ -354,7 +364,7 @@ public final actor SemanticIndexManager {
354364
await preparationUpToDateTracker.markOutOfDate(inProgressPreparationTasks.keys)
355365
}
356366

357-
await scheduleBackgroundIndex(files: changedFiles)
367+
await scheduleBackgroundIndex(files: changedFiles, indexFilesWithUpToDateUnit: false)
358368
}
359369

360370
/// Returns the files that should be indexed to get up-to-date index information for the given files.
@@ -507,6 +517,7 @@ public final actor SemanticIndexManager {
507517
/// Update the index store for the given files, assuming that their targets have already been prepared.
508518
private func updateIndexStore(
509519
for filesAndTargets: [FileAndTarget],
520+
indexFilesWithUpToDateUnit: Bool,
510521
preparationTaskID: UUID,
511522
priority: TaskPriority?
512523
) async {
@@ -516,6 +527,7 @@ public final actor SemanticIndexManager {
516527
buildSystemManager: self.buildSystemManager,
517528
index: index,
518529
indexStoreUpToDateTracker: indexStoreUpToDateTracker,
530+
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
519531
logMessageToIndexLog: logMessageToIndexLog,
520532
timeout: updateIndexStoreTimeout,
521533
testHooks: testHooks
@@ -553,6 +565,7 @@ public final actor SemanticIndexManager {
553565
/// The returned task finishes when all files are indexed.
554566
private func scheduleIndexing(
555567
of files: some Collection<DocumentURI>,
568+
indexFilesWithUpToDateUnit: Bool,
556569
priority: TaskPriority?
557570
) async -> Task<Void, Never> {
558571
// Perform a quick initial check to whether the files is up-to-date, in which case we don't need to schedule a
@@ -627,6 +640,7 @@ public final actor SemanticIndexManager {
627640
taskGroup.addTask {
628641
await self.updateIndexStore(
629642
for: fileBatch.map { FileAndTarget(file: $0, target: target) },
643+
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
630644
preparationTaskID: preparationTaskID,
631645
priority: priority
632646
)

Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,18 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
106106
/// The build system manager that is used to get the toolchain and build settings for the files to index.
107107
private let buildSystemManager: BuildSystemManager
108108

109-
private let indexStoreUpToDateTracker: UpToDateTracker<DocumentURI>
110-
111109
/// A reference to the underlying index store. Used to check if the index is already up-to-date for a file, in which
112110
/// case we don't need to index it again.
113111
private let index: UncheckedIndex
114112

113+
private let indexStoreUpToDateTracker: UpToDateTracker<DocumentURI>
114+
115+
/// Whether files that have an up-to-date unit file should be indexed.
116+
///
117+
/// In general, this should be `false`. The only situation when this should be set to `true` is when the user
118+
/// explicitly requested a re-index of all files.
119+
private let indexFilesWithUpToDateUnit: Bool
120+
115121
/// See `SemanticIndexManager.logMessageToIndexLog`.
116122
private let logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
117123

@@ -145,6 +151,7 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
145151
buildSystemManager: BuildSystemManager,
146152
index: UncheckedIndex,
147153
indexStoreUpToDateTracker: UpToDateTracker<DocumentURI>,
154+
indexFilesWithUpToDateUnit: Bool,
148155
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void,
149156
timeout: Duration,
150157
testHooks: IndexTestHooks
@@ -153,6 +160,7 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
153160
self.buildSystemManager = buildSystemManager
154161
self.index = index
155162
self.indexStoreUpToDateTracker = indexStoreUpToDateTracker
163+
self.indexFilesWithUpToDateUnit = indexFilesWithUpToDateUnit
156164
self.logMessageToIndexLog = logMessageToIndexLog
157165
self.timeout = timeout
158166
self.testHooks = testHooks
@@ -214,7 +222,9 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
214222
// If we know that the file is up-to-date without having ot hit the index, do that because it's fastest.
215223
return
216224
}
217-
guard !index.checked(for: .modifiedFiles).hasUpToDateUnit(for: file.sourceFile, mainFile: file.mainFile)
225+
guard
226+
indexFilesWithUpToDateUnit
227+
|| !index.checked(for: .modifiedFiles).hasUpToDateUnit(for: file.sourceFile, mainFile: file.mainFile)
218228
else {
219229
logger.debug("Not indexing \(file.forLogging) because index has an up-to-date unit")
220230
// We consider a file's index up-to-date if we have any up-to-date unit. Changing build settings does not

Sources/SourceKitLSP/MessageHandlingDependencyTracker.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ enum MessageHandlingDependencyTracker: DependencyTracker {
199199
self = .freestanding
200200
case is ShutdownRequest:
201201
self = .globalConfigurationChange
202+
case is TriggerReindexRequest:
203+
self = .globalConfigurationChange
202204
case is TypeHierarchySubtypesRequest:
203205
self = .freestanding
204206
case is TypeHierarchySupertypesRequest:

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,8 @@ extension SourceKitLSPServer: MessageHandler {
762762
await self.handleRequest(for: request, requestHandler: self.prepareRename)
763763
case let request as RequestAndReply<IndexedRenameRequest>:
764764
await self.handleRequest(for: request, requestHandler: self.indexedRename)
765+
case let request as RequestAndReply<TriggerReindexRequest>:
766+
await request.reply { try await triggerReindex(request.params) }
765767
// IMPORTANT: When adding a new entry to this switch, also add it to the `MessageHandlingDependencyTracker` initializer.
766768
default:
767769
await request.reply { throw ResponseError.methodNotFound(R.method) }
@@ -2340,6 +2342,13 @@ extension SourceKitLSPServer {
23402342
}
23412343
return VoidResponse()
23422344
}
2345+
2346+
func triggerReindex(_ req: TriggerReindexRequest) async throws -> VoidResponse {
2347+
for workspace in workspaces {
2348+
await workspace.semanticIndexManager?.scheduleReindex()
2349+
}
2350+
return VoidResponse()
2351+
}
23432352
}
23442353

23452354
private func languageClass(for language: Language) -> [Language] {

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,104 @@ final class BackgroundIndexingTests: XCTestCase {
12751275
// also testing that we don't wait for type checking of Test.swift to finish.
12761276
XCTAssert(Date().timeIntervalSince(dateStarted) < 30)
12771277
}
1278+
1279+
func testManualReindex() async throws {
1280+
// This test relies on the issue described in https://github.com/apple/sourcekit-lsp/issues/1264 that we don't
1281+
// re-index dependent files if a function of a low-level module gains a new default parameter, which changes the
1282+
// function's USR but is API compatible with all dependencies.
1283+
// Check that after running the re-index request, the index gets updated.
1284+
1285+
let project = try await SwiftPMTestProject(
1286+
files: [
1287+
"LibA/LibA.swift": """
1288+
public func 1️⃣getInt() -> Int {
1289+
return 1
1290+
}
1291+
""",
1292+
"LibB/LibB.swift": """
1293+
import LibA
1294+
1295+
public func 2️⃣test() -> Int {
1296+
return 3️⃣getInt()
1297+
}
1298+
""",
1299+
],
1300+
manifest: """
1301+
let package = Package(
1302+
name: "MyLibrary",
1303+
targets: [
1304+
.target(name: "LibA"),
1305+
.target(name: "LibB", dependencies: ["LibA"]),
1306+
]
1307+
)
1308+
""",
1309+
enableBackgroundIndexing: true
1310+
)
1311+
1312+
let expectedCallHierarchyItem = CallHierarchyIncomingCall(
1313+
from: CallHierarchyItem(
1314+
name: "test()",
1315+
kind: .function,
1316+
tags: nil,
1317+
uri: try project.uri(for: "LibB.swift"),
1318+
range: try project.range(from: "2️⃣", to: "2️⃣", in: "LibB.swift"),
1319+
selectionRange: try project.range(from: "2️⃣", to: "2️⃣", in: "LibB.swift"),
1320+
data: .dictionary([
1321+
"usr": .string("s:4LibB4testSiyF"),
1322+
"uri": .string(try project.uri(for: "LibB.swift").stringValue),
1323+
])
1324+
),
1325+
fromRanges: [try project.range(from: "3️⃣", to: "3️⃣", in: "LibB.swift")]
1326+
)
1327+
1328+
/// Start by making a call hierarchy request to check that we get the expected results without any edits.
1329+
let (uri, positions) = try project.openDocument("LibA.swift")
1330+
let prepareBeforeUpdate = try await project.testClient.send(
1331+
CallHierarchyPrepareRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
1332+
)
1333+
let callHierarchyBeforeUpdate = try await project.testClient.send(
1334+
CallHierarchyIncomingCallsRequest(item: XCTUnwrap(prepareBeforeUpdate?.only))
1335+
)
1336+
XCTAssertEqual(callHierarchyBeforeUpdate, [expectedCallHierarchyItem])
1337+
1338+
// Now add a new default parameter to `getInt`.
1339+
project.testClient.send(DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(uri)))
1340+
let newLibAContents = """
1341+
public func getInt(value: Int = 1) -> Int {
1342+
return value
1343+
}
1344+
"""
1345+
try newLibAContents.write(to: XCTUnwrap(uri.fileURL), atomically: true, encoding: .utf8)
1346+
project.testClient.send(
1347+
DidOpenTextDocumentNotification(
1348+
textDocument: TextDocumentItem(uri: uri, language: .swift, version: 0, text: newLibAContents)
1349+
)
1350+
)
1351+
project.testClient.send(DidChangeWatchedFilesNotification(changes: [FileEvent(uri: uri, type: .changed)]))
1352+
_ = try await project.testClient.send(PollIndexRequest())
1353+
1354+
// The USR of `getInt` has changed but LibB.swift has not been re-indexed due to
1355+
// https://github.com/apple/sourcekit-lsp/issues/1264. We expect to get an empty call hierarchy.
1356+
let prepareAfterUpdate = try await project.testClient.send(
1357+
CallHierarchyPrepareRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
1358+
)
1359+
let callHierarchyAfterUpdate = try await project.testClient.send(
1360+
CallHierarchyIncomingCallsRequest(item: XCTUnwrap(prepareAfterUpdate?.only))
1361+
)
1362+
XCTAssertEqual(callHierarchyAfterUpdate, [])
1363+
1364+
// After re-indexing, we expect to get a full call hierarchy again.
1365+
_ = try await project.testClient.send(TriggerReindexRequest())
1366+
_ = try await project.testClient.send(PollIndexRequest())
1367+
1368+
let prepareAfterReindex = try await project.testClient.send(
1369+
CallHierarchyPrepareRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
1370+
)
1371+
let callHierarchyAfterReindex = try await project.testClient.send(
1372+
CallHierarchyIncomingCallsRequest(item: XCTUnwrap(prepareAfterReindex?.only))
1373+
)
1374+
XCTAssertEqual(callHierarchyAfterReindex, [expectedCallHierarchyItem])
1375+
}
12781376
}
12791377

12801378
extension HoverResponseContents {

0 commit comments

Comments
 (0)