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