From a37f42fa9c21868cd822554aa816f8230989e983 Mon Sep 17 00:00:00 2001 From: coord_e Date: Wed, 11 Feb 2026 12:29:21 +0900 Subject: [PATCH 1/4] Move ReborrowVisitor to a separate module --- src/analyze/basic_block/visitor.rs | 138 +------------------- src/analyze/basic_block/visitor/reborrow.rs | 136 +++++++++++++++++++ 2 files changed, 138 insertions(+), 136 deletions(-) create mode 100644 src/analyze/basic_block/visitor/reborrow.rs diff --git a/src/analyze/basic_block/visitor.rs b/src/analyze/basic_block/visitor.rs index da2ab44..a723721 100644 --- a/src/analyze/basic_block/visitor.rs +++ b/src/analyze/basic_block/visitor.rs @@ -1,136 +1,2 @@ -use rustc_middle::mir::{self, Local}; -use rustc_middle::ty::{self as mir_ty, TyCtxt}; - -use crate::analyze::ReplacePlacesVisitor; - -pub struct ReborrowVisitor<'a, 'tcx, 'ctx> { - tcx: TyCtxt<'tcx>, - analyzer: &'a mut super::Analyzer<'tcx, 'ctx>, -} - -impl<'tcx> ReborrowVisitor<'_, 'tcx, '_> { - fn insert_borrow(&mut self, place: mir::Place<'tcx>, inner_ty: mir_ty::Ty<'tcx>) -> Local { - let r = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased); - let ty = mir_ty::Ty::new_mut_ref(self.tcx, r, inner_ty); - let decl = mir::LocalDecl::new(ty, Default::default()).immutable(); - let new_local = self.analyzer.local_decls.push(decl); - let new_local_ty = self.analyzer.borrow_place_(place, inner_ty); - self.analyzer.bind_local(new_local, new_local_ty); - tracing::info!(old_place = ?place, ?new_local, "implicitly borrowed"); - new_local - } - - fn insert_reborrow(&mut self, place: mir::Place<'tcx>, inner_ty: mir_ty::Ty<'tcx>) -> Local { - let r = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased); - let ty = mir_ty::Ty::new_mut_ref(self.tcx, r, inner_ty); - let decl = mir::LocalDecl::new(ty, Default::default()).immutable(); - let new_local = self.analyzer.local_decls.push(decl); - let new_local_ty = self.analyzer.borrow_place_(place, inner_ty); - self.analyzer.bind_local(new_local, new_local_ty); - tracing::info!(old_place = ?place, ?new_local, "implicitly reborrowed"); - new_local - } -} - -impl<'a, 'tcx, 'ctx> mir::visit::MutVisitor<'tcx> for ReborrowVisitor<'a, 'tcx, 'ctx> { - fn tcx(&self) -> TyCtxt<'tcx> { - self.tcx - } - - fn visit_assign( - &mut self, - place: &mut mir::Place<'tcx>, - rvalue: &mut mir::Rvalue<'tcx>, - location: mir::Location, - ) { - if !self.analyzer.is_defined(place.local) { - self.super_assign(place, rvalue, location); - return; - } - - if place.projection.is_empty() && self.analyzer.is_mut_local(place.local) { - let ty = self.analyzer.local_decls[place.local].ty; - let new_local = self.insert_borrow(place.local.into(), ty); - let new_place = self.tcx.mk_place_deref(new_local.into()); - ReplacePlacesVisitor::with_replacement(self.tcx, place.local.into(), new_place) - .visit_rvalue(rvalue, location); - *place = new_place; - self.super_assign(place, rvalue, location); - return; - } - - let inner_place = if place.projection.last() == Some(&mir::PlaceElem::Deref) { - // *m = *m + 1 => m1 = &mut m; *m1 = *m + 1 - let mut projection = place.projection.as_ref().to_vec(); - projection.pop(); - mir::Place { - local: place.local, - projection: self.tcx.mk_place_elems(&projection), - } - } else { - // s.0 = s.0 + 1 => m1 = &mut s.0; *m1 = *m1 + 1 - *place - }; - - let ty = inner_place.ty(&self.analyzer.local_decls, self.tcx).ty; - let (new_local, new_place) = match ty.kind() { - mir_ty::TyKind::Ref(_, inner_ty, m) if m.is_mut() => { - let new_local = self.insert_reborrow(*place, *inner_ty); - (new_local, new_local.into()) - } - mir_ty::TyKind::Adt(adt, args) if adt.is_box() => { - let inner_ty = args.type_at(0); - let new_local = self.insert_borrow(*place, inner_ty); - (new_local, new_local.into()) - } - _ => { - let new_local = self.insert_borrow(*place, ty); - (new_local, self.tcx.mk_place_deref(new_local.into())) - } - }; - - ReplacePlacesVisitor::with_replacement(self.tcx, inner_place, new_place) - .visit_rvalue(rvalue, location); - *place = self.tcx.mk_place_deref(new_local.into()); - self.super_assign(place, rvalue, location); - } - - // TODO: is it always true that the operand is not referred again in rvalue - fn visit_operand(&mut self, operand: &mut mir::Operand<'tcx>, location: mir::Location) { - let Some(p) = operand.place() else { - self.super_operand(operand, location); - return; - }; - - let mir_ty::TyKind::Ref(_, inner_ty, m) = - p.ty(&self.analyzer.local_decls, self.tcx).ty.kind() - else { - self.super_operand(operand, location); - return; - }; - - if m.is_mut() { - let new_local = self.insert_reborrow(self.tcx.mk_place_deref(p), *inner_ty); - *operand = mir::Operand::Move(new_local.into()); - } - - self.super_operand(operand, location); - } -} - -impl<'a, 'tcx, 'ctx> ReborrowVisitor<'a, 'tcx, 'ctx> { - pub fn new(analyzer: &'a mut super::Analyzer<'tcx, 'ctx>) -> Self { - let tcx = analyzer.tcx; - Self { analyzer, tcx } - } - - pub fn visit_statement(&mut self, stmt: &mut mir::Statement<'tcx>) { - // dummy location - mir::visit::MutVisitor::visit_statement(self, stmt, mir::Location::START); - } - - pub fn visit_terminator(&mut self, term: &mut mir::Terminator<'tcx>) { - // dummy location - mir::visit::MutVisitor::visit_terminator(self, term, mir::Location::START); - } -} +mod reborrow; +pub use reborrow::ReborrowVisitor; diff --git a/src/analyze/basic_block/visitor/reborrow.rs b/src/analyze/basic_block/visitor/reborrow.rs new file mode 100644 index 0000000..32af11d --- /dev/null +++ b/src/analyze/basic_block/visitor/reborrow.rs @@ -0,0 +1,136 @@ +use rustc_middle::mir::{self, Local}; +use rustc_middle::ty::{self as mir_ty, TyCtxt}; + +use crate::analyze::ReplacePlacesVisitor; + +pub struct ReborrowVisitor<'a, 'tcx, 'ctx> { + tcx: TyCtxt<'tcx>, + analyzer: &'a mut crate::analyze::basic_block::Analyzer<'tcx, 'ctx>, +} + +impl<'tcx> ReborrowVisitor<'_, 'tcx, '_> { + fn insert_borrow(&mut self, place: mir::Place<'tcx>, inner_ty: mir_ty::Ty<'tcx>) -> Local { + let r = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased); + let ty = mir_ty::Ty::new_mut_ref(self.tcx, r, inner_ty); + let decl = mir::LocalDecl::new(ty, Default::default()).immutable(); + let new_local = self.analyzer.local_decls.push(decl); + let new_local_ty = self.analyzer.borrow_place_(place, inner_ty); + self.analyzer.bind_local(new_local, new_local_ty); + tracing::info!(old_place = ?place, ?new_local, "implicitly borrowed"); + new_local + } + + fn insert_reborrow(&mut self, place: mir::Place<'tcx>, inner_ty: mir_ty::Ty<'tcx>) -> Local { + let r = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased); + let ty = mir_ty::Ty::new_mut_ref(self.tcx, r, inner_ty); + let decl = mir::LocalDecl::new(ty, Default::default()).immutable(); + let new_local = self.analyzer.local_decls.push(decl); + let new_local_ty = self.analyzer.borrow_place_(place, inner_ty); + self.analyzer.bind_local(new_local, new_local_ty); + tracing::info!(old_place = ?place, ?new_local, "implicitly reborrowed"); + new_local + } +} + +impl<'a, 'tcx, 'ctx> mir::visit::MutVisitor<'tcx> for ReborrowVisitor<'a, 'tcx, 'ctx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_assign( + &mut self, + place: &mut mir::Place<'tcx>, + rvalue: &mut mir::Rvalue<'tcx>, + location: mir::Location, + ) { + if !self.analyzer.is_defined(place.local) { + self.super_assign(place, rvalue, location); + return; + } + + if place.projection.is_empty() && self.analyzer.is_mut_local(place.local) { + let ty = self.analyzer.local_decls[place.local].ty; + let new_local = self.insert_borrow(place.local.into(), ty); + let new_place = self.tcx.mk_place_deref(new_local.into()); + ReplacePlacesVisitor::with_replacement(self.tcx, place.local.into(), new_place) + .visit_rvalue(rvalue, location); + *place = new_place; + self.super_assign(place, rvalue, location); + return; + } + + let inner_place = if place.projection.last() == Some(&mir::PlaceElem::Deref) { + // *m = *m + 1 => m1 = &mut m; *m1 = *m + 1 + let mut projection = place.projection.as_ref().to_vec(); + projection.pop(); + mir::Place { + local: place.local, + projection: self.tcx.mk_place_elems(&projection), + } + } else { + // s.0 = s.0 + 1 => m1 = &mut s.0; *m1 = *m1 + 1 + *place + }; + + let ty = inner_place.ty(&self.analyzer.local_decls, self.tcx).ty; + let (new_local, new_place) = match ty.kind() { + mir_ty::TyKind::Ref(_, inner_ty, m) if m.is_mut() => { + let new_local = self.insert_reborrow(*place, *inner_ty); + (new_local, new_local.into()) + } + mir_ty::TyKind::Adt(adt, args) if adt.is_box() => { + let inner_ty = args.type_at(0); + let new_local = self.insert_borrow(*place, inner_ty); + (new_local, new_local.into()) + } + _ => { + let new_local = self.insert_borrow(*place, ty); + (new_local, self.tcx.mk_place_deref(new_local.into())) + } + }; + + ReplacePlacesVisitor::with_replacement(self.tcx, inner_place, new_place) + .visit_rvalue(rvalue, location); + *place = self.tcx.mk_place_deref(new_local.into()); + self.super_assign(place, rvalue, location); + } + + // TODO: is it always true that the operand is not referred again in rvalue + fn visit_operand(&mut self, operand: &mut mir::Operand<'tcx>, location: mir::Location) { + let Some(p) = operand.place() else { + self.super_operand(operand, location); + return; + }; + + let mir_ty::TyKind::Ref(_, inner_ty, m) = + p.ty(&self.analyzer.local_decls, self.tcx).ty.kind() + else { + self.super_operand(operand, location); + return; + }; + + if m.is_mut() { + let new_local = self.insert_reborrow(self.tcx.mk_place_deref(p), *inner_ty); + *operand = mir::Operand::Move(new_local.into()); + } + + self.super_operand(operand, location); + } +} + +impl<'a, 'tcx, 'ctx> ReborrowVisitor<'a, 'tcx, 'ctx> { + pub fn new(analyzer: &'a mut crate::analyze::basic_block::Analyzer<'tcx, 'ctx>) -> Self { + let tcx = analyzer.tcx; + Self { analyzer, tcx } + } + + pub fn visit_statement(&mut self, stmt: &mut mir::Statement<'tcx>) { + // dummy location + mir::visit::MutVisitor::visit_statement(self, stmt, mir::Location::START); + } + + pub fn visit_terminator(&mut self, term: &mut mir::Terminator<'tcx>) { + // dummy location + mir::visit::MutVisitor::visit_terminator(self, term, mir::Location::START); + } +} From 80645546710a679b92c996ba983d30da333b8bf2 Mon Sep 17 00:00:00 2001 From: coord_e Date: Wed, 11 Feb 2026 14:37:35 +0900 Subject: [PATCH 2/4] Ensure Fn trait calls resolve to closure DefId --- src/analyze.rs | 8 ++++++++ src/analyze/basic_block.rs | 26 +++++++++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index f8078d5..5321aa0 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -487,4 +487,12 @@ impl<'tcx> Analyzer<'tcx> { } ensure_annot } + + /// Whether the given `def_id` corresponds to a method of one of the `Fn` traits. + fn is_fn_trait_method(&self, def_id: DefId) -> bool { + self.tcx + .trait_of_item(def_id) + .and_then(|trait_did| self.tcx.fn_trait_kind_from_def_id(trait_did)) + .is_some() + } } diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 0146e32..148794e 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -568,12 +568,28 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { { // TODO: handle const_fn_def on Env side let func_ty = if let Some((def_id, args)) = func.const_fn_def() { - let param_env = self.tcx.param_env(self.local_def_id); - let instance = mir_ty::Instance::resolve(self.tcx, param_env, def_id, args).unwrap(); - let resolved_def_id = if let Some(instance) = instance { - instance.def_id() + let resolved_def_id = if self.ctx.is_fn_trait_method(def_id) { + // When calling a closure via `Fn`/`FnMut`/`FnOnce` trait, + // we simply replace the def_id with the closure's function def_id. + // This skips shims, and makes self arguments mismatch. visitor::RustCallVisitor + // adjusts the arguments accordingly. + let mir_ty::TyKind::Closure(closure_def_id, _) = args.type_at(0).kind() else { + panic!("expected closure arg for fn trait"); + }; + tracing::debug!(?closure_def_id, "closure instance"); + *closure_def_id } else { - def_id + let param_env = self + .tcx + .param_env(self.local_def_id) + .with_reveal_all_normalized(self.tcx); + let instance = + mir_ty::Instance::resolve(self.tcx, param_env, def_id, args).unwrap(); + if let Some(instance) = instance { + instance.def_id() + } else { + def_id + } }; if def_id != resolved_def_id { tracing::info!(?def_id, ?resolved_def_id, "resolve",); From f08dfcfd2a87ef8177472e31866e1ee8b276b6a7 Mon Sep 17 00:00:00 2001 From: coord_e Date: Wed, 11 Feb 2026 14:40:29 +0900 Subject: [PATCH 3/4] Add RustCallVisitor to adjust self type of closure calls --- src/analyze/basic_block.rs | 14 ++ src/analyze/basic_block/visitor.rs | 3 + src/analyze/basic_block/visitor/rust_call.rs | 130 +++++++++++++++++++ tests/ui/fail/closure_param_weaken_1.rs | 16 +++ tests/ui/fail/closure_param_weaken_2.rs | 16 +++ tests/ui/pass/closure_param_weaken_1.rs | 16 +++ tests/ui/pass/closure_param_weaken_2.rs | 16 +++ 7 files changed, 211 insertions(+) create mode 100644 src/analyze/basic_block/visitor/rust_call.rs create mode 100644 tests/ui/fail/closure_param_weaken_1.rs create mode 100644 tests/ui/fail/closure_param_weaken_2.rs create mode 100644 tests/ui/pass/closure_param_weaken_1.rs create mode 100644 tests/ui/pass/closure_param_weaken_2.rs diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 148794e..a785495 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -41,6 +41,10 @@ pub struct Analyzer<'tcx, 'ctx> { } impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { + fn ctx(&self) -> &analyze::Analyzer<'tcx> { + &*self.ctx + } + fn is_defined(&self, local: Local) -> bool { self.env.contains_local(local) } @@ -53,6 +57,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { visitor::ReborrowVisitor::new(self) } + fn rust_call_visitor<'a>(&'a mut self) -> visitor::RustCallVisitor<'a, 'tcx, 'ctx> { + visitor::RustCallVisitor::new(self) + } + fn basic_block_ty(&self, bb: BasicBlock) -> &BasicBlockType { self.ctx.basic_block_ty(self.local_def_id, bb) } @@ -687,6 +695,11 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { self.env.borrow_place(place, prophecy).into() } + fn immut_borrow_place(&self, referent: mir::Place<'tcx>) -> rty::RefinedType { + let place = self.elaborate_place(&referent); + self.env.place_type(place).immut().into() + } + #[tracing::instrument(skip(self, lhs, rvalue))] fn analyze_assignment( &mut self, @@ -770,6 +783,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { source_info: term.source_info, }; } + self.rust_call_visitor().visit_terminator(&mut term); self.reborrow_visitor().visit_terminator(&mut term); tracing::debug!(term = ?term.kind); term diff --git a/src/analyze/basic_block/visitor.rs b/src/analyze/basic_block/visitor.rs index a723721..1498742 100644 --- a/src/analyze/basic_block/visitor.rs +++ b/src/analyze/basic_block/visitor.rs @@ -1,2 +1,5 @@ mod reborrow; +mod rust_call; + pub use reborrow::ReborrowVisitor; +pub use rust_call::RustCallVisitor; diff --git a/src/analyze/basic_block/visitor/rust_call.rs b/src/analyze/basic_block/visitor/rust_call.rs new file mode 100644 index 0000000..5b4a24d --- /dev/null +++ b/src/analyze/basic_block/visitor/rust_call.rs @@ -0,0 +1,130 @@ +use rustc_middle::mir::{self, Local}; +use rustc_middle::ty::{self as mir_ty, TyCtxt}; + +pub struct RustCallVisitor<'a, 'tcx, 'ctx> { + tcx: TyCtxt<'tcx>, + analyzer: &'a mut crate::analyze::basic_block::Analyzer<'tcx, 'ctx>, +} + +// TODO: consolidate logic with ReborrowVisitor +impl<'tcx> RustCallVisitor<'_, 'tcx, '_> { + fn insert_immut_borrow( + &mut self, + place: mir::Place<'tcx>, + inner_ty: mir_ty::Ty<'tcx>, + ) -> Local { + let r = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased); + let ty = mir_ty::Ty::new_imm_ref(self.tcx, r, inner_ty); + let decl = mir::LocalDecl::new(ty, Default::default()).immutable(); + let new_local = self.analyzer.local_decls.push(decl); + let new_local_ty = self.analyzer.immut_borrow_place(place); + self.analyzer.bind_local(new_local, new_local_ty); + tracing::info!(old_place = ?place, ?new_local, "implicitly (imm-)borrowed"); + new_local + } + + fn insert_immut_reborrow( + &mut self, + place: mir::Place<'tcx>, + inner_ty: mir_ty::Ty<'tcx>, + ) -> Local { + let r = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased); + let ty = mir_ty::Ty::new_imm_ref(self.tcx, r, inner_ty); + let decl = mir::LocalDecl::new(ty, Default::default()).immutable(); + let new_local = self.analyzer.local_decls.push(decl); + let new_local_ty = self.analyzer.immut_borrow_place(place); + self.analyzer.bind_local(new_local, new_local_ty); + tracing::info!(old_place = ?place, ?new_local, "implicitly (imm-)reborrowed"); + new_local + } +} + +impl<'a, 'tcx, 'ctx> mir::visit::MutVisitor<'tcx> for RustCallVisitor<'a, 'tcx, 'ctx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_terminator( + &mut self, + terminator: &mut mir::Terminator<'tcx>, + location: mir::Location, + ) { + if let mir::TerminatorKind::Call { func, args, .. } = &mut terminator.kind { + if let Some((def_id, generic_args)) = func.const_fn_def() { + if !self.analyzer.ctx().is_fn_trait_method(def_id) { + self.super_terminator(terminator, location); + return; + } + + // RustCallVisitor expects all generic args to be already instantiated + let mir_ty::TyKind::Closure(resolved_def_id, _) = generic_args.type_at(0).kind() + else { + panic!("expected closure arg for fn trait"); + }; + let fn_sig = self + .analyzer + .ctx() + .local_fn_sig(resolved_def_id.expect_local()); + if !matches!(fn_sig.abi, rustc_target::spec::abi::Abi::RustCall) { + self.super_terminator(terminator, location); + return; + } + tracing::info!(?def_id, ?resolved_def_id, "handling RustCall function call"); + + // three cases to consider: + // 1. function of resolved_def_id takes &{closure} but def_id is FnMut::call_mut + // 2. function of resolved_def_id takes &{closure} but def_id is FnOnce::call_once + // 3. function of resolved_def_id takes &mut {closure} but def_id is FnOnce::call_once + let resolved_closure_ty = fn_sig.inputs().first().unwrap(); + let arg_closure_ty = args + .first() + .unwrap() + .node + .ty(&self.analyzer.local_decls, self.tcx); + let arg_closure_place = args.first().unwrap().node.place().unwrap(); + if matches!( + resolved_closure_ty.ref_mutability(), + Some(mir_ty::Mutability::Not) + ) { + if let mir_ty::TyKind::Ref(_, closure_ty, mir_ty::Mutability::Mut) = + arg_closure_ty.kind() + { + // case 1: &mut {closure} -> &{closure} + let borrow_target_place = self.tcx.mk_place_deref(arg_closure_place); + let borrowed_closure_local = + self.insert_immut_reborrow(borrow_target_place, *closure_ty); + args[0].node = mir::Operand::Copy(borrowed_closure_local.into()); + tracing::debug!("applied immut-reborrow for closure argument"); + } else if arg_closure_ty.ref_mutability().is_none() { + // case 2: {closure} -> &{closure} + let borrowed_closure_local = + self.insert_immut_borrow(arg_closure_place, arg_closure_ty); + args[0].node = mir::Operand::Copy(borrowed_closure_local.into()); + tracing::debug!("applied immut-borrow for closure argument"); + } + } else if matches!( + resolved_closure_ty.ref_mutability(), + Some(mir_ty::Mutability::Mut) + if arg_closure_ty.ref_mutability().is_none() + ) { + // case 3: {closure} -> &mut {closure} + unimplemented!(); + } + } + } + + self.super_terminator(terminator, location); + } +} + +impl<'a, 'tcx, 'ctx> RustCallVisitor<'a, 'tcx, 'ctx> { + pub fn new(analyzer: &'a mut crate::analyze::basic_block::Analyzer<'tcx, 'ctx>) -> Self { + let tcx = analyzer.tcx; + Self { analyzer, tcx } + } + + pub fn visit_terminator(&mut self, term: &mut mir::Terminator<'tcx>) { + // dummy location + mir::visit::MutVisitor::visit_terminator(self, term, mir::Location::START); + } +} diff --git a/tests/ui/fail/closure_param_weaken_1.rs b/tests/ui/fail/closure_param_weaken_1.rs new file mode 100644 index 0000000..cbf09ef --- /dev/null +++ b/tests/ui/fail/closure_param_weaken_1.rs @@ -0,0 +1,16 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off + +fn apply(f: F) -> i32 +where + F: FnOnce(i32) -> i32, +{ + f(1) +} + +fn main() { + let x = 1; + let closure = |y: i32| x + y; + let result = apply(closure); + assert!(result == 1); +} diff --git a/tests/ui/fail/closure_param_weaken_2.rs b/tests/ui/fail/closure_param_weaken_2.rs new file mode 100644 index 0000000..51f682b --- /dev/null +++ b/tests/ui/fail/closure_param_weaken_2.rs @@ -0,0 +1,16 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off + +fn apply(mut f: F) -> i32 +where + F: FnMut(i32) -> i32, +{ + f(1) + f(2) +} + +fn main() { + let x = 1; + let closure = |y: i32| x + y; + let result = apply(closure); + assert!(result == 1); +} diff --git a/tests/ui/pass/closure_param_weaken_1.rs b/tests/ui/pass/closure_param_weaken_1.rs new file mode 100644 index 0000000..9eae664 --- /dev/null +++ b/tests/ui/pass/closure_param_weaken_1.rs @@ -0,0 +1,16 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off + +fn apply(f: F) -> i32 +where + F: FnOnce(i32) -> i32, +{ + f(1) +} + +fn main() { + let x = 1; + let closure = |y: i32| x + y; + let result = apply(closure); + assert!(result == 2); +} diff --git a/tests/ui/pass/closure_param_weaken_2.rs b/tests/ui/pass/closure_param_weaken_2.rs new file mode 100644 index 0000000..6f233a1 --- /dev/null +++ b/tests/ui/pass/closure_param_weaken_2.rs @@ -0,0 +1,16 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off + +fn apply(mut f: F) -> i32 +where + F: FnMut(i32) -> i32, +{ + f(1) + f(2) +} + +fn main() { + let x = 1; + let closure = |y: i32| x + y; + let result = apply(closure); + assert!(result == 5); +} From 2ea6cb20920a1162365d711f1141583f2891358a Mon Sep 17 00:00:00 2001 From: coord_e Date: Sat, 14 Feb 2026 12:02:23 +0900 Subject: [PATCH 4/4] Analyzer::local_fn_sig doesn't need to be limited to LocalDefId --- src/analyze.rs | 22 ++++++++------------ src/analyze/basic_block/visitor/rust_call.rs | 5 +---- src/analyze/crate_.rs | 2 +- src/analyze/local_def.rs | 4 ++-- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 5321aa0..1bd06d6 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -402,17 +402,13 @@ impl<'tcx> Analyzer<'tcx> { } } - /// Computes the signature of the local function. + /// Computes the signature of the function using the given `body`. /// - /// This works like `self.tcx.fn_sig(local_def_id).instantiate_identity().skip_binder()`, + /// This works like `self.tcx.fn_sig(def_id).instantiate_identity().skip_binder()`, /// but extracts parameter and return types directly from the given `body` to obtain a signature that /// reflects potential type instantiations happened after `optimized_mir`. - pub fn local_fn_sig_with_body( - &self, - local_def_id: LocalDefId, - body: &mir::Body<'tcx>, - ) -> mir_ty::FnSig<'tcx> { - let ty = self.tcx.type_of(local_def_id).instantiate_identity(); + pub fn fn_sig_with_body(&self, def_id: DefId, body: &mir::Body<'tcx>) -> mir_ty::FnSig<'tcx> { + let ty = self.tcx.type_of(def_id).instantiate_identity(); let sig = if let mir_ty::TyKind::Closure(_, substs) = ty.kind() { substs.as_closure().sig().skip_binder() } else { @@ -428,14 +424,14 @@ impl<'tcx> Analyzer<'tcx> { ) } - /// Computes the signature of the local function. + /// Computes the signature of the function. /// - /// This works like `self.tcx.fn_sig(local_def_id).instantiate_identity().skip_binder()`, + /// This works like `self.tcx.fn_sig(def_id).instantiate_identity().skip_binder()`, /// but extracts parameter and return types directly from [`mir::Body`] to obtain a signature that /// reflects the actual type of lifted closure functions. - pub fn local_fn_sig(&self, local_def_id: LocalDefId) -> mir_ty::FnSig<'tcx> { - let body = self.tcx.optimized_mir(local_def_id); - self.local_fn_sig_with_body(local_def_id, body) + pub fn fn_sig(&self, def_id: DefId) -> mir_ty::FnSig<'tcx> { + let body = self.tcx.optimized_mir(def_id); + self.fn_sig_with_body(def_id, body) } fn extract_require_annot( diff --git a/src/analyze/basic_block/visitor/rust_call.rs b/src/analyze/basic_block/visitor/rust_call.rs index 5b4a24d..73b6338 100644 --- a/src/analyze/basic_block/visitor/rust_call.rs +++ b/src/analyze/basic_block/visitor/rust_call.rs @@ -61,10 +61,7 @@ impl<'a, 'tcx, 'ctx> mir::visit::MutVisitor<'tcx> for RustCallVisitor<'a, 'tcx, else { panic!("expected closure arg for fn trait"); }; - let fn_sig = self - .analyzer - .ctx() - .local_fn_sig(resolved_def_id.expect_local()); + let fn_sig = self.analyzer.ctx().fn_sig(*resolved_def_id); if !matches!(fn_sig.abi, rustc_target::spec::abi::Abi::RustCall) { self.super_terminator(terminator, location); return; diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index cdbc721..ae29c7c 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -69,7 +69,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { #[tracing::instrument(skip(self), fields(def_id = %self.tcx.def_path_str(local_def_id)))] fn refine_fn_def(&mut self, local_def_id: LocalDefId) { - let sig = self.ctx.local_fn_sig(local_def_id); + let sig = self.ctx.fn_sig(local_def_id.to_def_id()); let mut analyzer = self.ctx.local_def_analyzer(local_def_id); diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 826d906..11a3524 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -147,7 +147,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .iter() .map(|ident| ident.to_string()); - let sig = self.ctx.local_fn_sig(local_def_id); + let sig = self.ctx.fn_sig(local_def_id.to_def_id()); let arg_sorts = sig .inputs() .iter() @@ -258,7 +258,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { pub fn expected_ty(&mut self) -> rty::RefinedType { let sig = self .ctx - .local_fn_sig_with_body(self.local_def_id, &self.body); + .fn_sig_with_body(self.local_def_id.to_def_id(), &self.body); let mut param_resolver = analyze::annot::ParamResolver::default(); for (input_ident, input_ty) in self