@@ -3,7 +3,7 @@ use std::{
33 sync:: Arc as StdArc ,
44} ;
55
6- use anyhow:: Result ;
6+ use anyhow:: { Context , Result } ;
77use hir:: base_db:: project:: CompilationProfileId ;
88use rustc_hash:: FxHashSet ;
99use utils:: {
@@ -25,12 +25,18 @@ use super::{
2525
2626#[ derive( Debug ) ]
2727pub ( crate ) struct SemanticCompilerUpdate {
28- snapshot : GlobalStateSnapshot ,
28+ delivery : SemanticDiagnosticsDelivery ,
2929 touched_files : FxHashSet < FileId > ,
3030 diagnostic_count : usize ,
3131 freshness : DiagnosticPublishFreshness ,
3232}
3333
34+ #[ derive( Debug ) ]
35+ enum SemanticDiagnosticsDelivery {
36+ PullRefresh ,
37+ Push ( PublishDiagnosticsBatch ) ,
38+ }
39+
3440impl SemanticCompilerUpdate {
3541 pub ( crate ) fn touched_file_count ( & self ) -> usize {
3642 self . touched_files . len ( )
@@ -118,8 +124,15 @@ impl SemanticCompiler {
118124 return ;
119125 }
120126
121- let SemanticCompilerUpdate { snapshot, touched_files, .. } = update;
122- self . publish_diagnostics ( snapshot, touched_files, ctx) ;
127+ let SemanticCompilerUpdate { delivery, touched_files, .. } = update;
128+ match delivery {
129+ SemanticDiagnosticsDelivery :: PullRefresh => {
130+ ctx. refresh_semantic_diagnostics ( touched_files) ;
131+ }
132+ SemanticDiagnosticsDelivery :: Push ( batch) => {
133+ ctx. publish_semantic_diagnostics ( batch) ;
134+ }
135+ }
123136 self . start_pending ( ctx) ;
124137 }
125138 SemanticCompilerTask :: Cancelled { run_id } => {
@@ -188,15 +201,6 @@ impl SemanticCompiler {
188201 self . start_run ( profile_ids, ctx) ;
189202 }
190203 }
191-
192- fn publish_diagnostics < C : SemanticCompilerCtx > (
193- & mut self ,
194- snapshot : GlobalStateSnapshot ,
195- changed_files : FxHashSet < FileId > ,
196- ctx : & mut C ,
197- ) {
198- ctx. publish_semantic_diagnostics ( snapshot, changed_files) ;
199- }
200204}
201205
202206pub ( crate ) trait SemanticCompilerCtx {
@@ -206,11 +210,8 @@ pub(crate) trait SemanticCompilerCtx {
206210 where
207211 F : FnOnce ( crossbeam_channel:: Sender < Task > ) + Send + ' static ;
208212 fn task_cancel_token ( & self ) -> CancellationToken ;
209- fn publish_semantic_diagnostics (
210- & mut self ,
211- snapshot : GlobalStateSnapshot ,
212- changed_files : FxHashSet < FileId > ,
213- ) ;
213+ fn refresh_semantic_diagnostics ( & mut self , changed_files : FxHashSet < FileId > ) ;
214+ fn publish_semantic_diagnostics ( & mut self , batch : PublishDiagnosticsBatch ) ;
214215}
215216
216217pub ( super ) struct SemanticCompilerGlobalCtx < ' a > {
@@ -288,52 +289,15 @@ impl SemanticCompilerCtx for SemanticCompilerGlobalCtx<'_> {
288289 self . tasks . task_pool . handle . task_token ( )
289290 }
290291
291- fn publish_semantic_diagnostics (
292- & mut self ,
293- snapshot : GlobalStateSnapshot ,
294- changed_files : FxHashSet < FileId > ,
295- ) {
296- if changed_files. is_empty ( ) {
297- return ;
298- }
292+ fn refresh_semantic_diagnostics ( & mut self , changed_files : FxHashSet < FileId > ) {
293+ self . refresh_pull_diagnostics ( changed_files) ;
294+ }
299295
300- if self . config_state . config . cli_pull_diagnostics_support ( ) {
301- self . refresh_pull_diagnostics ( changed_files ) ;
296+ fn publish_semantic_diagnostics ( & mut self , batch : PublishDiagnosticsBatch ) {
297+ if batch . touched_file_count ( ) == 0 {
302298 return ;
303299 }
304300
305- let mut publish_tasks = Vec :: with_capacity ( changed_files. len ( ) ) ;
306- let mut touched_file_ids = FxHashSet :: default ( ) ;
307- for file_id in changed_files. iter ( ) . copied ( ) {
308- let targets = match snapshot. diagnostic_publish_targets ( file_id) {
309- Ok ( targets) => targets,
310- Err ( error) => {
311- tracing:: debug!(
312- ?file_id,
313- "skipping semantic diagnostics for file without URI: {error:#}"
314- ) ;
315- continue ;
316- }
317- } ;
318- let diagnostics = match snapshot. lsp_diagnostics ( file_id) {
319- Ok ( diagnostics) => diagnostics,
320- Err ( error) if error. is :: < ide:: Cancelled > ( ) => {
321- tracing:: debug!( ?file_id, "semantic diagnostic publish cancelled" ) ;
322- continue ;
323- }
324- Err ( error) => {
325- tracing:: debug!( ?file_id, "semantic diagnostic publish failed: {error:#}" ) ;
326- continue ;
327- }
328- } ;
329- touched_file_ids. insert ( file_id) ;
330-
331- publish_tasks. extend (
332- targets
333- . into_iter ( )
334- . map ( |target| PublishDiagnosticsTask :: from_target ( target, diagnostics. clone ( ) ) ) ,
335- ) ;
336- }
337301 let current_freshness = self . diagnostic_publish_freshness ( ) ;
338302 DiagnosticsPublisher :: new (
339303 & self . config_state . config ,
@@ -342,11 +306,7 @@ impl SemanticCompilerCtx for SemanticCompilerGlobalCtx<'_> {
342306 & self . client . sender ,
343307 current_freshness,
344308 )
345- . publish ( PublishDiagnosticsBatch :: for_touched_files (
346- touched_file_ids,
347- publish_tasks,
348- snapshot. diagnostic_publish_freshness ,
349- ) ) ;
309+ . publish ( batch) ;
350310 }
351311}
352312
@@ -416,7 +376,56 @@ fn collect_semantic_diagnostics(
416376 diagnostic_count,
417377 "semantic compiler prewarmed profile diagnostics"
418378 ) ;
419- Ok ( SemanticCompilerUpdate { snapshot, touched_files, diagnostic_count, freshness } )
379+
380+ let delivery = if snapshot. config . cli_pull_diagnostics_support ( ) {
381+ SemanticDiagnosticsDelivery :: PullRefresh
382+ } else {
383+ SemanticDiagnosticsDelivery :: Push ( materialize_semantic_publish_batch (
384+ & snapshot,
385+ & touched_files,
386+ cancellation,
387+ ) ?)
388+ } ;
389+ drop ( snapshot) ;
390+
391+ Ok ( SemanticCompilerUpdate { delivery, touched_files, diagnostic_count, freshness } )
392+ }
393+
394+ fn materialize_semantic_publish_batch (
395+ snapshot : & GlobalStateSnapshot ,
396+ changed_files : & FxHashSet < FileId > ,
397+ cancellation : & CancellationToken ,
398+ ) -> Result < PublishDiagnosticsBatch > {
399+ let mut publish_tasks = Vec :: with_capacity ( changed_files. len ( ) ) ;
400+ let mut touched_file_ids = FxHashSet :: default ( ) ;
401+ for file_id in changed_files. iter ( ) . copied ( ) {
402+ cancellation. check ( ) ?;
403+ let targets = snapshot
404+ . diagnostic_publish_targets ( file_id)
405+ . with_context ( || format ! ( "failed to resolve diagnostic targets for {file_id:?}" ) ) ?;
406+ let diagnostics = match snapshot. lsp_diagnostics ( file_id) {
407+ Ok ( diagnostics) => diagnostics,
408+ Err ( error) if error. is :: < ide:: Cancelled > ( ) => return Err ( CancellationError . into ( ) ) ,
409+ Err ( error) => {
410+ return Err ( error. context ( format ! (
411+ "failed to materialize semantic diagnostics for {file_id:?}"
412+ ) ) ) ;
413+ }
414+ } ;
415+ touched_file_ids. insert ( file_id) ;
416+ publish_tasks. extend (
417+ targets
418+ . into_iter ( )
419+ . map ( |target| PublishDiagnosticsTask :: from_target ( target, diagnostics. clone ( ) ) ) ,
420+ ) ;
421+ }
422+ cancellation. check ( ) ?;
423+
424+ Ok ( PublishDiagnosticsBatch :: for_touched_files (
425+ touched_file_ids,
426+ publish_tasks,
427+ snapshot. diagnostic_publish_freshness ,
428+ ) )
420429}
421430
422431fn normalize_profile_ids ( mut profile_ids : Vec < CompilationProfileId > ) -> Vec < CompilationProfileId > {
@@ -431,3 +440,68 @@ fn panic_message(panic: &(dyn std::any::Any + Send)) -> Option<&str> {
431440 . map ( String :: as_str)
432441 . or_else ( || panic. downcast_ref :: < & str > ( ) . copied ( ) )
433442}
443+
444+ #[ cfg( test) ]
445+ mod tests {
446+ use std:: time:: Duration ;
447+
448+ use hir:: base_db:: change:: Change ;
449+ use lsp_server:: Connection ;
450+ use lsp_types:: { ClientCapabilities , TraceValue } ;
451+ use utils:: test_support:: TestDir ;
452+
453+ use super :: * ;
454+ use crate :: {
455+ Opt ,
456+ config:: { self , user_config:: UserConfig } ,
457+ i18n:: I18n ,
458+ } ;
459+
460+ #[ test]
461+ fn semantic_compiler_task_does_not_retain_analysis_snapshot ( ) {
462+ let root = TestDir :: new ( "semantic-compiler-snapshot-lifetime" ) ;
463+ let root_path = root. path ( ) . to_path_buf ( ) ;
464+ let config = config:: Config :: new (
465+ Opt {
466+ process_name : "vide-test" . to_owned ( ) ,
467+ log : "error" . to_owned ( ) ,
468+ log_filename : None ,
469+ profile_trace : None ,
470+ } ,
471+ root_path. clone ( ) ,
472+ ClientCapabilities :: default ( ) ,
473+ vec ! [ root_path] ,
474+ I18n :: default ( ) ,
475+ UserConfig :: default ( ) ,
476+ Vec :: new ( ) ,
477+ ) ;
478+ let ( server, _client) = Connection :: memory ( ) ;
479+ let mut state = GlobalState :: new ( server. sender , config, TraceValue :: Off ) ;
480+ let cancellation = CancellationToken :: new ( ) ;
481+ let snapshot = state. make_snapshot_with_cancel ( cancellation. clone ( ) ) ;
482+ let task = run_semantic_compiler_task (
483+ snapshot,
484+ Vec :: new ( ) ,
485+ SemanticCompilerRunId :: default ( ) ,
486+ cancellation,
487+ ) ;
488+
489+ let mut analysis_host = std:: mem:: take ( & mut state. analysis . analysis_host ) ;
490+ let ( finished_tx, finished_rx) = crossbeam_channel:: bounded ( 1 ) ;
491+ let writer = std:: thread:: spawn ( move || {
492+ analysis_host. apply_change ( Change :: new ( ) ) ;
493+ finished_tx. send ( ( ) ) . unwrap ( ) ;
494+ analysis_host
495+ } ) ;
496+
497+ let completed_while_task_was_retained =
498+ finished_rx. recv_timeout ( Duration :: from_secs ( 1 ) ) . is_ok ( ) ;
499+ drop ( task) ;
500+ state. analysis . analysis_host = writer. join ( ) . unwrap ( ) ;
501+
502+ assert ! (
503+ completed_while_task_was_retained,
504+ "semantic compiler task retained an analysis snapshot and blocked the next change"
505+ ) ;
506+ }
507+ }
0 commit comments