forge_lint/sol/gas/
unused_state_variables.rs1use super::UnusedStateVariables;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 ast::ContractKind,
8 interface::data_structures::Never,
9 sema::hir::{self, Visit as _},
10};
11use std::{collections::HashSet, ops::ControlFlow};
12
13declare_forge_lint!(
14 UNUSED_STATE_VARIABLES,
15 Severity::Gas,
16 "unused-state-variables",
17 "state variable is never used"
18);
19
20impl<'hir> LateLintPass<'hir> for UnusedStateVariables {
21 fn check_contract(
22 &mut self,
23 ctx: &LintContext,
24 hir: &'hir hir::Hir<'hir>,
25 contract: &'hir hir::Contract<'hir>,
26 ) {
27 if contract.kind == ContractKind::Interface {
29 return;
30 }
31
32 let state_vars: Vec<hir::VariableId> = contract
35 .variables()
36 .filter(|&var_id| {
37 let var = hir.variable(var_id);
38 !var.is_constant() && !var.is_immutable()
39 })
40 .collect();
41
42 if state_vars.is_empty() {
43 return;
44 }
45
46 let mut collector = UsedVarCollector { hir, used: HashSet::new() };
50 for func_id in contract.all_functions() {
51 let _ = collector.visit_nested_function(func_id);
52 }
53 for var_id in contract.variables() {
55 let _ = collector.visit_nested_var(var_id);
56 }
57
58 for var_id in state_vars {
60 if !collector.used.contains(&var_id) {
61 let var = hir.variable(var_id);
62 ctx.emit(&UNUSED_STATE_VARIABLES, var.span);
63 }
64 }
65 }
66}
67
68struct UsedVarCollector<'hir> {
69 hir: &'hir hir::Hir<'hir>,
70 used: HashSet<hir::VariableId>,
71}
72
73impl<'hir> hir::Visit<'hir> for UsedVarCollector<'hir> {
74 type BreakValue = Never;
75
76 fn hir(&self) -> &'hir hir::Hir<'hir> {
77 self.hir
78 }
79
80 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> ControlFlow<Self::BreakValue> {
81 if let hir::ExprKind::Ident(resolutions) = &expr.kind {
82 for res in *resolutions {
83 if let hir::Res::Item(hir::ItemId::Variable(var_id)) = res {
84 self.used.insert(*var_id);
85 }
86 }
87 }
88 self.walk_expr(expr)
89 }
90}