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