@@ -17,8 +17,11 @@ use crate::module_resolution::{ModuleResolution, ModuleResolutionAmbiguity, reso
1717
1818const AMBIGUOUS_MODULE_INSTANTIATION : VizslaDiagnosticDescriptor =
1919 VizslaDiagnosticDescriptor { code : 1 , subsystem : 0 , name : "ambiguous-module-instantiation" } ;
20+ const INACTIVE_PREPROCESSOR_BRANCH : VizslaDiagnosticDescriptor =
21+ VizslaDiagnosticDescriptor { code : 2 , subsystem : 0 , name : "inactive-preprocessor-branch" } ;
2022pub const DIAGNOSTIC_AMBIGUOUS_MODULE_STRICT : & str = "diagnostic.ambiguous_module.strict" ;
2123pub const DIAGNOSTIC_AMBIGUOUS_MODULE_BEST_EFFORT : & str = "diagnostic.ambiguous_module.best_effort" ;
24+ pub const DIAGNOSTIC_INACTIVE_PREPROCESSOR_BRANCH : & str = "diagnostic.inactive_preprocessor_branch" ;
2225
2326#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
2427pub enum DiagnosticSource {
@@ -41,6 +44,12 @@ pub struct Diagnostic {
4144 pub message : String ,
4245 pub message_key : Option < & ' static str > ,
4346 pub message_args : Vec < ( & ' static str , String ) > ,
47+ pub tags : Vec < DiagnosticTag > ,
48+ }
49+
50+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
51+ pub enum DiagnosticTag {
52+ Unnecessary ,
4453}
4554
4655#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
@@ -50,6 +59,12 @@ struct VizslaDiagnosticDescriptor {
5059 name : & ' static str ,
5160}
5261
62+ #[ derive( Debug , Clone , Default ) ]
63+ struct VizslaDiagnosticMetadata {
64+ message_args : Vec < ( & ' static str , String ) > ,
65+ tags : Vec < DiagnosticTag > ,
66+ }
67+
5368impl VizslaDiagnosticDescriptor {
5469 fn diagnostic (
5570 self ,
@@ -59,6 +74,44 @@ impl VizslaDiagnosticDescriptor {
5974 message : String ,
6075 message_key : & ' static str ,
6176 message_args : Vec < ( & ' static str , String ) > ,
77+ ) -> Diagnostic {
78+ self . diagnostic_with_metadata (
79+ file_id,
80+ range,
81+ severity,
82+ message,
83+ message_key,
84+ VizslaDiagnosticMetadata { message_args, tags : Vec :: new ( ) } ,
85+ )
86+ }
87+
88+ fn diagnostic_with_tags (
89+ self ,
90+ file_id : FileId ,
91+ range : TextRange ,
92+ severity : DiagnosticSeverity ,
93+ message : String ,
94+ message_key : & ' static str ,
95+ tags : Vec < DiagnosticTag > ,
96+ ) -> Diagnostic {
97+ self . diagnostic_with_metadata (
98+ file_id,
99+ range,
100+ severity,
101+ message,
102+ message_key,
103+ VizslaDiagnosticMetadata { message_args : Vec :: new ( ) , tags } ,
104+ )
105+ }
106+
107+ fn diagnostic_with_metadata (
108+ self ,
109+ file_id : FileId ,
110+ range : TextRange ,
111+ severity : DiagnosticSeverity ,
112+ message : String ,
113+ message_key : & ' static str ,
114+ metadata : VizslaDiagnosticMetadata ,
62115 ) -> Diagnostic {
63116 Diagnostic {
64117 file_id,
@@ -72,7 +125,8 @@ impl VizslaDiagnosticDescriptor {
72125 severity,
73126 message,
74127 message_key : Some ( message_key) ,
75- message_args,
128+ message_args : metadata. message_args ,
129+ tags : metadata. tags ,
76130 }
77131 }
78132}
@@ -95,23 +149,37 @@ pub(crate) fn compilation_profile_diagnostics(
95149 db : & RootDb ,
96150 profile_id : CompilationProfileId ,
97151) -> Vec < Diagnostic > {
98- db. compilation_profile_diagnostics ( profile_id)
152+ let mut diagnostics = db
153+ . compilation_profile_diagnostics ( profile_id)
99154 . iter ( )
100155 . map ( |diag| slang_diagnostic ( diag. file_id , diag. source , & diag. diagnostic ) )
101- . collect ( )
156+ . collect :: < Vec < _ > > ( ) ;
157+
158+ diagnostics. extend (
159+ compilation_profile_file_ids ( db, profile_id)
160+ . into_iter ( )
161+ . flat_map ( |file_id| inactive_preprocessor_branch_diagnostics ( db, file_id) ) ,
162+ ) ;
163+ diagnostics
102164}
103165
104166pub ( crate ) fn compilation_profile_syntax_diagnostics (
105167 db : & RootDb ,
106168 profile_id : CompilationProfileId ,
107169) -> Vec < Diagnostic > {
170+ compilation_profile_file_ids ( db, profile_id)
171+ . into_iter ( )
172+ . flat_map ( |file_id| syntax_diagnostics ( db, file_id) )
173+ . collect ( )
174+ }
175+
176+ fn compilation_profile_file_ids ( db : & RootDb , profile_id : CompilationProfileId ) -> Vec < FileId > {
108177 let plan = db. compilation_plan_for_profile ( Some ( profile_id) ) ;
109178 let mut file_ids = plan. roots . clone ( ) ;
110179 file_ids. extend ( plan. include_only . iter ( ) . copied ( ) ) ;
111180 file_ids. sort_unstable_by_key ( |file_id| file_id. 0 ) ;
112181 file_ids. dedup ( ) ;
113-
114- file_ids. into_iter ( ) . flat_map ( |file_id| syntax_diagnostics ( db, file_id) ) . collect ( )
182+ file_ids
115183}
116184
117185fn syntax_diagnostics ( db : & RootDb , file_id : FileId ) -> Vec < Diagnostic > {
@@ -141,6 +209,7 @@ fn slang_diagnostic(
141209 message : diag. message . clone ( ) ,
142210 message_key : None ,
143211 message_args : Vec :: new ( ) ,
212+ tags : Vec :: new ( ) ,
144213 }
145214}
146215
@@ -157,7 +226,7 @@ pub(crate) fn diagnostics(db: &RootDb, file_id: FileId) -> Vec<Diagnostic> {
157226 }
158227
159228 let mut diagnostics = if slang_semantic_diagnostics_active ( db, file_id) {
160- Vec :: new ( )
229+ inactive_preprocessor_branch_diagnostics ( db , file_id )
161230 } else {
162231 syntax_diagnostics ( db, file_id)
163232 } ;
@@ -184,6 +253,11 @@ pub(crate) fn source_root_diagnostics(db: &RootDb, file_id: FileId) -> Vec<Diagn
184253
185254 if slang_semantic_diagnostics_active ( db, file_id) {
186255 diagnostics. extend ( compilation_diagnostics ( db, file_id) ) ;
256+ diagnostics. extend (
257+ source_root
258+ . iter ( )
259+ . flat_map ( |file_id| inactive_preprocessor_branch_diagnostics ( db, file_id) ) ,
260+ ) ;
187261 } else {
188262 for file_id in source_root. iter ( ) {
189263 diagnostics. extend ( syntax_diagnostics ( db, file_id) ) ;
@@ -214,11 +288,21 @@ pub(crate) fn source_root_role(db: &RootDb, file_id: FileId) -> SourceRootRole {
214288}
215289
216290fn vizsla_diagnostics ( db : & RootDb , file_id : FileId ) -> Vec < Diagnostic > {
217- if slang_semantic_diagnostics_active ( db, file_id ) {
291+ if ! vizsla_diagnostics_enabled ( db) {
218292 return Vec :: new ( ) ;
219293 }
220294
221- module_instantiation_resolution_diagnostics ( db, file_id)
295+ let mut diagnostics = inactive_preprocessor_branch_diagnostics ( db, file_id) ;
296+
297+ if !slang_semantic_diagnostics_active ( db, file_id) {
298+ diagnostics. extend ( module_instantiation_resolution_diagnostics ( db, file_id) ) ;
299+ }
300+
301+ diagnostics
302+ }
303+
304+ fn vizsla_diagnostics_enabled ( db : & RootDb ) -> bool {
305+ db. diagnostics_config ( ) . enabled
222306}
223307
224308fn slang_semantic_diagnostics_active ( db : & RootDb , file_id : FileId ) -> bool {
@@ -273,6 +357,28 @@ fn module_instantiation_resolution_diagnostics(db: &RootDb, file_id: FileId) ->
273357 diagnostics
274358}
275359
360+ fn inactive_preprocessor_branch_diagnostics ( db : & RootDb , file_id : FileId ) -> Vec < Diagnostic > {
361+ if !vizsla_diagnostics_enabled ( db) {
362+ return Vec :: new ( ) ;
363+ }
364+
365+ db. preproc_file_index ( file_id)
366+ . inactive_ranges
367+ . iter ( )
368+ . copied ( )
369+ . map ( |range| {
370+ INACTIVE_PREPROCESSOR_BRANCH . diagnostic_with_tags (
371+ file_id,
372+ range,
373+ DiagnosticSeverity :: Note ,
374+ "code is inactive due to preprocessor conditionals" . to_owned ( ) ,
375+ DIAGNOSTIC_INACTIVE_PREPROCESSOR_BRANCH ,
376+ vec ! [ DiagnosticTag :: Unnecessary ] ,
377+ )
378+ } )
379+ . collect ( )
380+ }
381+
276382fn ambiguous_module_instantiation_diagnostic (
277383 module_name : & str ,
278384 candidate_count : usize ,
@@ -324,8 +430,10 @@ fn to_text_range(diag: &SyntaxDiagnostic) -> TextRange {
324430mod tests {
325431 use base_db:: {
326432 change:: Change ,
433+ diagnostics_config:: DiagnosticsConfig ,
327434 project:: { CompilationProfile , CompilationProfileId , PreprocessConfig , ProjectConfig } ,
328- source_db:: SourceRootDb ,
435+ salsa:: Durability ,
436+ source_db:: { SourceDb , SourceRootDb } ,
329437 source_root:: { SourceRoot , SourceRootId , SourceRootRole } ,
330438 } ;
331439 use ide_db:: root_db:: RootDb ;
@@ -334,13 +442,21 @@ mod tests {
334442 use vfs:: { ChangeKind , ChangedFile , FileId , FileSet , VfsPath } ;
335443
336444 use super :: {
337- AMBIGUOUS_MODULE_INSTANTIATION , DiagnosticSource , diagnostics, source_root_diagnostics,
445+ AMBIGUOUS_MODULE_INSTANTIATION , DIAGNOSTIC_INACTIVE_PREPROCESSOR_BRANCH , DiagnosticSource ,
446+ DiagnosticTag , INACTIVE_PREPROCESSOR_BRANCH , diagnostics, source_root_diagnostics,
338447 } ;
339448
340449 fn db_with_files ( files : & [ ( & str , & str ) ] , configured : bool ) -> RootDb {
341450 db_with_files_in_role ( files, SourceRootRole :: Local , configured)
342451 }
343452
453+ fn disable_diagnostics ( db : & mut RootDb ) {
454+ db. set_diagnostics_config_with_durability (
455+ Arc :: new ( DiagnosticsConfig { enabled : false , ..DiagnosticsConfig :: default ( ) } ) ,
456+ Durability :: HIGH ,
457+ ) ;
458+ }
459+
344460 fn db_with_files_in_role (
345461 files : & [ ( & str , & str ) ] ,
346462 role : SourceRootRole ,
@@ -463,6 +579,40 @@ mod tests {
463579 ) ;
464580 }
465581
582+ #[ test]
583+ fn inactive_preprocessor_branch_reports_unnecessary_hint ( ) {
584+ let db = db_with_files (
585+ & [ ( "/top.sv" , "`ifdef USE_IMPL\n logic active;\n `else\n logic inactive;\n `endif\n " ) ] ,
586+ false ,
587+ ) ;
588+
589+ let diagnostics = diagnostics ( & db, FileId ( 0 ) ) ;
590+ let inactive = diagnostics
591+ . iter ( )
592+ . find ( |diag| diag. name == INACTIVE_PREPROCESSOR_BRANCH . name )
593+ . expect ( "expected inactive preprocessor branch diagnostic" ) ;
594+
595+ assert_eq ! ( inactive. severity, syntax:: DiagnosticSeverity :: Note ) ;
596+ assert_eq ! ( inactive. tags, vec![ DiagnosticTag :: Unnecessary ] ) ;
597+ assert_eq ! ( inactive. message_key, Some ( DIAGNOSTIC_INACTIVE_PREPROCESSOR_BRANCH ) ) ;
598+ }
599+
600+ #[ test]
601+ fn inactive_preprocessor_branch_respects_global_diagnostics_switch ( ) {
602+ let mut db = db_with_files (
603+ & [ ( "/top.sv" , "`ifdef USE_IMPL\n logic active;\n `else\n logic inactive;\n `endif\n " ) ] ,
604+ false ,
605+ ) ;
606+ disable_diagnostics ( & mut db) ;
607+
608+ let diagnostics = diagnostics ( & db, FileId ( 0 ) ) ;
609+
610+ assert ! (
611+ diagnostics. is_empty( ) ,
612+ "global diagnostics switch must suppress Vizsla inactive diagnostics: {diagnostics:?}"
613+ ) ;
614+ }
615+
466616 #[ test]
467617 fn semantic_diagnostics_include_other_workspace_files ( ) {
468618 let db = db_with_files (
0 commit comments