rustc_trait_selection/error_reporting/infer/nice_region_error/
static_impl_trait.rs

1//! Error Reporting for static impl Traits.
2
3use rustc_data_structures::fx::FxIndexSet;
4use rustc_errors::{Applicability, Diag, ErrorGuaranteed};
5use rustc_hir::def_id::DefId;
6use rustc_hir::intravisit::{Visitor, VisitorExt, walk_ty};
7use rustc_hir::{
8    self as hir, AmbigArg, GenericBound, GenericParam, GenericParamKind, Item, ItemKind, Lifetime,
9    LifetimeKind, LifetimeParamKind, MissingLifetimeKind, Node, TyKind,
10};
11use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor};
12use rustc_span::def_id::LocalDefId;
13use rustc_span::{Ident, Span};
14use tracing::debug;
15
16use crate::error_reporting::infer::nice_region_error::NiceRegionError;
17use crate::errors::ButNeedsToSatisfy;
18use crate::infer::{RegionResolutionError, SubregionOrigin};
19
20impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
21    /// Print the error message for lifetime errors when the return type is a static `impl Trait`,
22    /// `dyn Trait` or if a method call on a trait object introduces a static requirement.
23    pub(super) fn try_report_static_impl_trait(&self) -> Option<ErrorGuaranteed> {
24        debug!("try_report_static_impl_trait(error={:?})", self.error);
25        let tcx = self.tcx();
26        let (var_origin, sub_origin, sub_r, sup_origin, sup_r, spans) = match self.error.as_ref()? {
27            RegionResolutionError::SubSupConflict(
28                _,
29                var_origin,
30                sub_origin,
31                sub_r,
32                sup_origin,
33                sup_r,
34                spans,
35            ) if sub_r.is_static() => (var_origin, sub_origin, sub_r, sup_origin, sup_r, spans),
36            _ => return None,
37        };
38        debug!(
39            "try_report_static_impl_trait(var={:?}, sub={:?} {:?} sup={:?} {:?})",
40            var_origin, sub_origin, sub_r, sup_origin, sup_r
41        );
42        let anon_reg_sup = tcx.is_suitable_region(self.generic_param_scope, *sup_r)?;
43        debug!("try_report_static_impl_trait: anon_reg_sup={:?}", anon_reg_sup);
44        let sp = var_origin.span();
45        let return_sp = sub_origin.span();
46        let param = self.find_param_with_region(*sup_r, *sub_r)?;
47        let simple_ident = param.param.pat.simple_ident();
48        let lifetime_name = if sup_r.has_name() { sup_r.to_string() } else { "'_".to_owned() };
49
50        let (mention_influencer, influencer_point) =
51            if sup_origin.span().overlaps(param.param_ty_span) {
52                // Account for `async fn` like in `async-await/issues/issue-62097.rs`.
53                // The desugaring of `async fn`s causes `sup_origin` and `param` to point at the same
54                // place (but with different `ctxt`, hence `overlaps` instead of `==` above).
55                //
56                // This avoids the following:
57                //
58                // LL |     pub async fn run_dummy_fn(&self) {
59                //    |                               ^^^^^
60                //    |                               |
61                //    |                               this data with an anonymous lifetime `'_`...
62                //    |                               ...is captured here...
63                (false, sup_origin.span())
64            } else {
65                (!sup_origin.span().overlaps(return_sp), param.param_ty_span)
66            };
67
68        debug!("try_report_static_impl_trait: param_info={:?}", param);
69
70        let mut spans = spans.clone();
71
72        if mention_influencer {
73            spans.push(sup_origin.span());
74        }
75        // We dedup the spans *ignoring* expansion context.
76        spans.sort();
77        spans.dedup_by_key(|span| (span.lo(), span.hi()));
78
79        // We try to make the output have fewer overlapping spans if possible.
80        let require_span =
81            if sup_origin.span().overlaps(return_sp) { sup_origin.span() } else { return_sp };
82
83        let spans_empty = spans.is_empty();
84        let require_as_note = spans.iter().any(|sp| sp.overlaps(return_sp) || *sp > return_sp);
85        let bound = if let SubregionOrigin::RelateParamBound(_, _, Some(bound)) = sub_origin {
86            Some(*bound)
87        } else {
88            None
89        };
90
91        let diag = ButNeedsToSatisfy {
92            sp,
93            influencer_point,
94            spans: spans.clone(),
95            // If any of the "captured here" labels appears on the same line or after
96            // `require_span`, we put it on a note to ensure the text flows by appearing
97            // always at the end.
98            require_span_as_note: require_as_note.then_some(require_span),
99            // We don't need a note, it's already at the end, it can be shown as a `span_label`.
100            require_span_as_label: (!require_as_note).then_some(require_span),
101
102            has_lifetime: sup_r.has_name(),
103            lifetime: lifetime_name.clone(),
104            has_param_name: simple_ident.is_some(),
105            param_name: simple_ident.map(|x| x.to_string()).unwrap_or_default(),
106            spans_empty,
107            bound,
108        };
109
110        let mut err = self.tcx().dcx().create_err(diag);
111
112        let fn_returns = tcx.return_type_impl_or_dyn_traits(anon_reg_sup.scope);
113
114        let arg = match param.param.pat.simple_ident() {
115            Some(simple_ident) => format!("argument `{simple_ident}`"),
116            None => "the argument".to_string(),
117        };
118        let captures = format!("captures data from {arg}");
119        suggest_new_region_bound(
120            tcx,
121            &mut err,
122            fn_returns,
123            lifetime_name,
124            Some(arg),
125            captures,
126            Some((param.param_ty_span, param.param_ty.to_string())),
127            Some(anon_reg_sup.scope),
128        );
129
130        let reported = err.emit();
131        Some(reported)
132    }
133}
134
135pub fn suggest_new_region_bound(
136    tcx: TyCtxt<'_>,
137    err: &mut Diag<'_>,
138    fn_returns: Vec<&rustc_hir::Ty<'_>>,
139    lifetime_name: String,
140    arg: Option<String>,
141    captures: String,
142    param: Option<(Span, String)>,
143    scope_def_id: Option<LocalDefId>,
144) {
145    debug!("try_report_static_impl_trait: fn_return={:?}", fn_returns);
146    // FIXME: account for the need of parens in `&(dyn Trait + '_)`
147    let consider = "consider changing";
148    let declare = "to declare that";
149    let explicit = format!("you can add an explicit `{lifetime_name}` lifetime bound");
150    let explicit_static =
151        arg.map(|arg| format!("explicit `'static` bound to the lifetime of {arg}"));
152    let add_static_bound = "alternatively, add an explicit `'static` bound to this reference";
153    let plus_lt = format!(" + {lifetime_name}");
154    for fn_return in fn_returns {
155        if fn_return.span.desugaring_kind().is_some() {
156            // Skip `async` desugaring `impl Future`.
157            continue;
158        }
159        match fn_return.kind {
160            // FIXME(precise_captures): Suggest adding to `use<...>` list instead.
161            TyKind::OpaqueDef(opaque) => {
162                // Get the identity type for this RPIT
163                let did = opaque.def_id.to_def_id();
164                let ty = Ty::new_opaque(tcx, did, ty::GenericArgs::identity_for_item(tcx, did));
165
166                if let Some(span) = opaque.bounds.iter().find_map(|arg| match arg {
167                    GenericBound::Outlives(Lifetime {
168                        kind: LifetimeKind::Static, ident, ..
169                    }) => Some(ident.span),
170                    _ => None,
171                }) {
172                    if let Some(explicit_static) = &explicit_static {
173                        err.span_suggestion_verbose(
174                            span,
175                            format!("{consider} `{ty}`'s {explicit_static}"),
176                            &lifetime_name,
177                            Applicability::MaybeIncorrect,
178                        );
179                    }
180                    if let Some((param_span, ref param_ty)) = param {
181                        err.span_suggestion_verbose(
182                            param_span,
183                            add_static_bound,
184                            param_ty,
185                            Applicability::MaybeIncorrect,
186                        );
187                    }
188                } else if opaque.bounds.iter().any(|arg| {
189                    matches!(arg,
190                        GenericBound::Outlives(Lifetime { ident, .. })
191                        if ident.name.to_string() == lifetime_name )
192                }) {
193                } else {
194                    // get a lifetime name of existing named lifetimes if any
195                    let existing_lt_name = if let Some(id) = scope_def_id
196                        && let Some(generics) = tcx.hir_get_generics(id)
197                        && let named_lifetimes = generics
198                            .params
199                            .iter()
200                            .filter(|p| {
201                                matches!(
202                                    p.kind,
203                                    GenericParamKind::Lifetime {
204                                        kind: hir::LifetimeParamKind::Explicit
205                                    }
206                                )
207                            })
208                            .map(|p| {
209                                if let hir::ParamName::Plain(name) = p.name {
210                                    Some(name.to_string())
211                                } else {
212                                    None
213                                }
214                            })
215                            .filter(|n| !matches!(n, None))
216                            .collect::<Vec<_>>()
217                        && named_lifetimes.len() > 0
218                    {
219                        named_lifetimes[0].clone()
220                    } else {
221                        None
222                    };
223                    let name = if let Some(name) = &existing_lt_name { name } else { "'a" };
224                    // if there are more than one elided lifetimes in inputs, the explicit `'_` lifetime cannot be used.
225                    // introducing a new lifetime `'a` or making use of one from existing named lifetimes if any
226                    if let Some(id) = scope_def_id
227                        && let Some(generics) = tcx.hir_get_generics(id)
228                        && let mut spans_suggs =
229                            make_elided_region_spans_suggs(name, generics.params.iter())
230                        && spans_suggs.len() > 1
231                    {
232                        let use_lt = if existing_lt_name == None {
233                            spans_suggs.push((generics.span.shrink_to_hi(), format!("<{name}>")));
234                            format!("you can introduce a named lifetime parameter `{name}`")
235                        } else {
236                            // make use the existing named lifetime
237                            format!("you can use the named lifetime parameter `{name}`")
238                        };
239                        spans_suggs.push((fn_return.span.shrink_to_hi(), format!(" + {name} ")));
240                        err.multipart_suggestion_verbose(
241                            format!("{declare} `{ty}` {captures}, {use_lt}",),
242                            spans_suggs,
243                            Applicability::MaybeIncorrect,
244                        );
245                    } else {
246                        err.span_suggestion_verbose(
247                            fn_return.span.shrink_to_hi(),
248                            format!("{declare} `{ty}` {captures}, {explicit}",),
249                            &plus_lt,
250                            Applicability::MaybeIncorrect,
251                        );
252                    }
253                }
254            }
255            TyKind::TraitObject(_, lt) => {
256                if let LifetimeKind::ImplicitObjectLifetimeDefault = lt.kind {
257                    err.span_suggestion_verbose(
258                        fn_return.span.shrink_to_hi(),
259                        format!("{declare} the trait object {captures}, {explicit}",),
260                        &plus_lt,
261                        Applicability::MaybeIncorrect,
262                    );
263                } else if lt.ident.name.to_string() != lifetime_name {
264                    // With this check we avoid suggesting redundant bounds. This
265                    // would happen if there are nested impl/dyn traits and only
266                    // one of them has the bound we'd suggest already there, like
267                    // in `impl Foo<X = dyn Bar> + '_`.
268                    if let Some(explicit_static) = &explicit_static {
269                        err.span_suggestion_verbose(
270                            lt.ident.span,
271                            format!("{consider} the trait object's {explicit_static}"),
272                            &lifetime_name,
273                            Applicability::MaybeIncorrect,
274                        );
275                    }
276                    if let Some((param_span, param_ty)) = param.clone() {
277                        err.span_suggestion_verbose(
278                            param_span,
279                            add_static_bound,
280                            param_ty,
281                            Applicability::MaybeIncorrect,
282                        );
283                    }
284                }
285            }
286            _ => {}
287        }
288    }
289}
290
291fn make_elided_region_spans_suggs<'a>(
292    name: &str,
293    generic_params: impl Iterator<Item = &'a GenericParam<'a>>,
294) -> Vec<(Span, String)> {
295    let mut spans_suggs = Vec::new();
296    let mut bracket_span = None;
297    let mut consecutive_brackets = 0;
298
299    let mut process_consecutive_brackets =
300        |span: Option<Span>, spans_suggs: &mut Vec<(Span, String)>| {
301            if let Some(span) = span
302                && bracket_span.is_none_or(|bracket_span| span == bracket_span)
303            {
304                consecutive_brackets += 1;
305            } else if let Some(bracket_span) = bracket_span.take() {
306                let sugg = std::iter::once("<")
307                    .chain(std::iter::repeat(name).take(consecutive_brackets).intersperse(", "))
308                    .chain([">"])
309                    .collect();
310                spans_suggs.push((bracket_span.shrink_to_hi(), sugg));
311                consecutive_brackets = 0;
312            }
313            bracket_span = span;
314        };
315
316    for p in generic_params {
317        if let GenericParamKind::Lifetime { kind: LifetimeParamKind::Elided(kind) } = p.kind {
318            match kind {
319                MissingLifetimeKind::Underscore => {
320                    process_consecutive_brackets(None, &mut spans_suggs);
321                    spans_suggs.push((p.span, name.to_string()))
322                }
323                MissingLifetimeKind::Ampersand => {
324                    process_consecutive_brackets(None, &mut spans_suggs);
325                    spans_suggs.push((p.span.shrink_to_hi(), format!("{name} ")));
326                }
327                MissingLifetimeKind::Comma => {
328                    process_consecutive_brackets(None, &mut spans_suggs);
329                    spans_suggs.push((p.span.shrink_to_hi(), format!("{name}, ")));
330                }
331                MissingLifetimeKind::Brackets => {
332                    process_consecutive_brackets(Some(p.span), &mut spans_suggs);
333                }
334            }
335        }
336    }
337    process_consecutive_brackets(None, &mut spans_suggs);
338
339    spans_suggs
340}
341
342impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
343    pub fn get_impl_ident_and_self_ty_from_trait(
344        tcx: TyCtxt<'tcx>,
345        def_id: DefId,
346        trait_objects: &FxIndexSet<DefId>,
347    ) -> Option<(Ident, &'tcx hir::Ty<'tcx>)> {
348        match tcx.hir_get_if_local(def_id)? {
349            Node::ImplItem(impl_item) => {
350                let impl_did = tcx.hir_get_parent_item(impl_item.hir_id());
351                if let hir::OwnerNode::Item(Item {
352                    kind: ItemKind::Impl(hir::Impl { self_ty, .. }),
353                    ..
354                }) = tcx.hir_owner_node(impl_did)
355                {
356                    Some((impl_item.ident, self_ty))
357                } else {
358                    None
359                }
360            }
361            Node::TraitItem(trait_item) => {
362                let trait_id = tcx.hir_get_parent_item(trait_item.hir_id());
363                debug_assert_eq!(tcx.def_kind(trait_id.def_id), hir::def::DefKind::Trait);
364                // The method being called is defined in the `trait`, but the `'static`
365                // obligation comes from the `impl`. Find that `impl` so that we can point
366                // at it in the suggestion.
367                let trait_did = trait_id.to_def_id();
368                tcx.local_trait_impls(trait_did).iter().find_map(|&impl_did| {
369                    if let Node::Item(Item {
370                        kind: ItemKind::Impl(hir::Impl { self_ty, .. }), ..
371                    }) = tcx.hir_node_by_def_id(impl_did)
372                        && trait_objects.iter().all(|did| {
373                            // FIXME: we should check `self_ty`, but for now, use
374                            // this imperfect proxy. This will fail if there are
375                            // multiple `impl`s for the same trait like
376                            // `impl Foo for Box<dyn Bar>` and `impl Foo for dyn Bar`.
377                            // In that case, only the first one will get suggestions.
378                            let mut traits = vec![];
379                            let mut hir_v = HirTraitObjectVisitor(&mut traits, *did);
380                            hir_v.visit_ty_unambig(self_ty);
381                            !traits.is_empty()
382                        })
383                    {
384                        Some((trait_item.ident, *self_ty))
385                    } else {
386                        None
387                    }
388                })
389            }
390            _ => None,
391        }
392    }
393}
394
395/// Collect all the trait objects in a type that could have received an implicit `'static` lifetime.
396pub struct TraitObjectVisitor(pub FxIndexSet<DefId>);
397
398impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for TraitObjectVisitor {
399    fn visit_ty(&mut self, t: Ty<'tcx>) {
400        match t.kind() {
401            ty::Dynamic(preds, re, _) if re.is_static() => {
402                if let Some(def_id) = preds.principal_def_id() {
403                    self.0.insert(def_id);
404                }
405            }
406            _ => t.super_visit_with(self),
407        }
408    }
409}
410
411/// Collect all `hir::Ty<'_>` `Span`s for trait objects with an implicit lifetime.
412pub struct HirTraitObjectVisitor<'a>(pub &'a mut Vec<Span>, pub DefId);
413
414impl<'a, 'tcx> Visitor<'tcx> for HirTraitObjectVisitor<'a> {
415    fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx, AmbigArg>) {
416        if let TyKind::TraitObject(poly_trait_refs, lifetime_ptr) = t.kind
417            && let Lifetime { kind: LifetimeKind::ImplicitObjectLifetimeDefault, .. } =
418                lifetime_ptr.pointer()
419        {
420            for ptr in poly_trait_refs {
421                if Some(self.1) == ptr.trait_ref.trait_def_id() {
422                    self.0.push(ptr.span);
423                }
424            }
425        }
426        walk_ty(self, t);
427    }
428}