Skip to content

Commit 4e147e7

Browse files
authored
Merge pull request #20419 from ShoyuVanilla/flyck-gen
internal: Make flycheck generational
2 parents d307bc6 + fc2190f commit 4e147e7

File tree

4 files changed

+148
-38
lines changed

4 files changed

+148
-38
lines changed

crates/rust-analyzer/src/diagnostics.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,27 @@ pub struct DiagnosticsMapConfig {
2626

2727
pub(crate) type DiagnosticsGeneration = usize;
2828

29+
#[derive(Debug, Clone)]
30+
pub(crate) struct WorkspaceFlycheckDiagnostic {
31+
pub(crate) generation: DiagnosticsGeneration,
32+
pub(crate) per_package:
33+
FxHashMap<Option<Arc<PackageId>>, FxHashMap<FileId, Vec<lsp_types::Diagnostic>>>,
34+
}
35+
36+
impl WorkspaceFlycheckDiagnostic {
37+
fn new(generation: DiagnosticsGeneration) -> Self {
38+
WorkspaceFlycheckDiagnostic { generation, per_package: Default::default() }
39+
}
40+
}
41+
2942
#[derive(Debug, Default, Clone)]
3043
pub(crate) struct DiagnosticCollection {
3144
// FIXME: should be FxHashMap<FileId, Vec<ra_id::Diagnostic>>
3245
pub(crate) native_syntax:
3346
FxHashMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>,
3447
pub(crate) native_semantic:
3548
FxHashMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>,
36-
// FIXME: should be Vec<flycheck::Diagnostic>
37-
pub(crate) check:
38-
Vec<FxHashMap<Option<Arc<PackageId>>, FxHashMap<FileId, Vec<lsp_types::Diagnostic>>>>,
49+
pub(crate) check: Vec<WorkspaceFlycheckDiagnostic>,
3950
pub(crate) check_fixes: CheckFixes,
4051
changes: FxHashSet<FileId>,
4152
/// Counter for supplying a new generation number for diagnostics.
@@ -57,7 +68,7 @@ impl DiagnosticCollection {
5768
let Some(check) = self.check.get_mut(flycheck_id) else {
5869
return;
5970
};
60-
self.changes.extend(check.drain().flat_map(|(_, v)| v.into_keys()));
71+
self.changes.extend(check.per_package.drain().flat_map(|(_, v)| v.into_keys()));
6172
if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) {
6273
fixes.clear();
6374
}
@@ -66,7 +77,9 @@ impl DiagnosticCollection {
6677
pub(crate) fn clear_check_all(&mut self) {
6778
Arc::make_mut(&mut self.check_fixes).clear();
6879
self.changes.extend(
69-
self.check.iter_mut().flat_map(|it| it.drain().flat_map(|(_, v)| v.into_keys())),
80+
self.check
81+
.iter_mut()
82+
.flat_map(|it| it.per_package.drain().flat_map(|(_, v)| v.into_keys())),
7083
)
7184
}
7285

@@ -79,14 +92,24 @@ impl DiagnosticCollection {
7992
return;
8093
};
8194
let package_id = Some(package_id);
82-
if let Some(checks) = check.remove(&package_id) {
95+
if let Some(checks) = check.per_package.remove(&package_id) {
8396
self.changes.extend(checks.into_keys());
8497
}
8598
if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) {
8699
fixes.remove(&package_id);
87100
}
88101
}
89102

103+
pub(crate) fn clear_check_older_than(
104+
&mut self,
105+
flycheck_id: usize,
106+
generation: DiagnosticsGeneration,
107+
) {
108+
if self.check[flycheck_id].generation < generation {
109+
self.clear_check(flycheck_id);
110+
}
111+
}
112+
90113
pub(crate) fn clear_native_for(&mut self, file_id: FileId) {
91114
self.native_syntax.remove(&file_id);
92115
self.native_semantic.remove(&file_id);
@@ -96,15 +119,23 @@ impl DiagnosticCollection {
96119
pub(crate) fn add_check_diagnostic(
97120
&mut self,
98121
flycheck_id: usize,
122+
generation: DiagnosticsGeneration,
99123
package_id: &Option<Arc<PackageId>>,
100124
file_id: FileId,
101125
diagnostic: lsp_types::Diagnostic,
102126
fix: Option<Box<Fix>>,
103127
) {
104128
if self.check.len() <= flycheck_id {
105-
self.check.resize_with(flycheck_id + 1, Default::default);
129+
self.check
130+
.resize_with(flycheck_id + 1, || WorkspaceFlycheckDiagnostic::new(generation));
131+
}
132+
133+
// Getting message from old generation. Might happen in restarting checks.
134+
if self.check[flycheck_id].generation > generation {
135+
return;
106136
}
107137
let diagnostics = self.check[flycheck_id]
138+
.per_package
108139
.entry(package_id.clone())
109140
.or_default()
110141
.entry(file_id)
@@ -177,7 +208,7 @@ impl DiagnosticCollection {
177208
let check = self
178209
.check
179210
.iter()
180-
.flat_map(|it| it.values())
211+
.flat_map(|it| it.per_package.values())
181212
.filter_map(move |it| it.get(&file_id))
182213
.flatten();
183214
native_syntax.chain(native_semantic).chain(check)

crates/rust-analyzer/src/flycheck.rs

Lines changed: 88 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
//! Flycheck provides the functionality needed to run `cargo check` to provide
22
//! LSP diagnostics based on the output of the command.
33
4-
use std::{fmt, io, process::Command, time::Duration};
4+
use std::{
5+
fmt, io,
6+
process::Command,
7+
sync::atomic::{AtomicUsize, Ordering},
8+
time::Duration,
9+
};
510

611
use cargo_metadata::PackageId;
712
use crossbeam_channel::{Receiver, Sender, select_biased, unbounded};
@@ -18,7 +23,10 @@ pub(crate) use cargo_metadata::diagnostic::{
1823
use toolchain::Tool;
1924
use triomphe::Arc;
2025

21-
use crate::command::{CargoParser, CommandHandle};
26+
use crate::{
27+
command::{CargoParser, CommandHandle},
28+
diagnostics::DiagnosticsGeneration,
29+
};
2230

2331
#[derive(Clone, Debug, Default, PartialEq, Eq)]
2432
pub(crate) enum InvocationStrategy {
@@ -138,36 +146,54 @@ pub(crate) struct FlycheckHandle {
138146
sender: Sender<StateChange>,
139147
_thread: stdx::thread::JoinHandle,
140148
id: usize,
149+
generation: AtomicUsize,
141150
}
142151

143152
impl FlycheckHandle {
144153
pub(crate) fn spawn(
145154
id: usize,
155+
generation: DiagnosticsGeneration,
146156
sender: Sender<FlycheckMessage>,
147157
config: FlycheckConfig,
148158
sysroot_root: Option<AbsPathBuf>,
149159
workspace_root: AbsPathBuf,
150160
manifest_path: Option<AbsPathBuf>,
151161
) -> FlycheckHandle {
152-
let actor =
153-
FlycheckActor::new(id, sender, config, sysroot_root, workspace_root, manifest_path);
162+
let actor = FlycheckActor::new(
163+
id,
164+
generation,
165+
sender,
166+
config,
167+
sysroot_root,
168+
workspace_root,
169+
manifest_path,
170+
);
154171
let (sender, receiver) = unbounded::<StateChange>();
155172
let thread =
156173
stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker, format!("Flycheck{id}"))
157174
.spawn(move || actor.run(receiver))
158175
.expect("failed to spawn thread");
159-
FlycheckHandle { id, sender, _thread: thread }
176+
FlycheckHandle { id, generation: generation.into(), sender, _thread: thread }
160177
}
161178

162179
/// Schedule a re-start of the cargo check worker to do a workspace wide check.
163180
pub(crate) fn restart_workspace(&self, saved_file: Option<AbsPathBuf>) {
164-
self.sender.send(StateChange::Restart { package: None, saved_file, target: None }).unwrap();
181+
let generation = self.generation.fetch_add(1, Ordering::Relaxed) + 1;
182+
self.sender
183+
.send(StateChange::Restart { generation, package: None, saved_file, target: None })
184+
.unwrap();
165185
}
166186

167187
/// Schedule a re-start of the cargo check worker to do a package wide check.
168188
pub(crate) fn restart_for_package(&self, package: String, target: Option<Target>) {
189+
let generation = self.generation.fetch_add(1, Ordering::Relaxed) + 1;
169190
self.sender
170-
.send(StateChange::Restart { package: Some(package), saved_file: None, target })
191+
.send(StateChange::Restart {
192+
generation,
193+
package: Some(package),
194+
saved_file: None,
195+
target,
196+
})
171197
.unwrap();
172198
}
173199

@@ -179,23 +205,31 @@ impl FlycheckHandle {
179205
pub(crate) fn id(&self) -> usize {
180206
self.id
181207
}
208+
209+
pub(crate) fn generation(&self) -> DiagnosticsGeneration {
210+
self.generation.load(Ordering::Relaxed)
211+
}
212+
}
213+
214+
#[derive(Debug)]
215+
pub(crate) enum ClearDiagnosticsKind {
216+
All,
217+
OlderThan(DiagnosticsGeneration),
218+
Package(Arc<PackageId>),
182219
}
183220

184221
pub(crate) enum FlycheckMessage {
185222
/// Request adding a diagnostic with fixes included to a file
186223
AddDiagnostic {
187224
id: usize,
225+
generation: DiagnosticsGeneration,
188226
workspace_root: Arc<AbsPathBuf>,
189227
diagnostic: Diagnostic,
190228
package_id: Option<Arc<PackageId>>,
191229
},
192230

193231
/// Request clearing all outdated diagnostics.
194-
ClearDiagnostics {
195-
id: usize,
196-
/// The package whose diagnostics to clear, or if unspecified, all diagnostics.
197-
package_id: Option<Arc<PackageId>>,
198-
},
232+
ClearDiagnostics { id: usize, kind: ClearDiagnosticsKind },
199233

200234
/// Request check progress notification to client
201235
Progress {
@@ -208,18 +242,23 @@ pub(crate) enum FlycheckMessage {
208242
impl fmt::Debug for FlycheckMessage {
209243
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210244
match self {
211-
FlycheckMessage::AddDiagnostic { id, workspace_root, diagnostic, package_id } => f
245+
FlycheckMessage::AddDiagnostic {
246+
id,
247+
generation,
248+
workspace_root,
249+
diagnostic,
250+
package_id,
251+
} => f
212252
.debug_struct("AddDiagnostic")
213253
.field("id", id)
254+
.field("generation", generation)
214255
.field("workspace_root", workspace_root)
215256
.field("package_id", package_id)
216257
.field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
217258
.finish(),
218-
FlycheckMessage::ClearDiagnostics { id, package_id } => f
219-
.debug_struct("ClearDiagnostics")
220-
.field("id", id)
221-
.field("package_id", package_id)
222-
.finish(),
259+
FlycheckMessage::ClearDiagnostics { id, kind } => {
260+
f.debug_struct("ClearDiagnostics").field("id", id).field("kind", kind).finish()
261+
}
223262
FlycheckMessage::Progress { id, progress } => {
224263
f.debug_struct("Progress").field("id", id).field("progress", progress).finish()
225264
}
@@ -237,7 +276,12 @@ pub(crate) enum Progress {
237276
}
238277

239278
enum StateChange {
240-
Restart { package: Option<String>, saved_file: Option<AbsPathBuf>, target: Option<Target> },
279+
Restart {
280+
generation: DiagnosticsGeneration,
281+
package: Option<String>,
282+
saved_file: Option<AbsPathBuf>,
283+
target: Option<Target>,
284+
},
241285
Cancel,
242286
}
243287

@@ -246,6 +290,7 @@ struct FlycheckActor {
246290
/// The workspace id of this flycheck instance.
247291
id: usize,
248292

293+
generation: DiagnosticsGeneration,
249294
sender: Sender<FlycheckMessage>,
250295
config: FlycheckConfig,
251296
manifest_path: Option<AbsPathBuf>,
@@ -283,6 +328,7 @@ pub(crate) const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
283328
impl FlycheckActor {
284329
fn new(
285330
id: usize,
331+
generation: DiagnosticsGeneration,
286332
sender: Sender<FlycheckMessage>,
287333
config: FlycheckConfig,
288334
sysroot_root: Option<AbsPathBuf>,
@@ -292,6 +338,7 @@ impl FlycheckActor {
292338
tracing::info!(%id, ?workspace_root, "Spawning flycheck");
293339
FlycheckActor {
294340
id,
341+
generation,
295342
sender,
296343
config,
297344
sysroot_root,
@@ -327,7 +374,12 @@ impl FlycheckActor {
327374
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
328375
self.cancel_check_process();
329376
}
330-
Event::RequestStateChange(StateChange::Restart { package, saved_file, target }) => {
377+
Event::RequestStateChange(StateChange::Restart {
378+
generation,
379+
package,
380+
saved_file,
381+
target,
382+
}) => {
331383
// Cancel the previously spawned process
332384
self.cancel_check_process();
333385
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
@@ -337,6 +389,8 @@ impl FlycheckActor {
337389
}
338390
}
339391

392+
self.generation = generation;
393+
340394
let Some(command) =
341395
self.check_command(package.as_deref(), saved_file.as_deref(), target)
342396
else {
@@ -383,7 +437,16 @@ impl FlycheckActor {
383437
// Clear everything for good measure
384438
self.send(FlycheckMessage::ClearDiagnostics {
385439
id: self.id,
386-
package_id: None,
440+
kind: ClearDiagnosticsKind::All,
441+
});
442+
} else if res.is_ok() {
443+
// We clear diagnostics for packages on
444+
// `[CargoCheckMessage::CompilerArtifact]` but there seem to be setups where
445+
// cargo may not report an artifact to our runner at all. To handle such
446+
// cases, clear stale diagnostics when flycheck completes successfully.
447+
self.send(FlycheckMessage::ClearDiagnostics {
448+
id: self.id,
449+
kind: ClearDiagnosticsKind::OlderThan(self.generation),
387450
});
388451
}
389452
self.clear_diagnostics_state();
@@ -412,7 +475,7 @@ impl FlycheckActor {
412475
);
413476
self.send(FlycheckMessage::ClearDiagnostics {
414477
id: self.id,
415-
package_id: Some(package_id),
478+
kind: ClearDiagnosticsKind::Package(package_id),
416479
});
417480
}
418481
}
@@ -435,7 +498,7 @@ impl FlycheckActor {
435498
);
436499
self.send(FlycheckMessage::ClearDiagnostics {
437500
id: self.id,
438-
package_id: Some(package_id.clone()),
501+
kind: ClearDiagnosticsKind::Package(package_id.clone()),
439502
});
440503
}
441504
} else if self.diagnostics_received
@@ -444,11 +507,12 @@ impl FlycheckActor {
444507
self.diagnostics_received = DiagnosticsReceived::YesAndClearedForAll;
445508
self.send(FlycheckMessage::ClearDiagnostics {
446509
id: self.id,
447-
package_id: None,
510+
kind: ClearDiagnosticsKind::All,
448511
});
449512
}
450513
self.send(FlycheckMessage::AddDiagnostic {
451514
id: self.id,
515+
generation: self.generation,
452516
package_id,
453517
workspace_root: self.root.clone(),
454518
diagnostic,

0 commit comments

Comments
 (0)