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 ;
@@ -150,10 +151,19 @@ impl FlycheckHandle {
150151
151152pub ( crate ) enum FlycheckMessage {
152153 /// Request adding a diagnostic with fixes included to a file
153- AddDiagnostic { id : usize , workspace_root : AbsPathBuf , diagnostic : Diagnostic } ,
154+ AddDiagnostic {
155+ id : usize ,
156+ workspace_root : AbsPathBuf ,
157+ diagnostic : Diagnostic ,
158+ package_id : Option < PackageId > ,
159+ } ,
154160
155- /// Request clearing all previous diagnostics
156- ClearDiagnostics { id : usize } ,
161+ /// Request clearing all outdated diagnostics.
162+ ClearDiagnostics {
163+ id : usize ,
164+ /// The pacakge whose diagnostics to clear, or if unspecified, all diagnostics.
165+ package_id : Option < PackageId > ,
166+ } ,
157167
158168 /// Request check progress notification to client
159169 Progress {
@@ -166,15 +176,18 @@ pub(crate) enum FlycheckMessage {
166176impl fmt:: Debug for FlycheckMessage {
167177 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
168178 match self {
169- FlycheckMessage :: AddDiagnostic { id, workspace_root, diagnostic } => f
179+ FlycheckMessage :: AddDiagnostic { id, workspace_root, diagnostic, package_id } => f
170180 . debug_struct ( "AddDiagnostic" )
171181 . field ( "id" , id)
172182 . field ( "workspace_root" , workspace_root)
183+ . field ( "package_id" , package_id)
173184 . field ( "diagnostic_code" , & diagnostic. code . as_ref ( ) . map ( |it| & it. code ) )
174185 . finish ( ) ,
175- FlycheckMessage :: ClearDiagnostics { id } => {
176- f. debug_struct ( "ClearDiagnostics" ) . field ( "id" , id) . finish ( )
177- }
186+ FlycheckMessage :: ClearDiagnostics { id, package_id } => f
187+ . debug_struct ( "ClearDiagnostics" )
188+ . field ( "id" , id)
189+ . field ( "package_id" , package_id)
190+ . finish ( ) ,
178191 FlycheckMessage :: Progress { id, progress } => {
179192 f. debug_struct ( "Progress" ) . field ( "id" , id) . field ( "progress" , progress) . finish ( )
180193 }
@@ -200,6 +213,7 @@ enum StateChange {
200213struct FlycheckActor {
201214 /// The workspace id of this flycheck instance.
202215 id : usize ,
216+
203217 sender : Sender < FlycheckMessage > ,
204218 config : FlycheckConfig ,
205219 manifest_path : Option < AbsPathBuf > ,
@@ -215,8 +229,13 @@ struct FlycheckActor {
215229 command_handle : Option < CommandHandle < CargoCheckMessage > > ,
216230 /// The receiver side of the channel mentioned above.
217231 command_receiver : Option < Receiver < CargoCheckMessage > > ,
232+ package_status : FxHashMap < PackageId , DiagnosticReceived > ,
233+ }
218234
219- status : FlycheckStatus ,
235+ #[ derive( PartialEq , Eq , Copy , Clone , Debug ) ]
236+ enum DiagnosticReceived {
237+ Yes ,
238+ No ,
220239}
221240
222241#[ allow( clippy:: large_enum_variant) ]
@@ -225,13 +244,6 @@ enum Event {
225244 CheckEvent ( Option < CargoCheckMessage > ) ,
226245}
227246
228- #[ derive( PartialEq ) ]
229- enum FlycheckStatus {
230- Started ,
231- DiagnosticSent ,
232- Finished ,
233- }
234-
235247pub ( crate ) const SAVED_FILE_PLACEHOLDER : & str = "$saved_file" ;
236248
237249impl FlycheckActor {
@@ -253,7 +265,7 @@ impl FlycheckActor {
253265 manifest_path,
254266 command_handle : None ,
255267 command_receiver : None ,
256- status : FlycheckStatus :: Finished ,
268+ package_status : FxHashMap :: default ( ) ,
257269 }
258270 }
259271
@@ -306,13 +318,11 @@ impl FlycheckActor {
306318 self . command_handle = Some ( command_handle) ;
307319 self . command_receiver = Some ( receiver) ;
308320 self . report_progress ( Progress :: DidStart ) ;
309- self . status = FlycheckStatus :: Started ;
310321 }
311322 Err ( error) => {
312323 self . report_progress ( Progress :: DidFailToRestart ( format ! (
313324 "Failed to run the following command: {formatted_command} error={error}"
314325 ) ) ) ;
315- self . status = FlycheckStatus :: Finished ;
316326 }
317327 }
318328 }
@@ -332,11 +342,25 @@ impl FlycheckActor {
332342 error
333343 ) ;
334344 }
335- if self . status == FlycheckStatus :: Started {
336- self . send ( FlycheckMessage :: ClearDiagnostics { id : self . id } ) ;
345+ if self . package_status . is_empty ( ) {
346+ // We finished without receiving any diagnostics.
347+ // That means all of them are stale.
348+ self . send ( FlycheckMessage :: ClearDiagnostics {
349+ id : self . id ,
350+ package_id : None ,
351+ } ) ;
352+ } else {
353+ for ( package_id, status) in mem:: take ( & mut self . package_status ) {
354+ if let DiagnosticReceived :: No = status {
355+ self . send ( FlycheckMessage :: ClearDiagnostics {
356+ id : self . id ,
357+ package_id : Some ( package_id) ,
358+ } ) ;
359+ }
360+ }
337361 }
362+
338363 self . report_progress ( Progress :: DidFinish ( res) ) ;
339- self . status = FlycheckStatus :: Finished ;
340364 }
341365 Event :: CheckEvent ( Some ( message) ) => match message {
342366 CargoCheckMessage :: CompilerArtifact ( msg) => {
@@ -346,23 +370,30 @@ impl FlycheckActor {
346370 "artifact received"
347371 ) ;
348372 self . report_progress ( Progress :: DidCheckCrate ( msg. target . name ) ) ;
373+ self . package_status . entry ( msg. package_id ) . or_insert ( DiagnosticReceived :: No ) ;
349374 }
350-
351- CargoCheckMessage :: Diagnostic ( msg) => {
375+ CargoCheckMessage :: Diagnostic { diagnostic, package_id } => {
352376 tracing:: trace!(
353377 flycheck_id = self . id,
354- message = msg . message,
378+ message = diagnostic . message,
355379 "diagnostic received"
356380 ) ;
357- if self . status == FlycheckStatus :: Started {
358- self . send ( FlycheckMessage :: ClearDiagnostics { id : self . id } ) ;
381+ if let Some ( package_id) = & package_id {
382+ if !self . package_status . contains_key ( package_id) {
383+ self . package_status
384+ . insert ( package_id. clone ( ) , DiagnosticReceived :: Yes ) ;
385+ self . send ( FlycheckMessage :: ClearDiagnostics {
386+ id : self . id ,
387+ package_id : Some ( package_id. clone ( ) ) ,
388+ } ) ;
389+ }
359390 }
360391 self . send ( FlycheckMessage :: AddDiagnostic {
361392 id : self . id ,
393+ package_id,
362394 workspace_root : self . root . clone ( ) ,
363- diagnostic : msg ,
395+ diagnostic,
364396 } ) ;
365- self . status = FlycheckStatus :: DiagnosticSent ;
366397 }
367398 } ,
368399 }
@@ -380,7 +411,7 @@ impl FlycheckActor {
380411 command_handle. cancel ( ) ;
381412 self . command_receiver . take ( ) ;
382413 self . report_progress ( Progress :: DidCancel ) ;
383- self . status = FlycheckStatus :: Finished ;
414+ self . package_status . clear ( ) ;
384415 }
385416 }
386417
@@ -486,7 +517,7 @@ impl FlycheckActor {
486517#[ allow( clippy:: large_enum_variant) ]
487518enum CargoCheckMessage {
488519 CompilerArtifact ( cargo_metadata:: Artifact ) ,
489- Diagnostic ( Diagnostic ) ,
520+ Diagnostic { diagnostic : Diagnostic , package_id : Option < PackageId > } ,
490521}
491522
492523impl ParseFromLine for CargoCheckMessage {
@@ -501,11 +532,16 @@ impl ParseFromLine for CargoCheckMessage {
501532 Some ( CargoCheckMessage :: CompilerArtifact ( artifact) )
502533 }
503534 cargo_metadata:: Message :: CompilerMessage ( msg) => {
504- Some ( CargoCheckMessage :: Diagnostic ( msg. message ) )
535+ Some ( CargoCheckMessage :: Diagnostic {
536+ diagnostic : msg. message ,
537+ package_id : Some ( msg. package_id ) ,
538+ } )
505539 }
506540 _ => None ,
507541 } ,
508- JsonMessage :: Rustc ( message) => Some ( CargoCheckMessage :: Diagnostic ( message) ) ,
542+ JsonMessage :: Rustc ( message) => {
543+ Some ( CargoCheckMessage :: Diagnostic { diagnostic : message, package_id : None } )
544+ }
509545 } ;
510546 }
511547
0 commit comments