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:: { fmt, io, mem , process:: Command , time:: Duration } ;
55
6+ use cargo_metadata:: PackageId ;
67use crossbeam_channel:: { select_biased, unbounded, Receiver , Sender } ;
78use paths:: { AbsPath , AbsPathBuf , Utf8PathBuf } ;
89use rustc_hash:: FxHashMap ;
@@ -13,6 +14,7 @@ pub(crate) use cargo_metadata::diagnostic::{
1314 Applicability , Diagnostic , DiagnosticCode , DiagnosticLevel , DiagnosticSpan ,
1415} ;
1516use toolchain:: Tool ;
17+ use triomphe:: Arc ;
1618
1719use crate :: command:: { CommandHandle , ParseFromLine } ;
1820
@@ -151,10 +153,19 @@ impl FlycheckHandle {
151153
152154pub ( crate ) enum FlycheckMessage {
153155 /// Request adding a diagnostic with fixes included to a file
154- AddDiagnostic { id : usize , workspace_root : AbsPathBuf , diagnostic : Diagnostic } ,
156+ AddDiagnostic {
157+ id : usize ,
158+ workspace_root : Arc < AbsPathBuf > ,
159+ diagnostic : Diagnostic ,
160+ package_id : Option < Arc < PackageId > > ,
161+ } ,
155162
156- /// Request clearing all previous diagnostics
157- ClearDiagnostics { id : usize } ,
163+ /// Request clearing all outdated diagnostics.
164+ ClearDiagnostics {
165+ id : usize ,
166+ /// The package whose diagnostics to clear, or if unspecified, all diagnostics.
167+ package_id : Option < Arc < PackageId > > ,
168+ } ,
158169
159170 /// Request check progress notification to client
160171 Progress {
@@ -167,15 +178,18 @@ pub(crate) enum FlycheckMessage {
167178impl fmt:: Debug for FlycheckMessage {
168179 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
169180 match self {
170- FlycheckMessage :: AddDiagnostic { id, workspace_root, diagnostic } => f
181+ FlycheckMessage :: AddDiagnostic { id, workspace_root, diagnostic, package_id } => f
171182 . debug_struct ( "AddDiagnostic" )
172183 . field ( "id" , id)
173184 . field ( "workspace_root" , workspace_root)
185+ . field ( "package_id" , package_id)
174186 . field ( "diagnostic_code" , & diagnostic. code . as_ref ( ) . map ( |it| & it. code ) )
175187 . finish ( ) ,
176- FlycheckMessage :: ClearDiagnostics { id } => {
177- f. debug_struct ( "ClearDiagnostics" ) . field ( "id" , id) . finish ( )
178- }
188+ FlycheckMessage :: ClearDiagnostics { id, package_id } => f
189+ . debug_struct ( "ClearDiagnostics" )
190+ . field ( "id" , id)
191+ . field ( "package_id" , package_id)
192+ . finish ( ) ,
179193 FlycheckMessage :: Progress { id, progress } => {
180194 f. debug_struct ( "Progress" ) . field ( "id" , id) . field ( "progress" , progress) . finish ( )
181195 }
@@ -201,12 +215,13 @@ enum StateChange {
201215struct FlycheckActor {
202216 /// The workspace id of this flycheck instance.
203217 id : usize ,
218+
204219 sender : Sender < FlycheckMessage > ,
205220 config : FlycheckConfig ,
206221 manifest_path : Option < AbsPathBuf > ,
207222 /// Either the workspace root of the workspace we are flychecking,
208223 /// or the project root of the project.
209- root : AbsPathBuf ,
224+ root : Arc < AbsPathBuf > ,
210225 sysroot_root : Option < AbsPathBuf > ,
211226 /// CargoHandle exists to wrap around the communication needed to be able to
212227 /// run `cargo check` without blocking. Currently the Rust standard library
@@ -216,8 +231,13 @@ struct FlycheckActor {
216231 command_handle : Option < CommandHandle < CargoCheckMessage > > ,
217232 /// The receiver side of the channel mentioned above.
218233 command_receiver : Option < Receiver < CargoCheckMessage > > ,
234+ package_status : FxHashMap < Arc < PackageId > , DiagnosticReceived > ,
235+ }
219236
220- status : FlycheckStatus ,
237+ #[ derive( PartialEq , Eq , Copy , Clone , Debug ) ]
238+ enum DiagnosticReceived {
239+ Yes ,
240+ No ,
221241}
222242
223243#[ allow( clippy:: large_enum_variant) ]
@@ -226,13 +246,6 @@ enum Event {
226246 CheckEvent ( Option < CargoCheckMessage > ) ,
227247}
228248
229- #[ derive( PartialEq ) ]
230- enum FlycheckStatus {
231- Started ,
232- DiagnosticSent ,
233- Finished ,
234- }
235-
236249pub ( crate ) const SAVED_FILE_PLACEHOLDER : & str = "$saved_file" ;
237250
238251impl FlycheckActor {
@@ -250,11 +263,11 @@ impl FlycheckActor {
250263 sender,
251264 config,
252265 sysroot_root,
253- root : workspace_root,
266+ root : Arc :: new ( workspace_root) ,
254267 manifest_path,
255268 command_handle : None ,
256269 command_receiver : None ,
257- status : FlycheckStatus :: Finished ,
270+ package_status : FxHashMap :: default ( ) ,
258271 }
259272 }
260273
@@ -307,13 +320,11 @@ impl FlycheckActor {
307320 self . command_handle = Some ( command_handle) ;
308321 self . command_receiver = Some ( receiver) ;
309322 self . report_progress ( Progress :: DidStart ) ;
310- self . status = FlycheckStatus :: Started ;
311323 }
312324 Err ( error) => {
313325 self . report_progress ( Progress :: DidFailToRestart ( format ! (
314326 "Failed to run the following command: {formatted_command} error={error}"
315327 ) ) ) ;
316- self . status = FlycheckStatus :: Finished ;
317328 }
318329 }
319330 }
@@ -333,11 +344,25 @@ impl FlycheckActor {
333344 error
334345 ) ;
335346 }
336- if self . status == FlycheckStatus :: Started {
337- self . send ( FlycheckMessage :: ClearDiagnostics { id : self . id } ) ;
347+ if self . package_status . is_empty ( ) {
348+ // We finished without receiving any diagnostics.
349+ // That means all of them are stale.
350+ self . send ( FlycheckMessage :: ClearDiagnostics {
351+ id : self . id ,
352+ package_id : None ,
353+ } ) ;
354+ } else {
355+ for ( package_id, status) in mem:: take ( & mut self . package_status ) {
356+ if let DiagnosticReceived :: No = status {
357+ self . send ( FlycheckMessage :: ClearDiagnostics {
358+ id : self . id ,
359+ package_id : Some ( package_id) ,
360+ } ) ;
361+ }
362+ }
338363 }
364+
339365 self . report_progress ( Progress :: DidFinish ( res) ) ;
340- self . status = FlycheckStatus :: Finished ;
341366 }
342367 Event :: CheckEvent ( Some ( message) ) => match message {
343368 CargoCheckMessage :: CompilerArtifact ( msg) => {
@@ -347,23 +372,32 @@ impl FlycheckActor {
347372 "artifact received"
348373 ) ;
349374 self . report_progress ( Progress :: DidCheckCrate ( msg. target . name ) ) ;
375+ self . package_status
376+ . entry ( Arc :: new ( msg. package_id ) )
377+ . or_insert ( DiagnosticReceived :: No ) ;
350378 }
351-
352- CargoCheckMessage :: Diagnostic ( msg) => {
379+ CargoCheckMessage :: Diagnostic { diagnostic, package_id } => {
353380 tracing:: trace!(
354381 flycheck_id = self . id,
355- message = msg . message,
382+ message = diagnostic . message,
356383 "diagnostic received"
357384 ) ;
358- if self . status == FlycheckStatus :: Started {
359- self . send ( FlycheckMessage :: ClearDiagnostics { id : self . id } ) ;
385+ if let Some ( package_id) = & package_id {
386+ if !self . package_status . contains_key ( package_id) {
387+ self . package_status
388+ . insert ( package_id. clone ( ) , DiagnosticReceived :: Yes ) ;
389+ self . send ( FlycheckMessage :: ClearDiagnostics {
390+ id : self . id ,
391+ package_id : Some ( package_id. clone ( ) ) ,
392+ } ) ;
393+ }
360394 }
361395 self . send ( FlycheckMessage :: AddDiagnostic {
362396 id : self . id ,
397+ package_id,
363398 workspace_root : self . root . clone ( ) ,
364- diagnostic : msg ,
399+ diagnostic,
365400 } ) ;
366- self . status = FlycheckStatus :: DiagnosticSent ;
367401 }
368402 } ,
369403 }
@@ -381,7 +415,7 @@ impl FlycheckActor {
381415 command_handle. cancel ( ) ;
382416 self . command_receiver . take ( ) ;
383417 self . report_progress ( Progress :: DidCancel ) ;
384- self . status = FlycheckStatus :: Finished ;
418+ self . package_status . clear ( ) ;
385419 }
386420 }
387421
@@ -401,7 +435,7 @@ impl FlycheckActor {
401435 cmd. env ( "RUSTUP_TOOLCHAIN" , AsRef :: < std:: path:: Path > :: as_ref ( sysroot_root) ) ;
402436 }
403437 cmd. arg ( command) ;
404- cmd. current_dir ( & self . root ) ;
438+ cmd. current_dir ( & * self . root ) ;
405439
406440 match package {
407441 Some ( pkg) => cmd. arg ( "-p" ) . arg ( pkg) ,
@@ -443,11 +477,11 @@ impl FlycheckActor {
443477
444478 match invocation_strategy {
445479 InvocationStrategy :: Once => {
446- cmd. current_dir ( & self . root ) ;
480+ cmd. current_dir ( & * self . root ) ;
447481 }
448482 InvocationStrategy :: PerWorkspace => {
449483 // FIXME: cmd.current_dir(&affected_workspace);
450- cmd. current_dir ( & self . root ) ;
484+ cmd. current_dir ( & * self . root ) ;
451485 }
452486 }
453487
@@ -487,7 +521,7 @@ impl FlycheckActor {
487521#[ allow( clippy:: large_enum_variant) ]
488522enum CargoCheckMessage {
489523 CompilerArtifact ( cargo_metadata:: Artifact ) ,
490- Diagnostic ( Diagnostic ) ,
524+ Diagnostic { diagnostic : Diagnostic , package_id : Option < Arc < PackageId > > } ,
491525}
492526
493527impl ParseFromLine for CargoCheckMessage {
@@ -502,11 +536,16 @@ impl ParseFromLine for CargoCheckMessage {
502536 Some ( CargoCheckMessage :: CompilerArtifact ( artifact) )
503537 }
504538 cargo_metadata:: Message :: CompilerMessage ( msg) => {
505- Some ( CargoCheckMessage :: Diagnostic ( msg. message ) )
539+ Some ( CargoCheckMessage :: Diagnostic {
540+ diagnostic : msg. message ,
541+ package_id : Some ( Arc :: new ( msg. package_id ) ) ,
542+ } )
506543 }
507544 _ => None ,
508545 } ,
509- JsonMessage :: Rustc ( message) => Some ( CargoCheckMessage :: Diagnostic ( message) ) ,
546+ JsonMessage :: Rustc ( message) => {
547+ Some ( CargoCheckMessage :: Diagnostic { diagnostic : message, package_id : None } )
548+ }
510549 } ;
511550 }
512551
0 commit comments