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
::ConstraintDescription
;
12 use borrow_check
::nll
::constraints
::{OutlivesConstraint}
;
13 use borrow_check
::nll
::region_infer
::RegionInferenceContext
;
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 crate use self::region_name
::{RegionName, RegionNameSource}
;
34 impl ConstraintDescription
for ConstraintCategory
{
35 fn description(&self) -> &'
static str {
36 // Must end with a space. Allows for empty names to be provided.
38 ConstraintCategory
::Assignment
=> "assignment ",
39 ConstraintCategory
::Return
=> "returning this value ",
40 ConstraintCategory
::UseAsConst
=> "using this value as a constant ",
41 ConstraintCategory
::UseAsStatic
=> "using this value as a static ",
42 ConstraintCategory
::Cast
=> "cast ",
43 ConstraintCategory
::CallArgument
=> "argument ",
44 ConstraintCategory
::TypeAnnotation
=> "type annotation ",
45 ConstraintCategory
::ClosureBounds
=> "closure body ",
46 ConstraintCategory
::SizedBound
=> "proving this value is `Sized` ",
47 ConstraintCategory
::CopyBound
=> "copying this value ",
48 ConstraintCategory
::OpaqueType
=> "opaque type ",
49 ConstraintCategory
::Boring
50 | ConstraintCategory
::BoringNoLocation
51 | ConstraintCategory
::Internal
=> "",
56 #[derive(Copy, Clone, PartialEq, Eq)]
59 FromOutlivesConstraint(OutlivesConstraint
),
63 impl<'tcx
> RegionInferenceContext
<'tcx
> {
64 /// Tries to find the best constraint to blame for the fact that
65 /// `R: from_region`, where `R` is some region that meets
66 /// `target_test`. This works by following the constraint graph,
67 /// creating a constraint path that forces `R` to outlive
68 /// `from_region`, and then finding the best choices within that
70 fn best_blame_constraint(
73 from_region
: RegionVid
,
74 target_test
: impl Fn(RegionVid
) -> bool
,
75 ) -> (ConstraintCategory
, bool
, Span
) {
76 debug
!("best_blame_constraint(from_region={:?})", from_region
);
79 let (path
, target_region
) = self
80 .find_constraint_paths_between_regions(from_region
, target_test
)
83 "best_blame_constraint: path={:#?}",
88 self.constraint_sccs
.scc(c
.sup
),
89 self.constraint_sccs
.scc(c
.sub
),
94 // Classify each of the constraints along the path.
95 let mut categorized_path
: Vec
<(ConstraintCategory
, bool
, Span
)> = path
98 if constraint
.category
== ConstraintCategory
::ClosureBounds
{
99 self.retrieve_closure_constraint_info(mir
, &constraint
)
101 (constraint
.category
, false, constraint
.locations
.span(mir
))
106 "best_blame_constraint: categorized_path={:#?}",
110 // To find the best span to cite, we first try to look for the
111 // final constraint that is interesting and where the `sup` is
112 // not unified with the ultimate target region. The reason
113 // for this is that we have a chain of constraints that lead
114 // from the source to the target region, something like:
116 // '0: '1 ('0 is the source)
121 // '5: '6 ('6 is the target)
123 // Some of those regions are unified with `'6` (in the same
124 // SCC). We want to screen those out. After that point, the
125 // "closest" constraint we have to the end is going to be the
126 // most likely to be the point where the value escapes -- but
127 // we still want to screen for an "interesting" point to
128 // highlight (e.g., a call site or something).
129 let target_scc
= self.constraint_sccs
.scc(target_region
);
130 let best_choice
= (0..path
.len()).rev().find(|&i
| {
131 let constraint
= path
[i
];
133 let constraint_sup_scc
= self.constraint_sccs
.scc(constraint
.sup
);
135 match categorized_path
[i
].0 {
136 ConstraintCategory
::OpaqueType
137 | ConstraintCategory
::Boring
138 | ConstraintCategory
::BoringNoLocation
139 | ConstraintCategory
::Internal
=> false,
140 ConstraintCategory
::TypeAnnotation
141 | ConstraintCategory
::Return
=> true,
142 _
=> constraint_sup_scc
!= target_scc
,
145 if let Some(i
) = best_choice
{
146 return categorized_path
[i
]
149 // If that search fails, that is.. unusual. Maybe everything
150 // is in the same SCC or something. In that case, find what
151 // appears to be the most interesting point to report to the
152 // user via an even more ad-hoc guess.
153 categorized_path
.sort_by(|p0
, p1
| p0
.0
.cmp(&p1
.0
));
154 debug
!("`: sorted_path={:#?}", categorized_path
);
156 *categorized_path
.first().unwrap()
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*.
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(
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
;
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
);
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.
185 let mut result
= vec
![];
189 Trace
::NotVisited
=> {
190 bug
!("found unvisited region {:?} on path to {:?}", p
, r
)
192 Trace
::FromOutlivesConstraint(c
) => {
197 Trace
::StartRegion
=> {
199 return Some((result
, r
));
205 // Otherwise, walk over the outgoing constraints and
206 // enqueue any regions we find, keeping track of how we
208 let fr_static
= self.universal_regions
.fr_static
;
209 for constraint
in self.constraint_graph
.outgoing_edges(r
,
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
);
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:
228 /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
231 /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`.
232 pub(super) fn report_error(
235 infcx
: &InferCtxt
<'_
, '_
, 'tcx
>,
238 outlived_fr
: RegionVid
,
239 errors_buffer
: &mut Vec
<Diagnostic
>,
241 debug
!("report_error(fr={:?}, outlived_fr={:?})", fr
, outlived_fr
);
243 let (category
, _
, span
) = self.best_blame_constraint(
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() {
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
),
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
::Return
, true, false) if self.is_closure_fn_mut(infcx
, fr
) =>
267 self.report_fnmut_error(mir
, infcx
, mir_def_id
, fr
, outlived_fr
, span
,
269 (ConstraintCategory
::Assignment
, true, false) |
270 (ConstraintCategory
::CallArgument
, true, false) =>
271 self.report_escaping_data_error(mir
, infcx
, mir_def_id
, fr
, outlived_fr
,
272 category
, span
, errors_buffer
),
274 self.report_general_error(mir
, infcx
, mir_def_id
, fr
, fr_is_local
,
275 outlived_fr
, outlived_fr_is_local
,
276 category
, span
, errors_buffer
),
280 /// Report a specialized error when `FnMut` closures return a reference to a captured variable.
281 /// This function expects `fr` to be local and `outlived_fr` to not be local.
284 /// error: captured variable cannot escape `FnMut` closure body
285 /// --> $DIR/issue-53040.rs:15:8
288 /// | -- ^^^^^^ creates a reference to a captured variable which escapes the closure body
290 /// | inferred to be a `FnMut` closure
292 /// = note: `FnMut` closures only have access to their captured variables while they are
294 /// = note: ...therefore, returned references to captured variables will escape the closure
296 fn report_fnmut_error(
299 infcx
: &InferCtxt
<'_
, '_
, 'tcx
>,
302 outlived_fr
: RegionVid
,
304 errors_buffer
: &mut Vec
<Diagnostic
>,
306 let mut diag
= infcx
.tcx
.sess
.struct_span_err(
308 "captured variable cannot escape `FnMut` closure body",
311 // We should check if the return type of this closure is in fact a closure - in that
312 // case, we can special case the error further.
313 let return_type_is_closure
= self.universal_regions
.unnormalized_output_ty
.is_closure();
314 let message
= if return_type_is_closure
{
315 "returns a closure that contains a reference to a captured variable, which then \
316 escapes the closure body"
318 "returns a reference to a captured variable which escapes the closure body"
326 match self.give_region_a_name(infcx
, mir
, mir_def_id
, outlived_fr
, &mut 1).source
{
327 RegionNameSource
::NamedEarlyBoundRegion(fr_span
) |
328 RegionNameSource
::NamedFreeRegion(fr_span
) |
329 RegionNameSource
::SynthesizedFreeEnvRegion(fr_span
, _
) |
330 RegionNameSource
::CannotMatchHirTy(fr_span
, _
) |
331 RegionNameSource
::MatchedHirTy(fr_span
) |
332 RegionNameSource
::MatchedAdtAndSegment(fr_span
) |
333 RegionNameSource
::AnonRegionFromUpvar(fr_span
, _
) |
334 RegionNameSource
::AnonRegionFromOutput(fr_span
, _
, _
) => {
335 diag
.span_label(fr_span
, "inferred to be a `FnMut` closure");
340 diag
.note("`FnMut` closures only have access to their captured variables while they are \
342 diag
.note("...therefore, they cannot allow references to captured variables to escape");
344 diag
.buffer(errors_buffer
);
347 /// Reports a error specifically for when data is escaping a closure.
350 /// error: borrowed data escapes outside of function
351 /// --> $DIR/lifetime-bound-will-change-warning.rs:44:5
353 /// LL | fn test2<'a>(x: &'a Box<Fn()+'a>) {
354 /// | - `x` is a reference that is only valid in the function body
355 /// LL | // but ref_obj will not, so warn.
357 /// | ^^^^^^^^^^ `x` escapes the function body here
359 fn report_escaping_data_error(
362 infcx
: &InferCtxt
<'_
, '_
, 'tcx
>,
365 outlived_fr
: RegionVid
,
366 category
: ConstraintCategory
,
368 errors_buffer
: &mut Vec
<Diagnostic
>,
370 let fr_name_and_span
= self.get_var_name_and_span_for_region(infcx
.tcx
, mir
, fr
);
371 let outlived_fr_name_and_span
=
372 self.get_var_name_and_span_for_region(infcx
.tcx
, mir
, outlived_fr
);
374 let escapes_from
= match self.universal_regions
.defining_ty
{
375 DefiningTy
::Closure(..) => "closure",
376 DefiningTy
::Generator(..) => "generator",
377 DefiningTy
::FnDef(..) => "function",
378 DefiningTy
::Const(..) => "const"
381 // Revert to the normal error in these cases.
382 // Assignments aren't "escapes" in function items.
383 if (fr_name_and_span
.is_none() && outlived_fr_name_and_span
.is_none())
384 || (category
== ConstraintCategory
::Assignment
&& escapes_from
== "function")
385 || escapes_from
== "const"
387 return self.report_general_error(mir
, infcx
, mir_def_id
,
388 fr
, true, outlived_fr
, false,
389 category
, span
, errors_buffer
);
392 let mut diag
= infcx
.tcx
.borrowed_data_escapes_closure(span
, escapes_from
, Origin
::Mir
);
394 if let Some((Some(outlived_fr_name
), outlived_fr_span
)) = outlived_fr_name_and_span
{
398 "`{}` is declared here, outside of the {} body",
399 outlived_fr_name
, escapes_from
404 if let Some((Some(fr_name
), fr_span
)) = fr_name_and_span
{
408 "`{}` is a reference that is only valid in the {} body",
409 fr_name
, escapes_from
413 diag
.span_label(span
, format
!("`{}` escapes the {} body here", fr_name
, escapes_from
));
416 diag
.buffer(errors_buffer
);
419 /// Reports a region inference error for the general case with named/synthesized lifetimes to
420 /// explain what is happening.
423 /// error: unsatisfied lifetime constraints
424 /// --> $DIR/regions-creating-enums3.rs:17:5
426 /// LL | fn mk_add_bad1<'a,'b>(x: &'a ast<'a>, y: &'b ast<'b>) -> ast<'a> {
427 /// | -- -- lifetime `'b` defined here
429 /// | lifetime `'a` defined here
430 /// LL | ast::add(x, y)
431 /// | ^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'a` but it
432 /// | is returning data with lifetime `'b`
434 fn report_general_error(
437 infcx
: &InferCtxt
<'_
, '_
, 'tcx
>,
441 outlived_fr
: RegionVid
,
442 outlived_fr_is_local
: bool
,
443 category
: ConstraintCategory
,
445 errors_buffer
: &mut Vec
<Diagnostic
>,
447 let mut diag
= infcx
.tcx
.sess
.struct_span_err(
449 "unsatisfied lifetime constraints", // FIXME
452 let counter
= &mut 1;
453 let fr_name
= self.give_region_a_name(infcx
, mir
, mir_def_id
, fr
, counter
);
454 fr_name
.highlight_region_name(&mut diag
);
455 let outlived_fr_name
= self.give_region_a_name(
456 infcx
, mir
, mir_def_id
, outlived_fr
, counter
);
457 outlived_fr_name
.highlight_region_name(&mut diag
);
459 let mir_def_name
= if infcx
.tcx
.is_closure(mir_def_id
) { "closure" }
else { "function" }
;
461 match (category
, outlived_fr_is_local
, fr_is_local
) {
462 (ConstraintCategory
::Return
, true, _
) => {
463 diag
.span_label(span
, format
!(
464 "{} was supposed to return data with lifetime `{}` but it is returning \
465 data with lifetime `{}`",
466 mir_def_name
, outlived_fr_name
, fr_name
470 diag
.span_label(span
, format
!(
471 "{}requires that `{}` must outlive `{}`",
472 category
.description(), fr_name
, outlived_fr_name
,
477 self.add_static_impl_trait_suggestion(
478 infcx
, &mut diag
, fr
, fr_name
, outlived_fr
,
481 diag
.buffer(errors_buffer
);
484 /// Adds a suggestion to errors where a `impl Trait` is returned.
487 /// help: to allow this impl Trait to capture borrowed data with lifetime `'1`, add `'_` as
490 /// LL | fn iter_values_anon(&self) -> impl Iterator<Item=u32> + 'a {
491 /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
493 fn add_static_impl_trait_suggestion(
495 infcx
: &InferCtxt
<'_
, '_
, 'tcx
>,
496 diag
: &mut DiagnosticBuilder
<'_
>,
498 // We need to pass `fr_name` - computing it again will label it twice.
500 outlived_fr
: RegionVid
,
504 Some(ty
::RegionKind
::ReStatic
)
505 ) = (self.to_error_region(fr
), self.to_error_region(outlived_fr
)) {
506 if let Some(ty
::TyS
{
507 sty
: ty
::TyKind
::Opaque(did
, substs
),
509 }) = infcx
.tcx
.is_suitable_region(f
)
511 .map(|id
| infcx
.tcx
.return_type_impl_trait(id
))
514 // Check whether or not the impl trait return type is intended to capture
515 // data with the static lifetime.
517 // eg. check for `impl Trait + 'static` instead of `impl Trait`.
518 let has_static_predicate
= {
519 let predicates_of
= infcx
.tcx
.predicates_of(*did
);
520 let bounds
= predicates_of
.instantiate(infcx
.tcx
, substs
);
522 let mut found
= false;
523 for predicate
in bounds
.predicates
{
524 if let ty
::Predicate
::TypeOutlives(binder
) = predicate
{
525 if let ty
::OutlivesPredicate(
527 ty
::RegionKind
::ReStatic
528 ) = binder
.skip_binder() {
538 debug
!("add_static_impl_trait_suggestion: has_static_predicate={:?}",
539 has_static_predicate
);
540 let static_str
= keywords
::StaticLifetime
.name();
541 // If there is a static predicate, then the only sensible suggestion is to replace
542 // fr with `'static`.
543 if has_static_predicate
{
546 "consider replacing `{}` with `{}`",
551 // Otherwise, we should suggest adding a constraint on the return type.
552 let span
= infcx
.tcx
.def_span(*did
);
553 if let Ok(snippet
) = infcx
.tcx
.sess
.source_map().span_to_snippet(span
) {
554 let suggestable_fr_name
= if fr_name
.was_named() {
560 diag
.span_suggestion_with_applicability(
563 "to allow this impl Trait to capture borrowed data with lifetime \
564 `{}`, add `{}` as a constraint",
565 fr_name
, suggestable_fr_name
,
567 format
!("{} + {}", snippet
, suggestable_fr_name
),
568 Applicability
::MachineApplicable
,
576 crate fn free_region_constraint_info(
580 infcx
: &InferCtxt
<'_
, '_
, 'tcx
>,
581 borrow_region
: RegionVid
,
582 outlived_region
: RegionVid
,
583 ) -> (ConstraintCategory
, bool
, Span
, RegionName
) {
584 let (category
, from_closure
, span
) = self.best_blame_constraint(
587 |r
| r
== outlived_region
589 let outlived_fr_name
= self.give_region_a_name(
590 infcx
, mir
, mir_def_id
, outlived_region
, &mut 1);
591 (category
, from_closure
, span
, outlived_fr_name
)
594 // Finds some region R such that `fr1: R` and `R` is live at
596 crate fn find_sub_region_live_at(&self, fr1
: RegionVid
, elem
: Location
) -> RegionVid
{
599 self.find_constraint_paths_between_regions(fr1
, |r
| {
600 self.liveness_constraints
.contains(r
, elem
)
605 // Finds a good span to blame for the fact that `fr1` outlives `fr2`.
606 crate fn find_outlives_blame_span(
611 ) -> (ConstraintCategory
, Span
) {
612 let (category
, _
, span
) = self.best_blame_constraint(mir
, fr1
, |r
| r
== fr2
);
616 fn retrieve_closure_constraint_info(
619 constraint
: &OutlivesConstraint
620 ) -> (ConstraintCategory
, bool
, Span
) {
621 let loc
= match constraint
.locations
{
622 Locations
::All(span
) => return (constraint
.category
, false, span
),
623 Locations
::Single(loc
) => loc
,
626 let opt_span_category
= self
627 .closure_bounds_mapping
[&loc
]
628 .get(&(constraint
.sup
, constraint
.sub
));
630 .map(|&(category
, span
)| (category
, true, span
))
631 .unwrap_or((constraint
.category
, false, mir
.source_info(loc
).span
))
634 /// Returns `true` if a closure is inferred to be an `FnMut` closure.
635 crate fn is_closure_fn_mut(
637 infcx
: &InferCtxt
<'_
, '_
, 'tcx
>,
640 if let Some(ty
::ReFree(free_region
)) = self.to_error_region(fr
) {
641 if let ty
::BoundRegion
::BrEnv
= free_region
.bound_region
{
642 if let DefiningTy
::Closure(def_id
, substs
) = self.universal_regions
.defining_ty
{
643 let closure_kind_ty
= substs
.closure_kind_ty(def_id
, infcx
.tcx
);
644 return Some(ty
::ClosureKind
::FnMut
) == closure_kind_ty
.to_opt_closure_kind();