Skip to main content

rustc_borrowck/diagnostics/
outlives_suggestion.rs

1//! Contains utilities for generating suggestions for borrowck errors related to unsatisfied
2//! outlives constraints.
3
4use std::collections::BTreeMap;
5
6use rustc_data_structures::fx::FxIndexSet;
7use rustc_errors::Diag;
8use rustc_middle::ty::RegionVid;
9use smallvec::SmallVec;
10use tracing::debug;
11
12use super::{ErrorConstraintInfo, RegionName, RegionNameSource};
13use crate::MirBorrowckCtxt;
14
15/// The different things we could suggest.
16enum 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.
31#[derive(#[automatically_derived]
impl ::core::default::Default for OutlivesSuggestionBuilder {
    #[inline]
    fn default() -> OutlivesSuggestionBuilder {
        OutlivesSuggestionBuilder {
            constraints_to_add: ::core::default::Default::default(),
        }
    }
}Default)]
32pub(crate) struct OutlivesSuggestionBuilder {
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
41impl OutlivesSuggestionBuilder {
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::NamedEarlyParamRegion(..)
55            | RegionNameSource::NamedLateParamRegion(..)
56            | RegionNameSource::Static => true,
57
58            // Don't give suggestions for upvars, closure return types, or other unnameable
59            // regions.
60            RegionNameSource::SynthesizedFreeEnvRegion(..)
61            | RegionNameSource::AnonRegionFromArgument(..)
62            | RegionNameSource::AnonRegionFromUpvar(..)
63            | RegionNameSource::AnonRegionFromOutput(..)
64            | RegionNameSource::AnonRegionFromYieldTy(..)
65            | RegionNameSource::AnonRegionFromAsyncFn(..)
66            | RegionNameSource::AnonRegionFromImplSignature(..) => {
67                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:67",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
                        ::tracing_core::__macro_support::Option::Some(67u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("Region {0:?} is NOT suggestable",
                                                    name) as &dyn Value))])
            });
    } else { ; }
};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,
76        mbcx: &MirBorrowckCtxt<'_, '_, '_>,
77        region: RegionVid,
78    ) -> Option<RegionName> {
79        mbcx.give_region_a_name(region).filter(Self::region_name_is_suggestable)
80    }
81
82    /// Compiles a list of all suggestions to be printed in the final big suggestion.
83    fn compile_all_suggestions(
84        &self,
85        mbcx: &MirBorrowckCtxt<'_, '_, '_>,
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 = FxIndexSet::default();
92
93        for (fr, outlived) in &self.constraints_to_add {
94            let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) else {
95                continue;
96            };
97
98            let outlived = outlived
99                .iter()
100                // if there is a `None`, we will just omit that constraint
101                .filter_map(|fr| self.region_vid_to_name(mbcx, *fr).map(|rname| (fr, rname)))
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
115            if outlived
116                .iter()
117                .any(|(_, outlived_name)| #[allow(non_exhaustive_omitted_patterns)] match outlived_name.source {
    RegionNameSource::Static => true,
    _ => false,
}matches!(outlived_name.source, RegionNameSource::Static))
118            {
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                            .is_some_and(|r_outlived| r_outlived.as_slice().contains(fr))
130                    },
131                );
132
133                for (r, bound) in unified.into_iter() {
134                    if !unified_already.contains(fr) {
135                        suggested.push(SuggestedConstraint::Equal(fr_name, bound));
136                        unified_already.insert(r);
137                    }
138                }
139
140                if !other.is_empty() {
141                    let other = other.iter().map(|(_, rname)| *rname).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.
151    pub(crate) fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) {
152        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:152",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
                        ::tracing_core::__macro_support::Option::Some(152u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("Collected {0:?}: {1:?}",
                                                    fr, outlived_fr) as &dyn Value))])
            });
    } else { ; }
};debug!("Collected {:?}: {:?}", fr, outlived_fr);
153
154        // Add to set of constraints for final help note.
155        self.constraints_to_add.entry(fr).or_default().push(outlived_fr);
156    }
157
158    /// Emit an intermediate note on the given `Diag` if the involved regions are suggestable.
159    pub(crate) fn intermediate_suggestion(
160        &mut self,
161        mbcx: &MirBorrowckCtxt<'_, '_, '_>,
162        errci: &ErrorConstraintInfo<'_>,
163        diag: &mut Diag<'_>,
164    ) {
165        // Emit an intermediate note.
166        let fr_name = self.region_vid_to_name(mbcx, errci.fr);
167        let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr);
168
169        if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name)
170            && !#[allow(non_exhaustive_omitted_patterns)] match outlived_fr_name.source {
    RegionNameSource::Static => true,
    _ => false,
}matches!(outlived_fr_name.source, RegionNameSource::Static)
171        {
172            diag.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider adding the following bound: `{0}: {1}`",
                fr_name, outlived_fr_name))
    })format!(
173                "consider adding the following bound: `{fr_name}: {outlived_fr_name}`",
174            ));
175        }
176    }
177
178    /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final
179    /// suggestion including all collected constraints.
180    pub(crate) fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_, '_>) {
181        // No constraints to add? Done.
182        if self.constraints_to_add.is_empty() {
183            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:183",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
                        ::tracing_core::__macro_support::Option::Some(183u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("No constraints to suggest.")
                                            as &dyn Value))])
            });
    } else { ; }
};debug!("No constraints to suggest.");
184            return;
185        }
186
187        // If there is only one constraint to suggest, then we already suggested it in the
188        // intermediate suggestion above.
189        if self.constraints_to_add.len() == 1
190            && self.constraints_to_add.values().next().unwrap().len() == 1
191        {
192            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:192",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
                        ::tracing_core::__macro_support::Option::Some(192u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("Only 1 suggestion. Skipping.")
                                            as &dyn Value))])
            });
    } else { ; }
};debug!("Only 1 suggestion. Skipping.");
193            return;
194        }
195
196        // Get all suggestable constraints.
197        let suggested = self.compile_all_suggestions(mbcx);
198
199        // If there are no suggestable constraints...
200        if suggested.is_empty() {
201            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:201",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
                        ::tracing_core::__macro_support::Option::Some(201u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_borrowck::diagnostics::outlives_suggestion"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("Only 1 suggestable constraint. Skipping.")
                                            as &dyn Value))])
            });
    } else { ; }
};debug!("Only 1 suggestable constraint. Skipping.");
202            return;
203        }
204
205        // If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a
206        // list of diagnostics.
207        let mut diag = if let [constraint] = suggested.as_slice() {
208            mbcx.dcx().struct_help(match constraint {
209                SuggestedConstraint::Outlives(a, bs) => {
210                    let bs: SmallVec<[String; 2]> = bs.iter().map(|r| r.to_string()).collect();
211                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("add bound `{1}: {0}`",
                bs.join(" + "), a))
    })format!("add bound `{a}: {}`", bs.join(" + "))
212                }
213
214                SuggestedConstraint::Equal(a, b) => {
215                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` and `{1}` must be the same: replace one with the other",
                a, b))
    })format!("`{a}` and `{b}` must be the same: replace one with the other")
216                }
217                SuggestedConstraint::Static(a) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("replace `{0}` with `\'static`", a))
    })format!("replace `{a}` with `'static`"),
