1 //! Contains utilities for generating suggestions for borrowck errors related to unsatisified
2 //! outlives constraints.
4 use std
::collections
::BTreeMap
;
7 use rustc_data_structures
::fx
::FxHashSet
;
8 use rustc_errors
::DiagnosticBuilder
;
9 use rustc_middle
::ty
::RegionVid
;
11 use smallvec
::SmallVec
;
13 use crate::borrow_check
::MirBorrowckCtxt
;
15 use super::{ErrorConstraintInfo, RegionName, RegionNameSource}
;
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]>),
23 Equal(RegionName
, RegionName
),
25 /// 'a: 'static i.e. 'a = 'static and the user should just use 'static
29 /// Collects information about outlives constraints that needed to be added for a given MIR node
30 /// corresponding to a function definition.
32 /// Adds a help note suggesting adding a where clause with the needed constraints.
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
>>,
43 impl OutlivesSuggestionBuilder
{
44 /// Returns `true` iff the `RegionNameSource` is a valid source for an outlives
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
{
56 RegionNameSource
::NamedEarlyBoundRegion(..)
57 | RegionNameSource
::NamedFreeRegion(..)
58 | RegionNameSource
::Static
=> true,
60 // Don't give suggestions for upvars, closure return types, or other unnamable
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
);
76 /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`.
77 fn region_vid_to_name(
79 mbcx
: &MirBorrowckCtxt
<'_
, '_
>,
81 ) -> Option
<RegionName
> {
82 mbcx
.give_region_a_name(region
).filter(Self::region_name_is_suggestable
)
85 /// Compiles a list of all suggestions to be printed in the final big suggestion.
86 fn compile_all_suggestions(
88 mbcx
: &MirBorrowckCtxt
<'_
, '_
>,
89 ) -> SmallVec
<[SuggestedConstraint
; 2]> {
90 let mut suggested
= SmallVec
::new();
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();
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
) {
103 let outlived
= outlived
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
<_
>>();
109 // No suggestable outlived lifetimes.
110 if outlived
.is_empty() {
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
120 if outlived
.iter().any(|(_
, outlived_name
)| {
121 if let RegionNameSource
::Static
= outlived_name
.source { true }
else { false }
123 suggested
.push(SuggestedConstraint
::Static(fr_name
));
125 // We want to isolate out all lifetimes that should be unified and print out
126 // separate messages for them.
128 let (unified
, other
): (Vec
<_
>, Vec
<_
>) = outlived
.into_iter().partition(
129 // Do we have both 'fr: 'r and 'r: 'fr?
131 self.constraints_to_add
133 .map(|r_outlived
| r_outlived
.as_slice().contains(fr
))
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
);
145 if !other
.is_empty() {
147 other
.iter().map(|(_
, rname
)| rname
.clone()).collect
::<SmallVec
<_
>>();
148 suggested
.push(SuggestedConstraint
::Outlives(fr_name
, other
))
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
);
160 // Add to set of constraints for final help note.
161 self.constraints_to_add
.entry(fr
).or_insert(Vec
::new()).push(outlived_fr
);
164 /// Emit an intermediate note on the given `Diagnostic` if the involved regions are
166 crate fn intermediate_suggestion(
168 mbcx
: &MirBorrowckCtxt
<'_
, '_
>,
169 errci
: &ErrorConstraintInfo
,
170 diag
: &mut DiagnosticBuilder
<'_
>,
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
);
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
));
181 "consider adding the following bound: `{}: {}`",
182 fr_name
, outlived_fr_name
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.");
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
202 debug
!("Only 1 suggestion. Skipping.");
206 // Get all suggestable constraints.
207 let suggested
= self.compile_all_suggestions(mbcx
);
209 // If there are no suggestable constraints...
210 if suggested
.is_empty() {
211 debug
!("Only 1 suggestable constraint. Skipping.");
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(" + "))
224 SuggestedConstraint
::Equal(a
, b
) => {
225 format
!("`{}` and `{}` must be the same: replace one with the other", a
, b
)
227 SuggestedConstraint
::Static(a
) => format
!("replace `{}` with `'static`", a
),
230 // Create a new diagnostic.
236 .struct_help("the following changes may resolve your lifetime errors");
239 for constraint
in suggested
{
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(" + ")));
246 SuggestedConstraint
::Equal(a
, b
) => {
248 "`{}` and `{}` must be the same: replace one with the other",
252 SuggestedConstraint
::Static(a
) => {
253 diag
.help(&format
!("replace `{}` with `'static`", a
));
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();
265 // Buffer the diagnostic
266 diag
.buffer(&mut mbcx
.errors_buffer
);