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