1use crate::analysis::DesignRoot;
8use crate::analysis::Library;
9use crate::analysis::LockedUnit;
10use crate::ast::search::Search;
11use crate::ast::search::SearchState;
12use crate::ast::search::Searcher;
13use crate::ast::UnitId;
14use crate::data::error_codes::ErrorCode;
15use crate::data::DiagnosticHandler;
16use crate::data::Symbol;
17use crate::named_entity::{HasEntityId, Reference, Related};
18use crate::syntax::TokenAccess;
19use crate::AnyEntKind;
20use crate::Config;
21use crate::Design;
22use crate::Diagnostic;
23use crate::EntRef;
24use crate::Overloaded;
25use crate::SrcPos;
26use fnv::FnvHashMap;
27use fnv::FnvHashSet;
28use itertools::Itertools;
29
30struct DeadCodeSearcher<'a> {
31 root: &'a DesignRoot,
32 references: FnvHashSet<EntRef<'a>>,
33 declarations: FnvHashSet<EntRef<'a>>,
34}
35
36impl<'a> DeadCodeSearcher<'a> {
37 fn new(root: &'a DesignRoot) -> Self {
38 DeadCodeSearcher {
39 root,
40 references: Default::default(),
41 declarations: Default::default(),
42 }
43 }
44}
45
46impl Searcher for DeadCodeSearcher<'_> {
47 fn search_pos_with_ref(
48 &mut self,
49 _ctx: &dyn TokenAccess,
50 _: &SrcPos,
51 reference: &Reference,
52 ) -> SearchState {
53 if let Some(id) = reference.get() {
54 let ent = self.root.get_ent(id);
55 self.references.insert(ent);
56
57 if let Related::DeclaredBy(other) = ent.related {
58 self.references.insert(other);
59 }
60 };
61 SearchState::NotFinished
62 }
63 fn search_decl(
64 &mut self,
65 _ctx: &dyn TokenAccess,
66 decl: crate::ast::search::FoundDeclaration<'_>,
67 ) -> SearchState {
68 if let Some(id) = decl.ent_id() {
69 self.declarations.insert(self.root.get_ent(id));
70 }
71 SearchState::NotFinished
72 }
73}
74
75fn search_unit(unit: &LockedUnit, searcher: &mut impl Searcher) {
76 let _ = unit.unit.write().search(&unit.tokens, searcher);
77}
78
79fn is_package_header(ent: EntRef<'_>) -> bool {
80 matches!(
81 ent.kind(),
82 AnyEntKind::Design(Design::Package(..)) | AnyEntKind::Design(Design::UninstPackage(..))
83 )
84}
85
86fn is_interface(ent: EntRef<'_>) -> bool {
87 matches!(
88 ent.kind(),
89 AnyEntKind::Object(o) if o.iface.is_some())
90 || matches!(
91 ent.kind(),
92 AnyEntKind::Overloaded(Overloaded::InterfaceSubprogram(..))
93 )
94}
95
96fn can_be_locally_unused(ent: EntRef<'_>) -> bool {
97 if let Related::DeclaredBy(related) = ent.related {
98 if !can_be_locally_unused(related) {
99 return false;
100 }
101 }
102
103 if matches!(ent.kind(), AnyEntKind::Design(_)) {
104 return false;
105 }
106
107 if matches!(
109 ent.kind(),
110 AnyEntKind::Concurrent(..) | AnyEntKind::Sequential(_)
111 ) {
112 return false;
113 }
114
115 if matches!(ent.kind(), AnyEntKind::LoopParameter(..)) {
117 return false;
118 }
119
120 if matches!(ent.kind(), AnyEntKind::ElementDeclaration(..)) {
122 return false;
123 }
124
125 if matches!(
127 ent.kind(),
128 AnyEntKind::Overloaded(Overloaded::EnumLiteral(..))
129 ) {
130 return false;
131 }
132
133 if let Some(parent) = ent.parent {
134 if is_package_header(parent) && !is_interface(ent) {
136 return false;
137 }
138
139 if matches!(parent.kind(), AnyEntKind::Component(..)) {
141 return false;
142 }
143
144 if matches!(parent.kind(), AnyEntKind::Type(crate::Type::Protected(..))) {
146 if let Some(grand_parent) = parent.parent {
147 if is_package_header(grand_parent) {
148 return false;
149 }
150 }
151 }
152
153 if matches!(
155 parent.kind(),
156 AnyEntKind::Overloaded(Overloaded::SubprogramDecl(..))
157 ) {
158 return false;
159 }
160 }
161
162 true
163}
164
165fn find_unused_declarations<'a>(
167 root: &'a DesignRoot,
168 lib: &Library,
169 primary_unit_name: &Symbol,
170) -> FnvHashSet<EntRef<'a>> {
171 let mut searcher = DeadCodeSearcher::new(root);
172
173 if let Some(unit) = lib.primary_unit(primary_unit_name) {
174 search_unit(unit, &mut searcher);
175 }
176
177 for unit in lib.secondary_units(primary_unit_name) {
178 search_unit(unit, &mut searcher);
179 }
180
181 searcher
182 .declarations
183 .difference(&searcher.references)
184 .filter(|ent| {
185 if let Related::DeclaredBy(other) = ent.related {
186 !searcher.references.contains(other)
189 } else {
190 true
191 }
192 })
193 .filter(|ent| can_be_locally_unused(ent))
194 .copied()
195 .collect()
196}
197
198#[derive(Default)]
200pub(crate) struct UnusedDeclarationsLinter {
201 diagnostics: FnvHashMap<(Symbol, Symbol), Vec<Diagnostic>>,
203}
204
205impl UnusedDeclarationsLinter {
206 pub fn lint(
207 &mut self,
208 root: &DesignRoot,
209 config: &Config,
210 analyzed_units: &[UnitId],
211 diagnostics: &mut dyn DiagnosticHandler,
212 ) {
213 for unit in analyzed_units {
215 let key = (unit.library_name().clone(), unit.primary_name().clone());
216 self.diagnostics.remove(&key);
217 }
218
219 self.diagnostics.retain(|(library_name, primary_name), _| {
221 if let Some(library) = root.get_lib(library_name) {
222 if library.primary_unit(primary_name).is_some() {
223 return true;
224 }
225 }
226 false
227 });
228
229 for unit in analyzed_units {
230 let key = (unit.library_name().clone(), unit.primary_name().clone());
231
232 if let Some(library) = root.get_lib(unit.library_name()) {
233 self.diagnostics.entry(key).or_insert_with(|| {
234 find_unused_declarations(root, library, unit.primary_name())
235 .into_iter()
236 .filter_map(|ent| {
237 Some(Diagnostic::new(
238 ent.decl_pos()?,
239 format!("Unused declaration of {}", ent.describe()),
240 ErrorCode::Unused,
241 ))
242 })
243 .collect_vec()
244 });
245 }
246 }
247
248 for ((library_name, _), unit_diagnostics) in self.diagnostics.iter() {
249 if let Some(library_config) = config.get_library(&library_name.name_utf8()) {
250 if !library_config.is_third_party {
251 diagnostics.append(unit_diagnostics.iter().cloned());
252 }
253 }
254 }
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::analysis::tests::LibraryBuilder;
262 use crate::syntax::test::check_no_diagnostics;
263 use crate::syntax::test::Code;
264
265 fn get_ent(root: &DesignRoot, code: Code) -> EntRef<'_> {
266 root.search_reference(code.source(), code.start()).unwrap()
267 }
268
269 fn check_unused(got: FnvHashSet<EntRef<'_>>, expected: FnvHashSet<EntRef<'_>>) {
270 fn fmt_ent(ent: EntRef<'_>) -> String {
271 format!(
272 "{}, line {}",
273 ent.describe(),
274 ent.decl_pos().unwrap().start().line
275 )
276 }
277
278 let mut fail = false;
279 for ent in expected.difference(&got) {
280 println!("Expected {}", fmt_ent(ent));
281 fail = true;
282 }
283 for ent in got.difference(&expected) {
284 println!("Got unexpected {}", fmt_ent(ent));
285 fail = true;
286 }
287
288 if fail {
289 panic!("Check unused failed");
290 }
291 }
292
293 #[test]
294 fn unused_signal() {
295 let mut builder = LibraryBuilder::new();
296
297 let code = builder.code(
298 "libname",
299 "
300entity ent is
301end entity;
302
303architecture a of ent is
304 signal unused : boolean;
305 signal used : boolean;
306begin
307 used <= true;
308end architecture;",
309 );
310
311 let (root, diagnostics) = builder.get_analyzed_root();
312 check_no_diagnostics(&diagnostics);
313
314 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
315 let ent = get_ent(&root, code.s1("unused"));
316
317 check_unused(
318 find_unused_declarations(&root, lib, &root.symbol_utf8("ent")),
319 FnvHashSet::from_iter(vec![ent]),
320 )
321 }
322
323 #[test]
324 fn unused_ports() {
325 let mut builder = LibraryBuilder::new();
326
327 let code = builder.code(
328 "libname",
329 "
330entity ent is
331 port (
332 used : out boolean;
333 unused : out boolean
334 );
335end entity;
336
337architecture a of ent is
338begin
339 used <= true;
340end architecture;",
341 );
342
343 let (root, diagnostics) = builder.get_analyzed_root();
344 check_no_diagnostics(&diagnostics);
345
346 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
347 let ent = get_ent(&root, code.s1("unused"));
348
349 check_unused(
350 find_unused_declarations(&root, lib, &root.symbol_utf8("ent")),
351 FnvHashSet::from_iter(vec![ent]),
352 )
353 }
354
355 #[test]
358 fn package_headers_are_public_and_will_never_be_unused() {
359 let mut builder = LibraryBuilder::new();
360
361 let code = builder.code(
362 "libname",
363 "
364package pkg is
365 constant unknown : boolean := false;
366end package;
367
368package body pkg is
369 constant unused : boolean := false;
370end package body;
371 ",
372 );
373
374 let (root, diagnostics) = builder.get_analyzed_root();
375 check_no_diagnostics(&diagnostics);
376
377 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
378 let ent = get_ent(&root, code.s1("unused"));
379
380 check_unused(
381 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
382 FnvHashSet::from_iter(vec![ent]),
383 )
384 }
385
386 #[test]
387 fn generic_package_headers_are_public_and_will_never_be_unused() {
388 let mut builder = LibraryBuilder::new();
389
390 let code = builder.code(
391 "libname",
392 "
393package pkg is
394 generic (arg : boolean; unused0 : natural);
395 constant unknown : boolean := arg;
396end package;
397
398package body pkg is
399 constant unused1 : boolean := false;
400end package body;
401 ",
402 );
403
404 let (root, diagnostics) = builder.get_analyzed_root();
405 check_no_diagnostics(&diagnostics);
406
407 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
408
409 check_unused(
410 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
411 FnvHashSet::from_iter(vec![
412 get_ent(&root, code.s1("unused0")),
413 get_ent(&root, code.s1("unused1")),
414 ]),
415 )
416 }
417
418 #[test]
420 fn enum_variants_are_never_unused() {
421 let mut builder = LibraryBuilder::new();
422
423 let code = builder.code(
424 "libname",
425 "
426package pkg is
427 type enum_pub_t is (v1, v2);
428end package;
429
430package body pkg is
431 type enum_priv_t is (v3, v4);
432end package body;
433 ",
434 );
435
436 let (root, diagnostics) = builder.get_analyzed_root();
437 check_no_diagnostics(&diagnostics);
438
439 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
440
441 check_unused(
442 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
443 FnvHashSet::from_iter(vec![get_ent(&root, code.s1("enum_priv_t"))]),
444 )
445 }
446
447 #[test]
449 fn record_elements_are_never_unused() {
450 let mut builder = LibraryBuilder::new();
451
452 let code = builder.code(
453 "libname",
454 "
455package pkg is
456 type rec_pub_t is record
457 elem : natural;
458 end record;
459end package;
460
461package body pkg is
462 type rec_priv_t is record
463 elem : natural;
464 end record;
465end package body;
466 ",
467 );
468
469 let (root, diagnostics) = builder.get_analyzed_root();
470 check_no_diagnostics(&diagnostics);
471
472 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
473
474 check_unused(
475 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
476 FnvHashSet::from_iter(vec![get_ent(&root, code.s1("rec_priv_t"))]),
477 )
478 }
479
480 #[test]
481 fn protected_type_methods_are_indirectly_public() {
482 let mut builder = LibraryBuilder::new();
483
484 builder.code(
485 "libname",
486 "
487package pkg is
488 type prot_t is protected
489 procedure method;
490 end protected;
491end package;
492
493package body pkg is
494 type prot_t is protected body
495 procedure method is
496 begin
497 end;
498 end protected body;
499end package body;
500 ",
501 );
502
503 let (root, diagnostics) = builder.get_analyzed_root();
504 check_no_diagnostics(&diagnostics);
505
506 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
507
508 check_unused(
509 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
510 FnvHashSet::default(),
511 )
512 }
513
514 #[test]
515 fn labels_are_not_unused() {
516 let mut builder = LibraryBuilder::new();
517
518 builder.code(
519 "libname",
520 "
521entity ent2 is
522end entity;
523
524architecture a of ent2 is
525begin
526end architecture;
527
528entity ent is
529end entity;
530
531architecture a of ent is
532begin
533 main : process
534 begin
535 l0: loop
536 end loop;
537 end process;
538
539 inst : entity work.ent2;
540end architecture;",
541 );
542
543 let (root, diagnostics) = builder.get_analyzed_root();
544 check_no_diagnostics(&diagnostics);
545
546 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
547 check_unused(
548 find_unused_declarations(&root, lib, &root.symbol_utf8("ent")),
549 FnvHashSet::default(),
550 )
551 }
552
553 #[test]
554 fn loop_parameters_are_not_unused() {
555 let mut builder = LibraryBuilder::new();
556
557 builder.code(
558 "libname",
559 "
560entity ent is
561end entity;
562
563architecture a of ent is
564begin
565 main : process
566 begin
567 for i in 0 to 3 loop
568 end loop;
569 end process;
570
571 gen: for j in 0 to 3 generate
572 end generate;
573
574end architecture;",
575 );
576
577 let (root, diagnostics) = builder.get_analyzed_root();
578 check_no_diagnostics(&diagnostics);
579
580 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
581 check_unused(
582 find_unused_declarations(&root, lib, &root.symbol_utf8("ent")),
583 FnvHashSet::default(),
584 )
585 }
586
587 #[test]
588 fn subprogram_declaration_arguments_are_not_unused() {
589 let mut builder = LibraryBuilder::new();
590
591 builder.code(
592 "libname",
593 "
594package pkg is
595 procedure myproc(arg : natural);
596end package;
597
598package body pkg is
599 procedure myproc(arg : natural) is
600 begin
601 report to_string(arg);
602 end;
603end package body;
604 ",
605 );
606
607 let (root, diagnostics) = builder.get_analyzed_root();
608 check_no_diagnostics(&diagnostics);
609
610 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
611
612 check_unused(
613 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
614 FnvHashSet::default(),
615 )
616 }
617
618 #[test]
619 fn subprogram_use_between_declaration_and_definition() {
620 let mut builder = LibraryBuilder::new();
621
622 let code = builder.code(
623 "libname",
624 "
625package pkg is
626 procedure pub_proc;
627end package;
628
629package body pkg is
630
631 procedure priv_proc;
632
633 procedure pub_proc is
634 begin
635 priv_proc;
636 end procedure;
637
638 procedure priv_proc is
639 begin
640 end;
641
642 procedure unused;
643 procedure unused is
644 begin
645 end;
646
647end package body;
648 ",
649 );
650
651 let (root, diagnostics) = builder.get_analyzed_root();
652 check_no_diagnostics(&diagnostics);
653
654 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
655
656 check_unused(
657 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
658 FnvHashSet::from_iter(vec![
659 get_ent(&root, code.s("unused", 1)),
660 get_ent(&root, code.s("unused", 2)),
661 ]),
662 )
663 }
664
665 #[test]
666 fn component_interface_is_not_unused() {
667 let mut builder = LibraryBuilder::new();
668
669 builder.code(
670 "libname",
671 "
672package pkg is
673 component comp is
674 generic (g : natural);
675 port (p : natural);
676 end component;
677end package;
678 ",
679 );
680
681 let (root, diagnostics) = builder.get_analyzed_root();
682 check_no_diagnostics(&diagnostics);
683
684 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
685
686 check_unused(
687 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
688 FnvHashSet::default(),
689 )
690 }
691
692 #[test]
693 fn protected_type_methods() {
694 let mut builder = LibraryBuilder::new();
695
696 let code = builder.code(
697 "libname",
698 "
699package pkg is
700 procedure public;
701end package;
702
703package body pkg is
704 type prot_t is protected
705 procedure method;
706
707 procedure unused;
708 end protected;
709
710 type prot_t is protected body
711 procedure method is
712 begin
713 end;
714
715 procedure unused is
716 begin
717 end;
718 end protected body;
719
720 procedure public is
721 variable p : prot_t;
722 begin
723 p.method;
724 end;
725end package body;
726 ",
727 );
728
729 let (root, diagnostics) = builder.get_analyzed_root();
730 check_no_diagnostics(&diagnostics);
731
732 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
733
734 check_unused(
735 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
736 FnvHashSet::from_iter(vec![
737 get_ent(&root, code.s("unused", 1)),
738 get_ent(&root, code.s("unused", 2)),
739 ]),
740 )
741 }
742
743 #[test]
744 fn end_label_is_not_valid_use() {
745 let mut builder = LibraryBuilder::new();
746
747 let code = builder.code(
748 "libname",
749 "
750package pkg is
751end package;
752
753package body pkg is
754 procedure unused is
755 begin
756 end unused;
757end package body;
758 ",
759 );
760
761 let (root, diagnostics) = builder.get_analyzed_root();
762 check_no_diagnostics(&diagnostics);
763
764 let lib = root.get_lib(&root.symbol_utf8("libname")).unwrap();
765
766 check_unused(
767 find_unused_declarations(&root, lib, &root.symbol_utf8("pkg")),
768 FnvHashSet::from_iter(vec![get_ent(&root, code.s1("unused"))]),
769 )
770 }
771}