From 46454074d5faa61c7bea99a0895510ee87c2dfdc Mon Sep 17 00:00:00 2001 From: solanaXpeter Date: Mon, 13 Apr 2026 14:52:35 +0800 Subject: [PATCH 1/4] fix(lint): add missing visit methods to LateLintVisitor --- crates/lint/src/linter/late.rs | 92 ++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/crates/lint/src/linter/late.rs b/crates/lint/src/linter/late.rs index 73620a4bc331b..bb7ff64a54b6a 100644 --- a/crates/lint/src/linter/late.rs +++ b/crates/lint/src/linter/late.rs @@ -5,6 +5,10 @@ use super::LintContext; /// Trait for lints that operate on the HIR (High-level Intermediate Representation). /// Its methods mirror `hir::visit::Visit`, with the addition of `LintContext`. +/// +/// The original `check_nested_*` hooks took borrowed IDs, but current `solar::hir::Visit` +/// dispatches nested IDs by value. Those legacy hooks are kept as deprecated compatibility shims; +/// `LateLintVisitor` dispatches the corresponding `*_id` hooks instead. pub trait LateLintPass<'hir>: Send + Sync { fn check_nested_source( &mut self, @@ -13,6 +17,9 @@ pub trait LateLintPass<'hir>: Send + Sync { _id: hir::SourceId, ) { } + #[deprecated( + note = "use check_nested_item_id instead; current solar::hir::Visit passes ItemId by value" + )] fn check_nested_item( &mut self, _ctx: &LintContext, @@ -20,6 +27,16 @@ pub trait LateLintPass<'hir>: Send + Sync { _id: &'hir hir::ItemId, ) { } + fn check_nested_item_id( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _id: hir::ItemId, + ) { + } + #[deprecated( + note = "use check_nested_contract_id instead; current solar::hir::Visit passes ContractId by value" + )] fn check_nested_contract( &mut self, _ctx: &LintContext, @@ -27,6 +44,16 @@ pub trait LateLintPass<'hir>: Send + Sync { _id: &'hir hir::ContractId, ) { } + fn check_nested_contract_id( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _id: hir::ContractId, + ) { + } + #[deprecated( + note = "use check_nested_function_id instead; current solar::hir::Visit passes FunctionId by value" + )] fn check_nested_function( &mut self, _ctx: &LintContext, @@ -34,6 +61,16 @@ pub trait LateLintPass<'hir>: Send + Sync { _id: &'hir hir::FunctionId, ) { } + fn check_nested_function_id( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _id: hir::FunctionId, + ) { + } + #[deprecated( + note = "use check_nested_var_id instead; current solar::hir::Visit passes VariableId by value" + )] fn check_nested_var( &mut self, _ctx: &LintContext, @@ -41,6 +78,13 @@ pub trait LateLintPass<'hir>: Send + Sync { _id: &'hir hir::VariableId, ) { } + fn check_nested_var_id( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _id: hir::VariableId, + ) { + } fn check_item( &mut self, _ctx: &LintContext, @@ -143,6 +187,34 @@ where self.walk_nested_source(id) } + fn visit_nested_item(&mut self, id: hir::ItemId) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_nested_item_id(self.ctx, self.hir, id); + } + self.walk_nested_item(id) + } + + fn visit_nested_contract(&mut self, id: hir::ContractId) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_nested_contract_id(self.ctx, self.hir, id); + } + self.walk_nested_contract(id) + } + + fn visit_nested_function(&mut self, id: hir::FunctionId) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_nested_function_id(self.ctx, self.hir, id); + } + self.walk_nested_function(id) + } + + fn visit_nested_var(&mut self, id: hir::VariableId) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_nested_var_id(self.ctx, self.hir, id); + } + self.walk_nested_var(id) + } + fn visit_contract( &mut self, contract: &'hir hir::Contract<'hir>, @@ -160,6 +232,16 @@ where self.walk_function(func) } + fn visit_modifier( + &mut self, + modifier: &'hir hir::Modifier<'hir>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_modifier(self.ctx, self.hir, modifier); + } + self.walk_modifier(modifier) + } + fn visit_item(&mut self, item: hir::Item<'hir, 'hir>) -> ControlFlow { for pass in self.passes.iter_mut() { pass.check_item(self.ctx, self.hir, item); @@ -181,6 +263,16 @@ where self.walk_expr(expr) } + fn visit_call_args( + &mut self, + args: &'hir hir::CallArgs<'hir>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_call_args(self.ctx, self.hir, args); + } + self.walk_call_args(args) + } + fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'hir>) -> ControlFlow { for pass in self.passes.iter_mut() { pass.check_stmt(self.ctx, self.hir, stmt); From fdeb64c1fd591b2efd91cc0d811912728cf0774f Mon Sep 17 00:00:00 2001 From: solanaXpeter Date: Fri, 17 Apr 2026 09:34:52 +0800 Subject: [PATCH 2/4] fix(lint): dispatch deprecated nested late lint hooks --- crates/lint/src/linter/late.rs | 167 +++++++++++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 7 deletions(-) diff --git a/crates/lint/src/linter/late.rs b/crates/lint/src/linter/late.rs index bb7ff64a54b6a..852f51c6f9c56 100644 --- a/crates/lint/src/linter/late.rs +++ b/crates/lint/src/linter/late.rs @@ -1,4 +1,7 @@ -use solar::{interface::data_structures::Never, sema::hir}; +use solar::{ + interface::data_structures::Never, + sema::hir::{self, Visit}, +}; use std::ops::ControlFlow; use super::LintContext; @@ -8,7 +11,7 @@ use super::LintContext; /// /// The original `check_nested_*` hooks took borrowed IDs, but current `solar::hir::Visit` /// dispatches nested IDs by value. Those legacy hooks are kept as deprecated compatibility shims; -/// `LateLintVisitor` dispatches the corresponding `*_id` hooks instead. +/// `LateLintVisitor` dispatches both the borrowed-ID hooks and the corresponding `*_id` hooks. pub trait LateLintPass<'hir>: Send + Sync { fn check_nested_source( &mut self, @@ -161,6 +164,51 @@ impl<'a, 's, 'hir> LateLintVisitor<'a, 's, 'hir> where 's: 'hir, { + #[allow(deprecated)] + fn visit_nested_item_ref(&mut self, id: &'hir hir::ItemId) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_nested_item(self.ctx, self.hir, id); + pass.check_nested_item_id(self.ctx, self.hir, *id); + } + + match id { + hir::ItemId::Contract(id) => self.visit_nested_contract_ref(id), + hir::ItemId::Function(id) => self.visit_nested_function_ref(id), + hir::ItemId::Variable(id) => self.visit_nested_var_ref(id), + _ => self.visit_item(self.hir.item(*id)), + } + } + + #[allow(deprecated)] + fn visit_nested_contract_ref(&mut self, id: &'hir hir::ContractId) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_nested_contract(self.ctx, self.hir, id); + pass.check_nested_contract_id(self.ctx, self.hir, *id); + } + + self.visit_contract(self.hir.contract(*id)) + } + + #[allow(deprecated)] + fn visit_nested_function_ref(&mut self, id: &'hir hir::FunctionId) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_nested_function(self.ctx, self.hir, id); + pass.check_nested_function_id(self.ctx, self.hir, *id); + } + + self.visit_function(self.hir.function(*id)) + } + + #[allow(deprecated)] + fn visit_nested_var_ref(&mut self, id: &'hir hir::VariableId) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_nested_var(self.ctx, self.hir, id); + pass.check_nested_var_id(self.ctx, self.hir, *id); + } + + self.visit_var(self.hir.variable(*id)) + } + pub fn new( ctx: &'a LintContext<'s, 'a>, passes: &'a mut [Box + 's>], @@ -184,7 +232,7 @@ where for pass in self.passes.iter_mut() { pass.check_nested_source(self.ctx, self.hir, id); } - self.walk_nested_source(id) + self.hir.source(id).items.iter().try_for_each(|id| self.visit_nested_item_ref(id)) } fn visit_nested_item(&mut self, id: hir::ItemId) -> ControlFlow { @@ -222,14 +270,31 @@ where for pass in self.passes.iter_mut() { pass.check_contract(self.ctx, self.hir, contract); } - self.walk_contract(contract) + for base in contract.bases_args { + self.visit_modifier(base)?; + } + contract.items.iter().try_for_each(|id| self.visit_nested_item_ref(id)) } fn visit_function(&mut self, func: &'hir hir::Function<'hir>) -> ControlFlow { for pass in self.passes.iter_mut() { pass.check_function(self.ctx, self.hir, func); } - self.walk_function(func) + for param in func.parameters { + self.visit_nested_var_ref(param)?; + } + for modifier in func.modifiers { + self.visit_modifier(modifier)?; + } + for ret in func.returns { + self.visit_nested_var_ref(ret)?; + } + if let Some(body) = func.body.as_ref() { + for stmt in body.iter() { + self.visit_stmt(stmt)?; + } + } + ControlFlow::Continue(()) } fn visit_modifier( @@ -242,6 +307,27 @@ where self.walk_modifier(modifier) } + fn visit_struct(&mut self, strukt: &'hir hir::Struct<'hir>) -> ControlFlow { + for field in strukt.fields { + self.visit_nested_var_ref(field)?; + } + ControlFlow::Continue(()) + } + + fn visit_error(&mut self, error: &'hir hir::Error<'hir>) -> ControlFlow { + for param in error.parameters { + self.visit_nested_var_ref(param)?; + } + ControlFlow::Continue(()) + } + + fn visit_event(&mut self, event: &'hir hir::Event<'hir>) -> ControlFlow { + for param in event.parameters { + self.visit_nested_var_ref(param)?; + } + ControlFlow::Continue(()) + } + fn visit_item(&mut self, item: hir::Item<'hir, 'hir>) -> ControlFlow { for pass in self.passes.iter_mut() { pass.check_item(self.ctx, self.hir, item); @@ -277,13 +363,80 @@ where for pass in self.passes.iter_mut() { pass.check_stmt(self.ctx, self.hir, stmt); } - self.walk_stmt(stmt) + match &stmt.kind { + hir::StmtKind::DeclSingle(var) => self.visit_nested_var_ref(var)?, + hir::StmtKind::DeclMulti(vars, expr) => { + for var in vars.iter().flatten() { + self.visit_nested_var_ref(var)?; + } + self.visit_expr(expr)?; + } + hir::StmtKind::Block(block) + | hir::StmtKind::UncheckedBlock(block) + | hir::StmtKind::Loop(block, _) => { + for stmt in block.stmts { + self.visit_stmt(stmt)?; + } + } + hir::StmtKind::Emit(expr) | hir::StmtKind::Revert(expr) | hir::StmtKind::Expr(expr) => { + self.visit_expr(expr)? + } + hir::StmtKind::Return(expr) => { + if let Some(expr) = expr { + self.visit_expr(expr)?; + } + } + hir::StmtKind::Break + | hir::StmtKind::Continue + | hir::StmtKind::Placeholder + | hir::StmtKind::Err(_) => {} + hir::StmtKind::If(cond, true_, false_) => { + self.visit_expr(cond)?; + self.visit_stmt(true_)?; + if let Some(false_) = false_ { + self.visit_stmt(false_)?; + } + } + hir::StmtKind::Try(try_) => { + self.visit_expr(&try_.expr)?; + for clause in try_.clauses { + for var in clause.args { + self.visit_nested_var_ref(var)?; + } + for stmt in clause.block.iter() { + self.visit_stmt(stmt)?; + } + } + } + } + ControlFlow::Continue(()) } fn visit_ty(&mut self, ty: &'hir hir::Type<'hir>) -> ControlFlow { for pass in self.passes.iter_mut() { pass.check_ty(self.ctx, self.hir, ty); } - self.walk_ty(ty) + match &ty.kind { + hir::TypeKind::Elementary(_) | hir::TypeKind::Custom(_) | hir::TypeKind::Err(_) => {} + hir::TypeKind::Array(arr) => { + self.visit_ty(&arr.element)?; + if let Some(len) = arr.size { + self.visit_expr(len)?; + } + } + hir::TypeKind::Function(func) => { + for param in func.parameters { + self.visit_nested_var_ref(param)?; + } + for ret in func.returns { + self.visit_nested_var_ref(ret)?; + } + } + hir::TypeKind::Mapping(map) => { + self.visit_ty(&map.key)?; + self.visit_ty(&map.value)?; + } + } + ControlFlow::Continue(()) } } From 7816f7636355f64a8e093da5ac5b2a0c6295793c Mon Sep 17 00:00:00 2001 From: solanaXpeter Date: Mon, 20 Apr 2026 16:16:34 +0800 Subject: [PATCH 3/4] fix(lint): use by-value nested late lint hooks --- crates/lint/src/linter/late.rs | 217 ++------------------------------- 1 file changed, 10 insertions(+), 207 deletions(-) diff --git a/crates/lint/src/linter/late.rs b/crates/lint/src/linter/late.rs index 852f51c6f9c56..e4d458cc6a252 100644 --- a/crates/lint/src/linter/late.rs +++ b/crates/lint/src/linter/late.rs @@ -1,17 +1,10 @@ -use solar::{ - interface::data_structures::Never, - sema::hir::{self, Visit}, -}; +use solar::{interface::data_structures::Never, sema::hir}; use std::ops::ControlFlow; use super::LintContext; /// Trait for lints that operate on the HIR (High-level Intermediate Representation). /// Its methods mirror `hir::visit::Visit`, with the addition of `LintContext`. -/// -/// The original `check_nested_*` hooks took borrowed IDs, but current `solar::hir::Visit` -/// dispatches nested IDs by value. Those legacy hooks are kept as deprecated compatibility shims; -/// `LateLintVisitor` dispatches both the borrowed-ID hooks and the corresponding `*_id` hooks. pub trait LateLintPass<'hir>: Send + Sync { fn check_nested_source( &mut self, @@ -20,68 +13,28 @@ pub trait LateLintPass<'hir>: Send + Sync { _id: hir::SourceId, ) { } - #[deprecated( - note = "use check_nested_item_id instead; current solar::hir::Visit passes ItemId by value" - )] fn check_nested_item( - &mut self, - _ctx: &LintContext, - _hir: &'hir hir::Hir<'hir>, - _id: &'hir hir::ItemId, - ) { - } - fn check_nested_item_id( &mut self, _ctx: &LintContext, _hir: &'hir hir::Hir<'hir>, _id: hir::ItemId, ) { } - #[deprecated( - note = "use check_nested_contract_id instead; current solar::hir::Visit passes ContractId by value" - )] fn check_nested_contract( - &mut self, - _ctx: &LintContext, - _hir: &'hir hir::Hir<'hir>, - _id: &'hir hir::ContractId, - ) { - } - fn check_nested_contract_id( &mut self, _ctx: &LintContext, _hir: &'hir hir::Hir<'hir>, _id: hir::ContractId, ) { } - #[deprecated( - note = "use check_nested_function_id instead; current solar::hir::Visit passes FunctionId by value" - )] fn check_nested_function( - &mut self, - _ctx: &LintContext, - _hir: &'hir hir::Hir<'hir>, - _id: &'hir hir::FunctionId, - ) { - } - fn check_nested_function_id( &mut self, _ctx: &LintContext, _hir: &'hir hir::Hir<'hir>, _id: hir::FunctionId, ) { } - #[deprecated( - note = "use check_nested_var_id instead; current solar::hir::Visit passes VariableId by value" - )] fn check_nested_var( - &mut self, - _ctx: &LintContext, - _hir: &'hir hir::Hir<'hir>, - _id: &'hir hir::VariableId, - ) { - } - fn check_nested_var_id( &mut self, _ctx: &LintContext, _hir: &'hir hir::Hir<'hir>, @@ -164,51 +117,6 @@ impl<'a, 's, 'hir> LateLintVisitor<'a, 's, 'hir> where 's: 'hir, { - #[allow(deprecated)] - fn visit_nested_item_ref(&mut self, id: &'hir hir::ItemId) -> ControlFlow { - for pass in self.passes.iter_mut() { - pass.check_nested_item(self.ctx, self.hir, id); - pass.check_nested_item_id(self.ctx, self.hir, *id); - } - - match id { - hir::ItemId::Contract(id) => self.visit_nested_contract_ref(id), - hir::ItemId::Function(id) => self.visit_nested_function_ref(id), - hir::ItemId::Variable(id) => self.visit_nested_var_ref(id), - _ => self.visit_item(self.hir.item(*id)), - } - } - - #[allow(deprecated)] - fn visit_nested_contract_ref(&mut self, id: &'hir hir::ContractId) -> ControlFlow { - for pass in self.passes.iter_mut() { - pass.check_nested_contract(self.ctx, self.hir, id); - pass.check_nested_contract_id(self.ctx, self.hir, *id); - } - - self.visit_contract(self.hir.contract(*id)) - } - - #[allow(deprecated)] - fn visit_nested_function_ref(&mut self, id: &'hir hir::FunctionId) -> ControlFlow { - for pass in self.passes.iter_mut() { - pass.check_nested_function(self.ctx, self.hir, id); - pass.check_nested_function_id(self.ctx, self.hir, *id); - } - - self.visit_function(self.hir.function(*id)) - } - - #[allow(deprecated)] - fn visit_nested_var_ref(&mut self, id: &'hir hir::VariableId) -> ControlFlow { - for pass in self.passes.iter_mut() { - pass.check_nested_var(self.ctx, self.hir, id); - pass.check_nested_var_id(self.ctx, self.hir, *id); - } - - self.visit_var(self.hir.variable(*id)) - } - pub fn new( ctx: &'a LintContext<'s, 'a>, passes: &'a mut [Box + 's>], @@ -232,33 +140,33 @@ where for pass in self.passes.iter_mut() { pass.check_nested_source(self.ctx, self.hir, id); } - self.hir.source(id).items.iter().try_for_each(|id| self.visit_nested_item_ref(id)) + self.walk_nested_source(id) } fn visit_nested_item(&mut self, id: hir::ItemId) -> ControlFlow { for pass in self.passes.iter_mut() { - pass.check_nested_item_id(self.ctx, self.hir, id); + pass.check_nested_item(self.ctx, self.hir, id); } self.walk_nested_item(id) } fn visit_nested_contract(&mut self, id: hir::ContractId) -> ControlFlow { for pass in self.passes.iter_mut() { - pass.check_nested_contract_id(self.ctx, self.hir, id); + pass.check_nested_contract(self.ctx, self.hir, id); } self.walk_nested_contract(id) } fn visit_nested_function(&mut self, id: hir::FunctionId) -> ControlFlow { for pass in self.passes.iter_mut() { - pass.check_nested_function_id(self.ctx, self.hir, id); + pass.check_nested_function(self.ctx, self.hir, id); } self.walk_nested_function(id) } fn visit_nested_var(&mut self, id: hir::VariableId) -> ControlFlow { for pass in self.passes.iter_mut() { - pass.check_nested_var_id(self.ctx, self.hir, id); + pass.check_nested_var(self.ctx, self.hir, id); } self.walk_nested_var(id) } @@ -270,31 +178,14 @@ where for pass in self.passes.iter_mut() { pass.check_contract(self.ctx, self.hir, contract); } - for base in contract.bases_args { - self.visit_modifier(base)?; - } - contract.items.iter().try_for_each(|id| self.visit_nested_item_ref(id)) + self.walk_contract(contract) } fn visit_function(&mut self, func: &'hir hir::Function<'hir>) -> ControlFlow { for pass in self.passes.iter_mut() { pass.check_function(self.ctx, self.hir, func); } - for param in func.parameters { - self.visit_nested_var_ref(param)?; - } - for modifier in func.modifiers { - self.visit_modifier(modifier)?; - } - for ret in func.returns { - self.visit_nested_var_ref(ret)?; - } - if let Some(body) = func.body.as_ref() { - for stmt in body.iter() { - self.visit_stmt(stmt)?; - } - } - ControlFlow::Continue(()) + self.walk_function(func) } fn visit_modifier( @@ -307,27 +198,6 @@ where self.walk_modifier(modifier) } - fn visit_struct(&mut self, strukt: &'hir hir::Struct<'hir>) -> ControlFlow { - for field in strukt.fields { - self.visit_nested_var_ref(field)?; - } - ControlFlow::Continue(()) - } - - fn visit_error(&mut self, error: &'hir hir::Error<'hir>) -> ControlFlow { - for param in error.parameters { - self.visit_nested_var_ref(param)?; - } - ControlFlow::Continue(()) - } - - fn visit_event(&mut self, event: &'hir hir::Event<'hir>) -> ControlFlow { - for param in event.parameters { - self.visit_nested_var_ref(param)?; - } - ControlFlow::Continue(()) - } - fn visit_item(&mut self, item: hir::Item<'hir, 'hir>) -> ControlFlow { for pass in self.passes.iter_mut() { pass.check_item(self.ctx, self.hir, item); @@ -363,80 +233,13 @@ where for pass in self.passes.iter_mut() { pass.check_stmt(self.ctx, self.hir, stmt); } - match &stmt.kind { - hir::StmtKind::DeclSingle(var) => self.visit_nested_var_ref(var)?, - hir::StmtKind::DeclMulti(vars, expr) => { - for var in vars.iter().flatten() { - self.visit_nested_var_ref(var)?; - } - self.visit_expr(expr)?; - } - hir::StmtKind::Block(block) - | hir::StmtKind::UncheckedBlock(block) - | hir::StmtKind::Loop(block, _) => { - for stmt in block.stmts { - self.visit_stmt(stmt)?; - } - } - hir::StmtKind::Emit(expr) | hir::StmtKind::Revert(expr) | hir::StmtKind::Expr(expr) => { - self.visit_expr(expr)? - } - hir::StmtKind::Return(expr) => { - if let Some(expr) = expr { - self.visit_expr(expr)?; - } - } - hir::StmtKind::Break - | hir::StmtKind::Continue - | hir::StmtKind::Placeholder - | hir::StmtKind::Err(_) => {} - hir::StmtKind::If(cond, true_, false_) => { - self.visit_expr(cond)?; - self.visit_stmt(true_)?; - if let Some(false_) = false_ { - self.visit_stmt(false_)?; - } - } - hir::StmtKind::Try(try_) => { - self.visit_expr(&try_.expr)?; - for clause in try_.clauses { - for var in clause.args { - self.visit_nested_var_ref(var)?; - } - for stmt in clause.block.iter() { - self.visit_stmt(stmt)?; - } - } - } - } - ControlFlow::Continue(()) + self.walk_stmt(stmt) } fn visit_ty(&mut self, ty: &'hir hir::Type<'hir>) -> ControlFlow { for pass in self.passes.iter_mut() { pass.check_ty(self.ctx, self.hir, ty); } - match &ty.kind { - hir::TypeKind::Elementary(_) | hir::TypeKind::Custom(_) | hir::TypeKind::Err(_) => {} - hir::TypeKind::Array(arr) => { - self.visit_ty(&arr.element)?; - if let Some(len) = arr.size { - self.visit_expr(len)?; - } - } - hir::TypeKind::Function(func) => { - for param in func.parameters { - self.visit_nested_var_ref(param)?; - } - for ret in func.returns { - self.visit_nested_var_ref(ret)?; - } - } - hir::TypeKind::Mapping(map) => { - self.visit_ty(&map.key)?; - self.visit_ty(&map.value)?; - } - } - ControlFlow::Continue(()) + self.walk_ty(ty) } } From 4cf76aaca0f29f56fe292e4e4a8fba6162efc80a Mon Sep 17 00:00:00 2001 From: solanaXpeter Date: Mon, 20 Apr 2026 22:49:30 +0800 Subject: [PATCH 4/4] test(lint): cover late visitor hooks --- crates/lint/src/linter/late.rs | 160 +++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/crates/lint/src/linter/late.rs b/crates/lint/src/linter/late.rs index e4d458cc6a252..f7e97d00e7612 100644 --- a/crates/lint/src/linter/late.rs +++ b/crates/lint/src/linter/late.rs @@ -243,3 +243,163 @@ where self.walk_ty(ty) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::linter::LinterConfig; + use foundry_common::comments::inline_config::InlineConfig; + use foundry_config::lint::LintSpecificConfig; + use solar::{ + interface::{Session, source_map::FileName}, + sema::Compiler, + }; + use std::sync::{Arc, Mutex}; + + #[derive(Debug, Default)] + struct HookCounts { + nested_item: usize, + nested_contract: usize, + nested_function: usize, + nested_var: usize, + modifier: usize, + call_args: usize, + } + + struct RecordingPass { + counts: Arc>, + } + + impl RecordingPass { + fn record(&self, update: impl FnOnce(&mut HookCounts)) { + update(&mut self.counts.lock().unwrap()); + } + } + + impl<'hir> LateLintPass<'hir> for RecordingPass { + fn check_nested_item( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _id: hir::ItemId, + ) { + self.record(|counts| counts.nested_item += 1); + } + + fn check_nested_contract( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _id: hir::ContractId, + ) { + self.record(|counts| counts.nested_contract += 1); + } + + fn check_nested_function( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _id: hir::FunctionId, + ) { + self.record(|counts| counts.nested_function += 1); + } + + fn check_nested_var( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _id: hir::VariableId, + ) { + self.record(|counts| counts.nested_var += 1); + } + + fn check_modifier( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _modifier: &'hir hir::Modifier<'hir>, + ) { + self.record(|counts| counts.modifier += 1); + } + + fn check_call_args( + &mut self, + _ctx: &LintContext, + _hir: &'hir hir::Hir<'hir>, + _args: &'hir hir::CallArgs<'hir>, + ) { + self.record(|counts| counts.call_args += 1); + } + } + + #[test] + fn calls_hooks_for_nested_items_modifiers_and_call_args() { + let counts = Arc::new(Mutex::new(HookCounts::default())); + let inline = InlineConfig::default(); + let lint_specific = LintSpecificConfig::default(); + let source = r#" + pragma solidity ^0.8.20; + + contract Base { + function hook(uint256 value) internal pure returns (uint256) { + return value; + } + } + + contract Test is Base { + uint256 stored; + + modifier gated(uint256 amount) { + _; + } + + function run(uint256 amount) public gated(amount) returns (uint256) { + return hook(amount + stored); + } + } + "#; + + let mut compiler = + Compiler::new(Session::builder().with_buffer_emitter(Default::default()).build()); + compiler + .enter_mut(|compiler| -> solar::interface::Result<()> { + let mut pcx = compiler.parse(); + pcx.set_resolve_imports(false); + let file = compiler + .sess() + .source_map() + .new_source_file(FileName::Stdin, source) + .expect("failed to create source file"); + pcx.add_file(file); + pcx.parse(); + + let ControlFlow::Continue(()) = compiler.lower_asts()? else { + panic!("expected HIR lowering to continue"); + }; + + let gcx = compiler.gcx(); + let source_id = gcx.hir.source_ids().next().expect("expected one lowered source"); + let ctx = LintContext::new( + gcx.sess, + false, + false, + LinterConfig { inline: &inline, lint_specific: &lint_specific }, + Vec::new(), + ); + let mut passes: Vec>> = + vec![Box::new(RecordingPass { counts: counts.clone() })]; + let mut visitor = LateLintVisitor::new(&ctx, &mut passes, &gcx.hir); + let _ = hir::Visit::visit_nested_source(&mut visitor, source_id); + Ok(()) + }) + .expect("failed to lower test source"); + + let counts = counts.lock().unwrap(); + assert!(counts.nested_item > 0, "expected nested item hook to run"); + assert!(counts.nested_contract > 0, "expected nested contract hook to run"); + assert!(counts.nested_function > 0, "expected nested function hook to run"); + assert!(counts.nested_var > 0, "expected nested var hook to run"); + assert!(counts.modifier > 0, "expected modifier hook to run"); + assert!(counts.call_args > 0, "expected call args hook to run"); + } +}