1use std::iter;
7
8use hir_def::{DefWithBodyId, HasModule};
9use la_arena::ArenaMap;
10use rustc_hash::FxHashMap;
11use stdx::never;
12use triomphe::Arc;
13
14use crate::{
15 InferenceResult,
16 db::{HirDatabase, InternedClosure, InternedClosureId},
17 display::DisplayTarget,
18 mir::OperandKind,
19 next_solver::{
20 DbInterner, GenericArgs, ParamEnv, Ty, TypingMode,
21 infer::{DbInternerInferExt, InferCtxt},
22 },
23};
24
25use super::{
26 BasicBlockId, BorrowKind, LocalId, MirBody, MirLowerError, MirSpan, MutBorrowKind, Operand,
27 Place, ProjectionElem, Rvalue, StatementKind, TerminatorKind,
28};
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum MutabilityReason {
33 Mut { spans: Vec<MirSpan> },
34 Not,
35 Unused,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct MovedOutOfRef<'db> {
40 pub ty: Ty<'db>,
41 pub span: MirSpan,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct PartiallyMoved<'db> {
46 pub ty: Ty<'db>,
47 pub span: MirSpan,
48 pub local: LocalId<'db>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct BorrowRegion<'db> {
53 pub local: LocalId<'db>,
54 pub kind: BorrowKind,
55 pub places: Vec<MirSpan>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct BorrowckResult<'db> {
60 pub mir_body: Arc<MirBody<'db>>,
61 pub mutability_of_locals: ArenaMap<LocalId<'db>, MutabilityReason>,
62 pub moved_out_of_ref: Vec<MovedOutOfRef<'db>>,
63 pub partially_moved: Vec<PartiallyMoved<'db>>,
64 pub borrow_regions: Vec<BorrowRegion<'db>>,
65}
66
67fn all_mir_bodies<'db>(
68 db: &'db dyn HirDatabase,
69 def: DefWithBodyId,
70 mut cb: impl FnMut(Arc<MirBody<'db>>),
71) -> Result<(), MirLowerError<'db>> {
72 fn for_closure<'db>(
73 db: &'db dyn HirDatabase,
74 c: InternedClosureId,
75 cb: &mut impl FnMut(Arc<MirBody<'db>>),
76 ) -> Result<(), MirLowerError<'db>> {
77 match db.mir_body_for_closure(c) {
78 Ok(body) => {
79 cb(body.clone());
80 body.closures.iter().try_for_each(|&it| for_closure(db, it, cb))
81 }
82 Err(e) => Err(e),
83 }
84 }
85 match db.mir_body(def) {
86 Ok(body) => {
87 cb(body.clone());
88 body.closures.iter().try_for_each(|&it| for_closure(db, it, &mut cb))
89 }
90 Err(e) => Err(e),
91 }
92}
93
94pub fn borrowck_query<'db>(
95 db: &'db dyn HirDatabase,
96 def: DefWithBodyId,
97) -> Result<Arc<[BorrowckResult<'db>]>, MirLowerError<'db>> {
98 let _p = tracing::info_span!("borrowck_query").entered();
99 let module = def.module(db);
100 let interner = DbInterner::new_with(db, module.krate(db));
101 let env = db.trait_environment_for_body(def);
102 let mut res = vec![];
103 let typing_mode = TypingMode::borrowck(interner, def.into());
105 all_mir_bodies(db, def, |body| {
106 let infcx = interner.infer_ctxt().build(typing_mode);
108 res.push(BorrowckResult {
109 mutability_of_locals: mutability_of_locals(&infcx, env, &body),
110 moved_out_of_ref: moved_out_of_ref(&infcx, env, &body),
111 partially_moved: partially_moved(&infcx, env, &body),
112 borrow_regions: borrow_regions(db, &body),
113 mir_body: body,
114 });
115 })?;
116 Ok(res.into())
117}
118
119fn make_fetch_closure_field<'db>(
120 db: &'db dyn HirDatabase,
121) -> impl FnOnce(InternedClosureId, GenericArgs<'db>, usize) -> Ty<'db> + use<'db> {
122 |c: InternedClosureId, subst: GenericArgs<'db>, f: usize| {
123 let InternedClosure(def, _) = db.lookup_intern_closure(c);
124 let infer = InferenceResult::for_body(db, def);
125 let (captures, _) = infer.closure_info(c);
126 let parent_subst = subst.split_closure_args_untupled().parent_args;
127 let interner = DbInterner::new_no_crate(db);
128 captures.get(f).expect("broken closure field").ty.instantiate(interner, parent_subst)
129 }
130}
131
132fn moved_out_of_ref<'db>(
133 infcx: &InferCtxt<'db>,
134 env: ParamEnv<'db>,
135 body: &MirBody<'db>,
136) -> Vec<MovedOutOfRef<'db>> {
137 let db = infcx.interner.db;
138 let mut result = vec![];
139 let mut for_operand = |op: &Operand<'db>, span: MirSpan| match op.kind {
140 OperandKind::Copy(p) | OperandKind::Move(p) => {
141 let mut ty: Ty<'db> = body.locals[p.local].ty;
142 let mut is_dereference_of_ref = false;
143 for proj in p.projection.lookup(&body.projection_store) {
144 if *proj == ProjectionElem::Deref && ty.as_reference().is_some() {
145 is_dereference_of_ref = true;
146 }
147 ty = proj.projected_ty(
148 infcx,
149 env,
150 ty,
151 make_fetch_closure_field(db),
152 body.owner.module(db).krate(db),
153 );
154 }
155 if is_dereference_of_ref
156 && !infcx.type_is_copy_modulo_regions(env, ty)
157 && !ty.references_non_lt_error()
158 {
159 result.push(MovedOutOfRef { span: op.span.unwrap_or(span), ty });
160 }
161 }
162 OperandKind::Constant { .. } | OperandKind::Static(_) => (),
163 };
164 for (_, block) in body.basic_blocks.iter() {
165 db.unwind_if_revision_cancelled();
166 for statement in &block.statements {
167 match &statement.kind {
168 StatementKind::Assign(_, r) => match r {
169 Rvalue::ShallowInitBoxWithAlloc(_) => (),
170 Rvalue::ShallowInitBox(o, _)
171 | Rvalue::UnaryOp(_, o)
172 | Rvalue::Cast(_, o, _)
173 | Rvalue::Repeat(o, _)
174 | Rvalue::Use(o) => for_operand(o, statement.span),
175 Rvalue::CopyForDeref(_)
176 | Rvalue::Discriminant(_)
177 | Rvalue::Len(_)
178 | Rvalue::Ref(_, _) => (),
179 Rvalue::CheckedBinaryOp(_, o1, o2) => {
180 for_operand(o1, statement.span);
181 for_operand(o2, statement.span);
182 }
183 Rvalue::Aggregate(_, ops) => {
184 for op in ops.iter() {
185 for_operand(op, statement.span);
186 }
187 }
188 Rvalue::ThreadLocalRef(n)
189 | Rvalue::AddressOf(n)
190 | Rvalue::BinaryOp(n)
191 | Rvalue::NullaryOp(n) => match *n {},
192 },
193 StatementKind::FakeRead(_)
194 | StatementKind::Deinit(_)
195 | StatementKind::StorageLive(_)
196 | StatementKind::StorageDead(_)
197 | StatementKind::Nop => (),
198 }
199 }
200 match &block.terminator {
201 Some(terminator) => match &terminator.kind {
202 TerminatorKind::SwitchInt { discr, .. } => for_operand(discr, terminator.span),
203 TerminatorKind::FalseEdge { .. }
204 | TerminatorKind::FalseUnwind { .. }
205 | TerminatorKind::Goto { .. }
206 | TerminatorKind::UnwindResume
207 | TerminatorKind::CoroutineDrop
208 | TerminatorKind::Abort
209 | TerminatorKind::Return
210 | TerminatorKind::Unreachable
211 | TerminatorKind::Drop { .. } => (),
212 TerminatorKind::DropAndReplace { value, .. } => {
213 for_operand(value, terminator.span);
214 }
215 TerminatorKind::Call { func, args, .. } => {
216 for_operand(func, terminator.span);
217 args.iter().for_each(|it| for_operand(it, terminator.span));
218 }
219 TerminatorKind::Assert { cond, .. } => {
220 for_operand(cond, terminator.span);
221 }
222 TerminatorKind::Yield { value, .. } => {
223 for_operand(value, terminator.span);
224 }
225 },
226 None => (),
227 }
228 }
229 result.shrink_to_fit();
230 result
231}
232
233fn partially_moved<'db>(
234 infcx: &InferCtxt<'db>,
235 env: ParamEnv<'db>,
236 body: &MirBody<'db>,
237) -> Vec<PartiallyMoved<'db>> {
238 let db = infcx.interner.db;
239 let mut result = vec![];
240 let mut for_operand = |op: &Operand<'db>, span: MirSpan| match op.kind {
241 OperandKind::Copy(p) | OperandKind::Move(p) => {
242 let mut ty: Ty<'db> = body.locals[p.local].ty;
243 for proj in p.projection.lookup(&body.projection_store) {
244 ty = proj.projected_ty(
245 infcx,
246 env,
247 ty,
248 make_fetch_closure_field(db),
249 body.owner.module(db).krate(db),
250 );
251 }
252 if !infcx.type_is_copy_modulo_regions(env, ty) && !ty.references_non_lt_error() {
253 result.push(PartiallyMoved { span, ty, local: p.local });
254 }
255 }
256 OperandKind::Constant { .. } | OperandKind::Static(_) => (),
257 };
258 for (_, block) in body.basic_blocks.iter() {
259 db.unwind_if_revision_cancelled();
260 for statement in &block.statements {
261 match &statement.kind {
262 StatementKind::Assign(_, r) => match r {
263 Rvalue::ShallowInitBoxWithAlloc(_) => (),
264 Rvalue::ShallowInitBox(o, _)
265 | Rvalue::UnaryOp(_, o)
266 | Rvalue::Cast(_, o, _)
267 | Rvalue::Repeat(o, _)
268 | Rvalue::Use(o) => for_operand(o, statement.span),
269 Rvalue::CopyForDeref(_)
270 | Rvalue::Discriminant(_)
271 | Rvalue::Len(_)
272 | Rvalue::Ref(_, _) => (),
273 Rvalue::CheckedBinaryOp(_, o1, o2) => {
274 for_operand(o1, statement.span);
275 for_operand(o2, statement.span);
276 }
277 Rvalue::Aggregate(_, ops) => {
278 for op in ops.iter() {
279 for_operand(op, statement.span);
280 }
281 }
282 Rvalue::ThreadLocalRef(n)
283 | Rvalue::AddressOf(n)
284 | Rvalue::BinaryOp(n)
285 | Rvalue::NullaryOp(n) => match *n {},
286 },
287 StatementKind::FakeRead(_)
288 | StatementKind::Deinit(_)
289 | StatementKind::StorageLive(_)
290 | StatementKind::StorageDead(_)
291 | StatementKind::Nop => (),
292 }
293 }
294 match &block.terminator {
295 Some(terminator) => match &terminator.kind {
296 TerminatorKind::SwitchInt { discr, .. } => for_operand(discr, terminator.span),
297 TerminatorKind::FalseEdge { .. }
298 | TerminatorKind::FalseUnwind { .. }
299 | TerminatorKind::Goto { .. }
300 | TerminatorKind::UnwindResume
301 | TerminatorKind::CoroutineDrop
302 | TerminatorKind::Abort
303 | TerminatorKind::Return
304 | TerminatorKind::Unreachable
305 | TerminatorKind::Drop { .. } => (),
306 TerminatorKind::DropAndReplace { value, .. } => {
307 for_operand(value, terminator.span);
308 }
309 TerminatorKind::Call { func, args, .. } => {
310 for_operand(func, terminator.span);
311 args.iter().for_each(|it| for_operand(it, terminator.span));
312 }
313 TerminatorKind::Assert { cond, .. } => {
314 for_operand(cond, terminator.span);
315 }
316 TerminatorKind::Yield { value, .. } => {
317 for_operand(value, terminator.span);
318 }
319 },
320 None => (),
321 }
322 }
323 result.shrink_to_fit();
324 result
325}
326
327fn borrow_regions<'db>(db: &'db dyn HirDatabase, body: &MirBody<'db>) -> Vec<BorrowRegion<'db>> {
328 let mut borrows = FxHashMap::default();
329 for (_, block) in body.basic_blocks.iter() {
330 db.unwind_if_revision_cancelled();
331 for statement in &block.statements {
332 if let StatementKind::Assign(_, Rvalue::Ref(kind, p)) = &statement.kind {
333 borrows
334 .entry(p.local)
335 .and_modify(|it: &mut BorrowRegion<'db>| {
336 it.places.push(statement.span);
337 })
338 .or_insert_with(|| BorrowRegion {
339 local: p.local,
340 kind: *kind,
341 places: vec![statement.span],
342 });
343 }
344 }
345 match &block.terminator {
346 Some(terminator) => match &terminator.kind {
347 TerminatorKind::FalseEdge { .. }
348 | TerminatorKind::FalseUnwind { .. }
349 | TerminatorKind::Goto { .. }
350 | TerminatorKind::UnwindResume
351 | TerminatorKind::CoroutineDrop
352 | TerminatorKind::Abort
353 | TerminatorKind::Return
354 | TerminatorKind::Unreachable
355 | TerminatorKind::Drop { .. } => (),
356 TerminatorKind::DropAndReplace { .. } => {}
357 TerminatorKind::Call { .. } => {}
358 _ => (),
359 },
360 None => (),
361 }
362 }
363
364 borrows.into_values().collect()
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368enum ProjectionCase {
369 Direct,
371 DirectPart,
373 Indirect,
375}
376
377fn place_case<'db>(
378 infcx: &InferCtxt<'db>,
379 env: ParamEnv<'db>,
380 body: &MirBody<'db>,
381 lvalue: &Place<'db>,
382) -> ProjectionCase {
383 let db = infcx.interner.db;
384 let mut is_part_of = false;
385 let mut ty = body.locals[lvalue.local].ty;
386 for proj in lvalue.projection.lookup(&body.projection_store).iter() {
387 match proj {
388 ProjectionElem::Deref if ty.as_adt().is_none() => return ProjectionCase::Indirect, ProjectionElem::Deref | ProjectionElem::ConstantIndex { .. }
391 | ProjectionElem::Subslice { .. }
392 | ProjectionElem::Field(_)
393 | ProjectionElem::ClosureField(_)
394 | ProjectionElem::Index(_) => {
395 is_part_of = true;
396 }
397 ProjectionElem::OpaqueCast(_) => (),
398 }
399 ty = proj.projected_ty(
400 infcx,
401 env,
402 ty,
403 make_fetch_closure_field(db),
404 body.owner.module(db).krate(db),
405 );
406 }
407 if is_part_of { ProjectionCase::DirectPart } else { ProjectionCase::Direct }
408}
409
410fn ever_initialized_map<'db>(
414 db: &'db dyn HirDatabase,
415 body: &MirBody<'db>,
416) -> ArenaMap<BasicBlockId<'db>, ArenaMap<LocalId<'db>, bool>> {
417 let mut result: ArenaMap<BasicBlockId<'db>, ArenaMap<LocalId<'db>, bool>> =
418 body.basic_blocks.iter().map(|it| (it.0, ArenaMap::default())).collect();
419 fn dfs<'db>(
420 db: &'db dyn HirDatabase,
421 body: &MirBody<'db>,
422 l: LocalId<'db>,
423 stack: &mut Vec<BasicBlockId<'db>>,
424 result: &mut ArenaMap<BasicBlockId<'db>, ArenaMap<LocalId<'db>, bool>>,
425 ) {
426 while let Some(b) = stack.pop() {
427 let mut is_ever_initialized = result[b][l]; let block = &body.basic_blocks[b];
429 for statement in &block.statements {
430 match &statement.kind {
431 StatementKind::Assign(p, _) => {
432 if p.projection.lookup(&body.projection_store).is_empty() && p.local == l {
433 is_ever_initialized = true;
434 }
435 }
436 StatementKind::StorageDead(p) => {
437 if *p == l {
438 is_ever_initialized = false;
439 }
440 }
441 StatementKind::Deinit(_)
442 | StatementKind::FakeRead(_)
443 | StatementKind::Nop
444 | StatementKind::StorageLive(_) => (),
445 }
446 }
447 let Some(terminator) = &block.terminator else {
448 never!(
449 "Terminator should be none only in construction.\nThe body:\n{}",
450 body.pretty_print(db, DisplayTarget::from_crate(db, body.owner.krate(db)))
451 );
452 return;
453 };
454 let mut process = |target, is_ever_initialized| {
455 if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized {
456 result[target].insert(l, is_ever_initialized);
457 stack.push(target);
458 }
459 };
460 match &terminator.kind {
461 TerminatorKind::Goto { target } => process(*target, is_ever_initialized),
462 TerminatorKind::SwitchInt { targets, .. } => {
463 targets.all_targets().iter().for_each(|&it| process(it, is_ever_initialized));
464 }
465 TerminatorKind::UnwindResume
466 | TerminatorKind::Abort
467 | TerminatorKind::Return
468 | TerminatorKind::Unreachable => (),
469 TerminatorKind::Call { target, cleanup, destination, .. } => {
470 if destination.projection.lookup(&body.projection_store).is_empty()
471 && destination.local == l
472 {
473 is_ever_initialized = true;
474 }
475 target.iter().chain(cleanup).for_each(|&it| process(it, is_ever_initialized));
476 }
477 TerminatorKind::Drop { target, unwind, place: _ } => {
478 iter::once(target)
479 .chain(unwind)
480 .for_each(|&it| process(it, is_ever_initialized));
481 }
482 TerminatorKind::DropAndReplace { .. }
483 | TerminatorKind::Assert { .. }
484 | TerminatorKind::Yield { .. }
485 | TerminatorKind::CoroutineDrop
486 | TerminatorKind::FalseEdge { .. }
487 | TerminatorKind::FalseUnwind { .. } => {
488 never!("We don't emit these MIR terminators yet");
489 }
490 }
491 }
492 }
493 let mut stack = Vec::new();
494 for &l in &body.param_locals {
495 result[body.start_block].insert(l, true);
496 stack.clear();
497 stack.push(body.start_block);
498 dfs(db, body, l, &mut stack, &mut result);
499 }
500 for l in body.locals.iter().map(|it| it.0) {
501 db.unwind_if_revision_cancelled();
502 if !result[body.start_block].contains_idx(l) {
503 result[body.start_block].insert(l, false);
504 stack.clear();
505 stack.push(body.start_block);
506 dfs(db, body, l, &mut stack, &mut result);
507 }
508 }
509 result
510}
511
512fn push_mut_span<'db>(
513 local: LocalId<'db>,
514 span: MirSpan,
515 result: &mut ArenaMap<LocalId<'db>, MutabilityReason>,
516) {
517 match &mut result[local] {
518 MutabilityReason::Mut { spans } => spans.push(span),
519 it @ (MutabilityReason::Not | MutabilityReason::Unused) => {
520 *it = MutabilityReason::Mut { spans: vec![span] }
521 }
522 };
523}
524
525fn record_usage<'db>(local: LocalId<'db>, result: &mut ArenaMap<LocalId<'db>, MutabilityReason>) {
526 if let it @ MutabilityReason::Unused = &mut result[local] {
527 *it = MutabilityReason::Not;
528 };
529}
530
531fn record_usage_for_operand<'db>(
532 arg: &Operand<'db>,
533 result: &mut ArenaMap<LocalId<'db>, MutabilityReason>,
534) {
535 if let OperandKind::Copy(p) | OperandKind::Move(p) = arg.kind {
536 record_usage(p.local, result);
537 }
538}
539
540fn mutability_of_locals<'db>(
541 infcx: &InferCtxt<'db>,
542 env: ParamEnv<'db>,
543 body: &MirBody<'db>,
544) -> ArenaMap<LocalId<'db>, MutabilityReason> {
545 let db = infcx.interner.db;
546 let mut result: ArenaMap<LocalId<'db>, MutabilityReason> =
547 body.locals.iter().map(|it| (it.0, MutabilityReason::Unused)).collect();
548
549 let ever_init_maps = ever_initialized_map(db, body);
550 for (block_id, mut ever_init_map) in ever_init_maps.into_iter() {
551 let block = &body.basic_blocks[block_id];
552 for statement in &block.statements {
553 match &statement.kind {
554 StatementKind::Assign(place, value) => {
555 match place_case(infcx, env, body, place) {
556 ProjectionCase::Direct => {
557 if ever_init_map.get(place.local).copied().unwrap_or_default() {
558 push_mut_span(place.local, statement.span, &mut result);
559 } else {
560 ever_init_map.insert(place.local, true);
561 }
562 }
563 ProjectionCase::DirectPart => {
564 push_mut_span(place.local, statement.span, &mut result);
566 }
567 ProjectionCase::Indirect => {
568 record_usage(place.local, &mut result);
569 }
570 }
571 match value {
572 Rvalue::CopyForDeref(p)
573 | Rvalue::Discriminant(p)
574 | Rvalue::Len(p)
575 | Rvalue::Ref(_, p) => {
576 record_usage(p.local, &mut result);
577 }
578 Rvalue::Use(o)
579 | Rvalue::Repeat(o, _)
580 | Rvalue::Cast(_, o, _)
581 | Rvalue::UnaryOp(_, o) => record_usage_for_operand(o, &mut result),
582 Rvalue::CheckedBinaryOp(_, o1, o2) => {
583 for o in [o1, o2] {
584 record_usage_for_operand(o, &mut result);
585 }
586 }
587 Rvalue::Aggregate(_, args) => {
588 for arg in args.iter() {
589 record_usage_for_operand(arg, &mut result);
590 }
591 }
592 Rvalue::ShallowInitBox(_, _) | Rvalue::ShallowInitBoxWithAlloc(_) => (),
593 Rvalue::ThreadLocalRef(n)
594 | Rvalue::AddressOf(n)
595 | Rvalue::BinaryOp(n)
596 | Rvalue::NullaryOp(n) => match *n {},
597 }
598 if let Rvalue::Ref(
599 BorrowKind::Mut {
600 kind: MutBorrowKind::Default | MutBorrowKind::TwoPhasedBorrow,
601 },
602 p,
603 ) = value
604 && place_case(infcx, env, body, p) != ProjectionCase::Indirect
605 {
606 push_mut_span(p.local, statement.span, &mut result);
607 }
608 }
609 StatementKind::FakeRead(p) => {
610 record_usage(p.local, &mut result);
611 }
612 StatementKind::StorageDead(p) => {
613 ever_init_map.insert(*p, false);
614 }
615 StatementKind::Deinit(_) | StatementKind::StorageLive(_) | StatementKind::Nop => (),
616 }
617 }
618 let Some(terminator) = &block.terminator else {
619 never!("Terminator should be none only in construction");
620 continue;
621 };
622 match &terminator.kind {
623 TerminatorKind::Goto { .. }
624 | TerminatorKind::UnwindResume
625 | TerminatorKind::Abort
626 | TerminatorKind::Return
627 | TerminatorKind::Unreachable
628 | TerminatorKind::FalseEdge { .. }
629 | TerminatorKind::FalseUnwind { .. }
630 | TerminatorKind::CoroutineDrop
631 | TerminatorKind::Drop { .. }
632 | TerminatorKind::DropAndReplace { .. }
633 | TerminatorKind::Assert { .. }
634 | TerminatorKind::Yield { .. } => (),
635 TerminatorKind::SwitchInt { discr, targets: _ } => {
636 record_usage_for_operand(discr, &mut result);
637 }
638 TerminatorKind::Call { destination, args, func, .. } => {
639 record_usage_for_operand(func, &mut result);
640 for arg in args.iter() {
641 record_usage_for_operand(arg, &mut result);
642 }
643 if destination.projection.lookup(&body.projection_store).is_empty() {
644 if ever_init_map.get(destination.local).copied().unwrap_or_default() {
645 push_mut_span(destination.local, terminator.span, &mut result);
646 } else {
647 ever_init_map.insert(destination.local, true);
648 }
649 }
650 }
651 }
652 }
653 result
654}