]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs
New upstream version 1.71.1+dfsg1
[rustc.git] / compiler / rustc_borrowck / src / diagnostics / outlives_suggestion.rs
CommitLineData
cdc7bbd5 1//! Contains utilities for generating suggestions for borrowck errors related to unsatisfied
60c5eb7d
XL
2//! outlives constraints.
3
353b0b11 4use rustc_data_structures::fx::FxIndexSet;
5e7ed085 5use rustc_errors::Diagnostic;
ba9703b0 6use rustc_middle::ty::RegionVid;
60c5eb7d 7use smallvec::SmallVec;
c295e0f8 8use std::collections::BTreeMap;
60c5eb7d 9
c295e0f8 10use crate::MirBorrowckCtxt;
60c5eb7d 11
dfeec247 12use super::{ErrorConstraintInfo, RegionName, RegionNameSource};
60c5eb7d
XL
13
14/// The different things we could suggest.
15enum 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)]
31pub 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 40impl 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}