Merge pull request #20419 from ShoyuVanilla/flyck-gen
Some checks are pending
metrics / other_metrics (diesel-1.4.8) (push) Blocked by required conditions
metrics / build_metrics (push) Waiting to run
metrics / other_metrics (hyper-0.14.18) (push) Blocked by required conditions
metrics / other_metrics (ripgrep-13.0.0) (push) Blocked by required conditions
metrics / other_metrics (self) (push) Blocked by required conditions
metrics / other_metrics (webrender-2022) (push) Blocked by required conditions
metrics / generate_final_metrics (push) Blocked by required conditions
rustdoc / rustdoc (push) Waiting to run
release / dist (x86_64-apple-darwin) (push) Waiting to run
release / dist (aarch64-unknown-linux-gnu) (push) Waiting to run
autopublish / publish (push) Waiting to run
release / dist (aarch64-apple-darwin) (push) Waiting to run
release / dist (arm-unknown-linux-gnueabihf) (push) Waiting to run
release / dist (x86_64-unknown-linux-gnu) (push) Waiting to run
release / dist (aarch64-pc-windows-msvc) (push) Waiting to run
release / dist (x86_64-pc-windows-msvc) (push) Waiting to run
release / dist (i686-pc-windows-msvc) (push) Waiting to run
release / dist (x86_64-unknown-linux-musl) (push) Waiting to run
release / publish (push) Blocked by required conditions

internal: Make flycheck generational
This commit is contained in:
Shoyu Vanilla (Flint) 2025-08-10 14:00:52 +00:00 committed by GitHub
commit 4e147e7879
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 148 additions and 38 deletions

View file

