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.
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.
11 use borrow_check::nll::constraints::{OutlivesConstraint};
12 use borrow_check::nll::region_infer::RegionInferenceContext;
13 use borrow_check::nll::region_infer::error_reporting::region_name::RegionNameSource;
14 use borrow_check::nll::type_check::Locations;
15 use borrow_check::nll::universal_regions::DefiningTy;
16 use util::borrowck_errors::{BorrowckErrors, Origin};
17 use rustc::hir::def_id::DefId;
18 use rustc::infer::error_reporting::nice_region_error::NiceRegionError;
19 use rustc::infer::InferCtxt;
20 use rustc::mir::{ConstraintCategory, Location, Mir};
21 use rustc::ty::{self, RegionVid};
22 use rustc_data_structures::indexed_vec::IndexVec;
23 use rustc_errors::{Diagnostic, DiagnosticBuilder};
24 use std::collections::VecDeque;
25 use syntax::symbol::keywords;
27 use syntax::errors::Applicability;
32 use self::region_name::RegionName;
34 trait ConstraintDescription {
35 fn description(&self) -> &'static str;
38 impl ConstraintDescription for ConstraintCategory {
39 fn description(&self) -> &'static str {
40 // Must end with a space. Allows for empty names to be provided.
42 ConstraintCategory::Assignment => "assignment ",
43 ConstraintCategory::Return => "returning this value ",
44 ConstraintCategory::UseAsConst => "using this value as a constant ",
45 ConstraintCategory::UseAsStatic => "using this value as a static ",
46 ConstraintCategory::Cast => "cast ",
47 ConstraintCategory::CallArgument => "argument ",
48 ConstraintCategory::TypeAnnotation => "type annotation ",
49 ConstraintCategory::ClosureBounds => "closure body ",
50 ConstraintCategory::SizedBound => "proving this value is `Sized` ",
51 ConstraintCategory::CopyBound => "copying this value ",
52 ConstraintCategory::OpaqueType => "opaque type ",
53 ConstraintCategory::Boring
54 | ConstraintCategory::BoringNoLocation
55 | ConstraintCategory::Internal => "",
60 #[derive(Copy, Clone, PartialEq, Eq)]
63 FromOutlivesConstraint(OutlivesConstraint),
67 impl<'tcx> RegionInferenceContext<'tcx> {
68 /// Tries to find the best constraint to blame for the fact that
69 /// `R: from_region`, where `R` is some region that meets
70 /// `target_test`. This works by following the constraint graph,
71 /// creating a constraint path that forces `R` to outlive
72 /// `from_region`, and then finding the best choices within that
74 fn best_blame_constraint(
77 from_region: RegionVid,
78 target_test: impl Fn(RegionVid) -> bool,
79 ) -> (ConstraintCategory, Span, RegionVid) {
80 debug!("best_blame_constraint(from_region={:?})", from_region);
83 let (path, target_region) = self
84 .find_constraint_paths_between_regions(from_region, target_test)
87 "best_blame_constraint: path={:#?}",
92 self.constraint_sccs.scc(c.sup),
93 self.constraint_sccs.scc(c.sub),
98 // Classify each of the constraints along the path.
99 let mut categorized_path: Vec<(ConstraintCategory, Span)> = path
102 if constraint.category == ConstraintCategory::ClosureBounds {
103 self.retrieve_closure_constraint_info(mir, &constraint)
105 (constraint.category, constraint.locations.span(mir))
110 "best_blame_constraint: categorized_path={:#?}",
114 // To find the best span to cite, we first try to look for the
115 // final constraint that is interesting and where the `sup` is
116 // not unified with the ultimate target region. The reason
117 // for this is that we have a chain of constraints that lead
118 // from the source to the target region, something like:
120 // '0: '1 ('0 is the source)
125 // '5: '6 ('6 is the target)
127 // Some of those regions are unified with `'6` (in the same
128 // SCC). We want to screen those out. After that point, the
129 // "closest" constraint we have to the end is going to be the
130 // most likely to be the point where the value escapes -- but
131 // we still want to screen for an "interesting" point to
132 // highlight (e.g., a call site or something).
133 let target_scc = self.constraint_sccs.scc(target_region);
134 let best_choice = (0..path.len()).rev().find(|&i| {
135 let constraint = path[i];
137 let constraint_sup_scc = self.constraint_sccs.scc(constraint.sup);
139 match categorized_path[i].0 {
140 ConstraintCategory::OpaqueType
141 | ConstraintCategory::Boring
142 | ConstraintCategory::BoringNoLocation
143 | ConstraintCategory::Internal => false,
144 ConstraintCategory::TypeAnnotation
145 | ConstraintCategory::Return => true,
146 _ => constraint_sup_scc != target_scc,
149 if let Some(i) = best_choice {
150 let (category, span) = categorized_path[i];
151 return (category, span, target_region);
154 // If that search fails, that is.. unusual. Maybe everything
155 // is in the same SCC or something. In that case, find what
156 // appears to be the most interesting point to report to the
157 // user via an even more ad-hoc guess.
158 categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0));
159 debug!("best_blame_constraint: sorted_path={:#?}", categorized_path);
161 let &(category, span) = categorized_path.first().unwrap();
163 (category, span, target_region)
166 /// Walks the graph of constraints (where `'a: 'b` is considered
167 /// an edge `'a -> 'b`) to find all paths from `from_region` to
168 /// `to_region`. The paths are accumulated into the vector
169 /// `results`. The paths are stored as a series of
170 /// `ConstraintIndex` values -- in other words, a list of *edges*.
172 /// Returns: a series of constraints as well as the region `R`
173 /// that passed the target test.
174 fn find_constraint_paths_between_regions(
176 from_region: RegionVid,
177 target_test: impl Fn(RegionVid) -> bool,
178 ) -> Option<(Vec<OutlivesConstraint>, RegionVid)> {
179 let mut context = IndexVec::from_elem(Trace::NotVisited, &self.definitions);
180 context[from_region] = Trace::StartRegion;
182 // Use a deque so that we do a breadth-first search. We will
183 // stop at the first match, which ought to be the shortest
184 // path (fewest constraints).
185 let mut deque = VecDeque::new();
186 deque.push_back(from_region);
188 while let Some(r) = deque.pop_front() {
189 // Check if we reached the region we were looking for. If so,
190 // we can reconstruct the path that led to it and return it.
192 let mut result = vec![];
196 Trace::NotVisited => {
197 bug!("found unvisited region {:?} on path to {:?}", p, r)
199 Trace::FromOutlivesConstraint(c) => {
204 Trace::StartRegion => {
206 return Some((result, r));
212 // Otherwise, walk over the outgoing constraints and
213 // enqueue any regions we find, keeping track of how we
215 let fr_static = self.universal_regions.fr_static;
216 for constraint in self.constraint_graph.outgoing_edges(r,
219 assert_eq!(constraint.sup, r);
220 let sub_region = constraint.sub;
221 if let Trace::NotVisited = context[sub_region] {
222 context[sub_region] = Trace::FromOutlivesConstraint(constraint);
223 deque.push_back(sub_region);
231 /// Report an error because the universal region `fr` was required to outlive
232 /// `outlived_fr` but it is not known to do so. For example:
235 /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
238 /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`.
239 pub(super) fn report_error(
242 infcx: &InferCtxt<'_, '_, 'tcx>,
245 outlived_fr: RegionVid,
246 errors_buffer: &mut Vec<Diagnostic>,
248 debug!("report_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
250 let (category, span, _) = self.best_blame_constraint(
256 // Check if we can use one of the "nice region errors".
257 if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
258 let tables = infcx.tcx.typeck_tables_of(mir_def_id);
259 let nice = NiceRegionError::new_from_span(infcx.tcx, span, o, f, Some(tables));
260 if let Some(_error_reported) = nice.try_report_from_nll() {
265 let (fr_is_local, outlived_fr_is_local): (bool, bool) = (
266 self.universal_regions.is_local_free_region(fr),
267 self.universal_regions.is_local_free_region(outlived_fr),
270 debug!("report_error: fr_is_local={:?} outlived_fr_is_local={:?} category={:?}",
271 fr_is_local, outlived_fr_is_local, category);
272 match (category, fr_is_local, outlived_fr_is_local) {
273 (ConstraintCategory::Return, true, false) if self.is_closure_fn_mut(infcx, fr) =>
274 self.report_fnmut_error(mir, infcx, mir_def_id, fr, outlived_fr, span,
276 (ConstraintCategory::Assignment, true, false) |
277 (ConstraintCategory::CallArgument, true, false) =>
278 self.report_escaping_data_error(mir, infcx, mir_def_id, fr, outlived_fr,
279 category, span, errors_buffer),
281 self.report_general_error(mir, infcx, mir_def_id, fr, fr_is_local,
282 outlived_fr, outlived_fr_is_local,
283 category, span, errors_buffer),
287 /// Report a specialized error when `FnMut` closures return a reference to a captured variable.
288 /// This function expects `fr` to be local and `outlived_fr` to not be local.
291 /// error: captured variable cannot escape `FnMut` closure body
292 /// --> $DIR/issue-53040.rs:15:8
295 /// | -- ^^^^^^ creates a reference to a captured variable which escapes the closure body
297 /// | inferred to be a `FnMut` closure
299 /// = note: `FnMut` closures only have access to their captured variables while they are
301 /// = note: ...therefore, returned references to captured variables will escape the closure
303 fn report_fnmut_error(
306 infcx: &InferCtxt<'_, '_, 'tcx>,
309 outlived_fr: RegionVid,
311 errors_buffer: &mut Vec<Diagnostic>,
313 let mut diag = infcx.tcx.sess.struct_span_err(
315 "captured variable cannot escape `FnMut` closure body",
318 // We should check if the return type of this closure is in fact a closure - in that
319 // case, we can special case the error further.
320 let return_type_is_closure = self.universal_regions.unnormalized_output_ty.is_closure();
321 let message = if return_type_is_closure {
322 "returns a closure that contains a reference to a captured variable, which then \
323 escapes the closure body"
325 "returns a reference to a captured variable which escapes the closure body"
333 match self.give_region_a_name(infcx, mir, mir_def_id, outlived_fr, &mut 1).source {
334 RegionNameSource::NamedEarlyBoundRegion(fr_span) |
335 RegionNameSource::NamedFreeRegion(fr_span) |
336 RegionNameSource::SynthesizedFreeEnvRegion(fr_span, _) |
337 RegionNameSource::CannotMatchHirTy(fr_span, _) |
338 RegionNameSource::MatchedHirTy(fr_span) |
339 RegionNameSource::MatchedAdtAndSegment(fr_span) |
340 RegionNameSource::AnonRegionFromUpvar(fr_span, _) |
341 RegionNameSource::AnonRegionFromOutput(fr_span, _, _) => {
342 diag.span_label(fr_span, "inferred to be a `FnMut` closure");
347 diag.note("`FnMut` closures only have access to their captured variables while they are \
349 diag.note("...therefore, they cannot allow references to captured variables to escape");
351 diag.buffer(errors_buffer);
354 /// Reports a error specifically for when data is escaping a closure.
357 /// error: borrowed data escapes outside of function
358 /// --> $DIR/lifetime-bound-will-change-warning.rs:44:5
360 /// LL | fn test2<'a>(x: &'a Box<Fn()+'a>) {
361 /// | - `x` is a reference that is only valid in the function body
362 /// LL | // but ref_obj will not, so warn.
364 /// | ^^^^^^^^^^ `x` escapes the function body here
366 fn report_escaping_data_error(
369 infcx: &InferCtxt<'_, '_, 'tcx>,
372 outlived_fr: RegionVid,
373 category: ConstraintCategory,
375 errors_buffer: &mut Vec<Diagnostic>,
377 let fr_name_and_span = self.get_var_name_and_span_for_region(infcx.tcx, mir, fr);
378 let outlived_fr_name_and_span =
379 self.get_var_name_and_span_for_region(infcx.tcx, mir, outlived_fr);
381 let escapes_from = match self.universal_regions.defining_ty {
382 DefiningTy::Closure(..) => "closure",
383 DefiningTy::Generator(..) => "generator",
384 DefiningTy::FnDef(..) => "function",
385 DefiningTy::Const(..) => "const"
388 // Revert to the normal error in these cases.
389 // Assignments aren't "escapes" in function items.
390 if (fr_name_and_span.is_none() && outlived_fr_name_and_span.is_none())
391 || (category == ConstraintCategory::Assignment && escapes_from == "function")
392 || escapes_from == "const"
394 return self.report_general_error(mir, infcx, mir_def_id,
395 fr, true, outlived_fr, false,
396 category, span, errors_buffer);
399 let mut diag = infcx.tcx.borrowed_data_escapes_closure(span, escapes_from, Origin::Mir);
401 if let Some((Some(outlived_fr_name), outlived_fr_span)) = outlived_fr_name_and_span {
405 "`{}` is declared here, outside of the {} body",
406 outlived_fr_name, escapes_from
411 if let Some((Some(fr_name), fr_span)) = fr_name_and_span {
415 "`{}` is a reference that is only valid in the {} body",
416 fr_name, escapes_from
420 diag.span_label(span, format!("`{}` escapes the {} body here", fr_name, escapes_from));
423 diag.buffer(errors_buffer);
426 /// Reports a region inference error for the general case with named/synthesized lifetimes to
427 /// explain what is happening.
430 /// error: unsatisfied lifetime constraints
431 /// --> $DIR/regions-creating-enums3.rs:17:5
433 /// LL | fn mk_add_bad1<'a,'b>(x: &'a ast<'a>, y: &'b ast<'b>) -> ast<'a> {
434 /// | -- -- lifetime `'b` defined here
436 /// | lifetime `'a` defined here
437 /// LL | ast::add(x, y)
438 /// | ^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'a` but it
439 /// | is returning data with lifetime `'b`
441 fn report_general_error(
444 infcx: &InferCtxt<'_, '_, 'tcx>,
448 outlived_fr: RegionVid,
449 outlived_fr_is_local: bool,
450 category: ConstraintCategory,
452 errors_buffer: &mut Vec<Diagnostic>,
454 let mut diag = infcx.tcx.sess.struct_span_err(
456 "unsatisfied lifetime constraints", // FIXME
459 let counter = &mut 1;
460 let fr_name = self.give_region_a_name(infcx, mir, mir_def_id, fr, counter);
461 fr_name.highlight_region_name(&mut diag);
462 let outlived_fr_name = self.give_region_a_name(
463 infcx, mir, mir_def_id, outlived_fr, counter);
464 outlived_fr_name.highlight_region_name(&mut diag);
466 let mir_def_name = if infcx.tcx.is_closure(mir_def_id) { "closure" } else { "function" };
468 match (category, outlived_fr_is_local, fr_is_local) {
469 (ConstraintCategory::Return, true, _) => {
470 diag.span_label(span, format!(
471 "{} was supposed to return data with lifetime `{}` but it is returning \
472 data with lifetime `{}`",
473 mir_def_name, outlived_fr_name, fr_name
477 diag.span_label(span, format!(
478 "{}requires that `{}` must outlive `{}`",
479 category.description(), fr_name, outlived_fr_name,
484 self.add_static_impl_trait_suggestion(
485 infcx, &mut diag, fr, fr_name, outlived_fr,
488 diag.buffer(errors_buffer);
491 /// Adds a suggestion to errors where a `impl Trait` is returned.
494 /// help: to allow this impl Trait to capture borrowed data with lifetime `'1`, add `'_` as
497 /// LL | fn iter_values_anon(&self) -> impl Iterator<Item=u32> + 'a {
498 /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
500 fn add_static_impl_trait_suggestion(
502 infcx: &InferCtxt<'_, '_, 'tcx>,
503 diag: &mut DiagnosticBuilder<'_>,
505 // We need to pass `fr_name` - computing it again will label it twice.
507 outlived_fr: RegionVid,
511 Some(ty::RegionKind::ReStatic)
512 ) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
513 if let Some(ty::TyS {
514 sty: ty::TyKind::Opaque(did, substs),
516 }) = infcx.tcx.is_suitable_region(f)
518 .map(|id| infcx.tcx.return_type_impl_trait(id))
521 // Check whether or not the impl trait return type is intended to capture
522 // data with the static lifetime.
524 // eg. check for `impl Trait + 'static` instead of `impl Trait`.
525 let has_static_predicate = {
526 let predicates_of = infcx.tcx.predicates_of(*did);
527 let bounds = predicates_of.instantiate(infcx.tcx, substs);
529 let mut found = false;
530 for predicate in bounds.predicates {
531 if let ty::Predicate::TypeOutlives(binder) = predicate {
532 if let ty::OutlivesPredicate(
534 ty::RegionKind::ReStatic
535 ) = binder.skip_binder() {
545 debug!("add_static_impl_trait_suggestion: has_static_predicate={:?}",
546 has_static_predicate);
547 let static_str = keywords::StaticLifetime.name();
548 // If there is a static predicate, then the only sensible suggestion is to replace
549 // fr with `'static`.
550 if has_static_predicate {
553 "consider replacing `{}` with `{}`",
558 // Otherwise, we should suggest adding a constraint on the return type.
559 let span = infcx.tcx.def_span(*did);
560 if let Ok(snippet) = infcx.tcx.sess.source_map().span_to_snippet(span) {
561 let suggestable_fr_name = if fr_name.was_named() {
567 diag.span_suggestion_with_applicability(
570 "to allow this impl Trait to capture borrowed data with lifetime \
571 `{}`, add `{}` as a constraint",
572 fr_name, suggestable_fr_name,
574 format!("{} + {}", snippet, suggestable_fr_name),
575 Applicability::MachineApplicable,
583 // Finds some region R such that `fr1: R` and `R` is live at
585 crate fn find_sub_region_live_at(&self, fr1: RegionVid, elem: Location) -> RegionVid {
588 self.find_constraint_paths_between_regions(fr1, |r| {
589 self.liveness_constraints.contains(r, elem)
594 // Finds a good span to blame for the fact that `fr1` outlives `fr2`.
595 crate fn find_outlives_blame_span(
600 ) -> (ConstraintCategory, Span) {
601 let (category, span, _) = self.best_blame_constraint(mir, fr1, |r| r == fr2);
605 fn retrieve_closure_constraint_info(
608 constraint: &OutlivesConstraint
609 ) -> (ConstraintCategory, Span) {
610 let loc = match constraint.locations {
611 Locations::All(span) => return (constraint.category, span),
612 Locations::Single(loc) => loc,
615 let opt_span_category = self
616 .closure_bounds_mapping[&loc]
617 .get(&(constraint.sup, constraint.sub));
618 *opt_span_category.unwrap_or(&(constraint.category, mir.source_info(loc).span))
621 /// Returns `true` if a closure is inferred to be an `FnMut` closure.
622 crate fn is_closure_fn_mut(
624 infcx: &InferCtxt<'_, '_, 'tcx>,
627 if let Some(ty::ReFree(free_region)) = self.to_error_region(fr) {
628 if let ty::BoundRegion::BrEnv = free_region.bound_region {
629 if let DefiningTy::Closure(def_id, substs) = self.universal_regions.defining_ty {
630 let closure_kind_ty = substs.closure_kind_ty(def_id, infcx.tcx);
631 return Some(ty::ClosureKind::FnMut) == closure_kind_ty.to_opt_closure_kind();