]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs
Auto merge of #54613 - matthiaskrgr:string_from_inline_53681, r=nagisa
[rust.git] / src / librustc_mir / borrow_check / nll / region_infer / error_reporting / mod.rs
1 // Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use borrow_check::nll::constraints::{OutlivesConstraint};
12 use borrow_check::nll::region_infer::RegionInferenceContext;
13 use borrow_check::nll::type_check::Locations;
14 use rustc::hir::def_id::DefId;
15 use rustc::infer::error_reporting::nice_region_error::NiceRegionError;
16 use rustc::infer::InferCtxt;
17 use rustc::mir::{ConstraintCategory, Location, Mir};
18 use rustc::ty::{self, RegionVid};
19 use rustc_data_structures::indexed_vec::IndexVec;
20 use rustc_errors::{Diagnostic, DiagnosticBuilder};
21 use std::collections::VecDeque;
22 use syntax::symbol::keywords;
23 use syntax_pos::Span;
24 use syntax::errors::Applicability;
25
26 mod region_name;
27 mod var_name;
28
29 use self::region_name::RegionName;
30
31 trait ConstraintDescription {
32     fn description(&self) -> &'static str;
33 }
34
35 impl ConstraintDescription for ConstraintCategory {
36     fn description(&self) -> &'static str {
37         // Must end with a space. Allows for empty names to be provided.
38         match self {
39             ConstraintCategory::Assignment => "assignment ",
40             ConstraintCategory::Return => "returning this value ",
41             ConstraintCategory::Cast => "cast ",
42             ConstraintCategory::CallArgument => "argument ",
43             ConstraintCategory::TypeAnnotation => "type annotation ",
44             ConstraintCategory::ClosureBounds => "closure body ",
45             ConstraintCategory::SizedBound => "proving this value is `Sized` ",
46             ConstraintCategory::CopyBound => "copying this value ",
47             ConstraintCategory::OpaqueType => "opaque type ",
48             ConstraintCategory::Boring
49             | ConstraintCategory::BoringNoLocation
50             | ConstraintCategory::Internal => "",
51         }
52     }
53 }
54
55 #[derive(Copy, Clone, PartialEq, Eq)]
56 enum Trace {
57     StartRegion,
58     FromOutlivesConstraint(OutlivesConstraint),
59     NotVisited,
60 }
61
62 impl<'tcx> RegionInferenceContext<'tcx> {
63     /// Tries to find the best constraint to blame for the fact that
64     /// `R: from_region`, where `R` is some region that meets
65     /// `target_test`. This works by following the constraint graph,
66     /// creating a constraint path that forces `R` to outlive
67     /// `from_region`, and then finding the best choices within that
68     /// path to blame.
69     fn best_blame_constraint(
70         &self,
71         mir: &Mir<'tcx>,
72         from_region: RegionVid,
73         target_test: impl Fn(RegionVid) -> bool,
74     ) -> (ConstraintCategory, Span, RegionVid) {
75         debug!("best_blame_constraint(from_region={:?})", from_region);
76
77         // Find all paths
78         let (path, target_region) = self
79             .find_constraint_paths_between_regions(from_region, target_test)
80             .unwrap();
81         debug!(
82             "best_blame_constraint: path={:#?}",
83             path.iter()
84                 .map(|&c| format!(
85                     "{:?} ({:?}: {:?})",
86                     c,
87                     self.constraint_sccs.scc(c.sup),
88                     self.constraint_sccs.scc(c.sub),
89                 ))
90                 .collect::<Vec<_>>()
91         );
92
93         // Classify each of the constraints along the path.
94         let mut categorized_path: Vec<(ConstraintCategory, Span)> = path
95             .iter()
96             .map(|constraint| {
97                 if constraint.category == ConstraintCategory::ClosureBounds {
98                     self.retrieve_closure_constraint_info(mir, &constraint)
99                 } else {
100                     (constraint.category, constraint.locations.span(mir))
101                 }
102             })
103             .collect();
104         debug!(
105             "best_blame_constraint: categorized_path={:#?}",
106             categorized_path
107         );
108
109         // To find the best span to cite, we first try to look for the
110         // final constraint that is interesting and where the `sup` is
111         // not unified with the ultimate target region. The reason
112         // for this is that we have a chain of constraints that lead
113         // from the source to the target region, something like:
114         //
115         //    '0: '1 ('0 is the source)
116         //    '1: '2
117         //    '2: '3
118         //    '3: '4
119         //    '4: '5
120         //    '5: '6 ('6 is the target)
121         //
122         // Some of those regions are unified with `'6` (in the same
123         // SCC).  We want to screen those out. After that point, the
124         // "closest" constraint we have to the end is going to be the
125         // most likely to be the point where the value escapes -- but
126         // we still want to screen for an "interesting" point to
127         // highlight (e.g., a call site or something).
128         let target_scc = self.constraint_sccs.scc(target_region);
129         let best_choice = (0..path.len()).rev().find(|&i| {
130             let constraint = path[i];
131
132             let constraint_sup_scc = self.constraint_sccs.scc(constraint.sup);
133
134             match categorized_path[i].0 {
135                 ConstraintCategory::OpaqueType
136                 | ConstraintCategory::Boring
137                 | ConstraintCategory::BoringNoLocation
138                 | ConstraintCategory::Internal => false,
139                 _ => constraint_sup_scc != target_scc,
140             }
141         });
142         if let Some(i) = best_choice {
143             let (category, span) = categorized_path[i];
144             return (category, span, target_region);
145         }
146
147         // If that search fails, that is.. unusual. Maybe everything
148         // is in the same SCC or something. In that case, find what
149         // appears to be the most interesting point to report to the
150         // user via an even more ad-hoc guess.
151         categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0));
152         debug!("best_blame_constraint: sorted_path={:#?}", categorized_path);
153
154         let &(category, span) = categorized_path.first().unwrap();
155
156         (category, span, target_region)
157     }
158
159     /// Walks the graph of constraints (where `'a: 'b` is considered
160     /// an edge `'a -> 'b`) to find all paths from `from_region` to
161     /// `to_region`. The paths are accumulated into the vector
162     /// `results`. The paths are stored as a series of
163     /// `ConstraintIndex` values -- in other words, a list of *edges*.
164     ///
165     /// Returns: a series of constraints as well as the region `R`
166     /// that passed the target test.
167     fn find_constraint_paths_between_regions(
168         &self,
169         from_region: RegionVid,
170         target_test: impl Fn(RegionVid) -> bool,
171     ) -> Option<(Vec<OutlivesConstraint>, RegionVid)> {
172         let mut context = IndexVec::from_elem(Trace::NotVisited, &self.definitions);
173         context[from_region] = Trace::StartRegion;
174
175         // Use a deque so that we do a breadth-first search. We will
176         // stop at the first match, which ought to be the shortest
177         // path (fewest constraints).
178         let mut deque = VecDeque::new();
179         deque.push_back(from_region);
180
181         while let Some(r) = deque.pop_front() {
182             // Check if we reached the region we were looking for. If so,
183             // we can reconstruct the path that led to it and return it.
184             if target_test(r) {
185                 let mut result = vec![];
186                 let mut p = r;
187                 loop {
188                     match context[p] {
189                         Trace::NotVisited => {
190                             bug!("found unvisited region {:?} on path to {:?}", p, r)
191                         }
192                         Trace::FromOutlivesConstraint(c) => {
193                             result.push(c);
194                             p = c.sup;
195                         }
196
197                         Trace::StartRegion => {
198                             result.reverse();
199                             return Some((result, r));
200                         }
201                     }
202                 }
203             }
204
205             // Otherwise, walk over the outgoing constraints and
206             // enqueue any regions we find, keeping track of how we
207             // reached them.
208             let fr_static = self.universal_regions.fr_static;
209             for constraint in self.constraint_graph.outgoing_edges(r,
210                                                                    &self.constraints,
211                                                                    fr_static) {
212                 assert_eq!(constraint.sup, r);
213                 let sub_region = constraint.sub;
214                 if let Trace::NotVisited = context[sub_region] {
215                     context[sub_region] = Trace::FromOutlivesConstraint(constraint);
216                     deque.push_back(sub_region);
217                 }
218             }
219         }
220
221         None
222     }
223
224     /// Report an error because the universal region `fr` was required to outlive
225     /// `outlived_fr` but it is not known to do so. For example:
226     ///
227     /// ```
228     /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
229     /// ```
230     ///
231     /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`.
232     pub(super) fn report_error(
233         &self,
234         mir: &Mir<'tcx>,
235         infcx: &InferCtxt<'_, '_, 'tcx>,
236         mir_def_id: DefId,
237         fr: RegionVid,
238         outlived_fr: RegionVid,
239         errors_buffer: &mut Vec<Diagnostic>,
240     ) {
241         debug!("report_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
242
243         let (category, span, _) = self.best_blame_constraint(
244             mir,
245             fr,
246             |r| r == outlived_fr
247         );
248
249         // Check if we can use one of the "nice region errors".
250         if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
251             let tables = infcx.tcx.typeck_tables_of(mir_def_id);
252             let nice = NiceRegionError::new_from_span(infcx.tcx, span, o, f, Some(tables));
253             if let Some(_error_reported) = nice.try_report_from_nll() {
254                 return;
255             }
256         }
257
258         let (fr_is_local, outlived_fr_is_local): (bool, bool) = (
259             self.universal_regions.is_local_free_region(fr),
260             self.universal_regions.is_local_free_region(outlived_fr),
261         );
262
263         debug!("report_error: fr_is_local={:?} outlived_fr_is_local={:?} category={:?}",
264                fr_is_local, outlived_fr_is_local, category);
265         match (category, fr_is_local, outlived_fr_is_local) {
266             (ConstraintCategory::Assignment, true, false) |
267             (ConstraintCategory::CallArgument, true, false) =>
268                 self.report_escaping_data_error(mir, infcx, mir_def_id, fr, outlived_fr,
269                                                 category, span, errors_buffer),
270             _ =>
271                 self.report_general_error(mir, infcx, mir_def_id, fr, fr_is_local,
272                                           outlived_fr, outlived_fr_is_local,
273                                           category, span, errors_buffer),
274         };
275     }
276
277     fn report_escaping_data_error(
278         &self,
279         mir: &Mir<'tcx>,
280         infcx: &InferCtxt<'_, '_, 'tcx>,
281         mir_def_id: DefId,
282         fr: RegionVid,
283         outlived_fr: RegionVid,
284         category: ConstraintCategory,
285         span: Span,
286         errors_buffer: &mut Vec<Diagnostic>,
287     ) {
288         let fr_name_and_span = self.get_var_name_and_span_for_region(infcx.tcx, mir, fr);
289         let outlived_fr_name_and_span =
290             self.get_var_name_and_span_for_region(infcx.tcx, mir, outlived_fr);
291
292         let escapes_from = if infcx.tcx.is_closure(mir_def_id) { "closure" } else { "function" };
293
294         // Revert to the normal error in these cases.
295         // Assignments aren't "escapes" in function items.
296         if (fr_name_and_span.is_none() && outlived_fr_name_and_span.is_none())
297             || (category == ConstraintCategory::Assignment && escapes_from == "function")
298         {
299             return self.report_general_error(mir, infcx, mir_def_id,
300                                              fr, true, outlived_fr, false,
301                                              category, span, errors_buffer);
302         }
303
304         let mut diag = infcx.tcx.sess.struct_span_err(
305             span, &format!("borrowed data escapes outside of {}", escapes_from),
306         );
307
308         if let Some((outlived_fr_name, outlived_fr_span)) = outlived_fr_name_and_span {
309             if let Some(name) = outlived_fr_name {
310                 diag.span_label(
311                     outlived_fr_span,
312                     format!("`{}` is declared here, outside of the {} body", name, escapes_from),
313                 );
314             }
315         }
316
317         if let Some((fr_name, fr_span)) = fr_name_and_span {
318             if let Some(name) = fr_name {
319                 diag.span_label(
320                     fr_span,
321                     format!("`{}` is a reference that is only valid in the {} body",
322                             name, escapes_from),
323                 );
324
325                 diag.span_label(span, format!("`{}` escapes the {} body here",
326                                                name, escapes_from));
327             }
328         }
329
330         diag.buffer(errors_buffer);
331     }
332
333     fn report_general_error(
334         &self,
335         mir: &Mir<'tcx>,
336         infcx: &InferCtxt<'_, '_, 'tcx>,
337         mir_def_id: DefId,
338         fr: RegionVid,
339         fr_is_local: bool,
340         outlived_fr: RegionVid,
341         outlived_fr_is_local: bool,
342         category: ConstraintCategory,
343         span: Span,
344         errors_buffer: &mut Vec<Diagnostic>,
345     ) {
346         let mut diag = infcx.tcx.sess.struct_span_err(
347             span,
348             "unsatisfied lifetime constraints", // FIXME
349         );
350
351         let counter = &mut 1;
352         let fr_name = self.give_region_a_name(infcx, mir, mir_def_id, fr, counter);
353         fr_name.highlight_region_name(&mut diag);
354         let outlived_fr_name = self.give_region_a_name(
355             infcx, mir, mir_def_id, outlived_fr, counter);
356         outlived_fr_name.highlight_region_name(&mut diag);
357
358         let mir_def_name = if infcx.tcx.is_closure(mir_def_id) { "closure" } else { "function" };
359
360         match (category, outlived_fr_is_local, fr_is_local) {
361             (ConstraintCategory::Return, true, _) => {
362                 diag.span_label(span, format!(
363                     "{} was supposed to return data with lifetime `{}` but it is returning \
364                     data with lifetime `{}`",
365                     mir_def_name, outlived_fr_name, fr_name
366                 ));
367             },
368             _ => {
369                 diag.span_label(span, format!(
370                     "{}requires that `{}` must outlive `{}`",
371                     category.description(), fr_name, outlived_fr_name,
372                 ));
373             },
374         }
375
376         self.add_static_impl_trait_suggestion(
377             infcx, &mut diag, fr, fr_name, outlived_fr,
378         );
379
380         diag.buffer(errors_buffer);
381     }
382
383     fn add_static_impl_trait_suggestion(
384         &self,
385         infcx: &InferCtxt<'_, '_, 'tcx>,
386         diag: &mut DiagnosticBuilder<'_>,
387         fr: RegionVid,
388         // We need to pass `fr_name` - computing it again will label it twice.
389         fr_name: RegionName,
390         outlived_fr: RegionVid,
391     ) {
392         if let (
393             Some(f),
394             Some(ty::RegionKind::ReStatic)
395         ) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
396             if let Some(ty::TyS {
397                 sty: ty::TyKind::Opaque(did, substs),
398                 ..
399             }) = infcx.tcx.is_suitable_region(f)
400                     .map(|r| r.def_id)
401                     .map(|id| infcx.tcx.return_type_impl_trait(id))
402                     .unwrap_or(None)
403             {
404                 // Check whether or not the impl trait return type is intended to capture
405                 // data with the static lifetime.
406                 //
407                 // eg. check for `impl Trait + 'static` instead of `impl Trait`.
408                 let has_static_predicate = {
409                     let predicates_of = infcx.tcx.predicates_of(*did);
410                     let bounds = predicates_of.instantiate(infcx.tcx, substs);
411
412                     let mut found = false;
413                     for predicate in bounds.predicates {
414                         if let ty::Predicate::TypeOutlives(binder) = predicate {
415                             if let ty::OutlivesPredicate(
416                                 _,
417                                 ty::RegionKind::ReStatic
418                             ) = binder.skip_binder() {
419                                 found = true;
420                                 break;
421                             }
422                         }
423                     }
424
425                     found
426                 };
427
428                 debug!("add_static_impl_trait_suggestion: has_static_predicate={:?}",
429                        has_static_predicate);
430                 let static_str = keywords::StaticLifetime.name();
431                 // If there is a static predicate, then the only sensible suggestion is to replace
432                 // fr with `'static`.
433                 if has_static_predicate {
434                     diag.help(
435                         &format!(
436                             "consider replacing `{}` with `{}`",
437                             fr_name, static_str,
438                         ),
439                     );
440                 } else {
441                     // Otherwise, we should suggest adding a constraint on the return type.
442                     let span = infcx.tcx.def_span(*did);
443                     if let Ok(snippet) = infcx.tcx.sess.source_map().span_to_snippet(span) {
444                         let suggestable_fr_name = if fr_name.was_named() {
445                             format!("{}", fr_name)
446                         } else {
447                             "'_".to_string()
448                         };
449
450                         diag.span_suggestion_with_applicability(
451                             span,
452                             &format!(
453                                 "to allow this impl Trait to capture borrowed data with lifetime \
454                                  `{}`, add `{}` as a constraint",
455                                 fr_name, suggestable_fr_name,
456                             ),
457                             format!("{} + {}", snippet, suggestable_fr_name),
458                             Applicability::MachineApplicable,
459                         );
460                     }
461                 }
462             }
463         }
464     }
465
466     // Finds some region R such that `fr1: R` and `R` is live at
467     // `elem`.
468     crate fn find_sub_region_live_at(&self, fr1: RegionVid, elem: Location) -> RegionVid {
469         // Find all paths
470         let (_path, r) =
471             self.find_constraint_paths_between_regions(fr1, |r| {
472                 self.liveness_constraints.contains(r, elem)
473             }).unwrap();
474         r
475     }
476
477     // Finds a good span to blame for the fact that `fr1` outlives `fr2`.
478     crate fn find_outlives_blame_span(
479         &self,
480         mir: &Mir<'tcx>,
481         fr1: RegionVid,
482         fr2: RegionVid,
483     ) -> (ConstraintCategory, Span) {
484         let (category, span, _) = self.best_blame_constraint(mir, fr1, |r| r == fr2);
485         (category, span)
486     }
487
488     fn retrieve_closure_constraint_info(
489         &self,
490         mir: &Mir<'tcx>,
491         constraint: &OutlivesConstraint
492     ) -> (ConstraintCategory, Span) {
493         let loc = match constraint.locations {
494             Locations::All(span) => return (constraint.category, span),
495             Locations::Single(loc) => loc,
496         };
497
498         let opt_span_category = self
499             .closure_bounds_mapping[&loc]
500             .get(&(constraint.sup, constraint.sub));
501         *opt_span_category.unwrap_or(&(constraint.category, mir.source_info(loc).span))
502     }
503 }