1
1
//! Flycheck provides the functionality needed to run `cargo check` to provide
2
2
//! LSP diagnostics based on the output of the command.
3
3
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
+ } ;
5
10
6
11
use cargo_metadata:: PackageId ;
7
12
use crossbeam_channel:: { Receiver , Sender , select_biased, unbounded} ;
@@ -18,7 +23,10 @@ pub(crate) use cargo_metadata::diagnostic::{
18
23
use toolchain:: Tool ;
19
24
use triomphe:: Arc ;
20
25
21
- use crate :: command:: { CargoParser , CommandHandle } ;
26
+ use crate :: {
27
+ command:: { CargoParser , CommandHandle } ,
28
+ diagnostics:: DiagnosticsGeneration ,
29
+ } ;
22
30
23
31
#[ derive( Clone , Debug , Default , PartialEq , Eq ) ]
24
32
pub ( crate ) enum InvocationStrategy {
@@ -138,36 +146,54 @@ pub(crate) struct FlycheckHandle {
138
146
sender : Sender < StateChange > ,
139
147
_thread : stdx:: thread:: JoinHandle ,
140
148
id : usize ,
149
+ generation : AtomicUsize ,
141
150
}
142
151
143
152
impl FlycheckHandle {
144
153
pub ( crate ) fn spawn (
145
154
id : usize ,
155
+ generation : DiagnosticsGeneration ,
146
156
sender : Sender < FlycheckMessage > ,
147
157
config : FlycheckConfig ,
148
158
sysroot_root : Option < AbsPathBuf > ,
149
159
workspace_root : AbsPathBuf ,
150
160
manifest_path : Option < AbsPathBuf > ,
151
161
) -> 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
+ ) ;
154
171
let ( sender, receiver) = unbounded :: < StateChange > ( ) ;
155
172
let thread =
156
173
stdx:: thread:: Builder :: new ( stdx:: thread:: ThreadIntent :: Worker , format ! ( "Flycheck{id}" ) )
157
174
. spawn ( move || actor. run ( receiver) )
158
175
. expect ( "failed to spawn thread" ) ;
159
- FlycheckHandle { id, sender, _thread : thread }
176
+ FlycheckHandle { id, generation : generation . into ( ) , sender, _thread : thread }
160
177
}
161
178
162
179
/// Schedule a re-start of the cargo check worker to do a workspace wide check.
163
180
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 ( ) ;
165
185
}
166
186
167
187
/// Schedule a re-start of the cargo check worker to do a package wide check.
168
188
pub ( crate ) fn restart_for_package ( & self , package : String , target : Option < Target > ) {
189
+ let generation = self . generation . fetch_add ( 1 , Ordering :: Relaxed ) + 1 ;
169
190
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
+ } )
171
197
. unwrap ( ) ;
172
198
}
173
199
@@ -179,23 +205,31 @@ impl FlycheckHandle {
179
205
pub ( crate ) fn id ( & self ) -> usize {
180
206
self . id
181
207
}
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 > ) ,
182
219
}
183
220
184
221
pub ( crate ) enum FlycheckMessage {
185
222
/// Request adding a diagnostic with fixes included to a file
186
223
AddDiagnostic {
187
224
id : usize ,
225
+ generation : DiagnosticsGeneration ,
188
226
workspace_root : Arc < AbsPathBuf > ,
189
227
diagnostic : Diagnostic ,
190
228
package_id : Option < Arc < PackageId > > ,
191
229
} ,
192
230
193
231
/// 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 } ,
199
233
200
234
/// Request check progress notification to client
201
235
Progress {
@@ -208,18 +242,23 @@ pub(crate) enum FlycheckMessage {
208
242
impl fmt:: Debug for FlycheckMessage {
209
243
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
210
244
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
212
252
. debug_struct ( "AddDiagnostic" )
213
253
. field ( "id" , id)
254
+ . field ( "generation" , generation)
214
255
. field ( "workspace_root" , workspace_root)
215
256
. field ( "package_id" , package_id)
216
257
. field ( "diagnostic_code" , & diagnostic. code . as_ref ( ) . map ( |it| & it. code ) )
217
258
. 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
+ }
223
262
FlycheckMessage :: Progress { id, progress } => {
224
263
f. debug_struct ( "Progress" ) . field ( "id" , id) . field ( "progress" , progress) . finish ( )
225
264
}
@@ -237,7 +276,12 @@ pub(crate) enum Progress {
237
276
}
238
277
239
278
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
+ } ,
241
285
Cancel ,
242
286
}
243
287
@@ -246,6 +290,7 @@ struct FlycheckActor {
246
290
/// The workspace id of this flycheck instance.
247
291
id : usize ,
248
292
293
+ generation : DiagnosticsGeneration ,
249
294
sender : Sender < FlycheckMessage > ,
250
295
config : FlycheckConfig ,
251
296
manifest_path : Option < AbsPathBuf > ,
@@ -283,6 +328,7 @@ pub(crate) const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
283
328
impl FlycheckActor {
284
329
fn new (
285
330
id : usize ,
331
+ generation : DiagnosticsGeneration ,
286
332
sender : Sender < FlycheckMessage > ,
287
333
config : FlycheckConfig ,
288
334
sysroot_root : Option < AbsPathBuf > ,
@@ -292,6 +338,7 @@ impl FlycheckActor {
292
338
tracing:: info!( %id, ?workspace_root, "Spawning flycheck" ) ;
293
339
FlycheckActor {
294
340
id,
341
+ generation,
295
342
sender,
296
343
config,
297
344
sysroot_root,
@@ -327,7 +374,12 @@ impl FlycheckActor {
327
374
tracing:: debug!( flycheck_id = self . id, "flycheck cancelled" ) ;
328
375
self . cancel_check_process ( ) ;
329
376
}
330
- Event :: RequestStateChange ( StateChange :: Restart { package, saved_file, target } ) => {
377
+ Event :: RequestStateChange ( StateChange :: Restart {
378
+ generation,
379
+ package,
380
+ saved_file,
381
+ target,
382
+ } ) => {
331
383
// Cancel the previously spawned process
332
384
self . cancel_check_process ( ) ;
333
385
while let Ok ( restart) = inbox. recv_timeout ( Duration :: from_millis ( 50 ) ) {
@@ -337,6 +389,8 @@ impl FlycheckActor {
337
389
}
338
390
}
339
391
392
+ self . generation = generation;
393
+
340
394
let Some ( command) =
341
395
self . check_command ( package. as_deref ( ) , saved_file. as_deref ( ) , target)
342
396
else {
@@ -383,7 +437,16 @@ impl FlycheckActor {
383
437
// Clear everything for good measure
384
438
self . send ( FlycheckMessage :: ClearDiagnostics {
385
439
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 ) ,
387
450
} ) ;
388
451
}
389
452
self . clear_diagnostics_state ( ) ;
@@ -412,7 +475,7 @@ impl FlycheckActor {
412
475
) ;
413
476
self . send ( FlycheckMessage :: ClearDiagnostics {
414
477
id : self . id ,
415
- package_id : Some ( package_id) ,
478
+ kind : ClearDiagnosticsKind :: Package ( package_id) ,
416
479
} ) ;
417
480
}
418
481
}
@@ -435,7 +498,7 @@ impl FlycheckActor {
435
498
) ;
436
499
self . send ( FlycheckMessage :: ClearDiagnostics {
437
500
id : self . id ,
438
- package_id : Some ( package_id. clone ( ) ) ,
501
+ kind : ClearDiagnosticsKind :: Package ( package_id. clone ( ) ) ,
439
502
} ) ;
440
503
}
441
504
} else if self . diagnostics_received
@@ -444,11 +507,12 @@ impl FlycheckActor {
444
507
self . diagnostics_received = DiagnosticsReceived :: YesAndClearedForAll ;
445
508
self . send ( FlycheckMessage :: ClearDiagnostics {
446
509
id : self . id ,
447
- package_id : None ,
510
+ kind : ClearDiagnosticsKind :: All ,
448
511
} ) ;
449
512
}
450
513
self . send ( FlycheckMessage :: AddDiagnostic {
451
514
id : self . id ,
515
+ generation : self . generation ,
452
516
package_id,
453
517
workspace_root : self . root . clone ( ) ,
454
518
diagnostic,
0 commit comments