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