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
611use cargo_metadata:: PackageId ;
712use crossbeam_channel:: { Receiver , Sender , select_biased, unbounded} ;
@@ -18,7 +23,10 @@ pub(crate) use cargo_metadata::diagnostic::{
1823use toolchain:: Tool ;
1924use 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 ) ]
2432pub ( 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
143152impl 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
184221pub ( 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 {
208242impl 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
239278enum 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";
283328impl 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