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