1use std::collections::{HashMap, HashSet};
2
3use gobject_ast::model::{
4 AssignmentOp, Designator, Expression, FileModel, GObjectTypeKind, Parameter,
5 PreprocessorDirective, SourceLocation, Statement, StructField, TopLevelItem, TypeDefItem,
6 TypeInfo, VariableDecl,
7};
8
9use crate::{
10 ast_context::AstContext,
11 config::Config,
12 rules::{Category, Rule, Violation},
13};
14
15pub struct DeadCode;
16
17impl Rule for DeadCode {
18 fn name(&self) -> &'static str {
19 "dead_code"
20 }
21
22 fn description(&self) -> &'static str {
23 "Detect unused internal functions and types"
24 }
25
26 fn long_description(&self) -> Option<&'static str> {
27 Some(
28 "Detects internal functions and types that are never used anywhere in the codebase. \
29 For functions: tracks both direct calls and function pointer usage (e.g., callbacks). \
30 For types: tracks usage in variable declarations, casts, sizeof, and GObject macros. \
31 Only reports items in private headers (not installed by meson) and static functions/types \
32 defined in .c files.",
33 )
34 }
35
36 fn category(&self) -> Category {
37 Category::Suspicious
38 }
39
40 fn requires_meson(&self) -> bool {
41 true
42 }
43
44 fn opt_in(&self) -> bool {
45 true
46 }
47
48 fn opt_in_reason(&self) -> Option<&'static str> {
49 Some(
50 "May produce false positives due to fundamental limitations of static analysis without a preprocessor or full call graph",
51 )
52 }
53
54 fn check_all(
55 &self,
56 ast_context: &AstContext,
57 _config: &Config,
58 violations: &mut Vec<Violation>,
59 ) {
60 if !ast_context.has_public_private_info() {
61 return;
62 }
63
64 let (func_defs, func_decls) = collect_function_maps(ast_context);
65 let refs = collect_all_refs(ast_context);
66
67 let (type_defs, field_defs, enum_value_defs) = collect_private_defs(ast_context);
68
69 self.report_function_violations(
70 ast_context,
71 &func_defs,
72 &func_decls,
73 &refs.func,
74 violations,
75 );
76 self.report_type_violations(ast_context, &type_defs, &refs.types, violations);
77 self.report_enum_value_violations(&enum_value_defs, &refs.func, violations);
78 self.report_field_violations(
79 ast_context,
80 &field_defs,
81 &refs.field_qualified,
82 &refs.field_unqualified,
83 violations,
84 );
85 }
86}
87
88fn collect_all_refs(ast_context: &AstContext) -> AllRefs {
89 let mut func_refs: HashSet<String> = HashSet::new();
90 let mut type_refs: HashSet<String> = HashSet::new();
91 let mut field_qualified: HashMap<String, HashSet<String>> = HashMap::new();
92 let mut field_unqualified: HashSet<String> = HashSet::new();
93 let empty_map: HashMap<&str, String> = HashMap::new();
94
95 for (_path, file) in ast_context.iter_all_files() {
96 for func in file.iter_function_definitions() {
97 collect_type_ref(&func.return_type, &mut type_refs);
98 for param in &func.parameters {
99 if let Parameter::Regular { type_info, .. } = param {
100 collect_type_ref(type_info, &mut type_refs);
101 }
102 }
103
104 let type_map: HashMap<&str, String> = func
105 .local_var_types()
106 .into_iter()
107 .filter(|(_, ti)| !ti.base_type.is_empty())
108 .map(|(name, ti)| {
109 (
110 name,
111 ast_context
112 .type_aliases()
113 .canonical(&ti.base_type)
114 .to_owned(),
115 )
116 })
117 .collect();
118
119 for stmt in &func.body_statements {
120 collect_func_refs_from_stmt(stmt, &mut func_refs);
121 collect_type_refs_from_stmt(stmt, &mut type_refs);
122 collect_field_refs_from_stmt(
123 ast_context,
124 stmt,
125 &type_map,
126 &mut field_qualified,
127 &mut field_unqualified,
128 );
129 }
130 }
131
132 for func in file.iter_function_declarations() {
133 collect_type_ref(&func.return_type, &mut type_refs);
134 for param in &func.parameters {
135 if let Parameter::Regular { type_info, .. } = param {
136 collect_type_ref(type_info, &mut type_refs);
137 }
138 }
139 }
140
141 for item in file.iter_all_items() {
142 if let TopLevelItem::Declaration(decl) = item {
143 collect_func_refs_from_decl(decl, &mut func_refs);
144 collect_field_refs_from_decl(
145 ast_context,
146 decl,
147 &empty_map,
148 &mut field_qualified,
149 &mut field_unqualified,
150 );
151 }
152 collect_type_refs_from_top_level_item(item, &mut type_refs);
153 match item {
154 TopLevelItem::Preprocessor(PreprocessorDirective::Define {
155 value: Some(value),
156 ..
157 }) => {
158 extract_function_calls_from_text(value.as_raw_str(), &mut func_refs);
159 extract_field_refs_from_text(value.as_raw_str(), &mut field_unqualified);
160 }
161 TopLevelItem::Preprocessor(
162 PreprocessorDirective::AutoptrCleanupFunc {
163 type_name,
164 cleanup_function,
165 ..
166 }
167 | PreprocessorDirective::AutoCleanupClearFunc {
168 type_name,
169 cleanup_function,
170 ..
171 },
172 ) => {
173 func_refs.insert(cleanup_function.clone());
174 type_refs.insert(type_name.clone());
175 }
176 _ => {}
177 }
178 }
179
180 collect_gobject_implicit_refs(file, &mut func_refs, &mut type_refs);
181
182 for enum_info in file.iter_all_enums() {
183 for value in &enum_info.values {
184 if let Some(expr) = &value.value_expr {
185 func_refs.extend(expr.collect_identifiers());
186 }
187 }
188 }
189 }
190
191 AllRefs {
192 func: func_refs,
193 types: type_refs,
194 field_qualified,
195 field_unqualified,
196 }
197}
198
199type FuncDefMap<'a> = HashMap<&'a str, Vec<(&'a std::path::Path, bool, &'a SourceLocation)>>;
200type FuncDeclMap<'a> = HashMap<&'a str, Vec<(&'a std::path::Path, &'a SourceLocation)>>;
201
202fn collect_function_maps<'a>(ast_context: &'a AstContext) -> (FuncDefMap<'a>, FuncDeclMap<'a>) {
203 let mut defs: FuncDefMap = HashMap::new();
204 let mut decls: FuncDeclMap = HashMap::new();
205
206 for (path, file) in ast_context.iter_all_files() {
207 let ext = path.extension().and_then(|e| e.to_str());
208 if ext == Some("c") {
209 for func in file.iter_function_definitions() {
210 defs.entry(func.name.as_str()).or_default().push((
211 path,
212 func.is_static,
213 &func.location,
214 ));
215 }
216 }
217 if ext == Some("h") {
218 for func in file.iter_function_declarations() {
219 decls
220 .entry(func.name.as_str())
221 .or_default()
222 .push((path, &func.location));
223 }
224 }
225 }
226
227 (defs, decls)
228}
229
230struct AllRefs {
231 func: HashSet<String>,
232 types: HashSet<String>,
233 field_qualified: HashMap<String, HashSet<String>>,
234 field_unqualified: HashSet<String>,
235}
236
237type TypeDefMap<'a> = HashMap<&'a str, Vec<(&'a std::path::Path, &'a SourceLocation)>>;
238
239type EnumValueDefMap<'a> = HashMap<&'a str, Vec<(&'a std::path::Path, &'a SourceLocation)>>;
240
241type FieldDefMap<'a> = HashMap<&'a str, Vec<(&'a std::path::Path, &'a SourceLocation, &'a str)>>;
242
243fn collect_private_defs<'a>(
244 ast_context: &'a AstContext,
245) -> (TypeDefMap<'a>, FieldDefMap<'a>, EnumValueDefMap<'a>) {
246 let mut type_defs: TypeDefMap = HashMap::new();
247 let mut field_defs: FieldDefMap = HashMap::new();
248 let mut enum_value_defs: EnumValueDefMap = HashMap::new();
249
250 for (path, file) in ast_context.iter_private_files() {
251 for item in file.iter_all_items() {
252 match item {
253 TopLevelItem::TypeDefinition(TypeDefItem::Struct { name, location, .. }) => {
254 type_defs
255 .entry(name.as_str())
256 .or_default()
257 .push((path, location));
258 }
259 TopLevelItem::TypeDefinition(TypeDefItem::Typedef { name, location, .. }) => {
260 type_defs
261 .entry(name.as_str())
262 .or_default()
263 .push((path, location));
264 }
265 _ => {}
266 }
267
268 match item {
269 TopLevelItem::TypeDefinition(td @ TypeDefItem::Struct { name, fields, .. })
270 if !td.is_vtable_struct() =>
271 {
272 collect_fields_into_defs(fields, name, path, &mut field_defs);
273 }
274 TopLevelItem::TypeDefinition(
275 td @ TypeDefItem::Typedef {
276 name,
277 struct_fields,
278 ..
279 },
280 ) if !td.is_vtable_struct() => {
281 collect_fields_into_defs(struct_fields, name, path, &mut field_defs);
282 }
283 _ => {}
284 }
285 }
286
287 for enum_info in file.iter_all_enums() {
288 for value in &enum_info.values {
289 if value.is_prop_0()
290 || value.is_prop_last()
291 || value.is_signal_last()
292 || (value.value == Some(0) && value.value_expr.is_some())
293 {
294 continue;
295 }
296 enum_value_defs
297 .entry(value.name.as_str())
298 .or_default()
299 .push((path, &value.location));
300 }
301 }
302 }
303
304 (type_defs, field_defs, enum_value_defs)
305}
306
307fn collect_fields_into_defs<'a>(
308 fields: &'a [StructField],
309 struct_name: &'a str,
310 path: &'a std::path::Path,
311 defs: &mut FieldDefMap<'a>,
312) {
313 let last_non_reserved = fields.iter().rposition(|f| !f.is_reserved());
314
315 for (idx, field) in fields.iter().enumerate() {
316 let Some(field_name) = &field.field_name else {
317 continue;
318 };
319 if !field.inner_fields.is_empty() {
320 collect_fields_into_defs(&field.inner_fields, struct_name, path, defs);
321 continue;
322 }
323 if idx == 0 && field_name.starts_with("parent") {
324 continue;
325 }
326 if field.is_reserved() && last_non_reserved.is_none_or(|last| idx > last) {
327 continue;
328 }
329 defs.entry(field_name.as_str())
330 .or_default()
331 .push((path, &field.location, struct_name));
332 }
333}
334
335fn collect_gobject_implicit_refs(
336 file: &FileModel,
337 func_refs: &mut HashSet<String>,
338 type_refs: &mut HashSet<String>,
339) {
340 for gt in file.iter_all_gobject_types() {
341 if gt.is_interface() {
342 func_refs.insert(gt.default_init_function_name());
343 } else if !matches!(
344 gt.kind,
345 GObjectTypeKind::DefineEnum { .. }
346 | GObjectTypeKind::DefineFlags { .. }
347 | GObjectTypeKind::DefineQuark { .. }
348 | GObjectTypeKind::DefineCustom { .. }
349 ) {
350 func_refs.insert(gt.class_init_function_name());
351 func_refs.insert(gt.init_function_name());
352 }
353
354 for iface in >.interfaces {
355 if let Some(init_func) = &iface.init_function {
356 func_refs.insert(init_func.clone());
357 }
358 }
359
360 if let GObjectTypeKind::DefineBoxed {
361 copy_func,
362 free_func,
363 } = >.kind
364 {
365 func_refs.insert(copy_func.clone());
366 func_refs.insert(free_func.clone());
367 }
368
369 if let Some(quark_fn) = gt.kind.quark_function_name() {
370 func_refs.insert(quark_fn);
371 }
372
373 if gt.has_private {
374 let priv_name = format!("{}Private", gt.type_name);
375 type_refs.insert(format!("_{priv_name}"));
376 type_refs.insert(priv_name);
377 }
378
379 let tn = >.type_name;
380 type_refs.insert(format!("_{tn}"));
381 if gt.is_interface() {
382 type_refs.insert(format!("_{tn}Interface"));
383 } else if !matches!(
384 gt.kind,
385 GObjectTypeKind::DefineBoxed { .. }
386 | GObjectTypeKind::DefineEnum { .. }
387 | GObjectTypeKind::DefineFlags { .. }
388 | GObjectTypeKind::DefineQuark { .. }
389 | GObjectTypeKind::DefineCustom { .. }
390 ) {
391 type_refs.insert(format!("_{tn}Class"));
392 }
393
394 for stmt in >.code_block_statements {
395 collect_func_refs_from_stmt(stmt, func_refs);
396 collect_type_refs_from_stmt(stmt, type_refs);
397 }
398 }
399}
400
401fn collect_type_ref(type_info: &TypeInfo, refs: &mut HashSet<String>) {
402 if !type_info.base_type.is_empty() {
403 refs.insert(type_info.base_type.clone());
404 }
405}
406
407fn collect_type_refs_from_stmt(stmt: &Statement, refs: &mut HashSet<String>) {
408 stmt.walk(&mut |s| {
409 if let Statement::Declaration(decl) = s {
410 collect_type_ref(&decl.type_info, refs);
411 }
412 });
413 stmt.walk_expressions(&mut |expr| {
414 expr.walk(&mut |e| match e {
415 Expression::Cast(cast) => collect_type_ref(&cast.type_info, refs),
416 Expression::Sizeof(sizeof) => {
417 if let Some(name) = sizeof.type_name()
418 && !name.is_empty()
419 {
420 refs.insert(name.to_owned());
421 }
422 }
423 _ => {}
424 });
425 });
426}
427
428fn collect_type_refs_from_top_level_item(item: &TopLevelItem, refs: &mut HashSet<String>) {
429 match item {
430 TopLevelItem::Declaration(decl) => collect_type_ref(&decl.type_info, refs),
431 TopLevelItem::Expression(_) => {}
432 TopLevelItem::TypeDefinition(TypeDefItem::Typedef {
433 target,
434 struct_fields,
435 ..
436 }) => {
437 if let Some(target_type) = target.as_type() {
438 collect_type_ref(target_type, refs);
439 }
440 collect_type_refs_from_fields(struct_fields, refs);
441 }
442 TopLevelItem::TypeDefinition(TypeDefItem::Struct { fields, .. }) => {
443 collect_type_refs_from_fields(fields, refs);
444 }
445 _ => {}
446 }
447}
448
449fn collect_type_refs_from_fields(fields: &[StructField], refs: &mut HashSet<String>) {
450 for field in fields {
451 field.walk(&mut |f| collect_type_ref(&f.field_type, refs));
452 }
453}
454
455fn collect_func_refs_from_stmt(stmt: &Statement, refs: &mut HashSet<String>) {
456 stmt.walk_expressions(&mut |expr| refs.extend(expr.collect_identifiers()));
457 stmt.walk(&mut |s| {
458 if let Statement::Preprocessor(PreprocessorDirective::Define {
459 value: Some(value), ..
460 }) = s
461 {
462 extract_function_calls_from_text(value.as_raw_str(), refs);
463 }
464 });
465}
466
467fn collect_func_refs_from_decl(decl: &VariableDecl, refs: &mut HashSet<String>) {
468 if let Some(init) = &decl.initializer {
469 init.walk(&mut |e| refs.extend(e.collect_identifiers()));
470 }
471 if let Some(size) = &decl.array_size {
472 size.walk(&mut |e| refs.extend(e.collect_identifiers()));
473 }
474}
475
476fn collect_field_refs_from_decl(
477 ast_context: &AstContext,
478 decl: &VariableDecl,
479 type_map: &HashMap<&str, String>,
480 qualified: &mut HashMap<String, HashSet<String>>,
481 unqualified: &mut HashSet<String>,
482) {
483 if let Some(init) = &decl.initializer {
484 collect_field_reads_impl(ast_context, init, false, type_map, qualified, unqualified);
485 }
486 if let Some(size) = &decl.array_size {
487 collect_field_reads_impl(ast_context, size, false, type_map, qualified, unqualified);
488 }
489}
490
491fn extract_function_calls_from_text(text: &str, refs: &mut HashSet<String>) {
492 let mut chars = text.chars().peekable();
493 let mut ident = String::new();
494
495 while let Some(c) = chars.next() {
496 if c.is_alphanumeric() || c == '_' {
497 ident.push(c);
498 } else {
499 if !ident.is_empty() {
500 if c == '(' {
501 refs.insert(ident.clone());
502 } else if c.is_whitespace() || c == '\\' {
503 let mut lookahead = chars.clone();
504 while let Some(&nc) = lookahead.peek() {
505 if nc.is_whitespace() || nc == '\\' {
506 lookahead.next();
507 } else {
508 if nc == '(' {
509 refs.insert(ident.clone());
510 }
511 break;
512 }
513 }
514 }
515 ident.clear();
516 }
517 }
518 }
519}
520
521fn collect_field_refs_from_stmt(
522 ast_context: &AstContext,
523 stmt: &Statement,
524 type_map: &HashMap<&str, String>,
525 qualified: &mut HashMap<String, HashSet<String>>,
526 unqualified: &mut HashSet<String>,
527) {
528 stmt.walk_expressions(&mut |expr| {
529 collect_field_reads_impl(ast_context, expr, false, type_map, qualified, unqualified);
530 });
531
532 stmt.walk(&mut |s| {
533 if let Statement::Preprocessor(PreprocessorDirective::Define {
534 value: Some(value), ..
535 }) = s
536 {
537 extract_field_refs_from_text(value.as_raw_str(), unqualified);
538 }
539 });
540}
541
542fn collect_field_reads_impl(
545 ast_context: &AstContext,
546 expr: &Expression,
547 is_write_lhs: bool,
548 type_map: &HashMap<&str, String>,
549 qualified: &mut HashMap<String, HashSet<String>>,
550 unqualified: &mut HashSet<String>,
551) {
552 match expr {
553 Expression::FieldAccess(f) => {
554 if !is_write_lhs {
555 if let Expression::Identifier(id) = f.base.as_ref()
556 && let Some(type_name) = type_map.get(id.name.as_str())
557 {
558 ast_context
559 .type_aliases()
560 .insert_qualified(type_name, &f.field, qualified);
561 } else {
562 unqualified.insert(f.field.clone());
563 }
564 }
565 collect_field_reads_impl(
566 ast_context,
567 &f.base,
568 false,
569 type_map,
570 qualified,
571 unqualified,
572 );
573 }
574 Expression::Assignment(assign) => {
575 let lhs_is_write = assign.operator == AssignmentOp::Assign;
576 collect_field_reads_impl(
577 ast_context,
578 &assign.lhs,
579 lhs_is_write,
580 type_map,
581 qualified,
582 unqualified,
583 );
584 collect_field_reads_impl(
585 ast_context,
586 &assign.rhs,
587 false,
588 type_map,
589 qualified,
590 unqualified,
591 );
592 }
593 Expression::InitializerList(init) => {
594 for item in &init.items {
595 if let Some(Designator::Field(name)) = &item.designator {
596 unqualified.insert(name.clone());
597 }
598 if let Some(Designator::Subscript(idx)) = &item.designator {
599 collect_field_reads_impl(
600 ast_context,
601 idx,
602 false,
603 type_map,
604 qualified,
605 unqualified,
606 );
607 }
608 collect_field_reads_impl(
609 ast_context,
610 &item.value,
611 false,
612 type_map,
613 qualified,
614 unqualified,
615 );
616 }
617 }
618 Expression::Call(call) => {
619 collect_field_reads_impl(
620 ast_context,
621 &call.function,
622 false,
623 type_map,
624 qualified,
625 unqualified,
626 );
627 for arg in &call.arguments {
628 collect_field_reads_impl(ast_context, arg, false, type_map, qualified, unqualified);
629 }
630 }
631 Expression::AllocCall(alloc) => {
632 collect_field_reads_impl(
633 ast_context,
634 &alloc.function,
635 false,
636 type_map,
637 qualified,
638 unqualified,
639 );
640 for arg in &alloc.arguments {
641 collect_field_reads_impl(ast_context, arg, false, type_map, qualified, unqualified);
642 }
643 }
644 Expression::Binary(b) => {
645 collect_field_reads_impl(
646 ast_context,
647 &b.left,
648 false,
649 type_map,
650 qualified,
651 unqualified,
652 );
653 collect_field_reads_impl(
654 ast_context,
655 &b.right,
656 false,
657 type_map,
658 qualified,
659 unqualified,
660 );
661 }
662 Expression::Unary(u) => {
663 collect_field_reads_impl(
664 ast_context,
665 &u.operand,
666 false,
667 type_map,
668 qualified,
669 unqualified,
670 );
671 }
672 Expression::Update(u) => {
673 collect_field_reads_impl(
674 ast_context,
675 &u.operand,
676 false,
677 type_map,
678 qualified,
679 unqualified,
680 );
681 }
682 Expression::Cast(c) => {
683 collect_field_reads_impl(
684 ast_context,
685 &c.operand,
686 false,
687 type_map,
688 qualified,
689 unqualified,
690 );
691 }
692 Expression::Conditional(c) => {
693 collect_field_reads_impl(
694 ast_context,
695 &c.condition,
696 false,
697 type_map,
698 qualified,
699 unqualified,
700 );
701 collect_field_reads_impl(
702 ast_context,
703 &c.then_expr,
704 false,
705 type_map,
706 qualified,
707 unqualified,
708 );
709 collect_field_reads_impl(
710 ast_context,
711 &c.else_expr,
712 false,
713 type_map,
714 qualified,
715 unqualified,
716 );
717 }
718 Expression::Subscript(s) => {
719 collect_field_reads_impl(
720 ast_context,
721 &s.array,
722 false,
723 type_map,
724 qualified,
725 unqualified,
726 );
727 collect_field_reads_impl(
728 ast_context,
729 &s.index,
730 false,
731 type_map,
732 qualified,
733 unqualified,
734 );
735 }
736 Expression::Identifier(_)
737 | Expression::StringLiteral(_)
738 | Expression::NumberLiteral(_)
739 | Expression::Null(_)
740 | Expression::Boolean(_)
741 | Expression::Sizeof(_)
742 | Expression::CharLiteral(_)
743 | Expression::Comment(_)
744 | Expression::OffsetOf(_)
745 | Expression::Generic(_) => {}
746 }
747}
748
749fn extract_field_refs_from_text(text: &str, unqualified: &mut HashSet<String>) {
750 let b = text.as_bytes();
751 let mut i = 0;
752 while i < b.len() {
753 let arrow = i + 1 < b.len() && b[i] == b'-' && b[i + 1] == b'>';
754 let dot = b[i] == b'.'
755 && i > 0
756 && (b[i - 1].is_ascii_alphanumeric()
757 || b[i - 1] == b'_'
758 || matches!(b[i - 1], b')' | b']'));
759 if arrow || dot {
760 let start = i + if arrow { 2 } else { 1 };
761 let mut end = start;
762 while end < b.len() && (b[end].is_ascii_alphanumeric() || b[end] == b'_') {
763 end += 1;
764 }
765 if end > start {
766 unqualified.insert(text[start..end].to_owned());
767 }
768 i = end;
769 } else {
770 i += 1;
771 }
772 }
773}
774
775impl DeadCode {
776 fn report_function_violations(
777 &self,
778 ast_context: &AstContext,
779 func_defs: &FuncDefMap,
780 func_decls: &FuncDeclMap,
781 func_refs: &HashSet<String>,
782 violations: &mut Vec<Violation>,
783 ) {
784 for (&func_name, defs) in func_defs {
785 if func_refs.contains(func_name) {
786 continue;
787 }
788 for (def_path, is_static, location) in defs {
789 if *is_static {
790 violations.push(self.violation_at(
791 def_path,
792 location,
793 format!("Static function '{}' is never used", func_name),
794 ));
795 continue;
796 }
797 if let Some(decls) = func_decls.get(func_name) {
798 if decls
799 .iter()
800 .any(|(p, _)| ast_context.is_public_header(p) == Some(true))
801 {
802 continue;
803 }
804 for (decl_path, decl_location) in decls {
805 violations.push(self.violation_at(
806 decl_path,
807 decl_location,
808 format!(
809 "Internal function '{}' is never used (declared in private header)",
810 func_name
811 ),
812 ));
813 }
814 }
815 }
816 }
817
818 for (&func_name, decls) in func_decls {
819 if func_refs.contains(func_name) || func_defs.contains_key(func_name) {
820 continue;
821 }
822 if decls
823 .iter()
824 .any(|(p, _)| ast_context.is_public_header(p) == Some(true))
825 {
826 continue;
827 }
828 for (decl_path, decl_location) in decls {
829 violations.push(self.violation_at(
830 decl_path,
831 decl_location,
832 format!(
833 "Internal function '{}' is never used (declared but not defined)",
834 func_name
835 ),
836 ));
837 }
838 }
839 }
840
841 fn report_type_violations(
842 &self,
843 ast_context: &AstContext,
844 type_defs: &TypeDefMap,
845 type_refs: &HashSet<String>,
846 violations: &mut Vec<Violation>,
847 ) {
848 for (&type_name, defs) in type_defs {
849 if ast_context
850 .type_aliases()
851 .is_referenced(type_name, type_refs)
852 {
853 continue;
854 }
855 for (def_path, location) in defs {
856 violations.push(self.violation_at(
857 def_path,
858 location,
859 format!("Type '{}' is defined but never used", type_name),
860 ));
861 }
862 }
863 }
864
865 fn report_enum_value_violations(
866 &self,
867 enum_value_defs: &EnumValueDefMap,
868 func_refs: &HashSet<String>,
869 violations: &mut Vec<Violation>,
870 ) {
871 for (&value_name, defs) in enum_value_defs {
872 if func_refs.contains(value_name) {
873 continue;
874 }
875 for (def_path, location) in defs {
876 violations.push(self.violation_at(
877 def_path,
878 location,
879 format!("Enum value '{}' is defined but never used", value_name),
880 ));
881 }
882 }
883 }
884
885 fn report_field_violations(
886 &self,
887 ast_context: &AstContext,
888 field_defs: &FieldDefMap,
889 field_refs_qualified: &HashMap<String, HashSet<String>>,
890 field_refs_unqualified: &HashSet<String>,
891 violations: &mut Vec<Violation>,
892 ) {
893 for (&field_name, defs) in field_defs {
894 if field_refs_unqualified.contains(field_name) {
895 continue;
896 }
897 for (def_path, location, struct_name) in defs {
898 if ast_context.type_aliases().field_is_referenced(
899 struct_name,
900 field_name,
901 field_refs_qualified,
902 ) {
903 continue;
904 }
905 violations.push(self.violation_at(
906 def_path,
907 location,
908 format!("Field '{}' in '{}' is never read", field_name, struct_name),
909 ));
910 }
911 }
912 }
913}