forge_lint/sol/info/
unused_error.rs1use crate::{
2 linter::{Lint, ProjectLintEmitter, ProjectLintPass, ProjectSource},
3 sol::{Severity, SolLint, info::UnusedError},
4};
5use solar::{
6 ast::ContractKind,
7 interface::{data_structures::Never, source_map::FileName},
8 sema::{
9 Gcx,
10 hir::{self, Visit as _},
11 },
12};
13use std::{
14 collections::{HashMap, HashSet},
15 ops::ControlFlow,
16};
17
18declare_forge_lint!(UNUSED_ERROR, Severity::Info, "unused-error", "custom error is never used");
19
20impl<'ast> ProjectLintPass<'ast> for UnusedError {
21 fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]) {
22 if !ctx.is_lint_enabled(UNUSED_ERROR.id()) {
23 return;
24 }
25 let gcx = ctx.gcx();
26
27 let input_source_idx: HashMap<_, _> = gcx
31 .hir
32 .sources_enumerated()
33 .filter_map(|(sid, src)| {
34 let FileName::Real(path) = &src.file.name else { return None };
35 Some((sid, sources.iter().position(|s| &s.path == path)?))
36 })
37 .collect();
38 if input_source_idx.is_empty() {
39 return;
40 }
41
42 let mut collector = UsedErrorCollector { gcx, used: HashSet::new() };
43 for source_id in gcx.hir.source_ids() {
44 let _ = collector.visit_nested_source(source_id);
45 }
46
47 for error_id in gcx.hir.error_ids() {
48 let error = gcx.hir.error(error_id);
49 let Some(&src_idx) = input_source_idx.get(&error.source) else { continue };
50 let abi_surface = error.contract.is_some_and(|id| {
53 matches!(
54 gcx.hir.contract(id).kind,
55 ContractKind::Interface | ContractKind::AbstractContract
56 )
57 });
58 if !abi_surface && !collector.used.contains(&error_id) {
59 ctx.emit(&sources[src_idx], &UNUSED_ERROR, error.span);
60 }
61 }
62 }
63}
64
65struct UsedErrorCollector<'gcx> {
67 gcx: Gcx<'gcx>,
68 used: HashSet<hir::ErrorId>,
69}
70
71impl<'gcx> hir::Visit<'gcx> for UsedErrorCollector<'gcx> {
72 type BreakValue = Never;
73
74 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
75 &self.gcx.hir
76 }
77
78 fn visit_expr(&mut self, expr: &'gcx hir::Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
79 if let Some(hir::Res::Item(hir::ItemId::Error(error_id))) = self.gcx.resolved_expr(expr) {
80 self.used.insert(error_id);
81 }
82 self.walk_expr(expr)
83 }
84}