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::{
10 Gcx,
11 hir::{self, Visit as _},
12 },
13};
14use std::{collections::HashSet, ops::ControlFlow};
15
16declare_forge_lint!(
17 UNUSED_STATE_VARIABLES,
18 Severity::Gas,
19 "unused-state-variables",
20 "state variable is never used"
21);
22
23impl<'gcx> LateLintPass<'gcx> for UnusedStateVariables {
24 fn check_contract(
25 &mut self,
26 ctx: &LintContext,
27 gcx: Gcx<'gcx>,
28 contract: &'gcx hir::Contract<'gcx>,
29 ) {
30 if contract.kind == ContractKind::Interface {
31 return;
32 }
33
34 let mut collector = UsedVarCollector { gcx, used: HashSet::new() };
37 for func_id in contract.all_functions() {
38 let _ = collector.visit_nested_function(func_id);
39 }
40 for var_id in contract.variables() {
41 let _ = collector.visit_nested_var(var_id);
42 }
43
44 for var_id in contract.variables() {
46 let var = gcx.hir.variable(var_id);
47 if !var.is_constant() && !var.is_immutable() && !collector.used.contains(&var_id) {
48 ctx.emit(&UNUSED_STATE_VARIABLES, var.span);
49 }
50 }
51 }
52}
53
54struct UsedVarCollector<'gcx> {
55 gcx: Gcx<'gcx>,
56 used: HashSet<hir::VariableId>,
57}
58
59impl<'gcx> hir::Visit<'gcx> for UsedVarCollector<'gcx> {
60 type BreakValue = Never;
61
62 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
63 &self.gcx.hir
64 }
65
66 fn visit_expr(&mut self, expr: &'gcx hir::Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
67 self.used.extend(self.gcx.resolved_variable(expr));
68 self.walk_expr(expr)
69 }
70}