]> git.proxmox.com Git - rustc.git/blob - src/librustc_mir/borrow_check/diagnostics/outlives_suggestion.rs
New upstream version 1.44.1+dfsg1
[rustc.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_data_structures::fx::FxHashSet;
8 use rustc_errors::DiagnosticBuilder;
9 use rustc_middle::ty::RegionVid;
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::CannotMatchHirTy(..)
64 | RegionNameSource::MatchedHirTy(..)
65 | RegionNameSource::MatchedAdtAndSegment(..)
66 | RegionNameSource::AnonRegionFromUpvar(..)
67 | RegionNameSource::AnonRegionFromOutput(..)
68 | RegionNameSource::AnonRegionFromYieldTy(..)
69 | RegionNameSource::AnonRegionFromAsyncFn(..) => {
70 debug!("Region {:?} is NOT suggestable", name);
71 false
72 }
73 }
74 }
75
76 /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`.
77 fn region_vid_to_name(
78 &self,
79 mbcx: &MirBorrowckCtxt<'_, '_>,
80 region: RegionVid,
81 ) -> Option<RegionName> {
82 mbcx.give_region_a_name(region).filter(Self::region_name_is_suggestable)
83 }
84
85 /// Compiles a list of all suggestions to be printed in the final big suggestion.
86 fn compile_all_suggestions(
87 &self,
88 mbcx: &MirBorrowckCtxt<'_, '_>,
89 ) -> SmallVec<[SuggestedConstraint; 2]> {
90 let mut suggested = SmallVec::new();
91
92 // Keep track of variables that we have already suggested unifying so that we don't print
93 // out silly duplicate messages.
94 let mut unified_already = FxHashSet::default();
95
96 for (fr, outlived) in &self.constraints_to_add {
97 let fr_name = if let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) {
98 fr_name
99 } else {
100 continue;
101 };
102
103 let outlived = outlived
104 .iter()
105 // if there is a `None`, we will just omit that constraint
106 .filter_map(|fr| self.region_vid_to_name(mbcx, *fr).map(|rname| (fr, rname)))
107 .collect::<Vec<_>>();
108
109 // No suggestable outlived lifetimes.
110 if outlived.is_empty() {
111 continue;
112 }
113
114 // There are three types of suggestions we can make:
115 // 1) Suggest a bound: 'a: 'b
116 // 2) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we
117 // should just replace 'a with 'static.
118 // 3) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a
119
120 if outlived.iter().any(|(_, outlived_name)| {
121 if let RegionNameSource::Static = outlived_name.source { true } else { false }
122 }) {
123 suggested.push(SuggestedConstraint::Static(fr_name));
124 } else {
125 // We want to isolate out all lifetimes that should be unified and print out
126 // separate messages for them.
127
128 let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition(
129 // Do we have both 'fr: 'r and 'r: 'fr?
130 |(r, _)| {
131 self.constraints_to_add
132 .get(r)
133 .map(|r_outlived| r_outlived.as_slice().contains(fr))
134 .unwrap_or(false)
135 },
136 );
137
138 for (r, bound) in unified.into_iter() {
139 if !unified_already.contains(fr) {
140 suggested.push(SuggestedConstraint::Equal(fr_name.clone(), bound));
141 unified_already.insert(r);
142 }
143 }
144
145 if !other.is_empty() {
146 let other =
147 other.iter().map(|(_, rname)| rname.clone()).collect::<SmallVec<_>>();
148 suggested.push(SuggestedConstraint::Outlives(fr_name, other))
149 }
150 }
151 }
152
153 suggested
154 }
155
156 /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest.
157 crate fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) {
158 debug!("Collected {:?}: {:?}", fr, outlived_fr);
159
160 // Add to set of constraints for final help note.
161 self.constraints_to_add.entry(fr).or_insert(Vec::new()).push(outlived_fr);
162 }
163
164 /// Emit an intermediate note on the given `Diagnostic` if the involved regions are
165 /// suggestable.
166 crate fn intermediate_suggestion(
167 &mut self,
168 mbcx: &MirBorrowckCtxt<'_, '_>,
169 errci: &ErrorConstraintInfo,
170 diag: &mut DiagnosticBuilder<'_>,
171 ) {
172 // Emit an intermediate note.
173 let fr_name = self.region_vid_to_name(mbcx, errci.fr);
174 let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr);
175
176 if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name) {
177 if let RegionNameSource::Static = outlived_fr_name.source {
178 diag.help(&format!("consider replacing `{}` with `'static`", fr_name));
179 } else {
180 diag.help(&format!(
181 "consider adding the following bound: `{}: {}`",
182 fr_name, outlived_fr_name
183 ));
184 }
185 }
186 }
187
188 /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final
189 /// suggestion including all collected constraints.
190 crate fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_>) {
191 // No constraints to add? Done.
192 if self.constraints_to_add.is_empty() {
193 debug!("No constraints to suggest.");
194 return;
195 }
196
197 // If there is only one constraint to suggest, then we already suggested it in the
198 // intermediate suggestion above.
199 if self.constraints_to_add.len() == 1
200 && self.constraints_to_add.values().next().unwrap().len() == 1
201 {
202 debug!("Only 1 suggestion. Skipping.");
203 return;
204 }
205
206 // Get all suggestable constraints.
207 let suggested = self.compile_all_suggestions(mbcx);
208
209 // If there are no suggestable constraints...
210 if suggested.is_empty() {
211 debug!("Only 1 suggestable constraint. Skipping.");
212 return;
213 }
214
215 // If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a
216 // list of diagnostics.
217 let mut diag = if suggested.len() == 1 {
218 mbcx.infcx.tcx.sess.diagnostic().struct_help(&match suggested.last().unwrap() {
219 SuggestedConstraint::Outlives(a, bs) => {
220 let bs: SmallVec<[String; 2]> = bs.iter().map(|r| format!("{}", r)).collect();
221 format!("add bound `{}: {}`", a, bs.join(" + "))
222 }
223
224 SuggestedConstraint::Equal(a, b) => {
225 format!("`{}` and `{}` must be the same: replace one with the other", a, b)
226 }
227 SuggestedConstraint::Static(a) => format!("replace `{}` with `'static`", a),
228 })
229 } else {
230 // Create a new diagnostic.
231 let mut diag = mbcx
232 .infcx
233 .tcx
234 .sess
235 .diagnostic()
236 .struct_help("the following changes may resolve your lifetime errors");
237
238 // Add suggestions.
239 for constraint in suggested {
240 match constraint {
241 SuggestedConstraint::Outlives(a, bs) => {
242 let bs: SmallVec<[String; 2]> =
243 bs.iter().map(|r| format!("{}", r)).collect();
244 diag.help(&format!("add bound `{}: {}`", a, bs.join(" + ")));
245 }
246 SuggestedConstraint::Equal(a, b) => {
247 diag.help(&format!(
248 "`{}` and `{}` must be the same: replace one with the other",
249 a, b
250 ));
251 }
252 SuggestedConstraint::Static(a) => {
253 diag.help(&format!("replace `{}` with `'static`", a));
254 }
255 }
256 }
257
258 diag
259 };
260
261 // We want this message to appear after other messages on the mir def.
262 let mir_span = mbcx.infcx.tcx.def_span(mbcx.mir_def_id);
263 diag.sort_span = mir_span.shrink_to_hi();
264
265 // Buffer the diagnostic
266 diag.buffer(&mut mbcx.errors_buffer);
267 }
268 }