]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir/src/borrow_check/diagnostics/explain_borrow.rs
Rollup merge of #76747 - GuillaumeGomez:more-missing-libcore-code-examples, r=Mark...
[rust.git] / compiler / rustc_mir / src / borrow_check / diagnostics / explain_borrow.rs
1 //! Print diagnostics to explain why values are borrowed.
2
3 use std::collections::VecDeque;
4
5 use rustc_data_structures::fx::FxHashSet;
6 use rustc_errors::{Applicability, DiagnosticBuilder};
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,
12 };
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::Span;
17
18 use crate::borrow_check::{
19     borrow_set::BorrowData, nll::ConstraintDescription, region_infer::Cause, MirBorrowckCtxt,
20     WriteKind,
21 };
22
23 use super::{find_use, RegionName, UseSpans};
24
25 #[derive(Debug)]
26 pub(in crate::borrow_check) enum BorrowExplanation {
27     UsedLater(LaterUseKind, Span),
28     UsedLaterInLoop(LaterUseKind, Span),
29     UsedLaterWhenDropped {
30         drop_loc: Location,
31         dropped_local: Local,
32         should_note_order: bool,
33     },
34     MustBeValidFor {
35         category: ConstraintCategory,
36         from_closure: bool,
37         span: Span,
38         region_name: RegionName,
39         opt_place_desc: Option<String>,
40     },
41     Unexplained,
42 }
43
44 #[derive(Clone, Copy, Debug)]
45 pub(in crate::borrow_check) enum LaterUseKind {
46     TraitCapture,
47     ClosureCapture,
48     Call,
49     FakeLetRead,
50     Other,
51 }
52
53 impl BorrowExplanation {
54     pub(in crate::borrow_check) fn is_explained(&self) -> bool {
55         match self {
56             BorrowExplanation::Unexplained => false,
57             _ => true,
58         }
59     }
60     pub(in crate::borrow_check) fn add_explanation_to_diagnostic<'tcx>(
61         &self,
62         tcx: TyCtxt<'tcx>,
63         body: &Body<'tcx>,
64         local_names: &IndexVec<Local, Option<Symbol>>,
65         err: &mut DiagnosticBuilder<'_>,
66         borrow_desc: &str,
67         borrow_span: Option<Span>,
68     ) {
69         match *self {
70             BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => {
71                 let message = match later_use_kind {
72                     LaterUseKind::TraitCapture => "captured here by trait object",
73                     LaterUseKind::ClosureCapture => "captured here by closure",
74                     LaterUseKind::Call => "used by call",
75                     LaterUseKind::FakeLetRead => "stored here",
76                     LaterUseKind::Other => "used here",
77                 };
78                 if !borrow_span.map(|sp| sp.overlaps(var_or_use_span)).unwrap_or(false) {
79                     err.span_label(
80                         var_or_use_span,
81                         format!("{}borrow later {}", borrow_desc, message),
82                     );
83                 }
84             }
85             BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => {
86                 let message = match later_use_kind {
87                     LaterUseKind::TraitCapture => {
88                         "borrow captured here by trait object, in later iteration of loop"
89                     }
90                     LaterUseKind::ClosureCapture => {
91                         "borrow captured here by closure, in later iteration of loop"
92                     }
93                     LaterUseKind::Call => "borrow used by call, in later iteration of loop",
94                     LaterUseKind::FakeLetRead => "borrow later stored here",
95                     LaterUseKind::Other => "borrow used here, in later iteration of loop",
96                 };
97                 err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
98             }
99             BorrowExplanation::UsedLaterWhenDropped {
100                 drop_loc,
101                 dropped_local,
102                 should_note_order,
103             } => {
104                 let local_decl = &body.local_decls[dropped_local];
105                 let (dtor_desc, type_desc) = match local_decl.ty.kind() {
106                     // If type is an ADT that implements Drop, then
107                     // simplify output by reporting just the ADT name.
108                     ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() => {
109                         ("`Drop` code", format!("type `{}`", tcx.def_path_str(adt.did)))
110                     }
111
112                     // Otherwise, just report the whole type (and use
113                     // the intentionally fuzzy phrase "destructor")
114                     ty::Closure(..) => ("destructor", "closure".to_owned()),
115                     ty::Generator(..) => ("destructor", "generator".to_owned()),
116
117                     _ => ("destructor", format!("type `{}`", local_decl.ty)),
118                 };
119
120                 match local_names[dropped_local] {
121                     Some(local_name) if !local_decl.from_compiler_desugaring() => {
122                         let message = format!(
123                             "{B}borrow might be used here, when `{LOC}` is dropped \
124                              and runs the {DTOR} for {TYPE}",
125                             B = borrow_desc,
126                             LOC = local_name,
127                             TYPE = type_desc,
128                             DTOR = dtor_desc
129                         );
130                         err.span_label(body.source_info(drop_loc).span, message);
131
132                         if should_note_order {
133                             err.note(
134                                 "values in a scope are dropped \
135                                  in the opposite order they are defined",
136                             );
137                         }
138                     }
139                     _ => {
140                         err.span_label(
141                             local_decl.source_info.span,
142                             format!(
143                                 "a temporary with access to the {B}borrow \
144                                  is created here ...",
145                                 B = borrow_desc
146                             ),
147                         );
148                         let message = format!(
149                             "... and the {B}borrow might be used here, \
150                              when that temporary is dropped \
151                              and runs the {DTOR} for {TYPE}",
152                             B = borrow_desc,
153                             TYPE = type_desc,
154                             DTOR = dtor_desc
155                         );
156                         err.span_label(body.source_info(drop_loc).span, message);
157
158                         if let Some(info) = &local_decl.is_block_tail {
159                             if info.tail_result_is_ignored {
160                                 err.span_suggestion_verbose(
161                                     info.span.shrink_to_hi(),
162                                     "consider adding semicolon after the expression so its \
163                                      temporaries are dropped sooner, before the local variables \
164                                      declared by the block are dropped",
165                                     ";".to_string(),
166                                     Applicability::MaybeIncorrect,
167                                 );
168                             } else {
169                                 err.note(
170                                     "the temporary is part of an expression at the end of a \
171                                      block;\nconsider forcing this temporary to be dropped sooner, \
172                                      before the block's local variables are dropped",
173                                 );
174                                 err.multipart_suggestion(
175                                     "for example, you could save the expression's value in a new \
176                                      local variable `x` and then make `x` be the expression at the \
177                                      end of the block",
178                                     vec![
179                                         (info.span.shrink_to_lo(), "let x = ".to_string()),
180                                         (info.span.shrink_to_hi(), "; x".to_string()),
181                                     ],
182                                     Applicability::MaybeIncorrect,
183                                 );
184                             };
185                         }
186                     }
187                 }
188             }
189             BorrowExplanation::MustBeValidFor {
190                 category,
191                 span,
192                 ref region_name,
193                 ref opt_place_desc,
194                 from_closure: _,
195             } => {
196                 region_name.highlight_region_name(err);
197
198                 if let Some(desc) = opt_place_desc {
199                     err.span_label(
200                         span,
201                         format!(
202                             "{}requires that `{}` is borrowed for `{}`",
203                             category.description(),
204                             desc,
205                             region_name,
206                         ),
207                     );
208                 } else {
209                     err.span_label(
210                         span,
211                         format!(
212                             "{}requires that {}borrow lasts for `{}`",
213                             category.description(),
214                             borrow_desc,
215                             region_name,
216                         ),
217                     );
218                 };
219
220                 self.add_lifetime_bound_suggestion_to_diagnostic(err, &category, span, region_name);
221             }
222             _ => {}
223         }
224     }
225     pub(in crate::borrow_check) fn add_lifetime_bound_suggestion_to_diagnostic(
226         &self,
227         err: &mut DiagnosticBuilder<'_>,
228         category: &ConstraintCategory,
229         span: Span,
230         region_name: &RegionName,
231     ) {
232         if let ConstraintCategory::OpaqueType = category {
233             let suggestable_name =
234                 if region_name.was_named() { region_name.to_string() } else { "'_".to_string() };
235
236             let msg = format!(
237                 "you can add a bound to the {}to make it last less than `'static` and match `{}`",
238                 category.description(),
239                 region_name,
240             );
241
242             err.span_suggestion_verbose(
243                 span.shrink_to_hi(),
244                 &msg,
245                 format!(" + {}", suggestable_name),
246                 Applicability::Unspecified,
247             );
248         }
249     }
250 }
251
252 impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
253     fn free_region_constraint_info(
254         &self,
255         borrow_region: RegionVid,
256         outlived_region: RegionVid,
257     ) -> (ConstraintCategory, bool, Span, Option<RegionName>) {
258         let (category, from_closure, span) = self.regioncx.best_blame_constraint(
259             &self.body,
260             borrow_region,
261             NLLRegionVariableOrigin::FreeRegion,
262             |r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
263         );
264
265         let outlived_fr_name = self.give_region_a_name(outlived_region);
266
267         (category, from_closure, span, outlived_fr_name)
268     }
269
270     /// Returns structured explanation for *why* the borrow contains the
271     /// point from `location`. This is key for the "3-point errors"
272     /// [described in the NLL RFC][d].
273     ///
274     /// # Parameters
275     ///
276     /// - `borrow`: the borrow in question
277     /// - `location`: where the borrow occurs
278     /// - `kind_place`: if Some, this describes the statement that triggered the error.
279     ///   - first half is the kind of write, if any, being performed
280     ///   - second half is the place being accessed
281     ///
282     /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
283     pub(in crate::borrow_check) fn explain_why_borrow_contains_point(
284         &self,
285         location: Location,
286         borrow: &BorrowData<'tcx>,
287         kind_place: Option<(WriteKind, Place<'tcx>)>,
288     ) -> BorrowExplanation {
289         debug!(
290             "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})",
291             location, borrow, kind_place
292         );
293
294         let regioncx = &self.regioncx;
295         let body: &Body<'_> = &self.body;
296         let tcx = self.infcx.tcx;
297
298         let borrow_region_vid = borrow.region;
299         debug!("explain_why_borrow_contains_point: borrow_region_vid={:?}", borrow_region_vid);
300
301         let region_sub = self.regioncx.find_sub_region_live_at(borrow_region_vid, location);
302         debug!("explain_why_borrow_contains_point: region_sub={:?}", region_sub);
303
304         match find_use::find(body, regioncx, tcx, region_sub, location) {
305             Some(Cause::LiveVar(local, location)) => {
306                 let span = body.source_info(location).span;
307                 let spans = self
308                     .move_spans(Place::from(local).as_ref(), location)
309                     .or_else(|| self.borrow_spans(span, location));
310
311                 let borrow_location = location;
312                 if self.is_use_in_later_iteration_of_loop(borrow_location, location) {
313                     let later_use = self.later_use_kind(borrow, spans, location);
314                     BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1)
315                 } else {
316                     // Check if the location represents a `FakeRead`, and adapt the error
317                     // message to the `FakeReadCause` it is from: in particular,
318                     // the ones inserted in optimized `let var = <expr>` patterns.
319                     let later_use = self.later_use_kind(borrow, spans, location);
320                     BorrowExplanation::UsedLater(later_use.0, later_use.1)
321                 }
322             }
323
324             Some(Cause::DropVar(local, location)) => {
325                 let mut should_note_order = false;
326                 if self.local_names[local].is_some() {
327                     if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place {
328                         if let Some(borrowed_local) = place.as_local() {
329                             if self.local_names[borrowed_local].is_some() && local != borrowed_local
330                             {
331                                 should_note_order = true;
332                             }
333                         }
334                     }
335                 }
336
337                 BorrowExplanation::UsedLaterWhenDropped {
338                     drop_loc: location,
339                     dropped_local: local,
340                     should_note_order,
341                 }
342             }
343
344             None => {
345                 if let Some(region) = self.to_error_region_vid(borrow_region_vid) {
346                     let (category, from_closure, span, region_name) =
347                         self.free_region_constraint_info(borrow_region_vid, region);
348                     if let Some(region_name) = region_name {
349                         let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref());
350                         BorrowExplanation::MustBeValidFor {
351                             category,
352                             from_closure,
353                             span,
354                             region_name,
355                             opt_place_desc,
356                         }
357                     } else {
358                         debug!(
359                             "explain_why_borrow_contains_point: \
360                              Could not generate a region name"
361                         );
362                         BorrowExplanation::Unexplained
363                     }
364                 } else {
365                     debug!(
366                         "explain_why_borrow_contains_point: \
367                          Could not generate an error region vid"
368                     );
369                     BorrowExplanation::Unexplained
370                 }
371             }
372         }
373     }
374
375     /// true if `borrow_location` can reach `use_location` by going through a loop and
376     /// `use_location` is also inside of that loop
377     fn is_use_in_later_iteration_of_loop(
378         &self,
379         borrow_location: Location,
380         use_location: Location,
381     ) -> bool {
382         let back_edge = self.reach_through_backedge(borrow_location, use_location);
383         back_edge.map_or(false, |back_edge| self.can_reach_head_of_loop(use_location, back_edge))
384     }
385
386     /// Returns the outmost back edge if `from` location can reach `to` location passing through
387     /// that back edge
388     fn reach_through_backedge(&self, from: Location, to: Location) -> Option<Location> {
389         let mut visited_locations = FxHashSet::default();
390         let mut pending_locations = VecDeque::new();
391         visited_locations.insert(from);
392         pending_locations.push_back(from);
393         debug!("reach_through_backedge: from={:?} to={:?}", from, to,);
394
395         let mut outmost_back_edge = None;
396         while let Some(location) = pending_locations.pop_front() {
397             debug!(
398                 "reach_through_backedge: location={:?} outmost_back_edge={:?}
399                    pending_locations={:?} visited_locations={:?}",
400                 location, outmost_back_edge, pending_locations, visited_locations
401             );
402
403             if location == to && outmost_back_edge.is_some() {
404                 // We've managed to reach the use location
405                 debug!("reach_through_backedge: found!");
406                 return outmost_back_edge;
407             }
408
409             let block = &self.body.basic_blocks()[location.block];
410
411             if location.statement_index < block.statements.len() {
412                 let successor = location.successor_within_block();
413                 if visited_locations.insert(successor) {
414                     pending_locations.push_back(successor);
415                 }
416             } else {
417                 pending_locations.extend(
418                     block
419                         .terminator()
420                         .successors()
421                         .map(|bb| Location { statement_index: 0, block: *bb })
422                         .filter(|s| visited_locations.insert(*s))
423                         .map(|s| {
424                             if self.is_back_edge(location, s) {
425                                 match outmost_back_edge {
426                                     None => {
427                                         outmost_back_edge = Some(location);
428                                     }
429
430                                     Some(back_edge)
431                                         if location.dominates(back_edge, &self.dominators) =>
432                                     {
433                                         outmost_back_edge = Some(location);
434                                     }
435
436                                     Some(_) => {}
437                                 }
438                             }
439
440                             s
441                         }),
442                 );
443             }
444         }
445
446         None
447     }
448
449     /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the
450     /// intermediate nodes
451     fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool {
452         self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default())
453     }
454
455     fn find_loop_head_dfs(
456         &self,
457         from: Location,
458         loop_head: Location,
459         visited_locations: &mut FxHashSet<Location>,
460     ) -> bool {
461         visited_locations.insert(from);
462
463         if from == loop_head {
464             return true;
465         }
466
467         if loop_head.dominates(from, &self.dominators) {
468             let block = &self.body.basic_blocks()[from.block];
469
470             if from.statement_index < block.statements.len() {
471                 let successor = from.successor_within_block();
472
473                 if !visited_locations.contains(&successor)
474                     && self.find_loop_head_dfs(successor, loop_head, visited_locations)
475                 {
476                     return true;
477                 }
478             } else {
479                 for bb in block.terminator().successors() {
480                     let successor = Location { statement_index: 0, block: *bb };
481
482                     if !visited_locations.contains(&successor)
483                         && self.find_loop_head_dfs(successor, loop_head, visited_locations)
484                     {
485                         return true;
486                     }
487                 }
488             }
489         }
490
491         false
492     }
493
494     /// True if an edge `source -> target` is a backedge -- in other words, if the target
495     /// dominates the source.
496     fn is_back_edge(&self, source: Location, target: Location) -> bool {
497         target.dominates(source, &self.dominators)
498     }
499
500     /// Determine how the borrow was later used.
501     fn later_use_kind(
502         &self,
503         borrow: &BorrowData<'tcx>,
504         use_spans: UseSpans<'tcx>,
505         location: Location,
506     ) -> (LaterUseKind, Span) {
507         match use_spans {
508             UseSpans::ClosureUse { var_span, .. } => {
509                 // Used in a closure.
510                 (LaterUseKind::ClosureCapture, var_span)
511             }
512             UseSpans::PatUse(span)
513             | UseSpans::OtherUse(span)
514             | UseSpans::FnSelfUse { var_span: span, .. } => {
515                 let block = &self.body.basic_blocks()[location.block];
516
517                 let kind = if let Some(&Statement {
518                     kind: StatementKind::FakeRead(FakeReadCause::ForLet, _),
519                     ..
520                 }) = block.statements.get(location.statement_index)
521                 {
522                     LaterUseKind::FakeLetRead
523                 } else if self.was_captured_by_trait_object(borrow) {
524                     LaterUseKind::TraitCapture
525                 } else if location.statement_index == block.statements.len() {
526                     if let TerminatorKind::Call { ref func, from_hir_call: true, .. } =
527                         block.terminator().kind
528                     {
529                         // Just point to the function, to reduce the chance of overlapping spans.
530                         let function_span = match func {
531                             Operand::Constant(c) => c.span,
532                             Operand::Copy(place) | Operand::Move(place) => {
533                                 if let Some(l) = place.as_local() {
534                                     let local_decl = &self.body.local_decls[l];
535                                     if self.local_names[l].is_none() {
536                                         local_decl.source_info.span
537                                     } else {
538                                         span
539                                     }
540                                 } else {
541                                     span
542                                 }
543                             }
544                         };
545                         return (LaterUseKind::Call, function_span);
546                     } else {
547                         LaterUseKind::Other
548                     }
549                 } else {
550                     LaterUseKind::Other
551                 };
552
553                 (kind, span)
554             }
555         }
556     }
557
558     /// Checks if a borrowed value was captured by a trait object. We do this by
559     /// looking forward in the MIR from the reserve location and checking if we see
560     /// a unsized cast to a trait object on our data.
561     fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
562         // Start at the reserve location, find the place that we want to see cast to a trait object.
563         let location = borrow.reserve_location;
564         let block = &self.body[location.block];
565         let stmt = block.statements.get(location.statement_index);
566         debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt);
567
568         // We make a `queue` vector that has the locations we want to visit. As of writing, this
569         // will only ever have one item at any given time, but by using a vector, we can pop from
570         // it which simplifies the termination logic.
571         let mut queue = vec![location];
572         let mut target = if let Some(&Statement {
573             kind: StatementKind::Assign(box (ref place, _)),
574             ..
575         }) = stmt
576         {
577             if let Some(local) = place.as_local() {
578                 local
579             } else {
580                 return false;
581             }
582         } else {
583             return false;
584         };
585
586         debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue);
587         while let Some(current_location) = queue.pop() {
588             debug!("was_captured_by_trait: target={:?}", target);
589             let block = &self.body[current_location.block];
590             // We need to check the current location to find out if it is a terminator.
591             let is_terminator = current_location.statement_index == block.statements.len();
592             if !is_terminator {
593                 let stmt = &block.statements[current_location.statement_index];
594                 debug!("was_captured_by_trait_object: stmt={:?}", stmt);
595
596                 // The only kind of statement that we care about is assignments...
597                 if let StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
598                     let into = match place.local_or_deref_local() {
599                         Some(into) => into,
600                         None => {
601                             // Continue at the next location.
602                             queue.push(current_location.successor_within_block());
603                             continue;
604                         }
605                     };
606
607                     match rvalue {
608                         // If we see a use, we should check whether it is our data, and if so
609                         // update the place that we're looking for to that new place.
610                         Rvalue::Use(operand) => match operand {
611                             Operand::Copy(place) | Operand::Move(place) => {
612                                 if let Some(from) = place.as_local() {
613                                     if from == target {
614                                         target = into;
615                                     }
616                                 }
617                             }
618                             _ => {}
619                         },
620                         // If we see a unsized cast, then if it is our data we should check
621                         // whether it is being cast to a trait object.
622                         Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), operand, ty) => {
623                             match operand {
624                                 Operand::Copy(place) | Operand::Move(place) => {
625                                     if let Some(from) = place.as_local() {
626                                         if from == target {
627                                             debug!("was_captured_by_trait_object: ty={:?}", ty);
628                                             // Check the type for a trait object.
629                                             return match ty.kind() {
630                                                 // `&dyn Trait`
631                                                 ty::Ref(_, ty, _) if ty.is_trait() => true,
632                                                 // `Box<dyn Trait>`
633                                                 _ if ty.is_box() && ty.boxed_ty().is_trait() => {
634                                                     true
635                                                 }
636                                                 // `dyn Trait`
637                                                 _ if ty.is_trait() => true,
638                                                 // Anything else.
639                                                 _ => false,
640                                             };
641                                         }
642                                     }
643                                     return false;
644                                 }
645                                 _ => return false,
646                             }
647                         }
648                         _ => {}
649                     }
650                 }
651
652                 // Continue at the next location.
653                 queue.push(current_location.successor_within_block());
654             } else {
655                 // The only thing we need to do for terminators is progress to the next block.
656                 let terminator = block.terminator();
657                 debug!("was_captured_by_trait_object: terminator={:?}", terminator);
658
659                 if let TerminatorKind::Call { destination: Some((place, block)), args, .. } =
660                     &terminator.kind
661                 {
662                     if let Some(dest) = place.as_local() {
663                         debug!(
664                             "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
665                             target, dest, args
666                         );
667                         // Check if one of the arguments to this function is the target place.
668                         let found_target = args.iter().any(|arg| {
669                             if let Operand::Move(place) = arg {
670                                 if let Some(potential) = place.as_local() {
671                                     potential == target
672                                 } else {
673                                     false
674                                 }
675                             } else {
676                                 false
677                             }
678                         });
679
680                         // If it is, follow this to the next block and update the target.
681                         if found_target {
682                             target = dest;
683                             queue.push(block.start_location());
684                         }
685                     }
686                 }
687             }
688
689             debug!("was_captured_by_trait: queue={:?}", queue);
690         }
691
692         // We didn't find anything and ran out of locations to check.
693         false
694     }
695 }