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