]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_middle/src/ty/diagnostics.rs
Rollup merge of #106798 - scottmcm:signum-via-cmp, r=Mark-Simulacrum
[rust.git] / compiler / rustc_middle / src / ty / diagnostics.rs
1 //! Diagnostics related methods for `Ty`.
2
3 use std::ops::ControlFlow;
4
5 use crate::ty::{
6     visit::TypeVisitable, AliasTy, Const, ConstKind, DefIdTree, InferConst, InferTy, Opaque,
7     PolyTraitPredicate, Projection, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor,
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::DefKind;
14 use rustc_hir::def_id::DefId;
15 use rustc_hir::WherePredicate;
16 use rustc_span::Span;
17 use rustc_type_ir::sty::TyKind::*;
18
19 impl<'tcx> IntoDiagnosticArg for Ty<'tcx> {
20     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
21         self.to_string().into_diagnostic_arg()
22     }
23 }
24
25 impl<'tcx> Ty<'tcx> {
26     /// Similar to `Ty::is_primitive`, but also considers inferred numeric values to be primitive.
27     pub fn is_primitive_ty(self) -> bool {
28         matches!(
29             self.kind(),
30             Bool | Char
31                 | Str
32                 | Int(_)
33                 | Uint(_)
34                 | Float(_)
35                 | Infer(
36                     InferTy::IntVar(_)
37                         | InferTy::FloatVar(_)
38                         | InferTy::FreshIntTy(_)
39                         | InferTy::FreshFloatTy(_)
40                 )
41         )
42     }
43
44     /// Whether the type is succinctly representable as a type instead of just referred to with a
45     /// description in error messages. This is used in the main error message.
46     pub fn is_simple_ty(self) -> bool {
47         match self.kind() {
48             Bool
49             | Char
50             | Str
51             | Int(_)
52             | Uint(_)
53             | Float(_)
54             | Infer(
55                 InferTy::IntVar(_)
56                 | InferTy::FloatVar(_)
57                 | InferTy::FreshIntTy(_)
58                 | InferTy::FreshFloatTy(_),
59             ) => true,
60             Ref(_, x, _) | Array(x, _) | Slice(x) => x.peel_refs().is_simple_ty(),
61             Tuple(tys) if tys.is_empty() => true,
62             _ => false,
63         }
64     }
65
66     /// Whether the type is succinctly representable as a type instead of just referred to with a
67     /// description in error messages. This is used in the primary span label. Beyond what
68     /// `is_simple_ty` includes, it also accepts ADTs with no type arguments and references to
69     /// ADTs with no type arguments.
70     pub fn is_simple_text(self) -> bool {
71         match self.kind() {
72             Adt(_, substs) => substs.non_erasable_generics().next().is_none(),
73             Ref(_, ty, _) => ty.is_simple_text(),
74             _ => self.is_simple_ty(),
75         }
76     }
77 }
78
79 pub trait IsSuggestable<'tcx> {
80     /// Whether this makes sense to suggest in a diagnostic.
81     ///
82     /// We filter out certain types and constants since they don't provide
83     /// meaningful rendered suggestions when pretty-printed. We leave some
84     /// nonsense, such as region vars, since those render as `'_` and are
85     /// usually okay to reinterpret as elided lifetimes.
86     ///
87     /// Only if `infer_suggestable` is true, we consider type and const
88     /// inference variables to be suggestable.
89     fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool;
90 }
91
92 impl<'tcx, T> IsSuggestable<'tcx> for T
93 where
94     T: TypeVisitable<'tcx>,
95 {
96     fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool {
97         self.visit_with(&mut IsSuggestableVisitor { tcx, infer_suggestable }).is_continue()
98     }
99 }
100
101 pub fn suggest_arbitrary_trait_bound<'tcx>(
102     tcx: TyCtxt<'tcx>,
103     generics: &hir::Generics<'_>,
104     err: &mut Diagnostic,
105     trait_pred: PolyTraitPredicate<'tcx>,
106     associated_ty: Option<(&'static str, Ty<'tcx>)>,
107 ) -> bool {
108     if !trait_pred.is_suggestable(tcx, false) {
109         return false;
110     }
111
112     let param_name = trait_pred.skip_binder().self_ty().to_string();
113     let mut constraint = trait_pred.print_modifiers_and_trait_path().to_string();
114
115     if let Some((name, term)) = associated_ty {
116         // FIXME: this case overlaps with code in TyCtxt::note_and_explain_type_err.
117         // That should be extracted into a helper function.
118         if constraint.ends_with('>') {
119             constraint = format!("{}, {} = {}>", &constraint[..constraint.len() - 1], name, term);
120         } else {
121             constraint.push_str(&format!("<{} = {}>", name, term));
122         }
123     }
124
125     let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
126
127     // Skip, there is a param named Self
128     if param.is_some() && param_name == "Self" {
129         return false;
130     }
131
132     // Suggest a where clause bound for a non-type parameter.
133     err.span_suggestion_verbose(
134         generics.tail_span_for_predicate_suggestion(),
135         &format!(
136             "consider {} `where` clause, but there might be an alternative better way to express \
137              this requirement",
138             if generics.where_clause_span.is_empty() { "introducing a" } else { "extending the" },
139         ),
140         format!("{} {}: {}", generics.add_where_or_trailing_comma(), param_name, constraint),
141         Applicability::MaybeIncorrect,
142     );
143     true
144 }
145
146 #[derive(Debug)]
147 enum SuggestChangingConstraintsMessage<'a> {
148     RestrictBoundFurther,
149     RestrictType { ty: &'a str },
150     RestrictTypeFurther { ty: &'a str },
151     RemovingQSized,
152 }
153
154 fn suggest_removing_unsized_bound(
155     generics: &hir::Generics<'_>,
156     suggestions: &mut Vec<(Span, String, SuggestChangingConstraintsMessage<'_>)>,
157     param: &hir::GenericParam<'_>,
158     def_id: Option<DefId>,
159 ) {
160     // See if there's a `?Sized` bound that can be removed to suggest that.
161     // First look at the `where` clause because we can have `where T: ?Sized`,
162     // then look at params.
163     for (where_pos, predicate) in generics.predicates.iter().enumerate() {
164         let WherePredicate::BoundPredicate(predicate) = predicate else {
165             continue;
166         };
167         if !predicate.is_param_bound(param.def_id.to_def_id()) {
168             continue;
169         };
170
171         for (pos, bound) in predicate.bounds.iter().enumerate() {
172             let hir::GenericBound::Trait(poly, hir::TraitBoundModifier::Maybe) = bound else {
173                 continue;
174             };
175             if poly.trait_ref.trait_def_id() != def_id {
176                 continue;
177             }
178             let sp = generics.span_for_bound_removal(where_pos, pos);
179             suggestions.push((
180                 sp,
181                 String::new(),
182                 SuggestChangingConstraintsMessage::RemovingQSized,
183             ));
184         }
185     }
186 }
187
188 /// Suggest restricting a type param with a new bound.
189 pub fn suggest_constraining_type_param(
190     tcx: TyCtxt<'_>,
191     generics: &hir::Generics<'_>,
192     err: &mut Diagnostic,
193     param_name: &str,
194     constraint: &str,
195     def_id: Option<DefId>,
196 ) -> bool {
197     suggest_constraining_type_params(
198         tcx,
199         generics,
200         err,
201         [(param_name, constraint, def_id)].into_iter(),
202     )
203 }
204
205 /// Suggest restricting a type param with a new bound.
206 pub fn suggest_constraining_type_params<'a>(
207     tcx: TyCtxt<'_>,
208     generics: &hir::Generics<'_>,
209     err: &mut Diagnostic,
210     param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>,
211 ) -> bool {
212     let mut grouped = FxHashMap::default();
213     param_names_and_constraints.for_each(|(param_name, constraint, def_id)| {
214         grouped.entry(param_name).or_insert(Vec::new()).push((constraint, def_id))
215     });
216
217     let mut applicability = Applicability::MachineApplicable;
218     let mut suggestions = Vec::new();
219
220     for (param_name, mut constraints) in grouped {
221         let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
222         let Some(param) = param else { return false };
223
224         {
225             let mut sized_constraints =
226                 constraints.drain_filter(|(_, def_id)| *def_id == tcx.lang_items().sized_trait());
227             if let Some((constraint, def_id)) = sized_constraints.next() {
228                 applicability = Applicability::MaybeIncorrect;
229
230                 err.span_label(
231                     param.span,
232                     &format!("this type parameter needs to be `{}`", constraint),
233                 );
234                 suggest_removing_unsized_bound(generics, &mut suggestions, param, def_id);
235             }
236         }
237
238         if constraints.is_empty() {
239             continue;
240         }
241
242         let mut constraint = constraints.iter().map(|&(c, _)| c).collect::<Vec<_>>();
243         constraint.sort();
244         constraint.dedup();
245         let constraint = constraint.join(" + ");
246         let mut suggest_restrict = |span, bound_list_non_empty| {
247             suggestions.push((
248                 span,
249                 if bound_list_non_empty {
250                     format!(" + {}", constraint)
251                 } else {
252                     format!(" {}", constraint)
253                 },
254                 SuggestChangingConstraintsMessage::RestrictBoundFurther,
255             ))
256         };
257
258         // When the type parameter has been provided bounds
259         //
260         //    Message:
261         //      fn foo<T>(t: T) where T: Foo { ... }
262         //                            ^^^^^^
263         //                            |
264         //                            help: consider further restricting this bound with `+ Bar`
265         //
266         //    Suggestion:
267         //      fn foo<T>(t: T) where T: Foo { ... }
268         //                                  ^
269         //                                  |
270         //                                  replace with: ` + Bar`
271         //
272         // Or, if user has provided some bounds, suggest restricting them:
273         //
274         //   fn foo<T: Foo>(t: T) { ... }
275         //             ---
276         //             |
277         //             help: consider further restricting this bound with `+ Bar`
278         //
279         // Suggestion for tools in this case is:
280         //
281         //   fn foo<T: Foo>(t: T) { ... }
282         //          --
283         //          |
284         //          replace with: `T: Bar +`
285         if let Some(span) = generics.bounds_span_for_suggestions(param.def_id) {
286             suggest_restrict(span, true);
287             continue;
288         }
289
290         if generics.has_where_clause_predicates {
291             // This part is a bit tricky, because using the `where` clause user can
292             // provide zero, one or many bounds for the same type parameter, so we
293             // have following cases to consider:
294             //
295             // When the type parameter has been provided zero bounds
296             //
297             //    Message:
298             //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
299             //             - help: consider restricting this type parameter with `where X: Bar`
300             //
301             //    Suggestion:
302             //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
303             //                                           - insert: `, X: Bar`
304             suggestions.push((
305                 generics.tail_span_for_predicate_suggestion(),
306                 constraints
307                     .iter()
308                     .map(|&(constraint, _)| format!(", {}: {}", param_name, constraint))
309                     .collect::<String>(),
310                 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
311             ));
312             continue;
313         }
314
315         // Additionally, there may be no `where` clause but the generic parameter has a default:
316         //
317         //    Message:
318         //      trait Foo<T=()> {... }
319         //                - help: consider further restricting this type parameter with `where T: Zar`
320         //
321         //    Suggestion:
322         //      trait Foo<T=()> {... }
323         //                     - insert: `where T: Zar`
324         if matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) {
325             // Suggest a bound, but there is no existing `where` clause *and* the type param has a
326             // default (`<T=Foo>`), so we suggest adding `where T: Bar`.
327             suggestions.push((
328                 generics.tail_span_for_predicate_suggestion(),
329                 format!(" where {}: {}", param_name, constraint),
330                 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
331             ));
332             continue;
333         }
334
335         // If user has provided a colon, don't suggest adding another:
336         //
337         //   fn foo<T:>(t: T) { ... }
338         //            - insert: consider restricting this type parameter with `T: Foo`
339         if let Some(colon_span) = param.colon_span {
340             suggestions.push((
341                 colon_span.shrink_to_hi(),
342                 format!(" {}", constraint),
343                 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
344             ));
345             continue;
346         }
347
348         // If user hasn't provided any bounds, suggest adding a new one:
349         //
350         //   fn foo<T>(t: T) { ... }
351         //          - help: consider restricting this type parameter with `T: Foo`
352         suggestions.push((
353             param.span.shrink_to_hi(),
354             format!(": {}", constraint),
355             SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
356         ));
357     }
358
359     // FIXME: remove the suggestions that are from derive, as the span is not correct
360     suggestions = suggestions
361         .into_iter()
362         .filter(|(span, _, _)| !span.in_derive_expansion())
363         .collect::<Vec<_>>();
364
365     if suggestions.len() == 1 {
366         let (span, suggestion, msg) = suggestions.pop().unwrap();
367
368         let s;
369         let msg = match msg {
370             SuggestChangingConstraintsMessage::RestrictBoundFurther => {
371                 "consider further restricting this bound"
372             }
373             SuggestChangingConstraintsMessage::RestrictType { ty } => {
374                 s = format!("consider restricting type parameter `{}`", ty);
375                 &s
376             }
377             SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => {
378                 s = format!("consider further restricting type parameter `{}`", ty);
379                 &s
380             }
381             SuggestChangingConstraintsMessage::RemovingQSized => {
382                 "consider removing the `?Sized` bound to make the type parameter `Sized`"
383             }
384         };
385
386         err.span_suggestion_verbose(span, msg, suggestion, applicability);
387     } else if suggestions.len() > 1 {
388         err.multipart_suggestion_verbose(
389             "consider restricting type parameters",
390             suggestions.into_iter().map(|(span, suggestion, _)| (span, suggestion)).collect(),
391             applicability,
392         );
393     }
394
395     true
396 }
397
398 /// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for.
399 pub struct TraitObjectVisitor<'tcx>(pub Vec<&'tcx hir::Ty<'tcx>>, pub crate::hir::map::Map<'tcx>);
400
401 impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> {
402     fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
403         match ty.kind {
404             hir::TyKind::TraitObject(
405                 _,
406                 hir::Lifetime {
407                     res:
408                         hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static,
409                     ..
410                 },
411                 _,
412             ) => {
413                 self.0.push(ty);
414             }
415             hir::TyKind::OpaqueDef(item_id, _, _) => {
416                 self.0.push(ty);
417                 let item = self.1.item(item_id);
418                 hir::intravisit::walk_item(self, item);
419             }
420             _ => {}
421         }
422         hir::intravisit::walk_ty(self, ty);
423     }
424 }
425
426 /// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for.
427 pub struct StaticLifetimeVisitor<'tcx>(pub Vec<Span>, pub crate::hir::map::Map<'tcx>);
428
429 impl<'v> hir::intravisit::Visitor<'v> for StaticLifetimeVisitor<'v> {
430     fn visit_lifetime(&mut self, lt: &'v hir::Lifetime) {
431         if let hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static = lt.res
432         {
433             self.0.push(lt.ident.span);
434         }
435     }
436 }
437
438 pub struct IsSuggestableVisitor<'tcx> {
439     tcx: TyCtxt<'tcx>,
440     infer_suggestable: bool,
441 }
442
443 impl<'tcx> TypeVisitor<'tcx> for IsSuggestableVisitor<'tcx> {
444     type BreakTy = ();
445
446     fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
447         match *t.kind() {
448             Infer(InferTy::TyVar(_)) if self.infer_suggestable => {}
449
450             FnDef(..)
451             | Closure(..)
452             | Infer(..)
453             | Generator(..)
454             | GeneratorWitness(..)
455             | Bound(_, _)
456             | Placeholder(_)
457             | Error(_) => {
458                 return ControlFlow::Break(());
459             }
460
461             Alias(Opaque, AliasTy { def_id, .. }) => {
462                 let parent = self.tcx.parent(def_id);
463                 if let DefKind::TyAlias | DefKind::AssocTy = self.tcx.def_kind(parent)
464                     && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) = *self.tcx.type_of(parent).kind()
465                     && parent_opaque_def_id == def_id
466                 {
467                     // Okay
468                 } else {
469                     return ControlFlow::Break(());
470                 }
471             }
472
473             Alias(Projection, AliasTy { def_id, .. }) => {
474                 if self.tcx.def_kind(def_id) != DefKind::AssocTy {
475                     return ControlFlow::Break(());
476                 }
477             }
478
479             Param(param) => {
480                 // FIXME: It would be nice to make this not use string manipulation,
481                 // but it's pretty hard to do this, since `ty::ParamTy` is missing
482                 // sufficient info to determine if it is synthetic, and we don't
483                 // always have a convenient way of getting `ty::Generics` at the call
484                 // sites we invoke `IsSuggestable::is_suggestable`.
485                 if param.name.as_str().starts_with("impl ") {
486                     return ControlFlow::Break(());
487                 }
488             }
489
490             _ => {}
491         }
492
493         t.super_visit_with(self)
494     }
495
496     fn visit_const(&mut self, c: Const<'tcx>) -> ControlFlow<Self::BreakTy> {
497         match c.kind() {
498             ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => {}
499
500             ConstKind::Infer(..)
501             | ConstKind::Bound(..)
502             | ConstKind::Placeholder(..)
503             | ConstKind::Error(..) => {
504                 return ControlFlow::Break(());
505             }
506             _ => {}
507         }
508
509         c.super_visit_with(self)
510     }
511 }