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