]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir/src/borrow_check/diagnostics/outlives_suggestion.rs
Rollup merge of #73963 - hellow554:unsafe_path, r=Mark-Simulacrum
[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.iter().any(|(_, outlived_name)| {
119                 if let RegionNameSource::Static = outlived_name.source { true } else { false }
120             }) {
121                 suggested.push(SuggestedConstraint::Static(fr_name));
122             } else {
123                 // We want to isolate out all lifetimes that should be unified and print out
124                 // separate messages for them.
125
126                 let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition(
127                     // Do we have both 'fr: 'r and 'r: 'fr?
128                     |(r, _)| {
129                         self.constraints_to_add
130                             .get(r)
131                             .map(|r_outlived| r_outlived.as_slice().contains(fr))
132                             .unwrap_or(false)
133                     },
134                 );
135
136                 for (r, bound) in unified.into_iter() {
137                     if !unified_already.contains(fr) {
138                         suggested.push(SuggestedConstraint::Equal(fr_name.clone(), bound));
139                         unified_already.insert(r);
140                     }
141                 }
142
143                 if !other.is_empty() {
144                     let other =
145                         other.iter().map(|(_, rname)| rname.clone()).collect::<SmallVec<_>>();
146                     suggested.push(SuggestedConstraint::Outlives(fr_name, other))
147                 }
148             }
149         }
150
151         suggested
152     }
153
154     /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest.
155     crate fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) {
156         debug!("Collected {:?}: {:?}", fr, outlived_fr);
157
158         // Add to set of constraints for final help note.
159         self.constraints_to_add.entry(fr).or_insert(Vec::new()).push(outlived_fr);
160     }
161
162     /// Emit an intermediate note on the given `Diagnostic` if the involved regions are
163     /// suggestable.
164     crate fn intermediate_suggestion(
165         &mut self,
166         mbcx: &MirBorrowckCtxt<'_, '_>,
167         errci: &ErrorConstraintInfo,
168         diag: &mut DiagnosticBuilder<'_>,
169     ) {
170         // Emit an intermediate note.
171         let fr_name = self.region_vid_to_name(mbcx, errci.fr);
172         let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr);
173
174         if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name) {
175             if let RegionNameSource::Static = outlived_fr_name.source {
176                 diag.help(&format!("consider replacing `{}` with `'static`", fr_name));
177             } else {
178                 diag.help(&format!(
179                     "consider adding the following bound: `{}: {}`",
180                     fr_name, outlived_fr_name
181                 ));
182             }
183         }
184     }
185
186     /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final
187     /// suggestion including all collected constraints.
188     crate fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_>) {
189         // No constraints to add? Done.
190         if self.constraints_to_add.is_empty() {
191             debug!("No constraints to suggest.");
192             return;
193         }
194
195         // If there is only one constraint to suggest, then we already suggested it in the
196         // intermediate suggestion above.
197         if self.constraints_to_add.len() == 1
198             && self.constraints_to_add.values().next().unwrap().len() == 1
199         {
200             debug!("Only 1 suggestion. Skipping.");
201             return;
202         }
203
204         // Get all suggestable constraints.
205         let suggested = self.compile_all_suggestions(mbcx);
206
207         // If there are no suggestable constraints...
208         if suggested.is_empty() {
209             debug!("Only 1 suggestable constraint. Skipping.");
210             return;
211         }
212
213         // If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a
214         // list of diagnostics.
215         let mut diag = if suggested.len() == 1 {
216             mbcx.infcx.tcx.sess.diagnostic().struct_help(&match suggested.last().unwrap() {
217                 SuggestedConstraint::Outlives(a, bs) => {
218                     let bs: SmallVec<[String; 2]> = bs.iter().map(|r| format!("{}", r)).collect();
219                     format!("add bound `{}: {}`", a, bs.join(" + "))
220                 }
221
222                 SuggestedConstraint::Equal(a, b) => {
223                     format!("`{}` and `{}` must be the same: replace one with the other", a, b)
224                 }
225                 SuggestedConstraint::Static(a) => format!("replace `{}` with `'static`", a),
226             })
227         } else {
228             // Create a new diagnostic.
229             let mut diag = mbcx
230                 .infcx
231                 .tcx
232                 .sess
233                 .diagnostic()
234                 .struct_help("the following changes may resolve your lifetime errors");
235
236             // Add suggestions.
237             for constraint in suggested {
238                 match constraint {
239                     SuggestedConstraint::Outlives(a, bs) => {
240                         let bs: SmallVec<[String; 2]> =
241                             bs.iter().map(|r| format!("{}", r)).collect();
242                         diag.help(&format!("add bound `{}: {}`", a, bs.join(" + ")));
243                     }
244                     SuggestedConstraint::Equal(a, b) => {
245                         diag.help(&format!(
246                             "`{}` and `{}` must be the same: replace one with the other",
247                             a, b
248                         ));
249                     }
250                     SuggestedConstraint::Static(a) => {
251                         diag.help(&format!("replace `{}` with `'static`", a));
252                     }
253                 }
254             }
255
256             diag
257         };
258
259         // We want this message to appear after other messages on the mir def.
260         let mir_span = mbcx.body.span;
261         diag.sort_span = mir_span.shrink_to_hi();
262
263         // Buffer the diagnostic
264         diag.buffer(&mut mbcx.errors_buffer);
265     }
266 }