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 _gcx: solar::sema::Gcx<'hir>,
25 hir: &'hir hir::Hir<'hir>,
26 contract: &'hir hir::Contract<'hir>,
27 ) {
28 if contract.kind == ContractKind::Interface {
30 return;
31 }
32
33 let state_vars: Vec<hir::VariableId> = contract
36 .variables()
37 .filter(|&var_id| {
38 let var = hir.variable(var_id);
39 !var.is_constant() && !var.is_immutable()
40 })
41 .collect();
42
43 if state_vars.is_empty() {
44 return;
45 }
46
47 let mut collector = UsedVarCollector { hir, used: HashSet::new() };
51 for func_id in contract.all_functions() {
52 let _ = collector.visit_nested_function(func_id);
53 }
54 for var_id in contract.variables() {
56 let _ = collector.visit_nested_var(var_id);
57 }
58
59 for var_id in state_vars {
61 if !collector.used.contains(&var_id) {
62 let var = hir.variable(var_id);
63 ctx.emit(&UNUSED_STATE_VARIABLES, var.span);
64 }
65 }
66 }
67}
68
69struct UsedVarCollector<'hir> {
70 hir: &'hir hir::Hir<'hir>,
71 used: HashSet<hir::VariableId>,
72}
73
74impl<'hir> hir::Visit<'hir> for UsedVarCollector<'hir> {
75 type BreakValue = Never;
76
77 fn hir(&self) -> &'hir hir::Hir<'hir> {
78 self.hir
79 }
80
81 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> ControlFlow<Self::BreakValue> {
82 if let hir::ExprKind::Ident(resolutions) = &expr.kind {
83 for res in *resolutions {
84 if let hir::Res::Item(hir::ItemId::Variable(var_id)) = res {
85 self.used.insert(*var_id);
86 }
87 }
88 }
89 self.walk_expr(expr)
90 }
91}