1 //! Error reporting machinery for lifetime errors.
3 use rustc_errors
::{Diagnostic, DiagnosticBuilder, ErrorGuaranteed}
;
4 use rustc_infer
::infer
::{
5 error_reporting
::nice_region_error
::{
6 self, find_anon_type
, find_param_with_region
, suggest_adding_lifetime_params
,
9 error_reporting
::unexpected_hidden_region_diagnostic
,
10 NllRegionVariableOrigin
, RelateParamBound
,
12 use rustc_middle
::hir
::place
::PlaceBase
;
13 use rustc_middle
::mir
::{ConstraintCategory, ReturnConstraint}
;
14 use rustc_middle
::ty
::subst
::InternalSubsts
;
15 use rustc_middle
::ty
::{self, RegionVid, Ty}
;
16 use rustc_span
::symbol
::sym
;
19 use crate::borrowck_errors
;
21 use super::{OutlivesSuggestionBuilder, RegionName}
;
22 use crate::region_infer
::BlameConstraint
;
24 nll
::ConstraintDescription
,
25 region_infer
::{values::RegionElement, TypeTest}
,
26 universal_regions
::DefiningTy
,
30 impl ConstraintDescription
for ConstraintCategory
{
31 fn description(&self) -> &'
static str {
32 // Must end with a space. Allows for empty names to be provided.
34 ConstraintCategory
::Assignment
=> "assignment ",
35 ConstraintCategory
::Return(_
) => "returning this value ",
36 ConstraintCategory
::Yield
=> "yielding this value ",
37 ConstraintCategory
::UseAsConst
=> "using this value as a constant ",
38 ConstraintCategory
::UseAsStatic
=> "using this value as a static ",
39 ConstraintCategory
::Cast
=> "cast ",
40 ConstraintCategory
::CallArgument
=> "argument ",
41 ConstraintCategory
::TypeAnnotation
=> "type annotation ",
42 ConstraintCategory
::ClosureBounds
=> "closure body ",
43 ConstraintCategory
::SizedBound
=> "proving this value is `Sized` ",
44 ConstraintCategory
::CopyBound
=> "copying this value ",
45 ConstraintCategory
::OpaqueType
=> "opaque type ",
46 ConstraintCategory
::ClosureUpvar(_
) => "closure capture ",
47 ConstraintCategory
::Usage
=> "this usage ",
48 ConstraintCategory
::Predicate(_
)
49 | ConstraintCategory
::Boring
50 | ConstraintCategory
::BoringNoLocation
51 | ConstraintCategory
::Internal
=> "",
56 /// A collection of errors encountered during region inference. This is needed to efficiently
57 /// report errors after borrow checking.
59 /// Usually we expect this to either be empty or contain a small number of items, so we can avoid
60 /// allocation most of the time.
61 crate type RegionErrors
<'tcx
> = Vec
<RegionErrorKind
<'tcx
>>;
63 #[derive(Clone, Debug)]
64 crate enum RegionErrorKind
<'tcx
> {
65 /// A generic bound failure for a type test (`T: 'a`).
66 TypeTestError { type_test: TypeTest<'tcx> }
,
68 /// An unexpected hidden region for an opaque type.
69 UnexpectedHiddenRegion
{
70 /// The span for the member constraint.
74 /// The unexpected region.
75 member_region
: ty
::Region
<'tcx
>,
78 /// Higher-ranked subtyping error.
79 BoundUniversalRegionError
{
80 /// The placeholder free region.
82 /// The region element that erroneously must be outlived by `longer_fr`.
83 error_element
: RegionElement
,
84 /// The placeholder region.
85 placeholder
: ty
::PlaceholderRegion
,
88 /// Any other lifetime error.
90 /// The origin of the region.
91 fr_origin
: NllRegionVariableOrigin
,
92 /// The region that should outlive `shorter_fr`.
94 /// The region that should be shorter, but we can't prove it.
95 shorter_fr
: RegionVid
,
96 /// Indicates whether this is a reported error. We currently only report the first error
97 /// encountered and leave the rest unreported so as not to overwhelm the user.
102 /// Information about the various region constraints involved in a borrow checker error.
103 #[derive(Clone, Debug)]
104 pub struct ErrorConstraintInfo
{
106 pub(super) fr
: RegionVid
,
107 pub(super) fr_is_local
: bool
,
108 pub(super) outlived_fr
: RegionVid
,
109 pub(super) outlived_fr_is_local
: bool
,
111 // Category and span for best blame constraint
112 pub(super) category
: ConstraintCategory
,
113 pub(super) span
: Span
,
116 impl<'a
, 'tcx
> MirBorrowckCtxt
<'a
, 'tcx
> {
117 /// Converts a region inference variable into a `ty::Region` that
118 /// we can use for error reporting. If `r` is universally bound,
119 /// then we use the name that we have on record for it. If `r` is
120 /// existentially bound, then we check its inferred value and try
121 /// to find a good name from that. Returns `None` if we can't find
122 /// one (e.g., this is just some random part of the CFG).
123 pub(super) fn to_error_region(&self, r
: RegionVid
) -> Option
<ty
::Region
<'tcx
>> {
124 self.to_error_region_vid(r
).and_then(|r
| self.regioncx
.region_definition(r
).external_name
)
127 /// Returns the `RegionVid` corresponding to the region returned by
128 /// `to_error_region`.
129 pub(super) fn to_error_region_vid(&self, r
: RegionVid
) -> Option
<RegionVid
> {
130 if self.regioncx
.universal_regions().is_universal_region(r
) {
133 // We just want something nameable, even if it's not
134 // actually an upper bound.
135 let upper_bound
= self.regioncx
.approx_universal_upper_bound(r
);
137 if self.regioncx
.upper_bound_in_region_scc(r
, upper_bound
) {
138 self.to_error_region_vid(upper_bound
)
145 /// Returns `true` if a closure is inferred to be an `FnMut` closure.
146 fn is_closure_fn_mut(&self, fr
: RegionVid
) -> bool
{
147 if let Some(ty
::ReFree(free_region
)) = self.to_error_region(fr
).as_deref()
148 && let ty
::BoundRegionKind
::BrEnv
= free_region
.bound_region
149 && let DefiningTy
::Closure(_
, substs
) = self.regioncx
.universal_regions().defining_ty
151 return substs
.as_closure().kind() == ty
::ClosureKind
::FnMut
;
157 /// Produces nice borrowck error diagnostics for all the errors collected in `nll_errors`.
158 pub(crate) fn report_region_errors(&mut self, nll_errors
: RegionErrors
<'tcx
>) {
159 // Iterate through all the errors, producing a diagnostic for each one. The diagnostics are
160 // buffered in the `MirBorrowckCtxt`.
162 let mut outlives_suggestion
= OutlivesSuggestionBuilder
::default();
164 for nll_error
in nll_errors
.into_iter() {
166 RegionErrorKind
::TypeTestError { type_test }
=> {
167 // Try to convert the lower-bound region into something named we can print for the user.
168 let lower_bound_region
= self.to_error_region(type_test
.lower_bound
);
170 let type_test_span
= type_test
.locations
.span(&self.body
);
172 if let Some(lower_bound_region
) = lower_bound_region
{
173 let generic_ty
= type_test
.generic_kind
.to_ty(self.infcx
.tcx
);
174 let origin
= RelateParamBound(type_test_span
, generic_ty
, None
);
175 self.buffer_error(self.infcx
.construct_generic_bound_failure(
178 type_test
.generic_kind
,
180 self.body
.source
.def_id().as_local(),
183 // FIXME. We should handle this case better. It
184 // indicates that we have e.g., some region variable
185 // whose value is like `'a+'b` where `'a` and `'b` are
186 // distinct unrelated universal regions that are not
187 // known to outlive one another. It'd be nice to have
188 // some examples where this arises to decide how best
189 // to report it; we could probably handle it by
190 // iterating over the universal regions and reporting
191 // an error that multiple bounds are required.
192 self.buffer_error(self.infcx
.tcx
.sess
.struct_span_err(
194 &format
!("`{}` does not live long enough", type_test
.generic_kind
),
199 RegionErrorKind
::UnexpectedHiddenRegion { span, hidden_ty, member_region }
=> {
200 let named_ty
= self.regioncx
.name_regions(self.infcx
.tcx
, hidden_ty
);
201 let named_region
= self.regioncx
.name_regions(self.infcx
.tcx
, member_region
);
202 self.buffer_error(unexpected_hidden_region_diagnostic(
210 RegionErrorKind
::BoundUniversalRegionError
{
215 let error_vid
= self.regioncx
.region_from_element(longer_fr
, &error_element
);
217 // Find the code to blame for the fact that `longer_fr` outlives `error_fr`.
218 let (_
, cause
) = self.regioncx
.find_outlives_blame_span(
221 NllRegionVariableOrigin
::Placeholder(placeholder
),
225 let universe
= placeholder
.universe
;
226 let universe_info
= self.regioncx
.universe_info(universe
);
228 universe_info
.report_error(self, placeholder
, error_element
, cause
);
231 RegionErrorKind
::RegionError { fr_origin, longer_fr, shorter_fr, is_reported }
=> {
233 self.report_region_error(
237 &mut outlives_suggestion
,
240 // We only report the first error, so as not to overwhelm the user. See
241 // `RegRegionErrorKind` docs.
243 // FIXME: currently we do nothing with these, but perhaps we can do better?
244 // FIXME: try collecting these constraints on the outlives suggestion
245 // builder. Does it make the suggestions any better?
247 "Unreported region error: can't prove that {:?}: {:?}",
248 longer_fr
, shorter_fr
255 // Emit one outlives suggestions for each MIR def we borrowck
256 outlives_suggestion
.add_suggestion(self);
259 /// Report an error because the universal region `fr` was required to outlive
260 /// `outlived_fr` but it is not known to do so. For example:
262 /// ```compile_fail,E0312
263 /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
266 /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`.
267 pub(crate) fn report_region_error(
270 fr_origin
: NllRegionVariableOrigin
,
271 outlived_fr
: RegionVid
,
272 outlives_suggestion
: &mut OutlivesSuggestionBuilder
,
274 debug
!("report_region_error(fr={:?}, outlived_fr={:?})", fr
, outlived_fr
);
276 let BlameConstraint { category, cause, variance_info, from_closure: _ }
=
277 self.regioncx
.best_blame_constraint(&self.body
, fr
, fr_origin
, |r
| {
278 self.regioncx
.provides_universal_region(r
, fr
, outlived_fr
)
281 debug
!("report_region_error: category={:?} {:?} {:?}", category
, cause
, variance_info
);
282 // Check if we can use one of the "nice region errors".
283 if let (Some(f
), Some(o
)) = (self.to_error_region(fr
), self.to_error_region(outlived_fr
)) {
284 let nice
= NiceRegionError
::new_from_span(self.infcx
, cause
.span
, o
, f
);
285 if let Some(diag
) = nice
.try_report_from_nll() {
286 self.buffer_error(diag
);
291 let (fr_is_local
, outlived_fr_is_local
): (bool
, bool
) = (
292 self.regioncx
.universal_regions().is_local_free_region(fr
),
293 self.regioncx
.universal_regions().is_local_free_region(outlived_fr
),
297 "report_region_error: fr_is_local={:?} outlived_fr_is_local={:?} category={:?}",
298 fr_is_local
, outlived_fr_is_local
, category
301 let errci
= ErrorConstraintInfo
{
305 outlived_fr_is_local
,
310 let mut diag
= match (category
, fr_is_local
, outlived_fr_is_local
) {
311 (ConstraintCategory
::Return(kind
), true, false) if self.is_closure_fn_mut(fr
) => {
312 self.report_fnmut_error(&errci
, kind
)
314 (ConstraintCategory
::Assignment
, true, false)
315 | (ConstraintCategory
::CallArgument
, true, false) => {
316 let mut db
= self.report_escaping_data_error(&errci
);
318 outlives_suggestion
.intermediate_suggestion(self, &errci
, &mut db
);
319 outlives_suggestion
.collect_constraint(fr
, outlived_fr
);
324 let mut db
= self.report_general_error(&errci
);
326 outlives_suggestion
.intermediate_suggestion(self, &errci
, &mut db
);
327 outlives_suggestion
.collect_constraint(fr
, outlived_fr
);
333 match variance_info
{
334 ty
::VarianceDiagInfo
::None
=> {}
335 ty
::VarianceDiagInfo
::Invariant { ty, param_index }
=> {
336 let (desc
, note
) = match ty
.kind() {
337 ty
::RawPtr(ty_mut
) => {
338 assert_eq
!(ty_mut
.mutbl
, rustc_hir
::Mutability
::Mut
);
340 format
!("a mutable pointer to `{}`", ty_mut
.ty
),
341 "mutable pointers are invariant over their type parameter".to_string(),
344 ty
::Ref(_
, inner_ty
, mutbl
) => {
345 assert_eq
!(*mutbl
, rustc_hir
::Mutability
::Mut
);
347 format
!("a mutable reference to `{inner_ty}`"),
348 "mutable references are invariant over their type parameter"
352 ty
::Adt(adt
, substs
) => {
353 let generic_arg
= substs
[param_index
as usize];
354 let identity_substs
=
355 InternalSubsts
::identity_for_item(self.infcx
.tcx
, adt
.did());
356 let base_ty
= self.infcx
.tcx
.mk_adt(*adt
, identity_substs
);
357 let base_generic_arg
= identity_substs
[param_index
as usize];
358 let adt_desc
= adt
.descr();
361 "the type `{ty}`, which makes the generic argument `{generic_arg}` invariant"
364 "the {adt_desc} `{base_ty}` is invariant over the parameter `{base_generic_arg}`"
368 ty
::FnDef(def_id
, _
) => {
369 let name
= self.infcx
.tcx
.item_name(*def_id
);
370 let identity_substs
=
371 InternalSubsts
::identity_for_item(self.infcx
.tcx
, *def_id
);
372 let desc
= format
!("a function pointer to `{name}`");
374 "the function `{name}` is invariant over the parameter `{}`",
375 identity_substs
[param_index
as usize]
379 _
=> panic
!("Unexpected type {:?}", ty
),
381 diag
.note(&format
!("requirement occurs because of {desc}",));
383 diag
.help("see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance");
387 self.buffer_error(diag
);
390 /// Report a specialized error when `FnMut` closures return a reference to a captured variable.
391 /// This function expects `fr` to be local and `outlived_fr` to not be local.
394 /// error: captured variable cannot escape `FnMut` closure body
395 /// --> $DIR/issue-53040.rs:15:8
398 /// | -- ^^^^^^ creates a reference to a captured variable which escapes the closure body
400 /// | inferred to be a `FnMut` closure
402 /// = note: `FnMut` closures only have access to their captured variables while they are
404 /// = note: ...therefore, returned references to captured variables will escape the closure
406 fn report_fnmut_error(
408 errci
: &ErrorConstraintInfo
,
409 kind
: ReturnConstraint
,
410 ) -> DiagnosticBuilder
<'tcx
, ErrorGuaranteed
> {
411 let ErrorConstraintInfo { outlived_fr, span, .. }
= errci
;
417 .struct_span_err(*span
, "captured variable cannot escape `FnMut` closure body");
419 let mut output_ty
= self.regioncx
.universal_regions().unnormalized_output_ty
;
420 if let ty
::Opaque(def_id
, _
) = *output_ty
.kind() {
421 output_ty
= self.infcx
.tcx
.type_of(def_id
)
424 debug
!("report_fnmut_error: output_ty={:?}", output_ty
);
426 let message
= match output_ty
.kind() {
427 ty
::Closure(_
, _
) => {
428 "returns a closure that contains a reference to a captured variable, which then \
429 escapes the closure body"
431 ty
::Adt(def
, _
) if self.infcx
.tcx
.is_diagnostic_item(sym
::gen_future
, def
.did()) => {
432 "returns an `async` block that contains a reference to a captured variable, which then \
433 escapes the closure body"
435 _
=> "returns a reference to a captured variable which escapes the closure body",
438 diag
.span_label(*span
, message
);
440 if let ReturnConstraint
::ClosureUpvar(upvar_field
) = kind
{
441 let def_id
= match self.regioncx
.universal_regions().defining_ty
{
442 DefiningTy
::Closure(def_id
, _
) => def_id
,
443 ty
=> bug
!("unexpected DefiningTy {:?}", ty
),
446 let captured_place
= &self.upvars
[upvar_field
.index()].place
;
447 let defined_hir
= match captured_place
.place
.base
{
448 PlaceBase
::Local(hirid
) => Some(hirid
),
449 PlaceBase
::Upvar(upvar
) => Some(upvar
.var_path
.hir_id
),
453 if defined_hir
.is_some() {
454 let upvars_map
= self.infcx
.tcx
.upvars_mentioned(def_id
).unwrap();
455 let upvar_def_span
= self.infcx
.tcx
.hir().span(defined_hir
.unwrap());
456 let upvar_span
= upvars_map
.get(&defined_hir
.unwrap()).unwrap().span
;
457 diag
.span_label(upvar_def_span
, "variable defined here");
458 diag
.span_label(upvar_span
, "variable captured here");
462 if let Some(fr_span
) = self.give_region_a_name(*outlived_fr
).unwrap().span() {
463 diag
.span_label(fr_span
, "inferred to be a `FnMut` closure");
467 "`FnMut` closures only have access to their captured variables while they are \
470 diag
.note("...therefore, they cannot allow references to captured variables to escape");
475 /// Reports an error specifically for when data is escaping a closure.
478 /// error: borrowed data escapes outside of function
479 /// --> $DIR/lifetime-bound-will-change-warning.rs:44:5
481 /// LL | fn test2<'a>(x: &'a Box<Fn()+'a>) {
482 /// | - `x` is a reference that is only valid in the function body
483 /// LL | // but ref_obj will not, so warn.
485 /// | ^^^^^^^^^^ `x` escapes the function body here
487 fn report_escaping_data_error(
489 errci
: &ErrorConstraintInfo
,
490 ) -> DiagnosticBuilder
<'tcx
, ErrorGuaranteed
> {
491 let ErrorConstraintInfo { span, category, .. }
= errci
;
493 let fr_name_and_span
= self.regioncx
.get_var_name_and_span_for_region(
500 let outlived_fr_name_and_span
= self.regioncx
.get_var_name_and_span_for_region(
508 let (_
, escapes_from
) = self
511 .article_and_description(self.regioncx
.universal_regions().defining_ty
.def_id());
513 // Revert to the normal error in these cases.
514 // Assignments aren't "escapes" in function items.
515 if (fr_name_and_span
.is_none() && outlived_fr_name_and_span
.is_none())
516 || (*category
== ConstraintCategory
::Assignment
517 && self.regioncx
.universal_regions().defining_ty
.is_fn_def())
518 || self.regioncx
.universal_regions().defining_ty
.is_const()
520 return self.report_general_error(&ErrorConstraintInfo
{
522 outlived_fr_is_local
: false,
528 borrowck_errors
::borrowed_data_escapes_closure(self.infcx
.tcx
, *span
, escapes_from
);
530 if let Some((Some(outlived_fr_name
), outlived_fr_span
)) = outlived_fr_name_and_span
{
533 format
!("`{outlived_fr_name}` declared here, outside of the {escapes_from} body",),
537 if let Some((Some(fr_name
), fr_span
)) = fr_name_and_span
{
541 "`{fr_name}` is a reference that is only valid in the {escapes_from} body",
545 diag
.span_label(*span
, format
!("`{fr_name}` escapes the {escapes_from} body here"));
548 // Only show an extra note if we can find an 'error region' for both of the region
549 // variables. This avoids showing a noisy note that just mentions 'synthetic' regions
550 // that don't help the user understand the error.
551 if self.to_error_region(errci
.fr
).is_some()
552 && self.to_error_region(errci
.outlived_fr
).is_some()
554 let fr_region_name
= self.give_region_a_name(errci
.fr
).unwrap();
555 fr_region_name
.highlight_region_name(&mut diag
);
556 let outlived_fr_region_name
= self.give_region_a_name(errci
.outlived_fr
).unwrap();
557 outlived_fr_region_name
.highlight_region_name(&mut diag
);
562 "{}requires that `{}` must outlive `{}`",
563 category
.description(),
565 outlived_fr_region_name
,
572 /// Reports a region inference error for the general case with named/synthesized lifetimes to
573 /// explain what is happening.
576 /// error: unsatisfied lifetime constraints
577 /// --> $DIR/regions-creating-enums3.rs:17:5
579 /// LL | fn mk_add_bad1<'a,'b>(x: &'a ast<'a>, y: &'b ast<'b>) -> ast<'a> {
580 /// | -- -- lifetime `'b` defined here
582 /// | lifetime `'a` defined here
583 /// LL | ast::add(x, y)
584 /// | ^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'a` but it
585 /// | is returning data with lifetime `'b`
587 fn report_general_error(
589 errci
: &ErrorConstraintInfo
,
590 ) -> DiagnosticBuilder
<'tcx
, ErrorGuaranteed
> {
591 let ErrorConstraintInfo
{
595 outlived_fr_is_local
,
602 self.infcx
.tcx
.sess
.struct_span_err(*span
, "lifetime may not live long enough");
604 let (_
, mir_def_name
) =
605 self.infcx
.tcx
.article_and_description(self.mir_def_id().to_def_id());
607 let fr_name
= self.give_region_a_name(*fr
).unwrap();
608 fr_name
.highlight_region_name(&mut diag
);
609 let outlived_fr_name
= self.give_region_a_name(*outlived_fr
).unwrap();
610 outlived_fr_name
.highlight_region_name(&mut diag
);
612 match (category
, outlived_fr_is_local
, fr_is_local
) {
613 (ConstraintCategory
::Return(_
), true, _
) => {
617 "{mir_def_name} was supposed to return data with lifetime `{outlived_fr_name}` but it is returning \
618 data with lifetime `{fr_name}`",
626 "{}requires that `{}` must outlive `{}`",
627 category
.description(),
635 self.add_static_impl_trait_suggestion(&mut diag
, *fr
, fr_name
, *outlived_fr
);
636 self.suggest_adding_lifetime_params(&mut diag
, *fr
, *outlived_fr
);
641 /// Adds a suggestion to errors where an `impl Trait` is returned.
644 /// help: to allow this `impl Trait` to capture borrowed data with lifetime `'1`, add `'_` as
647 /// LL | fn iter_values_anon(&self) -> impl Iterator<Item=u32> + 'a {
648 /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
650 fn add_static_impl_trait_suggestion(
652 diag
: &mut Diagnostic
,
654 // We need to pass `fr_name` - computing it again will label it twice.
656 outlived_fr
: RegionVid
,
658 if let (Some(f
), Some(outlived_f
)) =
659 (self.to_error_region(fr
), self.to_error_region(outlived_fr
))
661 if *outlived_f
!= ty
::ReStatic
{
665 let fn_returns
= self
668 .is_suitable_region(f
)
669 .map(|r
| self.infcx
.tcx
.return_type_impl_or_dyn_traits(r
.def_id
))
670 .unwrap_or_default();
672 if fn_returns
.is_empty() {
676 let param
= if let Some(param
) = find_param_with_region(self.infcx
.tcx
, f
, outlived_f
) {
682 let lifetime
= if f
.has_name() { fr_name.to_string() }
else { "'_".to_string() }
;
684 let arg
= match param
.param
.pat
.simple_ident() {
685 Some(simple_ident
) => format
!("argument `{}`", simple_ident
),
686 None
=> "the argument".to_string(),
688 let captures
= format
!("captures data from {}", arg
);
690 return nice_region_error
::suggest_new_region_bound(
697 Some((param
.param_ty_span
, param
.param_ty
.to_string())),
702 fn suggest_adding_lifetime_params(
704 diag
: &mut Diagnostic
,
708 let (Some(sub
), Some(sup
)) = (self.to_error_region(sub
), self.to_error_region(sup
)) else {
712 let Some((ty_sub
, _
)) = self
715 .is_suitable_region(sub
)
716 .and_then(|anon_reg
| find_anon_type(self.infcx
.tcx
, sub
, &anon_reg
.boundregion
)) else {
720 let Some((ty_sup
, _
)) = self
723 .is_suitable_region(sup
)
724 .and_then(|anon_reg
| find_anon_type(self.infcx
.tcx
, sup
, &anon_reg
.boundregion
)) else {
728 suggest_adding_lifetime_params(self.infcx
.tcx
, sub
, ty_sup
, ty_sub
, diag
);