1 //! Print diagnostics to explain why values are borrowed.
3 use std
::collections
::VecDeque
;
5 use rustc_data_structures
::fx
::FxHashSet
;
6 use rustc_errors
::{Applicability, Diagnostic}
;
7 use rustc_index
::vec
::IndexVec
;
8 use rustc_infer
::infer
::NllRegionVariableOrigin
;
9 use rustc_middle
::mir
::{
10 Body
, CastKind
, ConstraintCategory
, FakeReadCause
, Local
, Location
, Operand
, Place
, Rvalue
,
11 Statement
, StatementKind
, TerminatorKind
,
13 use rustc_middle
::ty
::adjustment
::PointerCast
;
14 use rustc_middle
::ty
::{self, RegionVid, TyCtxt}
;
15 use rustc_span
::symbol
::Symbol
;
16 use rustc_span
::{sym, DesugaringKind, Span}
;
18 use crate::region_infer
::BlameConstraint
;
20 borrow_set
::BorrowData
, nll
::ConstraintDescription
, region_infer
::Cause
, MirBorrowckCtxt
,
24 use super::{find_use, RegionName, UseSpans}
;
27 pub(crate) enum BorrowExplanation
{
28 UsedLater(LaterUseKind
, Span
, Option
<Span
>),
29 UsedLaterInLoop(LaterUseKind
, Span
, Option
<Span
>),
30 UsedLaterWhenDropped
{
33 should_note_order
: bool
,
36 category
: ConstraintCategory
,
39 region_name
: RegionName
,
40 opt_place_desc
: Option
<String
>,
45 #[derive(Clone, Copy, Debug)]
46 pub(crate) enum LaterUseKind
{
54 impl BorrowExplanation
{
55 pub(crate) fn is_explained(&self) -> bool
{
56 !matches
!(self, BorrowExplanation
::Unexplained
)
58 pub(crate) fn add_explanation_to_diagnostic
<'tcx
>(
62 local_names
: &IndexVec
<Local
, Option
<Symbol
>>,
65 borrow_span
: Option
<Span
>,
66 multiple_borrow_span
: Option
<(Span
, Span
)>,
69 BorrowExplanation
::UsedLater(later_use_kind
, var_or_use_span
, path_span
) => {
70 let message
= match later_use_kind
{
71 LaterUseKind
::TraitCapture
=> "captured here by trait object",
72 LaterUseKind
::ClosureCapture
=> "captured here by closure",
73 LaterUseKind
::Call
=> "used by call",
74 LaterUseKind
::FakeLetRead
=> "stored here",
75 LaterUseKind
::Other
=> "used here",
77 // We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
78 if path_span
.map(|path_span
| path_span
== var_or_use_span
).unwrap_or(true) {
79 if borrow_span
.map(|sp
| !sp
.overlaps(var_or_use_span
)).unwrap_or(true) {
82 format
!("{}borrow later {}", borrow_desc
, message
),
86 // path_span must be `Some` as otherwise the if condition is true
87 let path_span
= path_span
.unwrap();
88 // path_span is only present in the case of closure capture
89 assert
!(matches
!(later_use_kind
, LaterUseKind
::ClosureCapture
));
90 if !borrow_span
.map_or(false, |sp
| sp
.overlaps(var_or_use_span
)) {
91 let path_label
= "used here by closure";
92 let capture_kind_label
= message
;
95 format
!("{}borrow later {}", borrow_desc
, capture_kind_label
),
97 err
.span_label(path_span
, path_label
);
101 BorrowExplanation
::UsedLaterInLoop(later_use_kind
, var_or_use_span
, path_span
) => {
102 let message
= match later_use_kind
{
103 LaterUseKind
::TraitCapture
=> {
104 "borrow captured here by trait object, in later iteration of loop"
106 LaterUseKind
::ClosureCapture
=> {
107 "borrow captured here by closure, in later iteration of loop"
109 LaterUseKind
::Call
=> "borrow used by call, in later iteration of loop",
110 LaterUseKind
::FakeLetRead
=> "borrow later stored here",
111 LaterUseKind
::Other
=> "borrow used here, in later iteration of loop",
113 // We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same
114 if path_span
.map(|path_span
| path_span
== var_or_use_span
).unwrap_or(true) {
115 err
.span_label(var_or_use_span
, format
!("{}{}", borrow_desc
, message
));
117 // path_span must be `Some` as otherwise the if condition is true
118 let path_span
= path_span
.unwrap();
119 // path_span is only present in the case of closure capture
120 assert
!(matches
!(later_use_kind
, LaterUseKind
::ClosureCapture
));
121 if borrow_span
.map(|sp
| !sp
.overlaps(var_or_use_span
)).unwrap_or(true) {
122 let path_label
= "used here by closure";
123 let capture_kind_label
= message
;
126 format
!("{}borrow later {}", borrow_desc
, capture_kind_label
),
128 err
.span_label(path_span
, path_label
);
132 BorrowExplanation
::UsedLaterWhenDropped
{
137 let local_decl
= &body
.local_decls
[dropped_local
];
138 let mut ty
= local_decl
.ty
;
139 if local_decl
.source_info
.span
.desugaring_kind() == Some(DesugaringKind
::ForLoop
) {
140 if let ty
::Adt(adt
, substs
) = local_decl
.ty
.kind() {
141 if tcx
.is_diagnostic_item(sym
::Option
, adt
.did()) {
142 // in for loop desugaring, only look at the `Some(..)` inner type
143 ty
= substs
.type_at(0);
147 let (dtor_desc
, type_desc
) = match ty
.kind() {
148 // If type is an ADT that implements Drop, then
149 // simplify output by reporting just the ADT name.
150 ty
::Adt(adt
, _substs
) if adt
.has_dtor(tcx
) && !adt
.is_box() => {
151 ("`Drop` code", format
!("type `{}`", tcx
.def_path_str(adt
.did())))
154 // Otherwise, just report the whole type (and use
155 // the intentionally fuzzy phrase "destructor")
156 ty
::Closure(..) => ("destructor", "closure".to_owned()),
157 ty
::Generator(..) => ("destructor", "generator".to_owned()),
159 _
=> ("destructor", format
!("type `{}`", local_decl
.ty
)),
162 match local_names
[dropped_local
] {
163 Some(local_name
) if !local_decl
.from_compiler_desugaring() => {
164 let message
= format
!(
165 "{B}borrow might be used here, when `{LOC}` is dropped \
166 and runs the {DTOR} for {TYPE}",
172 err
.span_label(body
.source_info(drop_loc
).span
, message
);
174 if should_note_order
{
176 "values in a scope are dropped \
177 in the opposite order they are defined",
183 local_decl
.source_info
.span
,
185 "a temporary with access to the {B}borrow \
186 is created here ...",
190 let message
= format
!(
191 "... and the {B}borrow might be used here, \
192 when that temporary is dropped \
193 and runs the {DTOR} for {TYPE}",
198 err
.span_label(body
.source_info(drop_loc
).span
, message
);
200 if let Some(info
) = &local_decl
.is_block_tail
{
201 if info
.tail_result_is_ignored
{
202 // #85581: If the first mutable borrow's scope contains
203 // the second borrow, this suggestion isn't helpful.
204 if !multiple_borrow_span
206 old
.to(info
.span
.shrink_to_hi()).contains(new
)
210 err
.span_suggestion_verbose(
211 info
.span
.shrink_to_hi(),
212 "consider adding semicolon after the expression so its \
213 temporaries are dropped sooner, before the local variables \
214 declared by the block are dropped",
216 Applicability
::MaybeIncorrect
,
221 "the temporary is part of an expression at the end of a \
222 block;\nconsider forcing this temporary to be dropped sooner, \
223 before the block's local variables are dropped",
225 err
.multipart_suggestion(
226 "for example, you could save the expression's value in a new \
227 local variable `x` and then make `x` be the expression at the \
230 (info
.span
.shrink_to_lo(), "let x = ".to_string()),
231 (info
.span
.shrink_to_hi(), "; x".to_string()),
233 Applicability
::MaybeIncorrect
,
240 BorrowExplanation
::MustBeValidFor
{
247 region_name
.highlight_region_name(err
);
249 if let Some(desc
) = opt_place_desc
{
253 "{}requires that `{}` is borrowed for `{}`",
254 category
.description(),
263 "{}requires that {}borrow lasts for `{}`",
264 category
.description(),
271 self.add_lifetime_bound_suggestion_to_diagnostic(err
, &category
, span
, region_name
);
276 pub(crate) fn add_lifetime_bound_suggestion_to_diagnostic(
278 err
: &mut Diagnostic
,
279 category
: &ConstraintCategory
,
281 region_name
: &RegionName
,
283 if let ConstraintCategory
::OpaqueType
= category
{
284 let suggestable_name
=
285 if region_name
.was_named() { region_name.to_string() }
else { "'_".to_string() }
;
288 "you can add a bound to the {}to make it last less than `'static` and match `{}`",
289 category
.description(),
293 err
.span_suggestion_verbose(
296 format
!(" + {}", suggestable_name
),
297 Applicability
::Unspecified
,
303 impl<'cx
, 'tcx
> MirBorrowckCtxt
<'cx
, 'tcx
> {
304 fn free_region_constraint_info(
306 borrow_region
: RegionVid
,
307 outlived_region
: RegionVid
,
308 ) -> (ConstraintCategory
, bool
, Span
, Option
<RegionName
>) {
309 let BlameConstraint { category, from_closure, cause, variance_info: _ }
=
310 self.regioncx
.best_blame_constraint(
313 NllRegionVariableOrigin
::FreeRegion
,
314 |r
| self.regioncx
.provides_universal_region(r
, borrow_region
, outlived_region
),
317 let outlived_fr_name
= self.give_region_a_name(outlived_region
);
319 (category
, from_closure
, cause
.span
, outlived_fr_name
)
322 /// Returns structured explanation for *why* the borrow contains the
323 /// point from `location`. This is key for the "3-point errors"
324 /// [described in the NLL RFC][d].
328 /// - `borrow`: the borrow in question
329 /// - `location`: where the borrow occurs
330 /// - `kind_place`: if Some, this describes the statement that triggered the error.
331 /// - first half is the kind of write, if any, being performed
332 /// - second half is the place being accessed
334 /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
335 pub(crate) fn explain_why_borrow_contains_point(
338 borrow
: &BorrowData
<'tcx
>,
339 kind_place
: Option
<(WriteKind
, Place
<'tcx
>)>,
340 ) -> BorrowExplanation
{
342 "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})",
343 location
, borrow
, kind_place
346 let regioncx
= &self.regioncx
;
347 let body
: &Body
<'_
> = &self.body
;
348 let tcx
= self.infcx
.tcx
;
350 let borrow_region_vid
= borrow
.region
;
351 debug
!("explain_why_borrow_contains_point: borrow_region_vid={:?}", borrow_region_vid
);
353 let region_sub
= self.regioncx
.find_sub_region_live_at(borrow_region_vid
, location
);
354 debug
!("explain_why_borrow_contains_point: region_sub={:?}", region_sub
);
356 match find_use
::find(body
, regioncx
, tcx
, region_sub
, location
) {
357 Some(Cause
::LiveVar(local
, location
)) => {
358 let span
= body
.source_info(location
).span
;
360 .move_spans(Place
::from(local
).as_ref(), location
)
361 .or_else(|| self.borrow_spans(span
, location
));
363 let borrow_location
= location
;
364 if self.is_use_in_later_iteration_of_loop(borrow_location
, location
) {
365 let later_use
= self.later_use_kind(borrow
, spans
, location
);
366 BorrowExplanation
::UsedLaterInLoop(later_use
.0, later_use
.1, later_use
.2)
368 // Check if the location represents a `FakeRead`, and adapt the error
369 // message to the `FakeReadCause` it is from: in particular,
370 // the ones inserted in optimized `let var = <expr>` patterns.
371 let later_use
= self.later_use_kind(borrow
, spans
, location
);
372 BorrowExplanation
::UsedLater(later_use
.0, later_use
.1, later_use
.2)
376 Some(Cause
::DropVar(local
, location
)) => {
377 let mut should_note_order
= false;
378 if self.local_names
[local
].is_some()
379 && let Some((WriteKind
::StorageDeadOrDrop
, place
)) = kind_place
380 && let Some(borrowed_local
) = place
.as_local()
381 && self.local_names
[borrowed_local
].is_some() && local
!= borrowed_local
383 should_note_order
= true;
386 BorrowExplanation
::UsedLaterWhenDropped
{
388 dropped_local
: local
,
394 if let Some(region
) = self.to_error_region_vid(borrow_region_vid
) {
395 let (category
, from_closure
, span
, region_name
) =
396 self.free_region_constraint_info(borrow_region_vid
, region
);
397 if let Some(region_name
) = region_name
{
398 let opt_place_desc
= self.describe_place(borrow
.borrowed_place
.as_ref());
399 BorrowExplanation
::MustBeValidFor
{
408 "explain_why_borrow_contains_point: \
409 Could not generate a region name"
411 BorrowExplanation
::Unexplained
415 "explain_why_borrow_contains_point: \
416 Could not generate an error region vid"
418 BorrowExplanation
::Unexplained
424 /// true if `borrow_location` can reach `use_location` by going through a loop and
425 /// `use_location` is also inside of that loop
426 fn is_use_in_later_iteration_of_loop(
428 borrow_location
: Location
,
429 use_location
: Location
,
431 let back_edge
= self.reach_through_backedge(borrow_location
, use_location
);
432 back_edge
.map_or(false, |back_edge
| self.can_reach_head_of_loop(use_location
, back_edge
))
435 /// Returns the outmost back edge if `from` location can reach `to` location passing through
437 fn reach_through_backedge(&self, from
: Location
, to
: Location
) -> Option
<Location
> {
438 let mut visited_locations
= FxHashSet
::default();
439 let mut pending_locations
= VecDeque
::new();
440 visited_locations
.insert(from
);
441 pending_locations
.push_back(from
);
442 debug
!("reach_through_backedge: from={:?} to={:?}", from
, to
,);
444 let mut outmost_back_edge
= None
;
445 while let Some(location
) = pending_locations
.pop_front() {
447 "reach_through_backedge: location={:?} outmost_back_edge={:?}
448 pending_locations={:?} visited_locations={:?}",
449 location
, outmost_back_edge
, pending_locations
, visited_locations
452 if location
== to
&& outmost_back_edge
.is_some() {
453 // We've managed to reach the use location
454 debug
!("reach_through_backedge: found!");
455 return outmost_back_edge
;
458 let block
= &self.body
.basic_blocks()[location
.block
];
460 if location
.statement_index
< block
.statements
.len() {
461 let successor
= location
.successor_within_block();
462 if visited_locations
.insert(successor
) {
463 pending_locations
.push_back(successor
);
466 pending_locations
.extend(
470 .map(|bb
| Location { statement_index: 0, block: *bb }
)
471 .filter(|s
| visited_locations
.insert(*s
))
473 if self.is_back_edge(location
, s
) {
474 match outmost_back_edge
{
476 outmost_back_edge
= Some(location
);
480 if location
.dominates(back_edge
, &self.dominators
) =>
482 outmost_back_edge
= Some(location
);
498 /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the
499 /// intermediate nodes
500 fn can_reach_head_of_loop(&self, from
: Location
, loop_head
: Location
) -> bool
{
501 self.find_loop_head_dfs(from
, loop_head
, &mut FxHashSet
::default())
504 fn find_loop_head_dfs(
508 visited_locations
: &mut FxHashSet
<Location
>,
510 visited_locations
.insert(from
);
512 if from
== loop_head
{
516 if loop_head
.dominates(from
, &self.dominators
) {
517 let block
= &self.body
.basic_blocks()[from
.block
];
519 if from
.statement_index
< block
.statements
.len() {
520 let successor
= from
.successor_within_block();
522 if !visited_locations
.contains(&successor
)
523 && self.find_loop_head_dfs(successor
, loop_head
, visited_locations
)
528 for bb
in block
.terminator().successors() {
529 let successor
= Location { statement_index: 0, block: *bb }
;
531 if !visited_locations
.contains(&successor
)
532 && self.find_loop_head_dfs(successor
, loop_head
, visited_locations
)
543 /// True if an edge `source -> target` is a backedge -- in other words, if the target
544 /// dominates the source.
545 fn is_back_edge(&self, source
: Location
, target
: Location
) -> bool
{
546 target
.dominates(source
, &self.dominators
)
549 /// Determine how the borrow was later used.
550 /// First span returned points to the location of the conflicting use
551 /// Second span if `Some` is returned in the case of closures and points
552 /// to the use of the path
555 borrow
: &BorrowData
<'tcx
>,
556 use_spans
: UseSpans
<'tcx
>,
558 ) -> (LaterUseKind
, Span
, Option
<Span
>) {
560 UseSpans
::ClosureUse { capture_kind_span, path_span, .. }
=> {
561 // Used in a closure.
562 (LaterUseKind
::ClosureCapture
, capture_kind_span
, Some(path_span
))
564 UseSpans
::PatUse(span
)
565 | UseSpans
::OtherUse(span
)
566 | UseSpans
::FnSelfUse { var_span: span, .. }
=> {
567 let block
= &self.body
.basic_blocks()[location
.block
];
569 let kind
= if let Some(&Statement
{
570 kind
: StatementKind
::FakeRead(box (FakeReadCause
::ForLet(_
), _
)),
572 }) = block
.statements
.get(location
.statement_index
)
574 LaterUseKind
::FakeLetRead
575 } else if self.was_captured_by_trait_object(borrow
) {
576 LaterUseKind
::TraitCapture
577 } else if location
.statement_index
== block
.statements
.len() {
578 if let TerminatorKind
::Call { ref func, from_hir_call: true, .. }
=
579 block
.terminator().kind
581 // Just point to the function, to reduce the chance of overlapping spans.
582 let function_span
= match func
{
583 Operand
::Constant(c
) => c
.span
,
584 Operand
::Copy(place
) | Operand
::Move(place
) => {
585 if let Some(l
) = place
.as_local() {
586 let local_decl
= &self.body
.local_decls
[l
];
587 if self.local_names
[l
].is_none() {
588 local_decl
.source_info
.span
597 return (LaterUseKind
::Call
, function_span
, None
);
610 /// Checks if a borrowed value was captured by a trait object. We do this by
611 /// looking forward in the MIR from the reserve location and checking if we see
612 /// an unsized cast to a trait object on our data.
613 fn was_captured_by_trait_object(&self, borrow
: &BorrowData
<'tcx
>) -> bool
{
614 // Start at the reserve location, find the place that we want to see cast to a trait object.
615 let location
= borrow
.reserve_location
;
616 let block
= &self.body
[location
.block
];
617 let stmt
= block
.statements
.get(location
.statement_index
);
618 debug
!("was_captured_by_trait_object: location={:?} stmt={:?}", location
, stmt
);
620 // We make a `queue` vector that has the locations we want to visit. As of writing, this
621 // will only ever have one item at any given time, but by using a vector, we can pop from
622 // it which simplifies the termination logic.
623 let mut queue
= vec
![location
];
624 let mut target
= if let Some(&Statement
{
625 kind
: StatementKind
::Assign(box (ref place
, _
)),
629 if let Some(local
) = place
.as_local() {
638 debug
!("was_captured_by_trait: target={:?} queue={:?}", target
, queue
);
639 while let Some(current_location
) = queue
.pop() {
640 debug
!("was_captured_by_trait: target={:?}", target
);
641 let block
= &self.body
[current_location
.block
];
642 // We need to check the current location to find out if it is a terminator.
643 let is_terminator
= current_location
.statement_index
== block
.statements
.len();
645 let stmt
= &block
.statements
[current_location
.statement_index
];
646 debug
!("was_captured_by_trait_object: stmt={:?}", stmt
);
648 // The only kind of statement that we care about is assignments...
649 if let StatementKind
::Assign(box (place
, rvalue
)) = &stmt
.kind
{
650 let Some(into
) = place
.local_or_deref_local() else {
651 // Continue at the next location.
652 queue
.push(current_location
.successor_within_block());
657 // If we see a use, we should check whether it is our data, and if so
658 // update the place that we're looking for to that new place.
659 Rvalue
::Use(operand
) => match operand
{
660 Operand
::Copy(place
) | Operand
::Move(place
) => {
661 if let Some(from
) = place
.as_local() {
669 // If we see an unsized cast, then if it is our data we should check
670 // whether it is being cast to a trait object.
671 Rvalue
::Cast(CastKind
::Pointer(PointerCast
::Unsize
), operand
, ty
) => {
673 Operand
::Copy(place
) | Operand
::Move(place
) => {
674 if let Some(from
) = place
.as_local() {
676 debug
!("was_captured_by_trait_object: ty={:?}", ty
);
677 // Check the type for a trait object.
678 return match ty
.kind() {
680 ty
::Ref(_
, ty
, _
) if ty
.is_trait() => true,
682 _
if ty
.is_box() && ty
.boxed_ty().is_trait() => {
686 _
if ty
.is_trait() => true,
701 // Continue at the next location.
702 queue
.push(current_location
.successor_within_block());
704 // The only thing we need to do for terminators is progress to the next block.
705 let terminator
= block
.terminator();
706 debug
!("was_captured_by_trait_object: terminator={:?}", terminator
);
708 if let TerminatorKind
::Call { destination: Some((place, block)), args, .. }
=
711 if let Some(dest
) = place
.as_local() {
713 "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
716 // Check if one of the arguments to this function is the target place.
717 let found_target
= args
.iter().any(|arg
| {
718 if let Operand
::Move(place
) = arg
{
719 if let Some(potential
) = place
.as_local() {
729 // If it is, follow this to the next block and update the target.
732 queue
.push(block
.start_location());
738 debug
!("was_captured_by_trait: queue={:?}", queue
);
741 // We didn't find anything and ran out of locations to check.