]>
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; |
5e7ed085 | 5 | use rustc_errors::Diagnostic; |
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 | ||
5e7ed085 | 58 | // Don't give suggestions for upvars, closure return types, or other unnameable |
60c5eb7d XL |
59 | // regions. |
60 | RegionNameSource::SynthesizedFreeEnvRegion(..) | |
3dfed10e | 61 | | RegionNameSource::AnonRegionFromArgument(..) |
60c5eb7d XL |
62 | | RegionNameSource::AnonRegionFromUpvar(..) |
63 | | RegionNameSource::AnonRegionFromOutput(..) | |
64 | | RegionNameSource::AnonRegionFromYieldTy(..) | |
923072b8 FG |
65 | | RegionNameSource::AnonRegionFromAsyncFn(..) |
66 | | RegionNameSource::AnonRegionFromImplSignature(..) => { | |
60c5eb7d XL |
67 | debug!("Region {:?} is NOT suggestable", name); |
68 | false | |
69 | } | |
70 | } | |
71 | } | |
72 | ||
73 | /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`. | |
74 | fn region_vid_to_name( | |
75 | &self, | |
dfeec247 | 76 | mbcx: &MirBorrowckCtxt<'_, '_>, |
60c5eb7d XL |
77 | region: RegionVid, |
78 | ) -> Option<RegionName> { | |
dfeec247 | 79 | mbcx.give_region_a_name(region).filter(Self::region_name_is_suggestable) |
60c5eb7d XL |
80 | } |
81 | ||
82 | /// Compiles a list of all suggestions to be printed in the final big suggestion. | |
dfeec247 | 83 | fn compile_all_suggestions( |
60c5eb7d | 84 | &self, |
dfeec247 | 85 | mbcx: &MirBorrowckCtxt<'_, '_>, |
60c5eb7d XL |
86 | ) -> SmallVec<[SuggestedConstraint; 2]> { |
87 | let mut suggested = SmallVec::new(); | |
88 | ||
89 | // Keep track of variables that we have already suggested unifying so that we don't print | |
90 | // out silly duplicate messages. | |
91 | let mut unified_already = FxHashSet::default(); | |
92 | ||
60c5eb7d | 93 | for (fr, outlived) in &self.constraints_to_add { |
3c0e092e | 94 | let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) else { |
60c5eb7d XL |
95 | continue; |
96 | }; | |
97 | ||
98 | let outlived = outlived | |
99 | .iter() | |
100 | // if there is a `None`, we will just omit that constraint | |
dfeec247 | 101 | .filter_map(|fr| self.region_vid_to_name(mbcx, *fr).map(|rname| (fr, rname))) |
60c5eb7d XL |
102 | .collect::<Vec<_>>(); |
103 | ||
104 | // No suggestable outlived lifetimes. | |
105 | if outlived.is_empty() { | |
106 | continue; | |
107 | } | |
108 | ||
109 | // There are three types of suggestions we can make: | |
110 | // 1) Suggest a bound: 'a: 'b | |
111 | // 2) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we | |
112 | // should just replace 'a with 'static. | |
113 | // 3) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a | |
114 | ||
1b1a35ee XL |
115 | if outlived |
116 | .iter() | |
117 | .any(|(_, outlived_name)| matches!(outlived_name.source, RegionNameSource::Static)) | |
118 | { | |
60c5eb7d XL |
119 | suggested.push(SuggestedConstraint::Static(fr_name)); |
120 | } else { | |
121 | // We want to isolate out all lifetimes that should be unified and print out | |
122 | // separate messages for them. | |
123 | ||
124 | let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition( | |
125 | // Do we have both 'fr: 'r and 'r: 'fr? | |
126 | |(r, _)| { | |
127 | self.constraints_to_add | |
128 | .get(r) | |
129 | .map(|r_outlived| r_outlived.as_slice().contains(fr)) | |
130 | .unwrap_or(false) | |
131 | }, | |
132 | ); | |
133 | ||
134 | for (r, bound) in unified.into_iter() { | |
135 | if !unified_already.contains(fr) { | |
136 | suggested.push(SuggestedConstraint::Equal(fr_name.clone(), bound)); | |
137 | unified_already.insert(r); | |
138 | } | |
139 | } | |
140 | ||
141 | if !other.is_empty() { | |
142 | let other = | |
143 | other.iter().map(|(_, rname)| rname.clone()).collect::<SmallVec<_>>(); | |
144 | suggested.push(SuggestedConstraint::Outlives(fr_name, other)) | |
145 | } | |
146 | } | |
147 | } | |
148 | ||
149 | suggested | |
150 | } | |
151 | ||
152 | /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest. | |
923072b8 | 153 | pub(crate) fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) { |
60c5eb7d XL |
154 | debug!("Collected {:?}: {:?}", fr, outlived_fr); |
155 | ||
156 | // Add to set of constraints for final help note. | |
cdc7bbd5 | 157 | self.constraints_to_add.entry(fr).or_default().push(outlived_fr); |
60c5eb7d XL |
158 | } |
159 | ||
160 | /// Emit an intermediate note on the given `Diagnostic` if the involved regions are | |
161 | /// suggestable. | |
923072b8 | 162 | pub(crate) fn intermediate_suggestion( |
60c5eb7d | 163 | &mut self, |
dfeec247 | 164 | mbcx: &MirBorrowckCtxt<'_, '_>, |
923072b8 | 165 | errci: &ErrorConstraintInfo<'_>, |
5e7ed085 | 166 | diag: &mut Diagnostic, |
60c5eb7d XL |
167 | ) { |
168 | // Emit an intermediate note. | |
dfeec247 XL |
169 | let fr_name = self.region_vid_to_name(mbcx, errci.fr); |
170 | let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr); | |
60c5eb7d | 171 | |
04454e1e FG |
172 | if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name) |
173 | && !matches!(outlived_fr_name.source, RegionNameSource::Static) | |
174 | { | |
175 | diag.help(&format!( | |
176 | "consider adding the following bound: `{fr_name}: {outlived_fr_name}`", | |
177 | )); | |
60c5eb7d XL |
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. | |
923072b8 | 183 | pub(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 | } |