]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_middle/src/ty/diagnostics.rs
Merge commit '984330a6ee3c4d15626685d6dc8b7b759ff630bd' into clippyup
[rust.git] / compiler / rustc_middle / src / ty / diagnostics.rs
1 //! Diagnostics related methods for `Ty`.
2
3 use crate::ty::subst::{GenericArg, GenericArgKind};
4 use crate::ty::TyKind::*;
5 use crate::ty::{
6     ConstKind, ExistentialPredicate, ExistentialProjection, ExistentialTraitRef, InferTy,
7     ProjectionTy, Term, Ty, TyCtxt, TypeAndMut,
8 };
9
10 use rustc_data_structures::fx::FxHashMap;
11 use rustc_errors::{Applicability, Diagnostic, DiagnosticArgValue, IntoDiagnosticArg};
12 use rustc_hir as hir;
13 use rustc_hir::def_id::DefId;
14 use rustc_hir::{QPath, TyKind, WhereBoundPredicate, WherePredicate};
15 use rustc_span::Span;
16
17 impl<'tcx> IntoDiagnosticArg for Ty<'tcx> {
18     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
19         format!("{}", self).into_diagnostic_arg()
20     }
21 }
22
23 impl<'tcx> Ty<'tcx> {
24     /// Similar to `Ty::is_primitive`, but also considers inferred numeric values to be primitive.
25     pub fn is_primitive_ty(self) -> bool {
26         matches!(
27             self.kind(),
28             Bool | Char
29                 | Str
30                 | Int(_)
31                 | Uint(_)
32                 | Float(_)
33                 | Infer(
34                     InferTy::IntVar(_)
35                         | InferTy::FloatVar(_)
36                         | InferTy::FreshIntTy(_)
37                         | InferTy::FreshFloatTy(_)
38                 )
39         )
40     }
41
42     /// Whether the type is succinctly representable as a type instead of just referred to with a
43     /// description in error messages. This is used in the main error message.
44     pub fn is_simple_ty(self) -> bool {
45         match self.kind() {
46             Bool
47             | Char
48             | Str
49             | Int(_)
50             | Uint(_)
51             | Float(_)
52             | Infer(
53                 InferTy::IntVar(_)
54                 | InferTy::FloatVar(_)
55                 | InferTy::FreshIntTy(_)
56                 | InferTy::FreshFloatTy(_),
57             ) => true,
58             Ref(_, x, _) | Array(x, _) | Slice(x) => x.peel_refs().is_simple_ty(),
59             Tuple(tys) if tys.is_empty() => true,
60             _ => false,
61         }
62     }
63
64     /// Whether the type is succinctly representable as a type instead of just referred to with a
65     /// description in error messages. This is used in the primary span label. Beyond what
66     /// `is_simple_ty` includes, it also accepts ADTs with no type arguments and references to
67     /// ADTs with no type arguments.
68     pub fn is_simple_text(self) -> bool {
69         match self.kind() {
70             Adt(_, substs) => substs.non_erasable_generics().next().is_none(),
71             Ref(_, ty, _) => ty.is_simple_text(),
72             _ => self.is_simple_ty(),
73         }
74     }
75
76     /// Whether the type can be safely suggested during error recovery.
77     pub fn is_suggestable(self) -> bool {
78         fn generic_arg_is_suggestible(arg: GenericArg<'_>) -> bool {
79             match arg.unpack() {
80                 GenericArgKind::Type(ty) => ty.is_suggestable(),
81                 GenericArgKind::Const(c) => const_is_suggestable(c.val()),
82                 _ => true,
83             }
84         }
85
86         fn const_is_suggestable(kind: ConstKind<'_>) -> bool {
87             match kind {
88                 ConstKind::Infer(..)
89                 | ConstKind::Bound(..)
90                 | ConstKind::Placeholder(..)
91                 | ConstKind::Error(..) => false,
92                 _ => true,
93             }
94         }
95
96         // FIXME(compiler-errors): Some types are still not good to suggest,
97         // specifically references with lifetimes within the function. Not
98         //sure we have enough information to resolve whether a region is
99         // temporary, so I'll leave this as a fixme.
100
101         match self.kind() {
102             Opaque(..)
103             | FnDef(..)
104             | Closure(..)
105             | Infer(..)
106             | Generator(..)
107             | GeneratorWitness(..)
108             | Bound(_, _)
109             | Placeholder(_)
110             | Error(_) => false,
111             Dynamic(dty, _) => dty.iter().all(|pred| match pred.skip_binder() {
112                 ExistentialPredicate::Trait(ExistentialTraitRef { substs, .. }) => {
113                     substs.iter().all(generic_arg_is_suggestible)
114                 }
115                 ExistentialPredicate::Projection(ExistentialProjection {
116                     substs, term, ..
117                 }) => {
118                     let term_is_suggestable = match term {
119                         Term::Ty(ty) => ty.is_suggestable(),
120                         Term::Const(c) => const_is_suggestable(c.val()),
121                     };
122                     term_is_suggestable && substs.iter().all(generic_arg_is_suggestible)
123                 }
124                 _ => true,
125             }),
126             Projection(ProjectionTy { substs: args, .. }) | Adt(_, args) => {
127                 args.iter().all(generic_arg_is_suggestible)
128             }
129             Tuple(args) => args.iter().all(|ty| ty.is_suggestable()),
130             Slice(ty) | RawPtr(TypeAndMut { ty, .. }) | Ref(_, ty, _) => ty.is_suggestable(),
131             Array(ty, c) => ty.is_suggestable() && const_is_suggestable(c.val()),
132             _ => true,
133         }
134     }
135 }
136
137 pub fn suggest_arbitrary_trait_bound(
138     generics: &hir::Generics<'_>,
139     err: &mut Diagnostic,
140     param_name: &str,
141     constraint: &str,
142 ) -> bool {
143     let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
144     match (param, param_name) {
145         (Some(_), "Self") => return false,
146         _ => {}
147     }
148     // Suggest a where clause bound for a non-type parameter.
149     let (action, prefix) = if generics.where_clause.predicates.is_empty() {
150         ("introducing a", " where ")
151     } else {
152         ("extending the", ", ")
153     };
154     err.span_suggestion_verbose(
155         generics.where_clause.tail_span_for_suggestion(),
156         &format!(
157             "consider {} `where` bound, but there might be an alternative better way to express \
158              this requirement",
159             action,
160         ),
161         format!("{}{}: {}", prefix, param_name, constraint),
162         Applicability::MaybeIncorrect,
163     );
164     true
165 }
166
167 #[derive(Debug)]
168 enum SuggestChangingConstraintsMessage<'a> {
169     RestrictBoundFurther,
170     RestrictType { ty: &'a str },
171     RestrictTypeFurther { ty: &'a str },
172     RemovingQSized,
173 }
174
175 fn suggest_removing_unsized_bound(
176     generics: &hir::Generics<'_>,
177     suggestions: &mut Vec<(Span, String, SuggestChangingConstraintsMessage<'_>)>,
178     param_name: &str,
179     param: &hir::GenericParam<'_>,
180     def_id: Option<DefId>,
181 ) {
182     // See if there's a `?Sized` bound that can be removed to suggest that.
183     // First look at the `where` clause because we can have `where T: ?Sized`,
184     // then look at params.
185     for (where_pos, predicate) in generics.where_clause.predicates.iter().enumerate() {
186         match predicate {
187             WherePredicate::BoundPredicate(WhereBoundPredicate {
188                 bounded_ty:
189                     hir::Ty {
190                         kind:
191                             hir::TyKind::Path(hir::QPath::Resolved(
192                                 None,
193                                 hir::Path {
194                                     segments: [segment],
195                                     res: hir::def::Res::Def(hir::def::DefKind::TyParam, _),
196                                     ..
197                                 },
198                             )),
199                         ..
200                     },
201                 bounds,
202                 span,
203                 ..
204             }) if segment.ident.as_str() == param_name => {
205                 for (pos, bound) in bounds.iter().enumerate() {
206                     match bound {
207                         hir::GenericBound::Trait(poly, hir::TraitBoundModifier::Maybe)
208                             if poly.trait_ref.trait_def_id() == def_id => {}
209                         _ => continue,
210                     }
211                     let sp = match (
212                         bounds.len(),
213                         pos,
214                         generics.where_clause.predicates.len(),
215                         where_pos,
216                     ) {
217                         // where T: ?Sized
218                         // ^^^^^^^^^^^^^^^
219                         (1, _, 1, _) => generics.where_clause.span,
220                         // where Foo: Bar, T: ?Sized,
221                         //               ^^^^^^^^^^^
222                         (1, _, len, pos) if pos == len - 1 => generics.where_clause.predicates
223                             [pos - 1]
224                             .span()
225                             .shrink_to_hi()
226                             .to(*span),
227                         // where T: ?Sized, Foo: Bar,
228                         //       ^^^^^^^^^^^
229                         (1, _, _, pos) => {
230                             span.until(generics.where_clause.predicates[pos + 1].span())
231                         }
232                         // where T: ?Sized + Bar, Foo: Bar,
233                         //          ^^^^^^^^^
234                         (_, 0, _, _) => bound.span().to(bounds[1].span().shrink_to_lo()),
235                         // where T: Bar + ?Sized, Foo: Bar,
236                         //             ^^^^^^^^^
237                         (_, pos, _, _) => bounds[pos - 1].span().shrink_to_hi().to(bound.span()),
238                     };
239
240                     suggestions.push((
241                         sp,
242                         String::new(),
243                         SuggestChangingConstraintsMessage::RemovingQSized,
244                     ));
245                 }
246             }
247             _ => {}
248         }
249     }
250     for (pos, bound) in param.bounds.iter().enumerate() {
251         match bound {
252             hir::GenericBound::Trait(poly, hir::TraitBoundModifier::Maybe)
253                 if poly.trait_ref.trait_def_id() == def_id =>
254             {
255                 let sp = match (param.bounds.len(), pos) {
256                     // T: ?Sized,
257                     //  ^^^^^^^^
258                     (1, _) => param.span.shrink_to_hi().to(bound.span()),
259                     // T: ?Sized + Bar,
260                     //    ^^^^^^^^^
261                     (_, 0) => bound.span().to(param.bounds[1].span().shrink_to_lo()),
262                     // T: Bar + ?Sized,
263                     //       ^^^^^^^^^
264                     (_, pos) => param.bounds[pos - 1].span().shrink_to_hi().to(bound.span()),
265                 };
266
267                 suggestions.push((
268                     sp,
269                     String::new(),
270                     SuggestChangingConstraintsMessage::RemovingQSized,
271                 ));
272             }
273             _ => {}
274         }
275     }
276 }
277
278 /// Suggest restricting a type param with a new bound.
279 pub fn suggest_constraining_type_param(
280     tcx: TyCtxt<'_>,
281     generics: &hir::Generics<'_>,
282     err: &mut Diagnostic,
283     param_name: &str,
284     constraint: &str,
285     def_id: Option<DefId>,
286 ) -> bool {
287     suggest_constraining_type_params(
288         tcx,
289         generics,
290         err,
291         [(param_name, constraint, def_id)].into_iter(),
292     )
293 }
294
295 /// Suggest restricting a type param with a new bound.
296 pub fn suggest_constraining_type_params<'a>(
297     tcx: TyCtxt<'_>,
298     generics: &hir::Generics<'_>,
299     err: &mut Diagnostic,
300     param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>,
301 ) -> bool {
302     let mut grouped = FxHashMap::default();
303     param_names_and_constraints.for_each(|(param_name, constraint, def_id)| {
304         grouped.entry(param_name).or_insert(Vec::new()).push((constraint, def_id))
305     });
306
307     let mut applicability = Applicability::MachineApplicable;
308     let mut suggestions = Vec::new();
309
310     for (param_name, mut constraints) in grouped {
311         let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
312         let Some(param) = param else { return false };
313
314         {
315             let mut sized_constraints =
316                 constraints.drain_filter(|(_, def_id)| *def_id == tcx.lang_items().sized_trait());
317             if let Some((constraint, def_id)) = sized_constraints.next() {
318                 applicability = Applicability::MaybeIncorrect;
319
320                 err.span_label(
321                     param.span,
322                     &format!("this type parameter needs to be `{}`", constraint),
323                 );
324                 suggest_removing_unsized_bound(
325                     generics,
326                     &mut suggestions,
327                     param_name,
328                     param,
329                     def_id,
330                 );
331             }
332         }
333
334         if constraints.is_empty() {
335             continue;
336         }
337
338         let constraint = constraints.iter().map(|&(c, _)| c).collect::<Vec<_>>().join(" + ");
339         let mut suggest_restrict = |span| {
340             suggestions.push((
341                 span,
342                 format!(" + {}", constraint),
343                 SuggestChangingConstraintsMessage::RestrictBoundFurther,
344             ))
345         };
346
347         if param_name.starts_with("impl ") {
348             // If there's an `impl Trait` used in argument position, suggest
349             // restricting it:
350             //
351             //   fn foo(t: impl Foo) { ... }
352             //             --------
353             //             |
354             //             help: consider further restricting this bound with `+ Bar`
355             //
356             // Suggestion for tools in this case is:
357             //
358             //   fn foo(t: impl Foo) { ... }
359             //             --------
360             //             |
361             //             replace with: `impl Foo + Bar`
362
363             suggest_restrict(param.span.shrink_to_hi());
364             continue;
365         }
366
367         if generics.where_clause.predicates.is_empty()
368         // Given `trait Base<T = String>: Super<T>` where `T: Copy`, suggest restricting in the
369         // `where` clause instead of `trait Base<T: Copy = String>: Super<T>`.
370         && !matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. })
371         {
372             if let Some(span) = param.bounds_span_for_suggestions() {
373                 // If user has provided some bounds, suggest restricting them:
374                 //
375                 //   fn foo<T: Foo>(t: T) { ... }
376                 //             ---
377                 //             |
378                 //             help: consider further restricting this bound with `+ Bar`
379                 //
380                 // Suggestion for tools in this case is:
381                 //
382                 //   fn foo<T: Foo>(t: T) { ... }
383                 //          --
384                 //          |
385                 //          replace with: `T: Bar +`
386                 suggest_restrict(span);
387             } else {
388                 // If user hasn't provided any bounds, suggest adding a new one:
389                 //
390                 //   fn foo<T>(t: T) { ... }
391                 //          - help: consider restricting this type parameter with `T: Foo`
392                 suggestions.push((
393                     param.span.shrink_to_hi(),
394                     format!(": {}", constraint),
395                     SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
396                 ));
397             }
398         } else {
399             // This part is a bit tricky, because using the `where` clause user can
400             // provide zero, one or many bounds for the same type parameter, so we
401             // have following cases to consider:
402             //
403             // 1) When the type parameter has been provided zero bounds
404             //
405             //    Message:
406             //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
407             //             - help: consider restricting this type parameter with `where X: Bar`
408             //
409             //    Suggestion:
410             //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
411             //                                           - insert: `, X: Bar`
412             //
413             //
414             // 2) When the type parameter has been provided one bound
415             //
416             //    Message:
417             //      fn foo<T>(t: T) where T: Foo { ... }
418             //                            ^^^^^^
419             //                            |
420             //                            help: consider further restricting this bound with `+ Bar`
421             //
422             //    Suggestion:
423             //      fn foo<T>(t: T) where T: Foo { ... }
424             //                            ^^
425             //                            |
426             //                            replace with: `T: Bar +`
427             //
428             //
429             // 3) When the type parameter has been provided many bounds
430             //
431             //    Message:
432             //      fn foo<T>(t: T) where T: Foo, T: Bar {... }
433             //             - help: consider further restricting this type parameter with `where T: Zar`
434             //
435             //    Suggestion:
436             //      fn foo<T>(t: T) where T: Foo, T: Bar {... }
437             //                                          - insert: `, T: Zar`
438             //
439             // Additionally, there may be no `where` clause whatsoever in the case that this was
440             // reached because the generic parameter has a default:
441             //
442             //    Message:
443             //      trait Foo<T=()> {... }
444             //             - help: consider further restricting this type parameter with `where T: Zar`
445             //
446             //    Suggestion:
447             //      trait Foo<T=()> where T: Zar {... }
448             //                     - insert: `where T: Zar`
449
450             if matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. })
451                 && generics.where_clause.predicates.len() == 0
452             {
453                 // Suggest a bound, but there is no existing `where` clause *and* the type param has a
454                 // default (`<T=Foo>`), so we suggest adding `where T: Bar`.
455                 suggestions.push((
456                     generics.where_clause.tail_span_for_suggestion(),
457                     format!(" where {}: {}", param_name, constraint),
458                     SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
459                 ));
460             } else {
461                 let mut param_spans = Vec::new();
462
463                 for predicate in generics.where_clause.predicates {
464                     if let WherePredicate::BoundPredicate(WhereBoundPredicate {
465                         span,
466                         bounded_ty,
467                         ..
468                     }) = predicate
469                     {
470                         if let TyKind::Path(QPath::Resolved(_, path)) = &bounded_ty.kind {
471                             if let Some(segment) = path.segments.first() {
472                                 if segment.ident.to_string() == param_name {
473                                     param_spans.push(span);
474                                 }
475                             }
476                         }
477                     }
478                 }
479
480                 match param_spans[..] {
481                     [&param_span] => suggest_restrict(param_span.shrink_to_hi()),
482                     _ => {
483                         suggestions.push((
484                             generics.where_clause.tail_span_for_suggestion(),
485                             constraints
486                                 .iter()
487                                 .map(|&(constraint, _)| format!(", {}: {}", param_name, constraint))
488                                 .collect::<String>(),
489                             SuggestChangingConstraintsMessage::RestrictTypeFurther {
490                                 ty: param_name,
491                             },
492                         ));
493                     }
494                 }
495             }
496         }
497     }
498
499     if suggestions.len() == 1 {
500         let (span, suggestion, msg) = suggestions.pop().unwrap();
501
502         let s;
503         let msg = match msg {
504             SuggestChangingConstraintsMessage::RestrictBoundFurther => {
505                 "consider further restricting this bound"
506             }
507             SuggestChangingConstraintsMessage::RestrictType { ty } => {
508                 s = format!("consider restricting type parameter `{}`", ty);
509                 &s
510             }
511             SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => {
512                 s = format!("consider further restricting type parameter `{}`", ty);
513                 &s
514             }
515             SuggestChangingConstraintsMessage::RemovingQSized => {
516                 "consider removing the `?Sized` bound to make the type parameter `Sized`"
517             }
518         };
519
520         err.span_suggestion_verbose(span, msg, suggestion, applicability);
521     } else if suggestions.len() > 1 {
522         err.multipart_suggestion_verbose(
523             "consider restricting type parameters",
524             suggestions.into_iter().map(|(span, suggestion, _)| (span, suggestion)).collect(),
525             applicability,
526         );
527     }
528
529     true
530 }
531
532 /// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for.
533 pub struct TraitObjectVisitor<'tcx>(pub Vec<&'tcx hir::Ty<'tcx>>, pub crate::hir::map::Map<'tcx>);
534
535 impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> {
536     fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
537         match ty.kind {
538             hir::TyKind::TraitObject(
539                 _,
540                 hir::Lifetime {
541                     name:
542                         hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static,
543                     ..
544                 },
545                 _,
546             ) => {
547                 self.0.push(ty);
548             }
549             hir::TyKind::OpaqueDef(item_id, _) => {
550                 self.0.push(ty);
551                 let item = self.1.item(item_id);
552                 hir::intravisit::walk_item(self, item);
553             }
554             _ => {}
555         }
556         hir::intravisit::walk_ty(self, ty);
557     }
558 }
559
560 /// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for.
561 pub struct StaticLifetimeVisitor<'tcx>(pub Vec<Span>, pub crate::hir::map::Map<'tcx>);
562
563 impl<'v> hir::intravisit::Visitor<'v> for StaticLifetimeVisitor<'v> {
564     fn visit_lifetime(&mut self, lt: &'v hir::Lifetime) {
565         if let hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static =
566             lt.name
567         {
568             self.0.push(lt.span);
569         }
570     }
571 }