1//! Contains utilities for generating suggestions for borrowck errors related to unsatisfied
2//! outlives constraints.
34use std::collections::BTreeMap;
56use rustc_data_structures::fx::FxIndexSet;
7use rustc_errors::Diag;
8use rustc_middle::ty::RegionVid;
9use smallvec::SmallVec;
10use tracing::debug;
1112use super::{ErrorConstraintInfo, RegionName, RegionNameSource};
13use crate::MirBorrowckCtxt;
1415/// The different things we could suggest.
16enum SuggestedConstraint {
17/// 'a = 'b
18Equal(RegionName, RegionName),
1920/// 'a: 'static i.e. 'a = 'static and the user should just use 'static
21Static(RegionName),
22}
2324/// Collects information about outlives constraints that needed to be added for a given MIR node
25/// corresponding to a function definition.
26///
27/// Adds a help note suggesting adding a where clause with the needed constraints.
28#[derive(#[automatically_derived]
impl ::core::default::Default for OutlivesSuggestionBuilder {
#[inline]
fn default() -> Self {
Self { constraints_to_add: ::core::default::Default::default() }
}
}Default)]
29pub(crate) struct OutlivesSuggestionBuilder {
30/// The list of outlives constraints that need to be added. Specifically, we map each free
31 /// region to all other regions that it must outlive. I will use the shorthand `fr:
32 /// outlived_frs`. Not all of these regions will already have names necessarily. Some could be
33 /// implicit free regions that we inferred. These will need to be given names in the final
34 /// suggestion message.
35constraints_to_add: BTreeMap<RegionVid, Vec<RegionVid>>,
36}
3738impl OutlivesSuggestionBuilder {
39/// Returns `true` iff the `RegionNameSource` is a valid source for an outlives
40 /// suggestion.
41//
42 // FIXME: Currently, we only report suggestions if the `RegionNameSource` is an early-bound
43 // region or a named region, avoiding using regions with synthetic names altogether. This
44 // allows us to avoid giving impossible suggestions (e.g. adding bounds to closure args).
45 // We can probably be less conservative, since some inferred free regions are namable (e.g.
46 // the user can explicitly name them. To do this, we would allow some regions whose names
47 // come from `MatchedAdtAndSegment`, being careful to filter out bad suggestions, such as
48 // naming the `'self` lifetime in methods, etc.
49fn region_name_is_suggestable(name: &RegionName) -> bool {
50match name.source {
51 RegionNameSource::NamedEarlyParamRegion(..)
52 | RegionNameSource::NamedLateParamRegion(..)
53 | RegionNameSource::Static => true,
5455// Don't give suggestions for upvars, closure return types, or other unnameable
56 // regions.
57RegionNameSource::SynthesizedFreeEnvRegion(..)
58 | RegionNameSource::AnonRegionFromArgument(..)
59 | RegionNameSource::AnonRegionFromUpvar(..)
60 | RegionNameSource::AnonRegionFromOutput(..)
61 | RegionNameSource::AnonRegionFromYieldTy(..)
62 | RegionNameSource::AnonRegionFromAsyncFn(..)
63 | RegionNameSource::AnonRegionFromImplSignature(..) => {
64{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:64",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(64u32),
::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Region {0:?} is NOT suggestable",
name) as &dyn ::tracing::field::Value))])
});
} else { ; }
};debug!("Region {:?} is NOT suggestable", name);
65false
66}
67 }
68 }
6970/// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`.
71fn region_vid_to_name(
72&self,
73 mbcx: &MirBorrowckCtxt<'_, '_, '_>,
74 region: RegionVid,
75 ) -> Option<RegionName> {
76mbcx.give_region_a_name(region).filter(Self::region_name_is_suggestable)
77 }
7879/// Compiles a list of all suggestions to be printed in the final big suggestion.
80fn compile_all_suggestions(
81&self,
82 mbcx: &MirBorrowckCtxt<'_, '_, '_>,
83 ) -> SmallVec<[SuggestedConstraint; 2]> {
84let mut suggested = SmallVec::new();
8586// Keep track of variables that we have already suggested unifying so that we don't print
87 // out silly duplicate messages.
88let mut unified_already = FxIndexSet::default();
8990for (fr, outlived) in &self.constraints_to_add {
91let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) else {
92continue;
93 };
9495let outlived = outlived
96 .iter()
97// if there is a `None`, we will just omit that constraint
98.filter_map(|fr| self.region_vid_to_name(mbcx, *fr).map(|rname| (fr, rname)))
99 .collect::<Vec<_>>();
100101// No suggestable outlived lifetimes.
102if outlived.is_empty() {
103continue;
104 }
105106// There are two types of suggestions we can make:
107 // 1) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we
108 // should just replace 'a with 'static.
109 // 2) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a
110111if outlived
112 .iter()
113 .any(|(_, outlived_name)| #[allow(non_exhaustive_omitted_patterns)] match outlived_name.source {
RegionNameSource::Static => true,
_ => false,
}matches!(outlived_name.source, RegionNameSource::Static))
114 {
115 suggested.push(SuggestedConstraint::Static(fr_name));
116 } else {
117// We want to isolate out all lifetimes that should be unified and print out
118 // separate messages for them.
119120let unified = outlived.into_iter().filter(
121// Do we have both 'fr: 'r and 'r: 'fr?
122|(r, _)| {
123self.constraints_to_add
124 .get(r)
125 .is_some_and(|r_outlived| r_outlived.as_slice().contains(fr))
126 },
127 );
128129for (r, bound) in unified {
130if !unified_already.contains(fr) {
131 suggested.push(SuggestedConstraint::Equal(fr_name, bound));
132 unified_already.insert(r);
133 }
134 }
135 }
136 }
137138suggested139 }
140141/// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest.
142pub(crate) fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) {
143{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:143",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(143u32),
::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Collected {0:?}: {1:?}",
fr, outlived_fr) as &dyn ::tracing::field::Value))])
});
} else { ; }
};debug!("Collected {:?}: {:?}", fr, outlived_fr);
144145// Add to set of constraints for final help note.
146self.constraints_to_add.entry(fr).or_default().push(outlived_fr);
147 }
148149/// Emit an intermediate note on the given `Diag` if the involved regions are suggestable.
150pub(crate) fn intermediate_suggestion(
151&mut self,
152 mbcx: &MirBorrowckCtxt<'_, '_, '_>,
153 errci: &ErrorConstraintInfo<'_>,
154 diag: &mut Diag<'_>,
155 ) {
156// Emit an intermediate note.
157let fr_name = self.region_vid_to_name(mbcx, errci.fr);
158let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr);
159160if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name)
161 && !#[allow(non_exhaustive_omitted_patterns)] match outlived_fr_name.source {
RegionNameSource::Static => true,
_ => false,
}matches!(outlived_fr_name.source, RegionNameSource::Static)162 {
163diag.help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("consider adding the following bound: `{0}: {1}`",
fr_name, outlived_fr_name))
})format!(
164"consider adding the following bound: `{fr_name}: {outlived_fr_name}`",
165 ));
166 }
167 }
168169/// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final
170 /// suggestion including all collected constraints.
171pub(crate) fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_, '_>) {
172// No constraints to add? Done.
173if self.constraints_to_add.is_empty() {
174{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:174",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(174u32),
::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("No constraints to suggest.")
as &dyn ::tracing::field::Value))])
});
} else { ; }
};debug!("No constraints to suggest.");
175return;
176 }
177178// If there is only one constraint to suggest, then we already suggested it in the
179 // intermediate suggestion above.
180if self.constraints_to_add.len() == 1
181&& self.constraints_to_add.values().next().unwrap().len() == 1
182{
183{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:183",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(183u32),
::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Only 1 suggestion. Skipping.")
as &dyn ::tracing::field::Value))])
});
} else { ; }
};debug!("Only 1 suggestion. Skipping.");
184return;
185 }
186187// Get all suggestable constraints.
188let suggested = self.compile_all_suggestions(mbcx);
189190// If there are no suggestable constraints...
191if suggested.is_empty() {
192{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:192",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(192u32),
::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Only 1 suggestable constraint. Skipping.")
as &dyn ::tracing::field::Value))])
});
} else { ; }
};debug!("Only 1 suggestable constraint. Skipping.");
193return;
194 }
195196// Emit an error with a list of one or more help suggestions. This is a weird error because
197 // it's just there to provide somewhere to put the help suggestions that describe how to
198 // fix the one or more borrow errors already reported within the item.
199let tcx = mbcx.infcx.tcx;
200let def_id = mbcx.mir_def_id();
201let span = tcx.def_ident_span(def_id).unwrap_or_else(|| tcx.def_span(def_id));
202let mut diag = tcx203 .dcx()
204 .struct_err("one or more lifetime errors were found in this item")
205 .with_span(span);
206207// Add suggestions.
208for constraint in suggested {
209match constraint {
210 SuggestedConstraint::Equal(a, b) => {
211 diag.help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`{0}` and `{1}` must be the same: replace one with the other",
a, b))
})format!(
212"`{a}` and `{b}` must be the same: replace one with the other",
213 ));
214 }
215 SuggestedConstraint::Static(a) => {
216 diag.help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("replace `{0}` with `\'static`", a))
})format!("replace `{a}` with `'static`"));
217 }
218 }
219 }
220221// We want this message to appear after other messages on the mir def.
222let mir_span = mbcx.body.span;
223224// Buffer the diagnostic
225mbcx.buffer_error_with_sort_span(diag, mir_span.shrink_to_hi());
226 }
227}