1//! Contains utilities for generating suggestions for borrowck errors related to unsatisfied
2//! outlives constraints.
34#![allow(rustc::diagnostic_outside_of_impl)]
5#![allow(rustc::untranslatable_diagnostic)]
67use std::collections::BTreeMap;
89use rustc_data_structures::fx::FxIndexSet;
10use rustc_errors::Diag;
11use rustc_middle::ty::RegionVid;
12use smallvec::SmallVec;
13use tracing::debug;
1415use super::{ErrorConstraintInfo, RegionName, RegionNameSource};
16use crate::MirBorrowckCtxt;
1718/// The different things we could suggest.
19enum SuggestedConstraint {
20/// Outlives(a, [b, c, d, ...]) => 'a: 'b + 'c + 'd + ...
21Outlives(RegionName, SmallVec<[RegionName; 2]>),
2223/// 'a = 'b
24Equal(RegionName, RegionName),
2526/// 'a: 'static i.e. 'a = 'static and the user should just use 'static
27Static(RegionName),
28}
2930/// Collects information about outlives constraints that needed to be added for a given MIR node
31/// corresponding to a function definition.
32///
33/// Adds a help note suggesting adding a where clause with the needed constraints.
34#[derive(#[automatically_derived]
impl ::core::default::Default for OutlivesSuggestionBuilder {
#[inline]
fn default() -> OutlivesSuggestionBuilder {
OutlivesSuggestionBuilder {
constraints_to_add: ::core::default::Default::default(),
}
}
}Default)]
35pub(crate) struct OutlivesSuggestionBuilder {
36/// The list of outlives constraints that need to be added. Specifically, we map each free
37 /// region to all other regions that it must outlive. I will use the shorthand `fr:
38 /// outlived_frs`. Not all of these regions will already have names necessarily. Some could be
39 /// implicit free regions that we inferred. These will need to be given names in the final
40 /// suggestion message.
41constraints_to_add: BTreeMap<RegionVid, Vec<RegionVid>>,
42}
4344impl OutlivesSuggestionBuilder {
45/// Returns `true` iff the `RegionNameSource` is a valid source for an outlives
46 /// suggestion.
47//
48 // FIXME: Currently, we only report suggestions if the `RegionNameSource` is an early-bound
49 // region or a named region, avoiding using regions with synthetic names altogether. This
50 // allows us to avoid giving impossible suggestions (e.g. adding bounds to closure args).
51 // We can probably be less conservative, since some inferred free regions are namable (e.g.
52 // the user can explicitly name them. To do this, we would allow some regions whose names
53 // come from `MatchedAdtAndSegment`, being careful to filter out bad suggestions, such as
54 // naming the `'self` lifetime in methods, etc.
55fn region_name_is_suggestable(name: &RegionName) -> bool {
56match name.source {
57 RegionNameSource::NamedEarlyParamRegion(..)
58 | RegionNameSource::NamedLateParamRegion(..)
59 | RegionNameSource::Static => true,
6061// Don't give suggestions for upvars, closure return types, or other unnameable
62 // regions.
63RegionNameSource::SynthesizedFreeEnvRegion(..)
64 | RegionNameSource::AnonRegionFromArgument(..)
65 | RegionNameSource::AnonRegionFromUpvar(..)
66 | RegionNameSource::AnonRegionFromOutput(..)
67 | RegionNameSource::AnonRegionFromYieldTy(..)
68 | RegionNameSource::AnonRegionFromAsyncFn(..)
69 | RegionNameSource::AnonRegionFromImplSignature(..) => {
70{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:70",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(70u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("Region {0:?} is NOT suggestable",
name) as &dyn Value))])
});
} else { ; }
};debug!("Region {:?} is NOT suggestable", name);
71false
72}
73 }
74 }
7576/// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`.
77fn region_vid_to_name(
78&self,
79 mbcx: &MirBorrowckCtxt<'_, '_, '_>,
80 region: RegionVid,
81 ) -> Option<RegionName> {
82mbcx.give_region_a_name(region).filter(Self::region_name_is_suggestable)
83 }
8485/// Compiles a list of all suggestions to be printed in the final big suggestion.
86fn compile_all_suggestions(
87&self,
88 mbcx: &MirBorrowckCtxt<'_, '_, '_>,
89 ) -> SmallVec<[SuggestedConstraint; 2]> {
90let mut suggested = SmallVec::new();
9192// Keep track of variables that we have already suggested unifying so that we don't print
93 // out silly duplicate messages.
94let mut unified_already = FxIndexSet::default();
9596for (fr, outlived) in &self.constraints_to_add {
97let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) else {
98continue;
99 };
100101let outlived = outlived
102 .iter()
103// if there is a `None`, we will just omit that constraint
104.filter_map(|fr| self.region_vid_to_name(mbcx, *fr).map(|rname| (fr, rname)))
105 .collect::<Vec<_>>();
106107// No suggestable outlived lifetimes.
108if outlived.is_empty() {
109continue;
110 }
111112// There are three types of suggestions we can make:
113 // 1) Suggest a bound: 'a: 'b
114 // 2) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we
115 // should just replace 'a with 'static.
116 // 3) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a
117118if outlived
119 .iter()
120 .any(|(_, outlived_name)| #[allow(non_exhaustive_omitted_patterns)] match outlived_name.source {
RegionNameSource::Static => true,
_ => false,
}matches!(outlived_name.source, RegionNameSource::Static))
121 {
122 suggested.push(SuggestedConstraint::Static(fr_name));
123 } else {
124// We want to isolate out all lifetimes that should be unified and print out
125 // separate messages for them.
126127let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition(
128// Do we have both 'fr: 'r and 'r: 'fr?
129|(r, _)| {
130self.constraints_to_add
131 .get(r)
132 .is_some_and(|r_outlived| r_outlived.as_slice().contains(fr))
133 },
134 );
135136for (r, bound) in unified.into_iter() {
137if !unified_already.contains(fr) {
138 suggested.push(SuggestedConstraint::Equal(fr_name, bound));
139 unified_already.insert(r);
140 }
141 }
142143if !other.is_empty() {
144let other = other.iter().map(|(_, rname)| *rname).collect::<SmallVec<_>>();
145 suggested.push(SuggestedConstraint::Outlives(fr_name, other))
146 }
147 }
148 }
149150suggested151 }
152153/// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest.
154pub(crate) fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) {
155{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:155",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(155u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("Collected {0:?}: {1:?}",
fr, outlived_fr) as &dyn Value))])
});
} else { ; }
};debug!("Collected {:?}: {:?}", fr, outlived_fr);
156157// Add to set of constraints for final help note.
158self.constraints_to_add.entry(fr).or_default().push(outlived_fr);
159 }
160161/// Emit an intermediate note on the given `Diag` if the involved regions are suggestable.
162pub(crate) fn intermediate_suggestion(
163&mut self,
164 mbcx: &MirBorrowckCtxt<'_, '_, '_>,
165 errci: &ErrorConstraintInfo<'_>,
166 diag: &mut Diag<'_>,
167 ) {
168// Emit an intermediate note.
169let fr_name = self.region_vid_to_name(mbcx, errci.fr);
170let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr);
171172if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name)
173 && !#[allow(non_exhaustive_omitted_patterns)] match outlived_fr_name.source {
RegionNameSource::Static => true,
_ => false,
}matches!(outlived_fr_name.source, RegionNameSource::Static)174 {
175diag.help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("consider adding the following bound: `{0}: {1}`",
fr_name, outlived_fr_name))
})format!(
176"consider adding the following bound: `{fr_name}: {outlived_fr_name}`",
177 ));
178 }
179 }
180181/// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final
182 /// suggestion including all collected constraints.
183pub(crate) fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_, '_>) {
184// No constraints to add? Done.
185if self.constraints_to_add.is_empty() {
186{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:186",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(186u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("No constraints to suggest.")
as &dyn Value))])
});
} else { ; }
};debug!("No constraints to suggest.");
187return;
188 }
189190// If there is only one constraint to suggest, then we already suggested it in the
191 // intermediate suggestion above.
192if self.constraints_to_add.len() == 1
193&& self.constraints_to_add.values().next().unwrap().len() == 1
194{
195{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:195",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(195u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("Only 1 suggestion. Skipping.")
as &dyn Value))])
});
} else { ; }
};debug!("Only 1 suggestion. Skipping.");
196return;
197 }
198199// Get all suggestable constraints.
200let suggested = self.compile_all_suggestions(mbcx);
201202// If there are no suggestable constraints...
203if suggested.is_empty() {
204{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:204",
"rustc_borrowck::diagnostics::outlives_suggestion",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
::tracing_core::__macro_support::Option::Some(204u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("Only 1 suggestable constraint. Skipping.")
as &dyn Value))])
});
} else { ; }
};debug!("Only 1 suggestable constraint. Skipping.");
205return;
206 }
207208// If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a
209 // list of diagnostics.
210let mut diag = if let [constraint] = suggested.as_slice() {
211mbcx.dcx().struct_help(match constraint {
212 SuggestedConstraint::Outlives(a, bs) => {
213let bs: SmallVec<[String; 2]> = bs.iter().map(|r| r.to_string()).collect();
214::alloc::__export::must_use({
::alloc::fmt::format(format_args!("add bound `{1}: {0}`",
bs.join(" + "), a))
})format!("add bound `{a}: {}`", bs.join(" + "))215 }
216217 SuggestedConstraint::Equal(a, b) => {
218::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`{0}` and `{1}` must be the same: replace one with the other",
a, b))
})format!("`{a}` and `{b}` must be the same: replace one with the other")219 }
220 SuggestedConstraint::Static(a) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("replace `{0}` with `\'static`", a))
})format!("replace `{a}` with `'static`"),
221 })
222 } else {
223// Create a new diagnostic.
224let mut diag = mbcx225 .infcx
226 .tcx
227 .dcx()
228 .struct_help("the following changes may resolve your lifetime errors");
229230// Add suggestions.
231for constraint in suggested {
232match constraint {
233 SuggestedConstraint::Outlives(a, bs) => {
234let bs: SmallVec<[String; 2]> = bs.iter().map(|r| r.to_string()).collect();
235 diag.help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("add bound `{1}: {0}`",
bs.join(" + "), a))
})format!("add bound `{a}: {}`", bs.join(" + ")));
236 }
237 SuggestedConstraint::Equal(a, b) => {
238 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!(
239"`{a}` and `{b}` must be the same: replace one with the other",
240 ));
241 }
242 SuggestedConstraint::Static(a) => {
243 diag.help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("replace `{0}` with `\'static`", a))
})format!("replace `{a}` with `'static`"));
244 }
245 }
246 }
247248diag249 };
250251// We want this message to appear after other messages on the mir def.
252let mir_span = mbcx.body.span;
253diag.sort_span = mir_span.shrink_to_hi();
254255// Buffer the diagnostic
256mbcx.buffer_non_error(diag);
257 }
258}