]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_middle/src/ty/diagnostics.rs
Auto merge of #98961 - zeevm:issue-98958-fix, r=oli-obk
[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, 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     fn is_suggestable(self, tcx: TyCtxt<'tcx>) -> bool;
86 }
87
88 impl<'tcx, T> IsSuggestable<'tcx> for T
89 where
90     T: TypeVisitable<'tcx>,
91 {
92     fn is_suggestable(self, tcx: TyCtxt<'tcx>) -> bool {
93         self.visit_with(&mut IsSuggestableVisitor { tcx }).is_continue()
94     }
95 }
96
97 pub fn suggest_arbitrary_trait_bound<'tcx>(
98     tcx: TyCtxt<'tcx>,
99     generics: &hir::Generics<'_>,
100     err: &mut Diagnostic,
101     trait_pred: PolyTraitPredicate<'tcx>,
102 ) -> bool {
103     if !trait_pred.is_suggestable(tcx) {
104         return false;
105     }
106
107     let param_name = trait_pred.skip_binder().self_ty().to_string();
108     let constraint = trait_pred.print_modifiers_and_trait_path().to_string();
109     let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
110
111     // Skip, there is a param named Self
112     if param.is_some() && param_name == "Self" {
113         return false;
114     }
115
116     // Suggest a where clause bound for a non-type parameter.
117     err.span_suggestion_verbose(
118         generics.tail_span_for_predicate_suggestion(),
119         &format!(
120             "consider {} `where` clause, but there might be an alternative better way to express \
121              this requirement",
122             if generics.where_clause_span.is_empty() { "introducing a" } else { "extending the" },
123         ),
124         format!("{} {}: {}", generics.add_where_or_trailing_comma(), param_name, constraint),
125         Applicability::MaybeIncorrect,
126     );
127     true
128 }
129
130 #[derive(Debug)]
131 enum SuggestChangingConstraintsMessage<'a> {
132     RestrictBoundFurther,
133     RestrictType { ty: &'a str },
134     RestrictTypeFurther { ty: &'a str },
135     RemovingQSized,
136 }
137
138 fn suggest_removing_unsized_bound(
139     tcx: TyCtxt<'_>,
140     generics: &hir::Generics<'_>,
141     suggestions: &mut Vec<(Span, String, SuggestChangingConstraintsMessage<'_>)>,
142     param: &hir::GenericParam<'_>,
143     def_id: Option<DefId>,
144 ) {
145     // See if there's a `?Sized` bound that can be removed to suggest that.
146     // First look at the `where` clause because we can have `where T: ?Sized`,
147     // then look at params.
148     let param_def_id = tcx.hir().local_def_id(param.hir_id);
149     for (where_pos, predicate) in generics.predicates.iter().enumerate() {
150         let WherePredicate::BoundPredicate(predicate) = predicate else {
151             continue;
152         };
153         if !predicate.is_param_bound(param_def_id.to_def_id()) {
154             continue;
155         };
156
157         for (pos, bound) in predicate.bounds.iter().enumerate() {
158             let    hir::GenericBound::Trait(poly, hir::TraitBoundModifier::Maybe) = bound else {
159                 continue;
160             };
161             if poly.trait_ref.trait_def_id() != def_id {
162                 continue;
163             }
164             let sp = generics.span_for_bound_removal(where_pos, pos);
165             suggestions.push((
166                 sp,
167                 String::new(),
168                 SuggestChangingConstraintsMessage::RemovingQSized,
169             ));
170         }
171     }
172 }
173
174 /// Suggest restricting a type param with a new bound.
175 pub fn suggest_constraining_type_param(
176     tcx: TyCtxt<'_>,
177     generics: &hir::Generics<'_>,
178     err: &mut Diagnostic,
179     param_name: &str,
180     constraint: &str,
181     def_id: Option<DefId>,
182 ) -> bool {
183     suggest_constraining_type_params(
184         tcx,
185         generics,
186         err,
187         [(param_name, constraint, def_id)].into_iter(),
188     )
189 }
190
191 /// Suggest restricting a type param with a new bound.
192 pub fn suggest_constraining_type_params<'a>(
193     tcx: TyCtxt<'_>,
194     generics: &hir::Generics<'_>,
195     err: &mut Diagnostic,
196     param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>,
197 ) -> bool {
198     let mut grouped = FxHashMap::default();
199     param_names_and_constraints.for_each(|(param_name, constraint, def_id)| {
200         grouped.entry(param_name).or_insert(Vec::new()).push((constraint, def_id))
201     });
202
203     let mut applicability = Applicability::MachineApplicable;
204     let mut suggestions = Vec::new();
205
206     for (param_name, mut constraints) in grouped {
207         let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
208         let Some(param) = param else { return false };
209
210         {
211             let mut sized_constraints =
212                 constraints.drain_filter(|(_, def_id)| *def_id == tcx.lang_items().sized_trait());
213             if let Some((constraint, def_id)) = sized_constraints.next() {
214                 applicability = Applicability::MaybeIncorrect;
215
216                 err.span_label(
217                     param.span,
218                     &format!("this type parameter needs to be `{}`", constraint),
219                 );
220                 suggest_removing_unsized_bound(tcx, generics, &mut suggestions, param, def_id);
221             }
222         }
223
224         if constraints.is_empty() {
225             continue;
226         }
227
228         let mut constraint = constraints.iter().map(|&(c, _)| c).collect::<Vec<_>>();
229         constraint.sort();
230         constraint.dedup();
231         let constraint = constraint.join(" + ");
232         let mut suggest_restrict = |span, bound_list_non_empty| {
233             suggestions.push((
234                 span,
235                 if bound_list_non_empty {
236                     format!(" + {}", constraint)
237                 } else {
238                     format!(" {}", constraint)
239                 },
240                 SuggestChangingConstraintsMessage::RestrictBoundFurther,
241             ))
242         };
243
244         // When the type parameter has been provided bounds
245         //
246         //    Message:
247         //      fn foo<T>(t: T) where T: Foo { ... }
248         //                            ^^^^^^
249         //                            |
250         //                            help: consider further restricting this bound with `+ Bar`
251         //
252         //    Suggestion:
253         //      fn foo<T>(t: T) where T: Foo { ... }
254         //                                  ^
255         //                                  |
256         //                                  replace with: ` + Bar`
257         //
258         // Or, if user has provided some bounds, suggest restricting them:
259         //
260         //   fn foo<T: Foo>(t: T) { ... }
261         //             ---
262         //             |
263         //             help: consider further restricting this bound with `+ Bar`
264         //
265         // Suggestion for tools in this case is:
266         //
267         //   fn foo<T: Foo>(t: T) { ... }
268         //          --
269         //          |
270         //          replace with: `T: Bar +`
271         let param_def_id = tcx.hir().local_def_id(param.hir_id);
272         if let Some(span) = generics.bounds_span_for_suggestions(param_def_id) {
273             suggest_restrict(span, true);
274             continue;
275         }
276
277         if generics.has_where_clause_predicates {
278             // This part is a bit tricky, because using the `where` clause user can
279             // provide zero, one or many bounds for the same type parameter, so we
280             // have following cases to consider:
281             //
282             // When the type parameter has been provided zero bounds
283             //
284             //    Message:
285             //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
286             //             - help: consider restricting this type parameter with `where X: Bar`
287             //
288             //    Suggestion:
289             //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
290             //                                           - insert: `, X: Bar`
291             suggestions.push((
292                 generics.tail_span_for_predicate_suggestion(),
293                 constraints
294                     .iter()
295                     .map(|&(constraint, _)| format!(", {}: {}", param_name, constraint))
296                     .collect::<String>(),
297                 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
298             ));
299             continue;
300         }
301
302         // Additionally, there may be no `where` clause but the generic parameter has a default:
303         //
304         //    Message:
305         //      trait Foo<T=()> {... }
306         //                - help: consider further restricting this type parameter with `where T: Zar`
307         //
308         //    Suggestion:
309         //      trait Foo<T=()> {... }
310         //                     - insert: `where T: Zar`
311         if matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) {
312             // Suggest a bound, but there is no existing `where` clause *and* the type param has a
313             // default (`<T=Foo>`), so we suggest adding `where T: Bar`.
314             suggestions.push((
315                 generics.tail_span_for_predicate_suggestion(),
316                 format!(" where {}: {}", param_name, constraint),
317                 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
318             ));
319             continue;
320         }
321
322         // If user has provided a colon, don't suggest adding another:
323         //
324         //   fn foo<T:>(t: T) { ... }
325         //            - insert: consider restricting this type parameter with `T: Foo`
326         if let Some(colon_span) = param.colon_span {
327             suggestions.push((
328                 colon_span.shrink_to_hi(),
329                 format!(" {}", constraint),
330                 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
331             ));
332             continue;
333         }
334
335         // If user hasn't provided any bounds, suggest adding a new one:
336         //
337         //   fn foo<T>(t: T) { ... }
338         //          - help: consider restricting this type parameter with `T: Foo`
339         suggestions.push((
340             param.span.shrink_to_hi(),
341             format!(": {}", constraint),
342             SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
343         ));
344     }
345
346     if suggestions.len() == 1 {
347         let (span, suggestion, msg) = suggestions.pop().unwrap();
348
349         let s;
350         let msg = match msg {
351             SuggestChangingConstraintsMessage::RestrictBoundFurther => {
352                 "consider further restricting this bound"
353             }
354             SuggestChangingConstraintsMessage::RestrictType { ty } => {
355                 s = format!("consider restricting type parameter `{}`", ty);
356                 &s
357             }
358             SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => {
359                 s = format!("consider further restricting type parameter `{}`", ty);
360                 &s
361             }
362             SuggestChangingConstraintsMessage::RemovingQSized => {
363                 "consider removing the `?Sized` bound to make the type parameter `Sized`"
364             }
365         };
366
367         err.span_suggestion_verbose(span, msg, suggestion, applicability);
368     } else if suggestions.len() > 1 {
369         err.multipart_suggestion_verbose(
370             "consider restricting type parameters",
371             suggestions.into_iter().map(|(span, suggestion, _)| (span, suggestion)).collect(),
372             applicability,
373         );
374     }
375
376     true
377 }
378
379 /// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for.
380 pub struct TraitObjectVisitor<'tcx>(pub Vec<&'tcx hir::Ty<'tcx>>, pub crate::hir::map::Map<'tcx>);
381
382 impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> {
383     fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
384         match ty.kind {
385             hir::TyKind::TraitObject(
386                 _,
387                 hir::Lifetime {
388                     name:
389                         hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static,
390                     ..
391                 },
392                 _,
393             ) => {
394                 self.0.push(ty);
395             }
396             hir::TyKind::OpaqueDef(item_id, _) => {
397                 self.0.push(ty);
398                 let item = self.1.item(item_id);
399                 hir::intravisit::walk_item(self, item);
400             }
401             _ => {}
402         }
403         hir::intravisit::walk_ty(self, ty);
404     }
405 }
406
407 /// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for.
408 pub struct StaticLifetimeVisitor<'tcx>(pub Vec<Span>, pub crate::hir::map::Map<'tcx>);
409
410 impl<'v> hir::intravisit::Visitor<'v> for StaticLifetimeVisitor<'v> {
411     fn visit_lifetime(&mut self, lt: &'v hir::Lifetime) {
412         if let hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static =
413             lt.name
414         {
415             self.0.push(lt.span);
416         }
417     }
418 }
419
420 pub struct IsSuggestableVisitor<'tcx> {
421     tcx: TyCtxt<'tcx>,
422 }
423
424 impl<'tcx> TypeVisitor<'tcx> for IsSuggestableVisitor<'tcx> {
425     type BreakTy = ();
426
427     fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
428         match t.kind() {
429             FnDef(..)
430             | Closure(..)
431             | Infer(..)
432             | Generator(..)
433             | GeneratorWitness(..)
434             | Bound(_, _)
435             | Placeholder(_)
436             | Error(_) => {
437                 return ControlFlow::Break(());
438             }
439
440             Opaque(did, _) => {
441                 let parent = self.tcx.parent(*did);
442                 if let hir::def::DefKind::TyAlias | hir::def::DefKind::AssocTy = self.tcx.def_kind(parent)
443                     && let Opaque(parent_did, _) = self.tcx.type_of(parent).kind()
444                     && parent_did == did
445                 {
446                     // Okay
447                 } else {
448                     return ControlFlow::Break(());
449                 }
450             }
451
452             Dynamic(dty, _) => {
453                 for pred in *dty {
454                     match pred.skip_binder() {
455                         ExistentialPredicate::Trait(_) | ExistentialPredicate::Projection(_) => {
456                             // Okay
457                         }
458                         _ => return ControlFlow::Break(()),
459                     }
460                 }
461             }
462
463             Param(param) => {
464                 // FIXME: It would be nice to make this not use string manipulation,
465                 // but it's pretty hard to do this, since `ty::ParamTy` is missing
466                 // sufficient info to determine if it is synthetic, and we don't
467                 // always have a convenient way of getting `ty::Generics` at the call
468                 // sites we invoke `IsSuggestable::is_suggestable`.
469                 if param.name.as_str().starts_with("impl ") {
470                     return ControlFlow::Break(());
471                 }
472             }
473
474             _ => {}
475         }
476
477         t.super_visit_with(self)
478     }
479
480     fn visit_const(&mut self, c: Const<'tcx>) -> ControlFlow<Self::BreakTy> {
481         match c.kind() {
482             ConstKind::Infer(..)
483             | ConstKind::Bound(..)
484             | ConstKind::Placeholder(..)
485             | ConstKind::Error(..) => {
486                 return ControlFlow::Break(());
487             }
488             _ => {}
489         }
490
491         c.super_visit_with(self)
492     }
493 }