1 //! Contains utilities for generating suggestions for borrowck errors related to unsatisfied
2 //! outlives constraints.
4 use rustc_data_structures::fx::FxHashSet;
5 use rustc_errors::Diagnostic;
6 use rustc_middle::ty::RegionVid;
7 use smallvec::SmallVec;
8 use std::collections::BTreeMap;
11 use crate::MirBorrowckCtxt;
13 use super::{ErrorConstraintInfo, RegionName, RegionNameSource};
15 /// The different things we could suggest.
16 enum SuggestedConstraint {
17 /// Outlives(a, [b, c, d, ...]) => 'a: 'b + 'c + 'd + ...
18 Outlives(RegionName, SmallVec<[RegionName; 2]>),
21 Equal(RegionName, RegionName),
23 /// 'a: 'static i.e. 'a = 'static and the user should just use 'static
27 /// Collects information about outlives constraints that needed to be added for a given MIR node
28 /// corresponding to a function definition.
30 /// Adds a help note suggesting adding a where clause with the needed constraints.
32 pub struct OutlivesSuggestionBuilder {
33 /// The list of outlives constraints that need to be added. Specifically, we map each free
34 /// region to all other regions that it must outlive. I will use the shorthand `fr:
35 /// outlived_frs`. Not all of these regions will already have names necessarily. Some could be
36 /// implicit free regions that we inferred. These will need to be given names in the final
37 /// suggestion message.
38 constraints_to_add: BTreeMap<RegionVid, Vec<RegionVid>>,
41 impl OutlivesSuggestionBuilder {
42 /// Returns `true` iff the `RegionNameSource` is a valid source for an outlives
45 // FIXME: Currently, we only report suggestions if the `RegionNameSource` is an early-bound
46 // region or a named region, avoiding using regions with synthetic names altogether. This
47 // allows us to avoid giving impossible suggestions (e.g. adding bounds to closure args).
48 // We can probably be less conservative, since some inferred free regions are namable (e.g.
49 // the user can explicitly name them. To do this, we would allow some regions whose names
50 // come from `MatchedAdtAndSegment`, being careful to filter out bad suggestions, such as
51 // naming the `'self` lifetime in methods, etc.
52 fn region_name_is_suggestable(name: &RegionName) -> bool {
54 RegionNameSource::NamedEarlyBoundRegion(..)
55 | RegionNameSource::NamedFreeRegion(..)
56 | RegionNameSource::Static => true,
58 // Don't give suggestions for upvars, closure return types, or other unnamable
60 RegionNameSource::SynthesizedFreeEnvRegion(..)
61 | RegionNameSource::AnonRegionFromArgument(..)
62 | RegionNameSource::AnonRegionFromUpvar(..)
63 | RegionNameSource::AnonRegionFromOutput(..)
64 | RegionNameSource::AnonRegionFromYieldTy(..)
65 | RegionNameSource::AnonRegionFromAsyncFn(..) => {
66 debug!("Region {:?} is NOT suggestable", name);
72 /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`.
73 fn region_vid_to_name(
75 mbcx: &MirBorrowckCtxt<'_, '_>,
77 ) -> Option<RegionName> {
78 mbcx.give_region_a_name(region).filter(Self::region_name_is_suggestable)
81 /// Compiles a list of all suggestions to be printed in the final big suggestion.
82 fn compile_all_suggestions(
84 mbcx: &MirBorrowckCtxt<'_, '_>,
85 ) -> SmallVec<[SuggestedConstraint; 2]> {
86 let mut suggested = SmallVec::new();
88 // Keep track of variables that we have already suggested unifying so that we don't print
89 // out silly duplicate messages.
90 let mut unified_already = FxHashSet::default();
92 for (fr, outlived) in &self.constraints_to_add {
93 let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) else {
97 let outlived = outlived
99 // if there is a `None`, we will just omit that constraint
100 .filter_map(|fr| self.region_vid_to_name(mbcx, *fr).map(|rname| (fr, rname)))
101 .collect::<Vec<_>>();
103 // No suggestable outlived lifetimes.
104 if outlived.is_empty() {
108 // There are three types of suggestions we can make:
109 // 1) Suggest a bound: 'a: 'b
110 // 2) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we
111 // should just replace 'a with 'static.
112 // 3) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a
116 .any(|(_, outlived_name)| matches!(outlived_name.source, RegionNameSource::Static))
118 suggested.push(SuggestedConstraint::Static(fr_name));
120 // We want to isolate out all lifetimes that should be unified and print out
121 // separate messages for them.
123 let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition(
124 // Do we have both 'fr: 'r and 'r: 'fr?
126 self.constraints_to_add
128 .map(|r_outlived| r_outlived.as_slice().contains(fr))
133 for (r, bound) in unified.into_iter() {
134 if !unified_already.contains(fr) {
135 suggested.push(SuggestedConstraint::Equal(fr_name.clone(), bound));
136 unified_already.insert(r);
140 if !other.is_empty() {
142 other.iter().map(|(_, rname)| rname.clone()).collect::<SmallVec<_>>();
143 suggested.push(SuggestedConstraint::Outlives(fr_name, other))
151 /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest.
152 crate fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) {
153 debug!("Collected {:?}: {:?}", fr, outlived_fr);
155 // Add to set of constraints for final help note.
156 self.constraints_to_add.entry(fr).or_default().push(outlived_fr);
159 /// Emit an intermediate note on the given `Diagnostic` if the involved regions are
161 crate fn intermediate_suggestion(
163 mbcx: &MirBorrowckCtxt<'_, '_>,
164 errci: &ErrorConstraintInfo,
165 diag: &mut Diagnostic,
167 // Emit an intermediate note.
168 let fr_name = self.region_vid_to_name(mbcx, errci.fr);
169 let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr);
171 if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name) {
172 if !matches!(outlived_fr_name.source, RegionNameSource::Static) {
174 "consider adding the following bound: `{}: {}`",
175 fr_name, outlived_fr_name
181 /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final
182 /// suggestion including all collected constraints.
183 crate fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_>) {
184 // No constraints to add? Done.
185 if self.constraints_to_add.is_empty() {
186 debug!("No constraints to suggest.");
190 // If there is only one constraint to suggest, then we already suggested it in the
191 // intermediate suggestion above.
192 if self.constraints_to_add.len() == 1
193 && self.constraints_to_add.values().next().unwrap().len() == 1
195 debug!("Only 1 suggestion. Skipping.");
199 // Get all suggestable constraints.
200 let suggested = self.compile_all_suggestions(mbcx);
202 // If there are no suggestable constraints...
203 if suggested.is_empty() {
204 debug!("Only 1 suggestable constraint. Skipping.");
208 // If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a
209 // list of diagnostics.
210 let mut diag = if suggested.len() == 1 {
211 mbcx.infcx.tcx.sess.diagnostic().struct_help(&match suggested.last().unwrap() {
212 SuggestedConstraint::Outlives(a, bs) => {
213 let bs: SmallVec<[String; 2]> = bs.iter().map(|r| format!("{}", r)).collect();
214 format!("add bound `{}: {}`", a, bs.join(" + "))
217 SuggestedConstraint::Equal(a, b) => {
218 format!("`{}` and `{}` must be the same: replace one with the other", a, b)
220 SuggestedConstraint::Static(a) => format!("replace `{}` with `'static`", a),
223 // Create a new diagnostic.
229 .struct_help("the following changes may resolve your lifetime errors");
232 for constraint in suggested {
234 SuggestedConstraint::Outlives(a, bs) => {
235 let bs: SmallVec<[String; 2]> =
236 bs.iter().map(|r| format!("{}", r)).collect();
237 diag.help(&format!("add bound `{}: {}`", a, bs.join(" + ")));
239 SuggestedConstraint::Equal(a, b) => {
241 "`{}` and `{}` must be the same: replace one with the other",
245 SuggestedConstraint::Static(a) => {
246 diag.help(&format!("replace `{}` with `'static`", a));
254 // We want this message to appear after other messages on the mir def.
255 let mir_span = mbcx.body.span;
256 diag.sort_span = mir_span.shrink_to_hi();
258 // Buffer the diagnostic
259 mbcx.buffer_error(diag);