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