218            })
219        } else {
220            // Create a new diagnostic.
221            let mut diag = mbcx
222                .infcx
223                .tcx
224                .dcx()
225                .struct_help("the following changes may resolve your lifetime errors");
226
227            // Add suggestions.
228            for constraint in suggested {
229                match constraint {
230                    SuggestedConstraint::Outlives(a, bs) => {
231                        let bs: SmallVec<[String; 2]> = bs.iter().map(|r| r.to_string()).collect();
232                        diag.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("add bound `{1}: {0}`",
                bs.join(" + "), a))
    })format!("add bound `{a}: {}`", bs.join(" + ")));
233                    }
234                    SuggestedConstraint::Equal(a, b) => {
235                        diag.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` and `{1}` must be the same: replace one with the other",
                a, b))
    })format!(
236                            "`{a}` and `{b}` must be the same: replace one with the other",
237                        ));
238                    }
239                    SuggestedConstraint::Static(a) => {
240                        diag.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("replace `{0}` with `\'static`", a))
    })format!("replace `{a}` with `'static`"));
241                    }
242                }
243            }
244
245            diag
246        };
247
248        // We want this message to appear after other messages on the mir def.
249        let mir_span = mbcx.body.span;
250        diag.sort_span = mir_span.shrink_to_hi();
251
252        // Buffer the diagnostic
253        mbcx.buffer_non_error(diag);
254    }
255}