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