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