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    /// 'a = 'b
18    Equal(RegionName, RegionName),
19
20    /// 'a: 'static i.e. 'a = 'static and the user should just use 'static
21    Static(RegionName),
22}
23
24/// Collects information about outlives constraints that needed to be added for a given MIR node
25/// corresponding to a function definition.
26///
27/// Adds a help note suggesting adding a where clause with the needed constraints.
28#[derive(#[automatically_derived]
impl ::core::default::Default for OutlivesSuggestionBuilder {
    #[inline]
    fn default() -> Self {
        Self { constraints_to_add: ::core::default::Default::default() }
    }
}Default)]
29pub(crate) struct OutlivesSuggestionBuilder {
30    /// The list of outlives constraints that need to be added. Specifically, we map each free
31    /// region to all other regions that it must outlive. I will use the shorthand `fr:
32    /// outlived_frs`. Not all of these regions will already have names necessarily. Some could be
33    /// implicit free regions that we inferred. These will need to be given names in the final
34    /// suggestion message.
35    constraints_to_add: BTreeMap<RegionVid, Vec<RegionVid>>,
36}
37
38impl OutlivesSuggestionBuilder {
39    /// Returns `true` iff the `RegionNameSource` is a valid source for an outlives
40    /// suggestion.
41    //
42    // FIXME: Currently, we only report suggestions if the `RegionNameSource` is an early-bound
43    // region or a named region, avoiding using regions with synthetic names altogether. This
44    // allows us to avoid giving impossible suggestions (e.g. adding bounds to closure args).
45    // We can probably be less conservative, since some inferred free regions are namable (e.g.
46    // the user can explicitly name them. To do this, we would allow some regions whose names
47    // come from `MatchedAdtAndSegment`, being careful to filter out bad suggestions, such as
48    // naming the `'self` lifetime in methods, etc.
49    fn region_name_is_suggestable(name: &RegionName) -> bool {
50        match name.source {
51            RegionNameSource::NamedEarlyParamRegion(..)
52            | RegionNameSource::NamedLateParamRegion(..)
53            | RegionNameSource::Static => true,
54
55            // Don't give suggestions for upvars, closure return types, or other unnameable
56            // regions.
57            RegionNameSource::SynthesizedFreeEnvRegion(..)
58            | RegionNameSource::AnonRegionFromArgument(..)
59            | RegionNameSource::AnonRegionFromUpvar(..)
60            | RegionNameSource::AnonRegionFromOutput(..)
61            | RegionNameSource::AnonRegionFromYieldTy(..)
62            | RegionNameSource::AnonRegionFromAsyncFn(..)
63            | RegionNameSource::AnonRegionFromImplSignature(..) => {
64                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:64",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
                        ::tracing_core::__macro_support::Option::Some(64u32),
                        ::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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Region {0:?} is NOT suggestable",
                                                    name) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("Region {:?} is NOT suggestable", name);
65                false
66            }
67        }
68    }
69
70    /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`.
71    fn region_vid_to_name(
72        &self,
73        mbcx: &MirBorrowckCtxt<'_, '_, '_>,
74        region: RegionVid,
75    ) -> Option<RegionName> {
76        mbcx.give_region_a_name(region).filter(Self::region_name_is_suggestable)
77    }
78
79    /// Compiles a list of all suggestions to be printed in the final big suggestion.
80    fn compile_all_suggestions(
81        &self,
82        mbcx: &MirBorrowckCtxt<'_, '_, '_>,
83    ) -> SmallVec<[SuggestedConstraint; 2]> {
84        let mut suggested = SmallVec::new();
85
86        // Keep track of variables that we have already suggested unifying so that we don't print
87        // out silly duplicate messages.
88        let mut unified_already = FxIndexSet::default();
89
90        for (fr, outlived) in &self.constraints_to_add {
91            let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) else {
92                continue;
93            };
94
95            let outlived = outlived
96                .iter()
97                // if there is a `None`, we will just omit that constraint
98                .filter_map(|fr| self.region_vid_to_name(mbcx, *fr).map(|rname| (fr, rname)))
99                .collect::<Vec<_>>();
100
101            // No suggestable outlived lifetimes.
102            if outlived.is_empty() {
103                continue;
104            }
105
106            // There are two types of suggestions we can make:
107            // 1) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we
108            //    should just replace 'a with 'static.
109            // 2) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a
110
111            if outlived
112                .iter()
113                .any(|(_, outlived_name)| #[allow(non_exhaustive_omitted_patterns)] match outlived_name.source {
    RegionNameSource::Static => true,
    _ => false,
}matches!(outlived_name.source, RegionNameSource::Static))
114            {
115                suggested.push(SuggestedConstraint::Static(fr_name));
116            } else {
117                // We want to isolate out all lifetimes that should be unified and print out
118                // separate messages for them.
119
120                let unified = outlived.into_iter().filter(
121                    // Do we have both 'fr: 'r and 'r: 'fr?
122                    |(r, _)| {
123                        self.constraints_to_add
124                            .get(r)
125                            .is_some_and(|r_outlived| r_outlived.as_slice().contains(fr))
126                    },
127                );
128
129                for (r, bound) in unified {
130                    if !unified_already.contains(fr) {
131                        suggested.push(SuggestedConstraint::Equal(fr_name, bound));
132                        unified_already.insert(r);
133                    }
134                }
135            }
136        }
137
138        suggested
139    }
140
141    /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest.
142    pub(crate) fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) {
143        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:143",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
                        ::tracing_core::__macro_support::Option::Some(143u32),
                        ::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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Collected {0:?}: {1:?}",
                                                    fr, outlived_fr) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("Collected {:?}: {:?}", fr, outlived_fr);
144
145        // Add to set of constraints for final help note.
146        self.constraints_to_add.entry(fr).or_default().push(outlived_fr);
147    }
148
149    /// Emit an intermediate note on the given `Diag` if the involved regions are suggestable.
150    pub(crate) fn intermediate_suggestion(
151        &mut self,
152        mbcx: &MirBorrowckCtxt<'_, '_, '_>,
153        errci: &ErrorConstraintInfo<'_>,
154        diag: &mut Diag<'_>,
155    ) {
156        // Emit an intermediate note.
157        let fr_name = self.region_vid_to_name(mbcx, errci.fr);
158        let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr);
159
160        if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name)
161            && !#[allow(non_exhaustive_omitted_patterns)] match outlived_fr_name.source {
    RegionNameSource::Static => true,
    _ => false,
}matches!(outlived_fr_name.source, RegionNameSource::Static)
162        {
163            diag.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider adding the following bound: `{0}: {1}`",
                fr_name, outlived_fr_name))
    })format!(
164                "consider adding the following bound: `{fr_name}: {outlived_fr_name}`",
165            ));
166        }
167    }
168
169    /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final
170    /// suggestion including all collected constraints.
171    pub(crate) fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_, '_>) {
172        // No constraints to add? Done.
173        if self.constraints_to_add.is_empty() {
174            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:174",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs"),
                        ::tracing_core::__macro_support::Option::Some(174u32),
                        ::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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("No constraints to suggest.")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("No constraints to suggest.");
175            return;
176        }
177
178        // If there is only one constraint to suggest, then we already suggested it in the
179        // intermediate suggestion above.
180        if self.constraints_to_add.len() == 1
181            && self.constraints_to_add.values().next().unwrap().len() == 1
182        {
183            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:183",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Only 1 suggestion. Skipping.")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("Only 1 suggestion. Skipping.");
184            return;
185        }
186
187        // Get all suggestable constraints.
188        let suggested = self.compile_all_suggestions(mbcx);
189
190        // If there are no suggestable constraints...
191        if suggested.is_empty() {
192            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs:192",
                        "rustc_borrowck::diagnostics::outlives_suggestion",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5ceaf6608eb354c2f5bbb3b8d974caa367dac81c/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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Only 1 suggestable constraint. Skipping.")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("Only 1 suggestable constraint. Skipping.");
193            return;
194        }
195
196        // Emit an error with a list of one or more help suggestions. This is a weird error because
197        // it's just there to provide somewhere to put the help suggestions that describe how to
198        // fix the one or more borrow errors already reported within the item.
199        let tcx = mbcx.infcx.tcx;
200        let def_id = mbcx.mir_def_id();
201        let span = tcx.def_ident_span(def_id).unwrap_or_else(|| tcx.def_span(def_id));
202        let mut diag = tcx
203            .dcx()
204            .struct_err("one or more lifetime errors were found in this item")
205            .with_span(span);
206
207        // Add suggestions.
208        for constraint in suggested {
209            match constraint {
210                SuggestedConstraint::Equal(a, b) => {
211                    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!(
212                        "`{a}` and `{b}` must be the same: replace one with the other",
213                    ));
214                }
215                SuggestedConstraint::Static(a) => {
216                    diag.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("replace `{0}` with `\'static`", a))
    })format!("replace `{a}` with `'static`"));
217                }
218            }
219        }
220
221        // We want this message to appear after other messages on the mir def.
222        let mir_span = mbcx.body.span;
223
224        // Buffer the diagnostic
225        mbcx.buffer_error_with_sort_span(diag, mir_span.shrink_to_hi());
226    }
227}