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