1 //! Contains utilities for generating suggestions for borrowck errors related to unsatisified
2 //! outlives constraints.
4 use std::collections::BTreeMap;
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;
14 use smallvec::SmallVec;
16 use crate::borrow_check::nll::region_infer::RegionInferenceContext;
19 RegionName, RegionNameSource, ErrorConstraintInfo, ErrorReportingCtx, RegionErrorNamingCtx,
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]>),
28 Equal(RegionName, RegionName),
30 /// 'a: 'static i.e. 'a = 'static and the user should just use 'static
34 /// Collects information about outlives constraints that needed to be added for a given MIR node
35 /// corresponding to a function definition.
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.
42 local_names: &'a IndexVec<Local, Option<Symbol>>,
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>>,
52 impl OutlivesSuggestionBuilder<'a> {
53 /// Create a new builder for the given MIR node representing a fn definition.
56 local_names: &'a IndexVec<Local, Option<Symbol>>,
58 OutlivesSuggestionBuilder {
61 constraints_to_add: BTreeMap::default(),
65 /// Returns `true` iff the `RegionNameSource` is a valid source for an outlives
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 {
77 RegionNameSource::NamedEarlyBoundRegion(..)
78 | RegionNameSource::NamedFreeRegion(..)
79 | RegionNameSource::Static => true,
81 // Don't give suggestions for upvars, closure return types, or other unnamable
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);
97 /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`.
98 fn region_vid_to_name(
100 errctx: &ErrorReportingCtx<'_, '_, '_>,
101 renctx: &mut RegionErrorNamingCtx,
103 ) -> Option<RegionName> {
106 .give_region_a_name(errctx, renctx, region)
107 .filter(Self::region_name_is_suggestable)
110 /// Compiles a list of all suggestions to be printed in the final big suggestion.
111 fn compile_all_suggestions<'tcx>(
114 region_infcx: &RegionInferenceContext<'tcx>,
115 infcx: &InferCtxt<'_, 'tcx>,
116 renctx: &mut RegionErrorNamingCtx,
117 ) -> SmallVec<[SuggestedConstraint; 2]> {
118 let mut suggested = SmallVec::new();
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();
124 let errctx = ErrorReportingCtx {
128 mir_def_id: self.mir_def_id,
129 local_names: self.local_names,
131 // We should not be suggesting naming upvars, so we pass in a dummy set of upvars that
132 // should never be used.
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) {
143 let outlived = outlived
145 // if there is a `None`, we will just omit that constraint
147 self.region_vid_to_name(&errctx, renctx, *fr).map(|rname| (fr, rname))
149 .collect::<Vec<_>>();
151 // No suggestable outlived lifetimes.
152 if outlived.is_empty() {
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
162 if outlived.iter().any(|(_, outlived_name)| {
163 if let RegionNameSource::Static = outlived_name.source {
169 suggested.push(SuggestedConstraint::Static(fr_name));
171 // We want to isolate out all lifetimes that should be unified and print out
172 // separate messages for them.
174 let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition(
175 // Do we have both 'fr: 'r and 'r: 'fr?
177 self.constraints_to_add
179 .map(|r_outlived| r_outlived.as_slice().contains(fr))
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);
191 if !other.is_empty() {
193 other.iter().map(|(_, rname)| rname.clone()).collect::<SmallVec<_>>();
194 suggested.push(SuggestedConstraint::Outlives(fr_name, other))
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);
206 // Add to set of constraints for final help note.
207 self.constraints_to_add.entry(fr).or_insert(Vec::new()).push(outlived_fr);
210 /// Emit an intermediate note on the given `Diagnostic` if the involved regions are
212 crate fn intermediate_suggestion(
214 errctx: &ErrorReportingCtx<'_, '_, '_>,
215 errci: &ErrorConstraintInfo,
216 renctx: &mut RegionErrorNamingCtx,
217 diag: &mut DiagnosticBuilder<'_>,
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);
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));
228 "consider adding the following bound: `{}: {}`",
229 fr_name, outlived_fr_name
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>(
240 region_infcx: &RegionInferenceContext<'tcx>,
241 infcx: &InferCtxt<'_, 'tcx>,
242 errors_buffer: &mut Vec<Diagnostic>,
243 renctx: &mut RegionErrorNamingCtx,
245 // No constraints to add? Done.
246 if self.constraints_to_add.is_empty() {
247 debug!("No constraints to suggest.");
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.");
258 // Get all suggestable constraints.
259 let suggested = self.compile_all_suggestions(body, region_infcx, infcx, renctx);
261 // If there are no suggestable constraints...
262 if suggested.is_empty() {
263 debug!("Only 1 suggestable constraint. Skipping.");
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(" + "))
276 SuggestedConstraint::Equal(a, b) => {
277 format!("`{}` and `{}` must be the same: replace one with the other", a, b)
279 SuggestedConstraint::Static(a) => format!("replace `{}` with `'static`", a),
282 // Create a new diagnostic.
287 .struct_help("the following changes may resolve your lifetime errors");
290 for constraint in suggested {
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(" + ")));
297 SuggestedConstraint::Equal(a, b) => {
299 "`{}` and `{}` must be the same: replace one with the other",
303 SuggestedConstraint::Static(a) => {
304 diag.help(&format!("replace `{}` with `'static`", a));
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();
316 // Buffer the diagnostic
317 diag.buffer(errors_buffer);