Skip to content

Commit 86be2a4

Browse files
committed
Centralize up-to-date check in SemanticIndexManager
We had checks for whether we need to index a file or if it is up-to-date in multiple places of `SemanticIndexManager`. Centralize them in `filesToCover(toIndex:)`, so that it’s easier to reason about them.
1 parent 7882228 commit 86be2a4

File tree

1 file changed

+62
-56
lines changed

1 file changed

+62
-56
lines changed

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 62 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -255,20 +255,6 @@ package final actor SemanticIndexManager {
255255
self.indexProgressStatusDidChange = indexProgressStatusDidChange
256256
}
257257

258-
/// Schedules a task to index `files`. Files that are known to be up-to-date based on `indexStatus` will
259-
/// not be re-indexed. The method will re-index files even if they have a unit with a timestamp that matches the
260-
/// source file's mtime. This allows re-indexing eg. after compiler arguments or dependencies have changed.
261-
///
262-
/// Returns immediately after scheduling that task.
263-
///
264-
/// Indexing is being performed with a low priority.
265-
private func scheduleBackgroundIndex(
266-
files: some Collection<DocumentURI> & Sendable,
267-
indexFilesWithUpToDateUnit: Bool
268-
) async {
269-
_ = await self.scheduleIndexing(of: files, indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit, priority: .low)
270-
}
271-
272258
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
273259
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
274260
///
@@ -295,7 +281,7 @@ package final actor SemanticIndexManager {
295281
await hooks.buildGraphGenerationDidFinish?()
296282
// TODO: Ideally this would be a type like any Collection<DocumentURI> & Sendable but that doesn't work due to
297283
// https://github.com/swiftlang/swift/issues/75602
298-
var filesToIndex: [DocumentURI] =
284+
let filesToIndex: [DocumentURI] =
299285
if let filesToIndex {
300286
filesToIndex
301287
} else {
@@ -304,21 +290,11 @@ package final actor SemanticIndexManager {
304290
.sorted { $0.stringValue < $1.stringValue }
305291
} ?? []
306292
}
307-
if !indexFilesWithUpToDateUnit {
308-
let index = index.checked(for: .modifiedFiles)
309-
filesToIndex = filesToIndex.filter {
310-
if index.hasUpToDateUnit(for: $0) {
311-
return false
312-
}
313-
if case .waitingForPreparation = inProgressIndexTasks[$0] {
314-
// We haven't started preparing the file yet. Scheduling a new index operation for it won't produce any
315-
// more recent results.
316-
return false
317-
}
318-
return true
319-
}
320-
}
321-
await scheduleBackgroundIndex(files: filesToIndex, indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit)
293+
_ = await self.scheduleIndexing(
294+
of: filesToIndex,
295+
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
296+
priority: .low
297+
)
322298
scheduleIndexingTasks[taskId] = nil
323299
}
324300
}
@@ -421,33 +397,60 @@ package final actor SemanticIndexManager {
421397
///
422398
/// If `files` contains a header file, this will return a `FileToIndex` that re-indexes a main file which includes the
423399
/// header file to update the header file's index.
400+
///
401+
/// The returned results will not include files that are known to be up-to-date based on `indexStoreUpToDateTracker`
402+
/// or the unit timestamp will not be re-indexed unless `indexFilesWithUpToDateUnit` is `true`.
424403
private func filesToIndex(
425-
toCover files: some Collection<DocumentURI> & Sendable
404+
toCover files: some Collection<DocumentURI> & Sendable,
405+
indexFilesWithUpToDateUnits: Bool
426406
) async -> [FileToIndex] {
427407
let sourceFiles = await orLog("Getting source files in project") {
428408
Set(try await buildSystemManager.sourceFiles(includeNonBuildableFiles: false).keys)
429409
}
430410
guard let sourceFiles else {
431411
return []
432412
}
433-
let filesToReIndex = await files.asyncCompactMap { (uri) -> FileToIndex? in
434-
if sourceFiles.contains(uri) {
435-
// If this is a source file, just index it.
436-
return .indexableFile(uri)
437-
}
438-
// Otherwise, see if it is a header file. If so, index a main file that that imports it to update header file's
439-
// index.
440-
// Deterministically pick a main file. This ensures that we always pick the same main file for a header. This way,
441-
// if we request the same header to be indexed twice, we'll pick the same unit file the second time around,
442-
// realize that its timestamp is later than the modification date of the header and we don't need to re-index.
443-
let mainFile = index.checked(for: .deletedFiles)
444-
.mainFilesContainingFile(uri: uri, crossLanguage: false)
445-
.sorted(by: { $0.stringValue < $1.stringValue }).first
446-
guard let mainFile else {
447-
return nil
413+
let deletedFilesIndex = index.checked(for: .deletedFiles)
414+
let modifiedFilesIndex = index.checked(for: .modifiedFiles)
415+
let filesToReIndex =
416+
await files
417+
.asyncFilter {
418+
// First, check if we know that the file is up-to-date, in which case we don't need to hit the index or file
419+
// system at all
420+
if !indexFilesWithUpToDateUnits, await indexStoreUpToDateTracker.isUpToDate($0) {
421+
return false
422+
}
423+
if case .waitingForPreparation = inProgressIndexTasks[$0] {
424+
// We haven't started preparing the file yet. Scheduling a new index operation for it won't produce any
425+
// more recent results.
426+
return false
427+
}
428+
return true
429+
}.compactMap { (uri) -> FileToIndex? in
430+
if sourceFiles.contains(uri) {
431+
if !indexFilesWithUpToDateUnits, modifiedFilesIndex.hasUpToDateUnit(for: uri) {
432+
return nil
433+
}
434+
// If this is a source file, just index it.
435+
return .indexableFile(uri)
436+
}
437+
// Otherwise, see if it is a header file. If so, index a main file that that imports it to update header file's
438+
// index.
439+
// Deterministically pick a main file. This ensures that we always pick the same main file for a header. This way,
440+
// if we request the same header to be indexed twice, we'll pick the same unit file the second time around,
441+
// realize that its timestamp is later than the modification date of the header and we don't need to re-index.
442+
let mainFile =
443+
deletedFilesIndex
444+
.mainFilesContainingFile(uri: uri, crossLanguage: false)
445+
.sorted(by: { $0.stringValue < $1.stringValue }).first
446+
guard let mainFile else {
447+
return nil
448+
}
449+
if !indexFilesWithUpToDateUnits, modifiedFilesIndex.hasUpToDateUnit(for: uri, mainFile: mainFile) {
450+
return nil
451+
}
452+
return .headerFile(header: uri, mainFile: mainFile)
448453
}
449-
return .headerFile(header: uri, mainFile: mainFile)
450-
}
451454
return filesToReIndex
452455
}
453456

@@ -652,7 +655,9 @@ package final actor SemanticIndexManager {
652655
return await updateIndexTask.waitToFinishPropagatingCancellation()
653656
}
654657

655-
/// Index the given set of files at the given priority, preparing their targets beforehand, if needed.
658+
/// Index the given set of files at the given priority, preparing their targets beforehand, if needed. Files that are
659+
/// known to be up-to-date based on `indexStoreUpToDateTracker` or the unit timestamp will not be re-indexed unless
660+
/// `indexFilesWithUpToDateUnit` is `true`.
656661
///
657662
/// The returned task finishes when all files are indexed.
658663
private func scheduleIndexing(
@@ -665,14 +670,15 @@ package final actor SemanticIndexManager {
665670
// We will check the up-to-date status again in `IndexTaskDescription.execute`. This ensures that if we schedule
666671
// schedule two indexing jobs for the same file in quick succession, only the first one actually updates the index
667672
// store and the second one will be a no-op once it runs.
668-
let outOfDateFiles = await filesToIndex(toCover: files).asyncFilter {
669-
if await indexStoreUpToDateTracker.isUpToDate($0.sourceFile) {
670-
return false
671-
}
672-
return true
673+
let outOfDateFiles = await filesToIndex(toCover: files, indexFilesWithUpToDateUnits: indexFilesWithUpToDateUnit)
674+
// sort files to get deterministic indexing order
675+
.sorted(by: { $0.sourceFile.stringValue < $1.sourceFile.stringValue })
676+
677+
if outOfDateFiles.isEmpty {
678+
// Early exit if there are no files to index.
679+
return Task {}
673680
}
674-
// sort files to get deterministic indexing order
675-
.sorted(by: { $0.sourceFile.stringValue < $1.sourceFile.stringValue })
681+
676682
logger.debug("Scheduling indexing of \(outOfDateFiles.map(\.sourceFile.stringValue).joined(separator: ", "))")
677683

678684
// Sort the targets in topological order so that low-level targets get built before high-level targets, allowing us

0 commit comments

Comments
 (0)