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