]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir/src/borrow_check/diagnostics/outlives_suggestion.rs
Auto merge of #76748 - tmiasko:no-op-jumps, r=matthewjasper
[rust.git] / compiler / rustc_mir / src / borrow_check / diagnostics / outlives_suggestion.rs
1 //! Contains utilities for generating suggestions for borrowck errors related to unsatisified
2 //! outlives constraints.
3
4 use std::collections::BTreeMap;
5
6 use rustc_data_structures::fx::FxHashSet;
7 use rustc_errors::DiagnosticBuilder;
8 use rustc_middle::ty::RegionVid;
9 use tracing::debug;
10
11 use smallvec::SmallVec;
12
13 use crate::borrow_check::MirBorrowckCtxt;
14
15 use super::{ErrorConstraintInfo, RegionName, RegionNameSource};
16
17 /// The different things we could suggest.
18 enum SuggestedConstraint {
19     /// Outlives(a, [b, c, d, ...]) => 'a: 'b + 'c + 'd + ...
20     Outlives(RegionName, SmallVec<[RegionName; 2]>),
21
22     /// 'a = 'b
23     Equal(RegionName, RegionName),
24
25     /// 'a: 'static i.e. 'a = 'static and the user should just use 'static
26     Static(RegionName),
27 }
28
29 /// Collects information about outlives constraints that needed to be added for a given MIR node
30 /// corresponding to a function definition.
31 ///
32 /// Adds a help note suggesting adding a where clause with the needed constraints.
33 #[derive(Default)]
34 pub struct OutlivesSuggestionBuilder {
35     /// The list of outlives constraints that need to be added. Specifically, we map each free
36     /// region to all other regions that it must outlive. I will use the shorthand `fr:
37     /// outlived_frs`. Not all of these regions will already have names necessarily. Some could be
38     /// implicit free regions that we inferred. These will need to be given names in the final
39     /// suggestion message.
40     constraints_to_add: BTreeMap<RegionVid, Vec<RegionVid>>,
41 }
42
43 impl OutlivesSuggestionBuilder {
44     /// Returns `true` iff the `RegionNameSource` is a valid source for an outlives
45     /// suggestion.
46     //
47     // FIXME: Currently, we only report suggestions if the `RegionNameSource` is an early-bound
48     // region or a named region, avoiding using regions with synthetic names altogether. This
49     // allows us to avoid giving impossible suggestions (e.g. adding bounds to closure args).
50     // We can probably be less conservative, since some inferred free regions are namable (e.g.
51     // the user can explicitly name them. To do this, we would allow some regions whose names
52     // come from `MatchedAdtAndSegment`, being careful to filter out bad suggestions, such as
53     // naming the `'self` lifetime in methods, etc.
54     fn region_name_is_suggestable(name: &RegionName) -> bool {
55         match name.source {
56             RegionNameSource::NamedEarlyBoundRegion(..)
57             | RegionNameSource::NamedFreeRegion(..)
58             | RegionNameSource::Static => true,
59
60             // Don't give suggestions for upvars, closure return types, or other unnamable
61             // regions.
62             RegionNameSource::SynthesizedFreeEnvRegion(..)
63             | RegionNameSource::AnonRegionFromArgument(..)
64             | RegionNameSource::AnonRegionFromUpvar(..)
65             | RegionNameSource::AnonRegionFromOutput(..)
66             | RegionNameSource::AnonRegionFromYieldTy(..)
67             | RegionNameSource::AnonRegionFromAsyncFn(..) => {
68                 debug!("Region {:?} is NOT suggestable", name);
69                 false
70             }
71         }
72     }
73
74     /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`.
75     fn region_vid_to_name(
76         &self,
77         mbcx: &MirBorrowckCtxt<'_, '_>,
78         region: RegionVid,
79     ) -> Option<RegionName> {
80         mbcx.give_region_a_name(region).filter(Self::region_name_is_suggestable)
81     }
82
83     /// Compiles a list of all suggestions to be printed in the final big suggestion.
84     fn compile_all_suggestions(
85         &self,
86         mbcx: &MirBorrowckCtxt<'_, '_>,
87     ) -> SmallVec<[SuggestedConstraint; 2]> {
88         let mut suggested = SmallVec::new();
89
90         // Keep track of variables that we have already suggested unifying so that we don't print
91         // out silly duplicate messages.
92         let mut unified_already = FxHashSet::default();
93
94         for (fr, outlived) in &self.constraints_to_add {
95             let fr_name = if let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) {
96                 fr_name
97             } else {
98                 continue;
99             };
100
101             let 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<_>>();
106
107             // No suggestable outlived lifetimes.
108             if outlived.is_empty() {
109                 continue;
110             }
111
112             // 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
117
118             if outlived
119                 .iter()
120                 .any(|(_, outlived_name)| 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.
126
127                 let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition(
128                     // Do we have both 'fr: 'r and 'r: 'fr?
129                     |(r, _)| {
130                         self.constraints_to_add
131                             .get(r)
132                             .map(|r_outlived| r_outlived.as_slice().contains(fr))
133                             .unwrap_or(false)
134                     },
135                 );
136
137                 for (r, bound) in unified.into_iter() {
138                     if !unified_already.contains(fr) {
139                         suggested.push(SuggestedConstraint::Equal(fr_name.clone(), bound));
140                         unified_already.insert(r);
141                     }
142                 }
143
144                 if !other.is_empty() {
145                     let other =
146                         other.iter().map(|(_, rname)| rname.clone()).collect::<SmallVec<_>>();
147                     suggested.push(SuggestedConstraint::Outlives(fr_name, other))
148                 }
149             }
150         }
151
152         suggested
153     }
154
155     /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest.
156     crate fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) {
157         debug!("Collected {:?}: {:?}", fr, outlived_fr);
158
159         // Add to set of constraints for final help note.
160         self.constraints_to_add.entry(fr).or_insert(Vec::new()).push(outlived_fr);
161     }
162
163     /// Emit an intermediate note on the given `Diagnostic` if the involved regions are
164     /// suggestable.
165     crate fn intermediate_suggestion(
166         &mut self,
167         mbcx: &MirBorrowckCtxt<'_, '_>,
168         errci: &ErrorConstraintInfo,
169         diag: &mut DiagnosticBuilder<'_>,
170     ) {
171         // Emit an intermediate note.
172         let fr_name = self.region_vid_to_name(mbcx, errci.fr);
173         let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr);
174
175         if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name) {
176             if let RegionNameSource::Static = outlived_fr_name.source {
177                 diag.help(&format!("consider replacing `{}` with `'static`", fr_name));
178             } else {
179                 diag.help(&format!(
180                     "consider adding the following bound: `{}: {}`",
181                     fr_name, outlived_fr_name
182                 ));
183             }
184         }
185     }
186
187     /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final
188     /// suggestion including all collected constraints.
189     crate fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_>) {
190         // No constraints to add? Done.
191         if self.constraints_to_add.is_empty() {
192             debug!("No constraints to suggest.");
193             return;
194         }
195
196         // If there is only one constraint to suggest, then we already suggested it in the
197         // intermediate suggestion above.
198         if self.constraints_to_add.len() == 1
199             && self.constraints_to_add.values().next().unwrap().len() == 1
200         {
201             debug!("Only 1 suggestion. Skipping.");
202             return;
203         }
204
205         // Get all suggestable constraints.
206         let suggested = self.compile_all_suggestions(mbcx);
207
208         // If there are no suggestable constraints...
209         if suggested.is_empty() {
210             debug!("Only 1 suggestable constraint. Skipping.");
211             return;
212         }
213
214         // If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a
215         // list of diagnostics.
216         let mut diag = if suggested.len() == 1 {
217             mbcx.infcx.tcx.sess.diagnostic().struct_help(&match suggested.last().unwrap() {
218                 SuggestedConstraint::Outlives(a, bs) => {
219                     let bs: SmallVec<[String; 2]> = bs.iter().map(|r| format!("{}", r)).collect();
220                     format!("add bound `{}: {}`", a, bs.join(" + "))
221                 }
222
223                 SuggestedConstraint::Equal(a, b) => {
224                     format!("`{}` and `{}` must be the same: replace one with the other", a, b)
225                 }
226                 SuggestedConstraint::Static(a) => format!("replace `{}` with `'static`", a),
227             })
228         } else {
229             // Create a new diagnostic.
230             let mut diag = mbcx
231                 .infcx
232                 .tcx
233                 .sess
234                 .diagnostic()
235                 .struct_help("the following changes may resolve your lifetime errors");
236
237             // Add suggestions.
238             for constraint in suggested {
239                 match constraint {
240                     SuggestedConstraint::Outlives(a, bs) => {
241                         let bs: SmallVec<[String; 2]> =
242                             bs.iter().map(|r| format!("{}", r)).collect();
243                         diag.help(&format!("add bound `{}: {}`", a, bs.join(" + ")));
244                     }
245                     SuggestedConstraint::Equal(a, b) => {
246                         diag.help(&format!(
247                             "`{}` and `{}` must be the same: replace one with the other",
248                             a, b
249                         ));
250                     }
251                     SuggestedConstraint::Static(a) => {
252                         diag.help(&format!("replace `{}` with `'static`", a));
253                     }
254                 }
255             }
256
257             diag
258         };
259
260         // We want this message to appear after other messages on the mir def.
261         let mir_span = mbcx.body.span;
262         diag.sort_span = mir_span.shrink_to_hi();
263
264         // Buffer the diagnostic
265         diag.buffer(&mut mbcx.errors_buffer);
266     }
267 }