1use std::iter;
2
3use rustc_data_structures::either::Either;
4use rustc_hir::{Expr, HirId};
5use rustc_index::bit_set::DenseBitSet;
6use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
7use rustc_middle::mir::{
8 BasicBlock, Body, InlineAsmOperand, Local, Location, Place, START_BLOCK, StatementKind, TerminatorKind, traversal,
9};
10use rustc_middle::ty::TyCtxt;
11
12mod possible_borrower;
13pub use possible_borrower::PossibleBorrowerMap;
14
15mod possible_origin;
16
17mod transitive_relation;
18
19#[derive(Clone, Debug, Default)]
20pub struct LocalUsage {
21 pub local_use_locs: Vec<Location>,
23 pub local_consume_or_mutate_locs: Vec<Location>,
25}
26
27pub fn visit_local_usage<const N: usize>(
28 locals: [Local; N],
29 mir: &Body<'_>,
30 location: Location,
31) -> Option<[LocalUsage; N]> {
32 let init = [const {
33 LocalUsage {
34 local_use_locs: Vec::new(),
35 local_consume_or_mutate_locs: Vec::new(),
36 }
37 }; N];
38
39 traversal::Postorder::new(&mir.basic_blocks, location.block, None)
40 .collect::<Vec<_>>()
41 .into_iter()
42 .rev()
43 .try_fold(init, |usage, tbb| {
44 let tdata = &mir.basic_blocks[tbb];
45
46 if tdata.terminator().successors().any(|s| s == location.block) {
48 return None;
49 }
50
51 let mut v = V {
52 locals: &locals,
53 location,
54 results: usage,
55 };
56 v.visit_basic_block_data(tbb, tdata);
57 Some(v.results)
58 })
59}
60
61struct V<'a, const N: usize> {
62 locals: &'a [Local; N],
63 location: Location,
64 results: [LocalUsage; N],
65}
66
67impl<'tcx, const N: usize> Visitor<'tcx> for V<'_, N> {
68 fn visit_place(&mut self, place: &Place<'tcx>, ctx: PlaceContext, loc: Location) {
69 if loc.block == self.location.block && loc.statement_index <= self.location.statement_index {
70 return;
71 }
72
73 let local = place.local;
74
75 for (self_local, result) in iter::zip(self.locals, &mut self.results) {
76 if local == *self_local {
77 if !matches!(
78 ctx,
79 PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
80 ) {
81 result.local_use_locs.push(loc);
82 }
83 if matches!(
84 ctx,
85 PlaceContext::NonMutatingUse(NonMutatingUseContext::Move | NonMutatingUseContext::Inspect)
86 | PlaceContext::MutatingUse(MutatingUseContext::Borrow)
87 ) {
88 result.local_consume_or_mutate_locs.push(loc);
89 }
90 }
91 }
92 }
93}
94
95pub fn block_in_cycle(body: &Body<'_>, block: BasicBlock) -> bool {
97 let mut seen = DenseBitSet::new_empty(body.basic_blocks.len());
98 let mut to_visit = Vec::with_capacity(body.basic_blocks.len() / 2);
99
100 seen.insert(block);
101 let mut next = block;
102 loop {
103 for succ in body.basic_blocks[next].terminator().successors() {
104 if seen.insert(succ) {
105 to_visit.push(succ);
106 } else if succ == block {
107 return true;
108 }
109 }
110
111 if let Some(x) = to_visit.pop() {
112 next = x;
113 } else {
114 return false;
115 }
116 }
117}
118
119pub fn used_exactly_once(mir: &Body<'_>, local: Local) -> Option<bool> {
121 visit_local_usage(
122 [local],
123 mir,
124 Location {
125 block: START_BLOCK,
126 statement_index: 0,
127 },
128 )
129 .map(|[local_usage]| {
130 let mut locations = local_usage
131 .local_use_locs
132 .into_iter()
133 .filter(|&location| !is_local_assignment(mir, local, location));
134 if let Some(location) = locations.next() {
135 locations.next().is_none() && !block_in_cycle(mir, location.block)
136 } else {
137 false
138 }
139 })
140}
141
142#[expect(clippy::module_name_repetitions)]
144pub fn enclosing_mir(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<&Body<'_>> {
145 let body_owner_local_def_id = tcx.hir_enclosing_body_owner(hir_id);
146 if tcx.hir_body_owner_kind(body_owner_local_def_id).is_fn_or_closure() {
147 Some(tcx.optimized_mir(body_owner_local_def_id.to_def_id()))
148 } else {
149 None
150 }
151}
152
153pub fn expr_local(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Local> {
156 enclosing_mir(tcx, expr.hir_id).and_then(|mir| {
157 mir.local_decls.iter_enumerated().find_map(|(local, local_decl)| {
158 if local_decl.source_info.span == expr.span {
159 Some(local)
160 } else {
161 None
162 }
163 })
164 })
165}
166
167pub fn local_assignments(mir: &Body<'_>, local: Local) -> Vec<Location> {
169 let mut locations = Vec::new();
170 for (block, data) in mir.basic_blocks.iter_enumerated() {
171 for statement_index in 0..=data.statements.len() {
172 let location = Location { block, statement_index };
173 if is_local_assignment(mir, local, location) {
174 locations.push(location);
175 }
176 }
177 }
178 locations
179}
180
181fn is_local_assignment(mir: &Body<'_>, local: Local, location: Location) -> bool {
184 match mir.stmt_at(location) {
185 Either::Left(statement) => {
186 if let StatementKind::Assign(box (place, _)) = statement.kind {
187 place.as_local() == Some(local)
188 } else {
189 false
190 }
191 },
192 Either::Right(terminator) => match &terminator.kind {
193 TerminatorKind::Call { destination, .. } => destination.as_local() == Some(local),
194 TerminatorKind::InlineAsm { operands, .. } => operands.iter().any(|operand| {
195 if let InlineAsmOperand::Out { place: Some(place), .. } = operand {
196 place.as_local() == Some(local)
197 } else {
198 false
199 }
200 }),
201 _ => false,
202 },
203 }
204}