@ -26,6 +26,19 @@ pub struct DiagnosticsMapConfig {
pub(crate) type DiagnosticsGeneration = usize; pub(crate) type DiagnosticsGeneration = usize;
#[derive(Debug, Clone)]
pub(crate) struct WorkspaceFlycheckDiagnostic {
pub(crate) generation: DiagnosticsGeneration,
pub(crate) per_package:
FxHashMap<Option<Arc<PackageId>>, FxHashMap<FileId, Vec<lsp_types::Diagnostic>>>,
}
impl WorkspaceFlycheckDiagnostic {
fn new(generation: DiagnosticsGeneration) -> Self {
WorkspaceFlycheckDiagnostic { generation, per_package: Default::default() }
}
}
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub(crate) struct DiagnosticCollection { pub(crate) struct DiagnosticCollection {
// FIXME: should be FxHashMap<FileId, Vec<ra_id::Diagnostic>> // FIXME: should be FxHashMap<FileId, Vec<ra_id::Diagnostic>>
@ -33,9 +46,7 @@ pub(crate) struct DiagnosticCollection {
FxHashMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>, FxHashMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>,
pub(crate) native_semantic: pub(crate) native_semantic:
FxHashMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>, FxHashMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>,
// FIXME: should be Vec<flycheck::Diagnostic> pub(crate) check: Vec<WorkspaceFlycheckDiagnostic>,
pub(crate) check:
Vec<FxHashMap<Option<Arc<PackageId>>, FxHashMap<FileId, Vec<lsp_types::Diagnostic>>>>,
pub(crate) check_fixes: CheckFixes, pub(crate) check_fixes: CheckFixes,
changes: FxHashSet<FileId>, changes: FxHashSet<FileId>,
/// Counter for supplying a new generation number for diagnostics. /// Counter for supplying a new generation number for diagnostics.
@ -57,7 +68,7 @@ impl DiagnosticCollection {
let Some(check) = self.check.get_mut(flycheck_id) else { let Some(check) = self.check.get_mut(flycheck_id) else {
return; return;
}; };
self.changes.extend(check.drain().flat_map(|(_, v)| v.into_keys())); self.changes.extend(check.per_package.drain().flat_map(|(_, v)| v.into_keys()));
if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) { if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) {
fixes.clear(); fixes.clear();
} }
@ -66,7 +77,9 @@ impl DiagnosticCollection {
pub(crate) fn clear_check_all(&mut self) { pub(crate) fn clear_check_all(&mut self) {
Arc::make_mut(&mut self.check_fixes).clear(); Arc::make_mut(&mut self.check_fixes).clear();
self.changes.extend( self.changes.extend(
self.check.iter_mut().flat_map(|it| it.drain().flat_map(|(_, v)| v.into_keys())), self.check
.iter_mut()
.flat_map(|it| it.per_package.drain().flat_map(|(_, v)| v.into_keys())),
) )
} }
@ -79,7 +92,7 @@ impl DiagnosticCollection {
return; return;
}; };
let package_id = Some(package_id); let package_id = Some(package_id);
if let Some(checks) = check.remove(&package_id) { if let Some(checks) = check.per_package.remove(&package_id) {
self.changes.extend(checks.into_keys()); self.changes.extend(checks.into_keys());
} }
if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) { if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) {
@ -87,6 +100,16 @@ impl DiagnosticCollection {
} }
} }
pub(crate) fn clear_check_older_than(
&mut self,
flycheck_id: usize,
generation: DiagnosticsGeneration,
) {
if self.check[flycheck_id].generation < generation {
self.clear_check(flycheck_id);
}
}
pub(crate) fn clear_native_for(&mut self, file_id: FileId) { pub(crate) fn clear_native_for(&mut self, file_id: FileId) {
self.native_syntax.remove(&file_id); self.native_syntax.remove(&file_id);
self.native_semantic.remove(&file_id); self.native_semantic.remove(&file_id);
@ -96,15 +119,23 @@ impl DiagnosticCollection {
pub(crate) fn add_check_diagnostic( pub(crate) fn add_check_diagnostic(
&mut self, &mut self,
flycheck_id: usize, flycheck_id: usize,
generation: DiagnosticsGeneration,
package_id: &Option<Arc<PackageId>>, package_id: &Option<Arc<PackageId>>,
file_id: FileId, file_id: FileId,
diagnostic: lsp_types::Diagnostic, diagnostic: lsp_types::Diagnostic,
fix: Option<Box<Fix>>, fix: Option<Box<Fix>>,
) { ) {
if self.check.len() <= flycheck_id { if self.check.len() <= flycheck_id {
self.check.resize_with(flycheck_id + 1, Default::default); self.check
.resize_with(flycheck_id + 1, || WorkspaceFlycheckDiagnostic::new(generation));
}
// Getting message from old generation. Might happen in restarting checks.
if self.check[flycheck_id].generation > generation {
return;
} }
let diagnostics = self.check[flycheck_id] let diagnostics = self.check[flycheck_id]
.per_package
.entry(package_id.clone()) .entry(package_id.clone())
.or_default() .or_default()
.entry(file_id) .entry(file_id)
@ -177,7 +208,7 @@ impl DiagnosticCollection {
let check = self let check = self
.check .check
.iter() .iter()
.flat_map(|it| it.values()) .flat_map(|it| it.per_package.values())
.filter_map(move |it| it.get(&file_id)) .filter_map(move |it| it.get(&file_id))
.flatten(); .flatten();
native_syntax.chain(native_semantic).chain(check) native_syntax.chain(native_semantic).chain(check)

View file

@ -1,7 +1,12 @@
//! Flycheck provides the functionality needed to run `cargo check` to provide //! Flycheck provides the functionality needed to run `cargo check` to provide
//! LSP diagnostics based on the output of the command. //! LSP diagnostics based on the output of the command.
use std::{fmt, io, process::Command, time::Duration}; use std::{
fmt, io,
process::Command,
sync::atomic::{AtomicUsize, Ordering},
time::Duration,
};
use cargo_metadata::PackageId; use cargo_metadata::PackageId;
use crossbeam_channel::{Receiver, Sender, select_biased, unbounded}; use crossbeam_channel::{Receiver, Sender, select_biased, unbounded};
@ -18,7 +23,10 @@ pub(crate) use cargo_metadata::diagnostic::{
use toolchain::Tool; use toolchain::Tool;
use triomphe::Arc; use triomphe::Arc;
use crate::command::{CargoParser, CommandHandle}; use crate::{
command::{CargoParser, CommandHandle},
diagnostics::DiagnosticsGeneration,
};
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) enum InvocationStrategy { pub(crate) enum InvocationStrategy {
@ -138,36 +146,54 @@ pub(crate) struct FlycheckHandle {
sender: Sender<StateChange>, sender: Sender<StateChange>,
_thread: stdx::thread::JoinHandle, _thread: stdx::thread::JoinHandle,
id: usize, id: usize,
generation: AtomicUsize,
} }
impl FlycheckHandle { impl FlycheckHandle {
pub(crate) fn spawn( pub(crate) fn spawn(
id: usize, id: usize,
generation: DiagnosticsGeneration,
sender: Sender<FlycheckMessage>, sender: Sender<FlycheckMessage>,
config: FlycheckConfig, config: FlycheckConfig,
sysroot_root: Option<AbsPathBuf>, sysroot_root: Option<AbsPathBuf>,
workspace_root: AbsPathBuf, workspace_root: AbsPathBuf,
manifest_path: Option<AbsPathBuf>, manifest_path: Option<AbsPathBuf>,
) -> FlycheckHandle { ) -> FlycheckHandle {
let actor = let actor = FlycheckActor::new(
FlycheckActor::new(id, sender, config, sysroot_root, workspace_root, manifest_path); id,
generation,
sender,
config,
sysroot_root,
workspace_root,
manifest_path,
);
let (sender, receiver) = unbounded::<StateChange>(); let (sender, receiver) = unbounded::<StateChange>();
let thread = let thread =
stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker, format!("Flycheck{id}")) stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker, format!("Flycheck{id}"))
.spawn(move || actor.run(receiver)) .spawn(move || actor.run(receiver))
.expect("failed to spawn thread"); .expect("failed to spawn thread");
FlycheckHandle { id, sender, _thread: thread } FlycheckHandle { id, generation: generation.into(), sender, _thread: thread }
} }
/// Schedule a re-start of the cargo check worker to do a workspace wide check. /// Schedule a re-start of the cargo check worker to do a workspace wide check.
pub(crate) fn restart_workspace(&self, saved_file: Option<AbsPathBuf>) { pub(crate) fn restart_workspace(&self, saved_file: Option<AbsPathBuf>) {
self.sender.send(StateChange::Restart { package: None, saved_file, target: None }).unwrap(); let generation = self.generation.fetch_add(1, Ordering::Relaxed) + 1;
self.sender
.send(StateChange::Restart { generation, package: None, saved_file, target: None })
.unwrap();
} }
/// Schedule a re-start of the cargo check worker to do a package wide check. /// Schedule a re-start of the cargo check worker to do a package wide check.
pub(crate) fn restart_for_package(&self, package: String, target: Option<Target>) { pub(crate) fn restart_for_package(&self, package: String, target: Option<Target>) {
let generation = self.generation.fetch_add(1, Ordering::Relaxed) + 1;
self.sender self.sender
.send(StateChange::Restart { package: Some(package), saved_file: None, target }) .send(StateChange::Restart {
generation,
package: Some(package),
saved_file: None,
target,
})
.unwrap(); .unwrap();
} }
@ -179,23 +205,31 @@ impl FlycheckHandle {
pub(crate) fn id(&self) -> usize { pub(crate) fn id(&self) -> usize {
self.id self.id
} }
pub(crate) fn generation(&self) -> DiagnosticsGeneration {
self.generation.load(Ordering::Relaxed)
}
}
#[derive(Debug)]
pub(crate) enum ClearDiagnosticsKind {
All,
OlderThan(DiagnosticsGeneration),
Package(Arc<PackageId>),
} }
pub(crate) enum FlycheckMessage { pub(crate) enum FlycheckMessage {
/// Request adding a diagnostic with fixes included to a file /// Request adding a diagnostic with fixes included to a file
AddDiagnostic { AddDiagnostic {
id: usize, id: usize,
generation: DiagnosticsGeneration,
workspace_root: Arc<AbsPathBuf>, workspace_root: Arc<AbsPathBuf>,
diagnostic: Diagnostic, diagnostic: Diagnostic,
package_id: Option<Arc<PackageId>>, package_id: Option<Arc<PackageId>>,
}, },
/// Request clearing all outdated diagnostics. /// Request clearing all outdated diagnostics.
ClearDiagnostics { ClearDiagnostics { id: usize, kind: ClearDiagnosticsKind },
id: usize,
/// The package whose diagnostics to clear, or if unspecified, all diagnostics.
package_id: Option<Arc<PackageId>>,
},
/// Request check progress notification to client /// Request check progress notification to client
Progress { Progress {
@ -208,18 +242,23 @@ pub(crate) enum FlycheckMessage {
impl fmt::Debug for FlycheckMessage { impl fmt::Debug for FlycheckMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
FlycheckMessage::AddDiagnostic { id, workspace_root, diagnostic, package_id } => f FlycheckMessage::AddDiagnostic {
id,
generation,
workspace_root,
diagnostic,
package_id,
} => f
.debug_struct("AddDiagnostic") .debug_struct("AddDiagnostic")
.field("id", id) .field("id", id)
.field("generation", generation)
.field("workspace_root", workspace_root) .field("workspace_root", workspace_root)
.field("package_id", package_id) .field("package_id", package_id)
.field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code)) .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
.finish(), .finish(),
FlycheckMessage::ClearDiagnostics { id, package_id } => f FlycheckMessage::ClearDiagnostics { id, kind } => {
.debug_struct("ClearDiagnostics") f.debug_struct("ClearDiagnostics").field("id", id).field("kind", kind).finish()
.field("id", id) }
.field("package_id", package_id)
.finish(),
FlycheckMessage::Progress { id, progress } => { FlycheckMessage::Progress { id, progress } => {
f.debug_struct("Progress").field("id", id).field("progress", progress).finish() f.debug_struct("Progress").field("id", id).field("progress", progress).finish()
} }
@ -237,7 +276,12 @@ pub(crate) enum Progress {
} }
enum StateChange { enum StateChange {
Restart { package: Option<String>, saved_file: Option<AbsPathBuf>, target: Option<Target> }, Restart {
generation: DiagnosticsGeneration,
package: Option<String>,
saved_file: Option<AbsPathBuf>,
target: Option<Target>,
},
Cancel, Cancel,
} }
@ -246,6 +290,7 @@ struct FlycheckActor {
/// The workspace id of this flycheck instance. /// The workspace id of this flycheck instance.
id: usize, id: usize,
generation: DiagnosticsGeneration,
sender: Sender<FlycheckMessage>, sender: Sender<FlycheckMessage>,
config: FlycheckConfig, config: FlycheckConfig,
manifest_path: Option<AbsPathBuf>, manifest_path: Option<AbsPathBuf>,
@ -283,6 +328,7 @@ pub(crate) const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
impl FlycheckActor { impl FlycheckActor {
fn new( fn new(
id: usize, id: usize,
generation: DiagnosticsGeneration,
sender: Sender<FlycheckMessage>, sender: Sender<FlycheckMessage>,
config: FlycheckConfig, config: FlycheckConfig,
sysroot_root: Option<AbsPathBuf>, sysroot_root: Option<AbsPathBuf>,
@ -292,6 +338,7 @@ impl FlycheckActor {
tracing::info!(%id, ?workspace_root, "Spawning flycheck"); tracing::info!(%id, ?workspace_root, "Spawning flycheck");
FlycheckActor { FlycheckActor {
id, id,
generation,
sender, sender,
config, config,
sysroot_root, sysroot_root,
@ -327,7 +374,12 @@ impl FlycheckActor {
tracing::debug!(flycheck_id = self.id, "flycheck cancelled"); tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
self.cancel_check_process(); self.cancel_check_process();
} }
Event::RequestStateChange(StateChange::Restart { package, saved_file, target }) => { Event::RequestStateChange(StateChange::Restart {
generation,
package,
saved_file,
target,
}) => {
// Cancel the previously spawned process // Cancel the previously spawned process
self.cancel_check_process(); self.cancel_check_process();
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) { while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
@ -337,6 +389,8 @@ impl FlycheckActor {
} }
} }
self.generation = generation;
let Some(command) = let Some(command) =
self.check_command(package.as_deref(), saved_file.as_deref(), target) self.check_command(package.as_deref(), saved_file.as_deref(), target)
else { else {
@ -383,7 +437,16 @@ impl FlycheckActor {
// Clear everything for good measure // Clear everything for good measure
self.send(FlycheckMessage::ClearDiagnostics { self.send(FlycheckMessage::ClearDiagnostics {
id: self.id, id: self.id,
package_id: None, kind: ClearDiagnosticsKind::All,
});
} else if res.is_ok() {
// We clear diagnostics for packages on
// `[CargoCheckMessage::CompilerArtifact]` but there seem to be setups where
// cargo may not report an artifact to our runner at all. To handle such
// cases, clear stale diagnostics when flycheck completes successfully.
self.send(FlycheckMessage::ClearDiagnostics {
id: self.id,
kind: ClearDiagnosticsKind::OlderThan(self.generation),
}); });
} }
self.clear_diagnostics_state(); self.clear_diagnostics_state();
@ -412,7 +475,7 @@ impl FlycheckActor {
); );
self.send(FlycheckMessage::ClearDiagnostics { self.send(FlycheckMessage::ClearDiagnostics {
id: self.id, id: self.id,
package_id: Some(package_id), kind: ClearDiagnosticsKind::Package(package_id),
}); });
} }
} }
@ -435,7 +498,7 @@ impl FlycheckActor {
); );
self.send(FlycheckMessage::ClearDiagnostics { self.send(FlycheckMessage::ClearDiagnostics {
id: self.id, id: self.id,
package_id: Some(package_id.clone()), kind: ClearDiagnosticsKind::Package(package_id.clone()),
}); });
} }
} else if self.diagnostics_received } else if self.diagnostics_received
@ -444,11 +507,12 @@ impl FlycheckActor {
self.diagnostics_received = DiagnosticsReceived::YesAndClearedForAll; self.diagnostics_received = DiagnosticsReceived::YesAndClearedForAll;
self.send(FlycheckMessage::ClearDiagnostics { self.send(FlycheckMessage::ClearDiagnostics {
id: self.id, id: self.id,
package_id: None, kind: ClearDiagnosticsKind::All,
}); });
} }
self.send(FlycheckMessage::AddDiagnostic { self.send(FlycheckMessage::AddDiagnostic {
id: self.id, id: self.id,
generation: self.generation,
package_id, package_id,
workspace_root: self.root.clone(), workspace_root: self.root.clone(),
diagnostic, diagnostic,

View file

@ -20,7 +20,7 @@ use crate::{
config::Config, config::Config,
diagnostics::{DiagnosticsGeneration, NativeDiagnosticsFetchKind, fetch_native_diagnostics}, diagnostics::{DiagnosticsGeneration, NativeDiagnosticsFetchKind, fetch_native_diagnostics},
discover::{DiscoverArgument, DiscoverCommand, DiscoverProjectMessage}, discover::{DiscoverArgument, DiscoverCommand, DiscoverProjectMessage},
flycheck::{self, FlycheckMessage}, flycheck::{self, ClearDiagnosticsKind, FlycheckMessage},
global_state::{ global_state::{
FetchBuildDataResponse, FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState, FetchBuildDataResponse, FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState,
file_id_to_url, url_to_file_id, file_id_to_url, url_to_file_id,
@ -1008,7 +1008,13 @@ impl GlobalState {
fn handle_flycheck_msg(&mut self, message: FlycheckMessage) { fn handle_flycheck_msg(&mut self, message: FlycheckMessage) {
match message { match message {
FlycheckMessage::AddDiagnostic { id, workspace_root, diagnostic, package_id } => { FlycheckMessage::AddDiagnostic {
id,
generation,
workspace_root,
diagnostic,
package_id,
} => {
let snap = self.snapshot(); let snap = self.snapshot();
let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
&self.config.diagnostics_map(None), &self.config.diagnostics_map(None),
@ -1020,6 +1026,7 @@ impl GlobalState {
match url_to_file_id(&self.vfs.read().0, &diag.url) { match url_to_file_id(&self.vfs.read().0, &diag.url) {
Ok(Some(file_id)) => self.diagnostics.add_check_diagnostic( Ok(Some(file_id)) => self.diagnostics.add_check_diagnostic(
id, id,
generation,
&package_id, &package_id,
file_id, file_id,
diag.diagnostic, diag.diagnostic,
@ -1035,12 +1042,17 @@ impl GlobalState {
}; };
} }
} }
FlycheckMessage::ClearDiagnostics { id, package_id: None } => { FlycheckMessage::ClearDiagnostics { id, kind: ClearDiagnosticsKind::All } => {
self.diagnostics.clear_check(id) self.diagnostics.clear_check(id)
} }
FlycheckMessage::ClearDiagnostics { id, package_id: Some(package_id) } => { FlycheckMessage::ClearDiagnostics {
self.diagnostics.clear_check_for_package(id, package_id) id,
} kind: ClearDiagnosticsKind::OlderThan(generation),
} => self.diagnostics.clear_check_older_than(id, generation),
FlycheckMessage::ClearDiagnostics {
id,
kind: ClearDiagnosticsKind::Package(package_id),
} => self.diagnostics.clear_check_for_package(id, package_id),
FlycheckMessage::Progress { id, progress } => { FlycheckMessage::Progress { id, progress } => {
let (state, message) = match progress { let (state, message) = match progress {
flycheck::Progress::DidStart => (Progress::Begin, None), flycheck::Progress::DidStart => (Progress::Begin, None),

View file

@ -855,11 +855,13 @@ impl GlobalState {
invocation_strategy.clone() invocation_strategy.clone()
} }
}; };
let next_gen = self.flycheck.iter().map(|f| f.generation() + 1).max().unwrap_or_default();
self.flycheck = match invocation_strategy { self.flycheck = match invocation_strategy {
crate::flycheck::InvocationStrategy::Once => { crate::flycheck::InvocationStrategy::Once => {
vec![FlycheckHandle::spawn( vec![FlycheckHandle::spawn(
0, 0,
next_gen,
sender, sender,
config, config,
None, None,
@ -898,6 +900,7 @@ impl GlobalState {
.map(|(id, (root, manifest_path), sysroot_root)| { .map(|(id, (root, manifest_path), sysroot_root)| {
FlycheckHandle::spawn( FlycheckHandle::spawn(
id, id,
next_gen,
sender.clone(), sender.clone(),
config.clone(), config.clone(),
sysroot_root, sysroot_root,