]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
Merge commit '1d8491b120223272b13451fc81265aa64f7f4d5b' into sync-from-rustfmt
[rust.git] / compiler / rustc_infer / src / infer / error_reporting / nice_region_error / static_impl_trait.rs
1 //! Error Reporting for static impl Traits.
2
3 use crate::errors::{
4     ButCallingIntroduces, ButNeedsToSatisfy, DynTraitConstraintSuggestion, MoreTargeted,
5     ReqIntroducedLocations,
6 };
7 use crate::infer::error_reporting::nice_region_error::NiceRegionError;
8 use crate::infer::lexical_region_resolve::RegionResolutionError;
9 use crate::infer::{SubregionOrigin, TypeTrace};
10 use crate::traits::{ObligationCauseCode, UnifyReceiverContext};
11 use rustc_data_structures::fx::FxIndexSet;
12 use rustc_errors::{AddToDiagnostic, Applicability, Diagnostic, ErrorGuaranteed, MultiSpan};
13 use rustc_hir::def_id::DefId;
14 use rustc_hir::intravisit::{walk_ty, Visitor};
15 use rustc_hir::{
16     self as hir, GenericBound, GenericParamKind, Item, ItemKind, Lifetime, LifetimeName, Node,
17     TyKind,
18 };
19 use rustc_middle::ty::{
20     self, AssocItemContainer, StaticLifetimeVisitor, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor,
21 };
22 use rustc_span::symbol::Ident;
23 use rustc_span::Span;
24
25 use rustc_span::def_id::LocalDefId;
26 use std::ops::ControlFlow;
27
28 impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
29     /// Print the error message for lifetime errors when the return type is a static `impl Trait`,
30     /// `dyn Trait` or if a method call on a trait object introduces a static requirement.
31     pub(super) fn try_report_static_impl_trait(&self) -> Option<ErrorGuaranteed> {
32         debug!("try_report_static_impl_trait(error={:?})", self.error);
33         let tcx = self.tcx();
34         let (var_origin, sub_origin, sub_r, sup_origin, sup_r, spans) = match self.error.as_ref()? {
35             RegionResolutionError::SubSupConflict(
36                 _,
37                 var_origin,
38                 sub_origin,
39                 sub_r,
40                 sup_origin,
41                 sup_r,
42                 spans,
43             ) if sub_r.is_static() => (var_origin, sub_origin, sub_r, sup_origin, sup_r, spans),
44             RegionResolutionError::ConcreteFailure(
45                 SubregionOrigin::Subtype(box TypeTrace { cause, .. }),
46                 sub_r,
47                 sup_r,
48             ) if sub_r.is_static() => {
49                 // This is for an implicit `'static` requirement coming from `impl dyn Trait {}`.
50                 if let ObligationCauseCode::UnifyReceiver(ctxt) = cause.code() {
51                     // This may have a closure and it would cause ICE
52                     // through `find_param_with_region` (#78262).
53                     let anon_reg_sup = tcx.is_suitable_region(*sup_r)?;
54                     let fn_returns = tcx.return_type_impl_or_dyn_traits(anon_reg_sup.def_id);
55                     if fn_returns.is_empty() {
56                         return None;
57                     }
58
59                     let param = self.find_param_with_region(*sup_r, *sub_r)?;
60                     let simple_ident = param.param.pat.simple_ident();
61
62                     let (has_impl_path, impl_path) = match ctxt.assoc_item.container {
63                         AssocItemContainer::TraitContainer => {
64                             let id = ctxt.assoc_item.container_id(tcx);
65                             (true, tcx.def_path_str(id))
66                         }
67                         AssocItemContainer::ImplContainer => (false, String::new()),
68                     };
69
70                     let mut err = self.tcx().sess.create_err(ButCallingIntroduces {
71                         param_ty_span: param.param_ty_span,
72                         cause_span: cause.span,
73                         has_param_name: simple_ident.is_some(),
74                         param_name: simple_ident.map(|x| x.to_string()).unwrap_or_default(),
75                         has_lifetime: sup_r.has_name(),
76                         lifetime: sup_r.to_string(),
77                         assoc_item: ctxt.assoc_item.name,
78                         has_impl_path,
79                         impl_path,
80                     });
81                     if self.find_impl_on_dyn_trait(&mut err, param.param_ty, &ctxt) {
82                         let reported = err.emit();
83                         return Some(reported);
84                     } else {
85                         err.cancel()
86                     }
87                 }
88                 return None;
89             }
90             _ => return None,
91         };
92         debug!(
93             "try_report_static_impl_trait(var={:?}, sub={:?} {:?} sup={:?} {:?})",
94             var_origin, sub_origin, sub_r, sup_origin, sup_r
95         );
96         let anon_reg_sup = tcx.is_suitable_region(*sup_r)?;
97         debug!("try_report_static_impl_trait: anon_reg_sup={:?}", anon_reg_sup);
98         let sp = var_origin.span();
99         let return_sp = sub_origin.span();
100         let param = self.find_param_with_region(*sup_r, *sub_r)?;
101         let simple_ident = param.param.pat.simple_ident();
102         let lifetime_name = if sup_r.has_name() { sup_r.to_string() } else { "'_".to_owned() };
103
104         let (mention_influencer, influencer_point) =
105             if sup_origin.span().overlaps(param.param_ty_span) {
106                 // Account for `async fn` like in `async-await/issues/issue-62097.rs`.
107                 // The desugaring of `async `fn`s causes `sup_origin` and `param` to point at the same
108                 // place (but with different `ctxt`, hence `overlaps` instead of `==` above).
109                 //
110                 // This avoids the following:
111                 //
112                 // LL |     pub async fn run_dummy_fn(&self) {
113                 //    |                               ^^^^^
114                 //    |                               |
115                 //    |                               this data with an anonymous lifetime `'_`...
116                 //    |                               ...is captured here...
117                 (false, sup_origin.span())
118             } else {
119                 (!sup_origin.span().overlaps(return_sp), param.param_ty_span)
120             };
121
122         debug!("try_report_static_impl_trait: param_info={:?}", param);
123
124         let mut spans = spans.clone();
125
126         if mention_influencer {
127             spans.push(sup_origin.span());
128         }
129         // We dedup the spans *ignoring* expansion context.
130         spans.sort();
131         spans.dedup_by_key(|span| (span.lo(), span.hi()));
132
133         // We try to make the output have fewer overlapping spans if possible.
134         let require_span =
135             if sup_origin.span().overlaps(return_sp) { sup_origin.span() } else { return_sp };
136
137         let spans_empty = spans.is_empty();
138         let require_as_note = spans.iter().any(|sp| sp.overlaps(return_sp) || *sp > return_sp);
139         let bound = if let SubregionOrigin::RelateParamBound(_, _, Some(bound)) = sub_origin {
140             Some(*bound)
141         } else {
142             None
143         };
144
145         let mut subdiag = None;
146
147         if let SubregionOrigin::Subtype(box TypeTrace { cause, .. }) = sub_origin {
148             if let ObligationCauseCode::ReturnValue(hir_id)
149             | ObligationCauseCode::BlockTailExpression(hir_id) = cause.code()
150             {
151                 let parent_id = tcx.hir().get_parent_item(*hir_id);
152                 if let Some(fn_decl) = tcx.hir().fn_decl_by_hir_id(parent_id.into()) {
153                     let mut span: MultiSpan = fn_decl.output.span().into();
154                     let mut spans = Vec::new();
155                     let mut add_label = true;
156                     if let hir::FnRetTy::Return(ty) = fn_decl.output {
157                         let mut v = StaticLifetimeVisitor(vec![], tcx.hir());
158                         v.visit_ty(ty);
159                         if !v.0.is_empty() {
160                             span = v.0.clone().into();
161                             spans = v.0;
162                             add_label = false;
163                         }
164                     }
165                     let fn_decl_span = fn_decl.output.span();
166
167                     subdiag = Some(ReqIntroducedLocations {
168                         span,
169                         spans,
170                         fn_decl_span,
171                         cause_span: cause.span,
172                         add_label,
173                     });
174                 }
175             }
176         }
177
178         let diag = ButNeedsToSatisfy {
179             sp,
180             influencer_point,
181             spans: spans.clone(),
182             // If any of the "captured here" labels appears on the same line or after
183             // `require_span`, we put it on a note to ensure the text flows by appearing
184             // always at the end.
185             require_span_as_note: require_as_note.then_some(require_span),
186             // We don't need a note, it's already at the end, it can be shown as a `span_label`.
187             require_span_as_label: (!require_as_note).then_some(require_span),
188             req_introduces_loc: subdiag,
189
190             has_lifetime: sup_r.has_name(),
191             lifetime: lifetime_name.clone(),
192             has_param_name: simple_ident.is_some(),
193             param_name: simple_ident.map(|x| x.to_string()).unwrap_or_default(),
194             spans_empty,
195             bound,
196         };
197
198         let mut err = self.tcx().sess.create_err(diag);
199
200         let fn_returns = tcx.return_type_impl_or_dyn_traits(anon_reg_sup.def_id);
201
202         let mut override_error_code = None;
203         if let SubregionOrigin::Subtype(box TypeTrace { cause, .. }) = &sup_origin
204             && let ObligationCauseCode::UnifyReceiver(ctxt) = cause.code()
205             // Handle case of `impl Foo for dyn Bar { fn qux(&self) {} }` introducing a
206             // `'static` lifetime when called as a method on a binding: `bar.qux()`.
207             && self.find_impl_on_dyn_trait(&mut err, param.param_ty, &ctxt)
208         {
209             override_error_code = Some(ctxt.assoc_item.name);
210         }
211
212         if let SubregionOrigin::Subtype(box TypeTrace { cause, .. }) = &sub_origin
213             && let code = match cause.code() {
214                 ObligationCauseCode::MatchImpl(parent, ..) => parent.code(),
215                 _ => cause.code(),
216             }
217             && let (&ObligationCauseCode::ItemObligation(item_def_id) | &ObligationCauseCode::ExprItemObligation(item_def_id, ..), None) = (code, override_error_code)
218         {
219             // Same case of `impl Foo for dyn Bar { fn qux(&self) {} }` introducing a `'static`
220             // lifetime as above, but called using a fully-qualified path to the method:
221             // `Foo::qux(bar)`.
222             let mut v = TraitObjectVisitor(FxIndexSet::default());
223             v.visit_ty(param.param_ty);
224             if let Some((ident, self_ty)) =
225                 NiceRegionError::get_impl_ident_and_self_ty_from_trait(tcx, item_def_id, &v.0)
226                 && self.suggest_constrain_dyn_trait_in_impl(&mut err, &v.0, ident, self_ty)
227             {
228                 override_error_code = Some(ident.name);
229             }
230         }
231         if let (Some(ident), true) = (override_error_code, fn_returns.is_empty()) {
232             // Provide a more targeted error code and description.
233             let retarget_subdiag = MoreTargeted { ident };
234             retarget_subdiag.add_to_diagnostic(&mut err);
235         }
236
237         let arg = match param.param.pat.simple_ident() {
238             Some(simple_ident) => format!("argument `{}`", simple_ident),
239             None => "the argument".to_string(),
240         };
241         let captures = format!("captures data from {}", arg);
242         suggest_new_region_bound(
243             tcx,
244             &mut err,
245             fn_returns,
246             lifetime_name,
247             Some(arg),
248             captures,
249             Some((param.param_ty_span, param.param_ty.to_string())),
250             Some(anon_reg_sup.def_id),
251         );
252
253         let reported = err.emit();
254         Some(reported)
255     }
256 }
257
258 pub fn suggest_new_region_bound(
259     tcx: TyCtxt<'_>,
260     err: &mut Diagnostic,
261     fn_returns: Vec<&rustc_hir::Ty<'_>>,
262     lifetime_name: String,
263     arg: Option<String>,
264     captures: String,
265     param: Option<(Span, String)>,
266     scope_def_id: Option<LocalDefId>,
267 ) {
268     debug!("try_report_static_impl_trait: fn_return={:?}", fn_returns);
269     // FIXME: account for the need of parens in `&(dyn Trait + '_)`
270     let consider = "consider changing";
271     let declare = "to declare that";
272     let explicit = format!("you can add an explicit `{}` lifetime bound", lifetime_name);
273     let explicit_static =
274         arg.map(|arg| format!("explicit `'static` bound to the lifetime of {}", arg));
275     let add_static_bound = "alternatively, add an explicit `'static` bound to this reference";
276     let plus_lt = format!(" + {}", lifetime_name);
277     for fn_return in fn_returns {
278         if fn_return.span.desugaring_kind().is_some() {
279             // Skip `async` desugaring `impl Future`.
280             continue;
281         }
282         match fn_return.kind {
283             TyKind::OpaqueDef(item_id, _, _) => {
284                 let item = tcx.hir().item(item_id);
285                 let ItemKind::OpaqueTy(opaque) = &item.kind else {
286                     return;
287                 };
288
289                 // Get the identity type for this RPIT
290                 let did = item_id.owner_id.to_def_id();
291                 let ty = tcx.mk_opaque(did, ty::InternalSubsts::identity_for_item(tcx, did));
292
293                 if let Some(span) = opaque.bounds.iter().find_map(|arg| match arg {
294                     GenericBound::Outlives(Lifetime {
295                         res: LifetimeName::Static, ident, ..
296                     }) => Some(ident.span),
297                     _ => None,
298                 }) {
299                     if let Some(explicit_static) = &explicit_static {
300                         err.span_suggestion_verbose(
301                             span,
302                             &format!("{consider} `{ty}`'s {explicit_static}"),
303                             &lifetime_name,
304                             Applicability::MaybeIncorrect,
305                         );
306                     }
307                     if let Some((param_span, ref param_ty)) = param {
308                         err.span_suggestion_verbose(
309                             param_span,
310                             add_static_bound,
311                             param_ty,
312                             Applicability::MaybeIncorrect,
313                         );
314                     }
315                 } else if opaque.bounds.iter().any(|arg| match arg {
316                     GenericBound::Outlives(Lifetime { ident, .. })
317                         if ident.name.to_string() == lifetime_name =>
318                     {
319                         true
320                     }
321                     _ => false,
322                 }) {
323                 } else {
324                     // get a lifetime name of existing named lifetimes if any
325                     let existing_lt_name = if let Some(id) = scope_def_id
326                         && let Some(generics) = tcx.hir().get_generics(id)
327                         && let named_lifetimes = generics
328                         .params
329                         .iter()
330                         .filter(|p| matches!(p.kind, GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit }))
331                         .map(|p| { if let hir::ParamName::Plain(name) = p.name {Some(name.to_string())} else {None}})
332                         .filter(|n| ! matches!(n, None))
333                         .collect::<Vec<_>>()
334                         && named_lifetimes.len() > 0 {
335                         named_lifetimes[0].clone()
336                     } else {
337                         None
338                     };
339                     let name = if let Some(name) = &existing_lt_name {
340                         format!("{}", name)
341                     } else {
342                         format!("'a")
343                     };
344                     // if there are more than one elided lifetimes in inputs, the explicit `'_` lifetime cannot be used.
345                     // introducing a new lifetime `'a` or making use of one from existing named lifetimes if any
346                     if let Some(id) = scope_def_id
347                         && let Some(generics) = tcx.hir().get_generics(id)
348                         && let mut spans_suggs = generics
349                             .params
350                             .iter()
351                             .filter(|p| p.is_elided_lifetime())
352                             .map(|p|
353                                   if p.span.hi() - p.span.lo() == rustc_span::BytePos(1) { // Ampersand (elided without '_)
354                                       (p.span.shrink_to_hi(),format!("{name} "))
355                                   } else { // Underscore (elided with '_)
356                                       (p.span, format!("{name}"))
357                                   }
358                             )
359                             .collect::<Vec<_>>()
360                         && spans_suggs.len() > 1
361                     {
362                         let use_lt =
363                         if existing_lt_name == None {
364                             spans_suggs.push((generics.span.shrink_to_hi(), format!("<{name}>")));
365                             format!("you can introduce a named lifetime parameter `{name}`")
366                         } else {
367                             // make use the existing named lifetime
368                             format!("you can use the named lifetime parameter `{name}`")
369                         };
370                         spans_suggs
371                             .push((fn_return.span.shrink_to_hi(), format!(" + {name} ")));
372                         err.multipart_suggestion_verbose(
373                             &format!(
374                                 "{declare} `{ty}` {captures}, {use_lt}",
375                             ),
376                             spans_suggs,
377                             Applicability::MaybeIncorrect,
378                         );
379                     } else {
380                         err.span_suggestion_verbose(
381                             fn_return.span.shrink_to_hi(),
382                             &format!("{declare} `{ty}` {captures}, {explicit}",),
383                             &plus_lt,
384                             Applicability::MaybeIncorrect,
385                         );
386                     }
387                 }
388             }
389             TyKind::TraitObject(_, lt, _) => {
390                 if let LifetimeName::ImplicitObjectLifetimeDefault = lt.res {
391                     err.span_suggestion_verbose(
392                         fn_return.span.shrink_to_hi(),
393                         &format!(
394                             "{declare} the trait object {captures}, {explicit}",
395                             declare = declare,
396                             captures = captures,
397                             explicit = explicit,
398                         ),
399                         &plus_lt,
400                         Applicability::MaybeIncorrect,
401                     );
402                 } else if lt.ident.name.to_string() != lifetime_name {
403                     // With this check we avoid suggesting redundant bounds. This
404                     // would happen if there are nested impl/dyn traits and only
405                     // one of them has the bound we'd suggest already there, like
406                     // in `impl Foo<X = dyn Bar> + '_`.
407                     if let Some(explicit_static) = &explicit_static {
408                         err.span_suggestion_verbose(
409                             lt.ident.span,
410                             &format!("{} the trait object's {}", consider, explicit_static),
411                             &lifetime_name,
412                             Applicability::MaybeIncorrect,
413                         );
414                     }
415                     if let Some((param_span, param_ty)) = param.clone() {
416                         err.span_suggestion_verbose(
417                             param_span,
418                             add_static_bound,
419                             param_ty,
420                             Applicability::MaybeIncorrect,
421                         );
422                     }
423                 }
424             }
425             _ => {}
426         }
427     }
428 }
429
430 impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
431     pub fn get_impl_ident_and_self_ty_from_trait(
432         tcx: TyCtxt<'tcx>,
433         def_id: DefId,
434         trait_objects: &FxIndexSet<DefId>,
435     ) -> Option<(Ident, &'tcx hir::Ty<'tcx>)> {
436         match tcx.hir().get_if_local(def_id)? {
437             Node::ImplItem(impl_item) => {
438                 let impl_did = tcx.hir().get_parent_item(impl_item.hir_id());
439                 if let hir::OwnerNode::Item(Item {
440                     kind: ItemKind::Impl(hir::Impl { self_ty, .. }),
441                     ..
442                 }) = tcx.hir().owner(impl_did)
443                 {
444                     Some((impl_item.ident, self_ty))
445                 } else {
446                     None
447                 }
448             }
449             Node::TraitItem(trait_item) => {
450                 let trait_id = tcx.hir().get_parent_item(trait_item.hir_id());
451                 debug_assert_eq!(tcx.def_kind(trait_id.def_id), hir::def::DefKind::Trait);
452                 // The method being called is defined in the `trait`, but the `'static`
453                 // obligation comes from the `impl`. Find that `impl` so that we can point
454                 // at it in the suggestion.
455                 let trait_did = trait_id.to_def_id();
456                 tcx.hir().trait_impls(trait_did).iter().find_map(|&impl_did| {
457                     if let Node::Item(Item {
458                         kind: ItemKind::Impl(hir::Impl { self_ty, .. }),
459                         ..
460                     }) = tcx.hir().find_by_def_id(impl_did)?
461                         && trait_objects.iter().all(|did| {
462                             // FIXME: we should check `self_ty` against the receiver
463                             // type in the `UnifyReceiver` context, but for now, use
464                             // this imperfect proxy. This will fail if there are
465                             // multiple `impl`s for the same trait like
466                             // `impl Foo for Box<dyn Bar>` and `impl Foo for dyn Bar`.
467                             // In that case, only the first one will get suggestions.
468                             let mut traits = vec![];
469                             let mut hir_v = HirTraitObjectVisitor(&mut traits, *did);
470                             hir_v.visit_ty(self_ty);
471                             !traits.is_empty()
472                         })
473                     {
474                         Some((trait_item.ident, *self_ty))
475                     } else {
476                         None
477                     }
478                 })
479             }
480             _ => None,
481         }
482     }
483
484     /// When we call a method coming from an `impl Foo for dyn Bar`, `dyn Bar` introduces a default
485     /// `'static` obligation. Suggest relaxing that implicit bound.
486     fn find_impl_on_dyn_trait(
487         &self,
488         err: &mut Diagnostic,
489         ty: Ty<'_>,
490         ctxt: &UnifyReceiverContext<'tcx>,
491     ) -> bool {
492         let tcx = self.tcx();
493
494         // Find the method being called.
495         let Ok(Some(instance)) = ty::Instance::resolve(
496             tcx,
497             ctxt.param_env,
498             ctxt.assoc_item.def_id,
499             self.cx.resolve_vars_if_possible(ctxt.substs),
500         ) else {
501             return false;
502         };
503
504         let mut v = TraitObjectVisitor(FxIndexSet::default());
505         v.visit_ty(ty);
506
507         // Get the `Ident` of the method being called and the corresponding `impl` (to point at
508         // `Bar` in `impl Foo for dyn Bar {}` and the definition of the method being called).
509         let Some((ident, self_ty)) = NiceRegionError::get_impl_ident_and_self_ty_from_trait(tcx, instance.def_id(), &v.0) else {
510             return false;
511         };
512
513         // Find the trait object types in the argument, so we point at *only* the trait object.
514         self.suggest_constrain_dyn_trait_in_impl(err, &v.0, ident, self_ty)
515     }
516
517     fn suggest_constrain_dyn_trait_in_impl(
518         &self,
519         err: &mut Diagnostic,
520         found_dids: &FxIndexSet<DefId>,
521         ident: Ident,
522         self_ty: &hir::Ty<'_>,
523     ) -> bool {
524         let mut suggested = false;
525         for found_did in found_dids {
526             let mut traits = vec![];
527             let mut hir_v = HirTraitObjectVisitor(&mut traits, *found_did);
528             hir_v.visit_ty(&self_ty);
529             for &span in &traits {
530                 let subdiag = DynTraitConstraintSuggestion { span, ident };
531                 subdiag.add_to_diagnostic(err);
532                 suggested = true;
533             }
534         }
535         suggested
536     }
537 }
538
539 /// Collect all the trait objects in a type that could have received an implicit `'static` lifetime.
540 pub struct TraitObjectVisitor(pub FxIndexSet<DefId>);
541
542 impl<'tcx> TypeVisitor<'tcx> for TraitObjectVisitor {
543     fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
544         match t.kind() {
545             ty::Dynamic(preds, re, _) if re.is_static() => {
546                 if let Some(def_id) = preds.principal_def_id() {
547                     self.0.insert(def_id);
548                 }
549                 ControlFlow::Continue(())
550             }
551             _ => t.super_visit_with(self),
552         }
553     }
554 }
555
556 /// Collect all `hir::Ty<'_>` `Span`s for trait objects with an implicit lifetime.
557 pub struct HirTraitObjectVisitor<'a>(pub &'a mut Vec<Span>, pub DefId);
558
559 impl<'a, 'tcx> Visitor<'tcx> for HirTraitObjectVisitor<'a> {
560     fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx>) {
561         if let TyKind::TraitObject(
562             poly_trait_refs,
563             Lifetime { res: LifetimeName::ImplicitObjectLifetimeDefault, .. },
564             _,
565         ) = t.kind
566         {
567             for ptr in poly_trait_refs {
568                 if Some(self.1) == ptr.trait_ref.trait_def_id() {
569                     self.0.push(ptr.span);
570                 }
571             }
572         }
573         walk_ty(self, t);
574     }
575 }