]>
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}; | |
6a06907d | 10 | use rustc_hir::{self as hir, GenericBound, Item, ItemKind, Lifetime, LifetimeName, Node, TyKind}; |
3dfed10e XL |
11 | use rustc_middle::ty::{self, AssocItemContainer, RegionKind, Ty, TypeFoldable, TypeVisitor}; |
12 | use rustc_span::symbol::Ident; | |
13 | use rustc_span::{MultiSpan, Span}; | |
8faf50e0 | 14 | |
29967ef6 XL |
15 | use std::ops::ControlFlow; |
16 | ||
dc9dc135 | 17 | impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { |
3dfed10e XL |
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. | |
8faf50e0 | 20 | pub(super) fn try_report_static_impl_trait(&self) -> Option<ErrorReported> { |
f035d41b | 21 | debug!("try_report_static_impl_trait(error={:?})", self.error); |
3dfed10e XL |
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 { | |
1b1a35ee XL |
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 | ||
3dfed10e XL |
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 | } | |
f035d41b XL |
91 | return None; |
92 | } | |
3dfed10e XL |
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 | // | ---- ^ | |
f035d41b | 150 | err.span_label( |
3dfed10e XL |
151 | sup_origin.span(), |
152 | "...is captured here, requiring it to live as long as `'static`", | |
f035d41b | 153 | ); |
3dfed10e XL |
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); | |
0bf4aa26 | 176 | |
3dfed10e XL |
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[..]) | |
f035d41b | 196 | { |
3dfed10e XL |
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, _) => { | |
6a06907d | 234 | let item = tcx.hir().item(item_id); |
3dfed10e XL |
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, | |
f9f354fc | 266 | ); |
3dfed10e XL |
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 | { | |
f035d41b | 281 | } else { |
3dfed10e XL |
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 | ); | |
f9f354fc | 293 | } |
f035d41b | 294 | } |
6a06907d | 295 | TyKind::TraitObject(_, lt, _) => match lt.name { |
3dfed10e XL |
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 | } | |
f9f354fc | 335 | |
3dfed10e XL |
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) { | |
6a06907d XL |
343 | Some(Node::ImplItem(impl_item)) => { |
344 | match tcx.hir().find(tcx.hir().get_parent_item(impl_item.hir_id())) { | |
5869c6ff XL |
345 | Some(Node::Item(Item { |
346 | kind: ItemKind::Impl(hir::Impl { self_ty, .. }), | |
347 | .. | |
6a06907d | 348 | })) => Some((impl_item.ident, self_ty)), |
3dfed10e XL |
349 | _ => None, |
350 | } | |
351 | } | |
6a06907d XL |
352 | Some(Node::TraitItem(trait_item)) => { |
353 | let parent_id = tcx.hir().get_parent_item(trait_item.hir_id()); | |
3dfed10e XL |
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() | |
6a06907d | 364 | .filter_map(|&impl_did| { |
3dfed10e XL |
365 | match tcx.hir().get_if_local(impl_did.to_def_id()) { |
366 | Some(Node::Item(Item { | |
5869c6ff | 367 | kind: ItemKind::Impl(hir::Impl { self_ty, .. }), |
f035d41b | 368 | .. |
3dfed10e XL |
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 | }) => | |
f035d41b | 380 | { |
3dfed10e | 381 | Some(self_ty) |
f035d41b XL |
382 | } |
383 | _ => None, | |
3dfed10e XL |
384 | } |
385 | }) | |
386 | .next() | |
387 | { | |
6a06907d | 388 | Some(self_ty) => Some((trait_item.ident, self_ty)), |
3dfed10e | 389 | _ => None, |
f035d41b | 390 | } |
3dfed10e XL |
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, | |
fc512014 | 414 | self.infcx.resolve_vars_if_possible(ctxt.substs), |
3dfed10e XL |
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 { | |
fc512014 | 474 | fn visit_ty(&mut self, t: Ty<'_>) -> ControlFlow<Self::BreakTy> { |
1b1a35ee | 475 | match t.kind() { |
3dfed10e XL |
476 | ty::Dynamic(preds, RegionKind::ReStatic) => { |
477 | if let Some(def_id) = preds.principal_def_id() { | |
478 | self.0.push(def_id); | |
479 | } | |
29967ef6 | 480 | ControlFlow::CONTINUE |
3dfed10e XL |
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>) { | |
1b1a35ee XL |
498 | if let TyKind::TraitObject( |
499 | poly_trait_refs, | |
500 | Lifetime { name: LifetimeName::ImplicitObjectLifetimeDefault, .. }, | |
6a06907d | 501 | _, |
1b1a35ee XL |
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); | |
8faf50e0 | 507 | } |
8faf50e0 XL |
508 | } |
509 | } | |
3dfed10e | 510 | walk_ty(self, t); |
8faf50e0 XL |
511 | } |
512 | } |