1 //! Diagnostics related methods for `Ty`.
3 use std::ops::ControlFlow;
6 visit::TypeVisitable, AliasTy, Const, ConstKind, DefIdTree, FallibleTypeFolder, InferConst,
7 InferTy, Opaque, PolyTraitPredicate, Projection, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable,
8 TypeSuperVisitable, TypeVisitor,
11 use rustc_data_structures::fx::FxHashMap;
12 use rustc_errors::{Applicability, Diagnostic, DiagnosticArgValue, IntoDiagnosticArg};
14 use rustc_hir::def::DefKind;
15 use rustc_hir::def_id::DefId;
16 use rustc_hir::WherePredicate;
18 use rustc_type_ir::sty::TyKind::*;
20 impl<'tcx> IntoDiagnosticArg for Ty<'tcx> {
21 fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
22 self.to_string().into_diagnostic_arg()
27 /// Similar to `Ty::is_primitive`, but also considers inferred numeric values to be primitive.
28 pub fn is_primitive_ty(self) -> bool {
38 | InferTy::FloatVar(_)
39 | InferTy::FreshIntTy(_)
40 | InferTy::FreshFloatTy(_)
45 /// Whether the type is succinctly representable as a type instead of just referred to with a
46 /// description in error messages. This is used in the main error message.
47 pub fn is_simple_ty(self) -> bool {
57 | InferTy::FloatVar(_)
58 | InferTy::FreshIntTy(_)
59 | InferTy::FreshFloatTy(_),
61 Ref(_, x, _) | Array(x, _) | Slice(x) => x.peel_refs().is_simple_ty(),
62 Tuple(tys) if tys.is_empty() => true,
67 /// Whether the type is succinctly representable as a type instead of just referred to with a
68 /// description in error messages. This is used in the primary span label. Beyond what
69 /// `is_simple_ty` includes, it also accepts ADTs with no type arguments and references to
70 /// ADTs with no type arguments.
71 pub fn is_simple_text(self) -> bool {
73 Adt(_, substs) => substs.non_erasable_generics().next().is_none(),
74 Ref(_, ty, _) => ty.is_simple_text(),
75 _ => self.is_simple_ty(),
80 pub trait IsSuggestable<'tcx>: Sized {
81 /// Whether this makes sense to suggest in a diagnostic.
83 /// We filter out certain types and constants since they don't provide
84 /// meaningful rendered suggestions when pretty-printed. We leave some
85 /// nonsense, such as region vars, since those render as `'_` and are
86 /// usually okay to reinterpret as elided lifetimes.
88 /// Only if `infer_suggestable` is true, we consider type and const
89 /// inference variables to be suggestable.
90 fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool;
92 fn make_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> Option<Self>;
95 impl<'tcx, T> IsSuggestable<'tcx> for T
97 T: TypeVisitable<'tcx> + TypeFoldable<'tcx>,
99 fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool {
100 self.visit_with(&mut IsSuggestableVisitor { tcx, infer_suggestable }).is_continue()
103 fn make_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> Option<T> {
104 self.try_fold_with(&mut MakeSuggestableFolder { tcx, infer_suggestable }).ok()
108 pub fn suggest_arbitrary_trait_bound<'tcx>(
110 generics: &hir::Generics<'_>,
111 err: &mut Diagnostic,
112 trait_pred: PolyTraitPredicate<'tcx>,
113 associated_ty: Option<(&'static str, Ty<'tcx>)>,
115 if !trait_pred.is_suggestable(tcx, false) {
119 let param_name = trait_pred.skip_binder().self_ty().to_string();
120 let mut constraint = trait_pred.print_modifiers_and_trait_path().to_string();
122 if let Some((name, term)) = associated_ty {
123 // FIXME: this case overlaps with code in TyCtxt::note_and_explain_type_err.
124 // That should be extracted into a helper function.
125 if constraint.ends_with('>') {
126 constraint = format!("{}, {} = {}>", &constraint[..constraint.len() - 1], name, term);
128 constraint.push_str(&format!("<{} = {}>", name, term));
132 let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
134 // Skip, there is a param named Self
135 if param.is_some() && param_name == "Self" {
139 // Suggest a where clause bound for a non-type parameter.
140 err.span_suggestion_verbose(
141 generics.tail_span_for_predicate_suggestion(),
143 "consider {} `where` clause, but there might be an alternative better way to express \
145 if generics.where_clause_span.is_empty() { "introducing a" } else { "extending the" },
147 format!("{} {}: {}", generics.add_where_or_trailing_comma(), param_name, constraint),
148 Applicability::MaybeIncorrect,
154 enum SuggestChangingConstraintsMessage<'a> {
155 RestrictBoundFurther,
156 RestrictType { ty: &'a str },
157 RestrictTypeFurther { ty: &'a str },
161 fn suggest_removing_unsized_bound(
162 generics: &hir::Generics<'_>,
163 suggestions: &mut Vec<(Span, String, SuggestChangingConstraintsMessage<'_>)>,
164 param: &hir::GenericParam<'_>,
165 def_id: Option<DefId>,
167 // See if there's a `?Sized` bound that can be removed to suggest that.
168 // First look at the `where` clause because we can have `where T: ?Sized`,
169 // then look at params.
170 for (where_pos, predicate) in generics.predicates.iter().enumerate() {
171 let WherePredicate::BoundPredicate(predicate) = predicate else {
174 if !predicate.is_param_bound(param.def_id.to_def_id()) {
178 for (pos, bound) in predicate.bounds.iter().enumerate() {
179 let hir::GenericBound::Trait(poly, hir::TraitBoundModifier::Maybe) = bound else {
182 if poly.trait_ref.trait_def_id() != def_id {
185 let sp = generics.span_for_bound_removal(where_pos, pos);
189 SuggestChangingConstraintsMessage::RemovingQSized,
195 /// Suggest restricting a type param with a new bound.
196 pub fn suggest_constraining_type_param(
198 generics: &hir::Generics<'_>,
199 err: &mut Diagnostic,
202 def_id: Option<DefId>,
204 suggest_constraining_type_params(
208 [(param_name, constraint, def_id)].into_iter(),
212 /// Suggest restricting a type param with a new bound.
213 pub fn suggest_constraining_type_params<'a>(
215 generics: &hir::Generics<'_>,
216 err: &mut Diagnostic,
217 param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>,
219 let mut grouped = FxHashMap::default();
220 param_names_and_constraints.for_each(|(param_name, constraint, def_id)| {
221 grouped.entry(param_name).or_insert(Vec::new()).push((constraint, def_id))
224 let mut applicability = Applicability::MachineApplicable;
225 let mut suggestions = Vec::new();
227 for (param_name, mut constraints) in grouped {
228 let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
229 let Some(param) = param else { return false };
232 let mut sized_constraints =
233 constraints.drain_filter(|(_, def_id)| *def_id == tcx.lang_items().sized_trait());
234 if let Some((constraint, def_id)) = sized_constraints.next() {
235 applicability = Applicability::MaybeIncorrect;
239 &format!("this type parameter needs to be `{}`", constraint),
241 suggest_removing_unsized_bound(generics, &mut suggestions, param, def_id);
245 if constraints.is_empty() {
249 let mut constraint = constraints.iter().map(|&(c, _)| c).collect::<Vec<_>>();
252 let constraint = constraint.join(" + ");
253 let mut suggest_restrict = |span, bound_list_non_empty| {
256 if bound_list_non_empty {
257 format!(" + {}", constraint)
259 format!(" {}", constraint)
261 SuggestChangingConstraintsMessage::RestrictBoundFurther,
265 // When the type parameter has been provided bounds
268 // fn foo<T>(t: T) where T: Foo { ... }
271 // help: consider further restricting this bound with `+ Bar`
274 // fn foo<T>(t: T) where T: Foo { ... }
277 // replace with: ` + Bar`
279 // Or, if user has provided some bounds, suggest restricting them:
281 // fn foo<T: Foo>(t: T) { ... }
284 // help: consider further restricting this bound with `+ Bar`
286 // Suggestion for tools in this case is:
288 // fn foo<T: Foo>(t: T) { ... }
291 // replace with: `T: Bar +`
292 if let Some(span) = generics.bounds_span_for_suggestions(param.def_id) {
293 suggest_restrict(span, true);
297 if generics.has_where_clause_predicates {
298 // This part is a bit tricky, because using the `where` clause user can
299 // provide zero, one or many bounds for the same type parameter, so we
300 // have following cases to consider:
302 // When the type parameter has been provided zero bounds
305 // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
306 // - help: consider restricting this type parameter with `where X: Bar`
309 // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
310 // - insert: `, X: Bar`
312 generics.tail_span_for_predicate_suggestion(),
315 .map(|&(constraint, _)| format!(", {}: {}", param_name, constraint))
316 .collect::<String>(),
317 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
322 // Additionally, there may be no `where` clause but the generic parameter has a default:
325 // trait Foo<T=()> {... }
326 // - help: consider further restricting this type parameter with `where T: Zar`
329 // trait Foo<T=()> {... }
330 // - insert: `where T: Zar`
331 if matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) {
332 // Suggest a bound, but there is no existing `where` clause *and* the type param has a
333 // default (`<T=Foo>`), so we suggest adding `where T: Bar`.
335 generics.tail_span_for_predicate_suggestion(),
336 format!(" where {}: {}", param_name, constraint),
337 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
342 // If user has provided a colon, don't suggest adding another:
344 // fn foo<T:>(t: T) { ... }
345 // - insert: consider restricting this type parameter with `T: Foo`
346 if let Some(colon_span) = param.colon_span {
348 colon_span.shrink_to_hi(),
349 format!(" {}", constraint),
350 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
355 // If user hasn't provided any bounds, suggest adding a new one:
357 // fn foo<T>(t: T) { ... }
358 // - help: consider restricting this type parameter with `T: Foo`
360 param.span.shrink_to_hi(),
361 format!(": {}", constraint),
362 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
366 // FIXME: remove the suggestions that are from derive, as the span is not correct
367 suggestions = suggestions
369 .filter(|(span, _, _)| !span.in_derive_expansion())
370 .collect::<Vec<_>>();
372 if suggestions.len() == 1 {
373 let (span, suggestion, msg) = suggestions.pop().unwrap();
376 let msg = match msg {
377 SuggestChangingConstraintsMessage::RestrictBoundFurther => {
378 "consider further restricting this bound"
380 SuggestChangingConstraintsMessage::RestrictType { ty } => {
381 s = format!("consider restricting type parameter `{}`", ty);
384 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => {
385 s = format!("consider further restricting type parameter `{}`", ty);
388 SuggestChangingConstraintsMessage::RemovingQSized => {
389 "consider removing the `?Sized` bound to make the type parameter `Sized`"
393 err.span_suggestion_verbose(span, msg, suggestion, applicability);
394 } else if suggestions.len() > 1 {
395 err.multipart_suggestion_verbose(
396 "consider restricting type parameters",
397 suggestions.into_iter().map(|(span, suggestion, _)| (span, suggestion)).collect(),
405 /// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for.
406 pub struct TraitObjectVisitor<'tcx>(pub Vec<&'tcx hir::Ty<'tcx>>, pub crate::hir::map::Map<'tcx>);
408 impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> {
409 fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
411 hir::TyKind::TraitObject(
415 hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static,
422 hir::TyKind::OpaqueDef(item_id, _, _) => {
424 let item = self.1.item(item_id);
425 hir::intravisit::walk_item(self, item);
429 hir::intravisit::walk_ty(self, ty);
433 /// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for.
434 pub struct StaticLifetimeVisitor<'tcx>(pub Vec<Span>, pub crate::hir::map::Map<'tcx>);
436 impl<'v> hir::intravisit::Visitor<'v> for StaticLifetimeVisitor<'v> {
437 fn visit_lifetime(&mut self, lt: &'v hir::Lifetime) {
438 if let hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static = lt.res
440 self.0.push(lt.ident.span);
445 pub struct IsSuggestableVisitor<'tcx> {
447 infer_suggestable: bool,
450 impl<'tcx> TypeVisitor<'tcx> for IsSuggestableVisitor<'tcx> {
453 fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
455 Infer(InferTy::TyVar(_)) if self.infer_suggestable => {}
461 | GeneratorWitness(..)
465 return ControlFlow::Break(());
468 Alias(Opaque, AliasTy { def_id, .. }) => {
469 let parent = self.tcx.parent(def_id);
470 if let DefKind::TyAlias | DefKind::AssocTy = self.tcx.def_kind(parent)
471 && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) = *self.tcx.type_of(parent).kind()
472 && parent_opaque_def_id == def_id
476 return ControlFlow::Break(());
480 Alias(Projection, AliasTy { def_id, .. }) => {
481 if self.tcx.def_kind(def_id) != DefKind::AssocTy {
482 return ControlFlow::Break(());
487 // FIXME: It would be nice to make this not use string manipulation,
488 // but it's pretty hard to do this, since `ty::ParamTy` is missing
489 // sufficient info to determine if it is synthetic, and we don't
490 // always have a convenient way of getting `ty::Generics` at the call
491 // sites we invoke `IsSuggestable::is_suggestable`.
492 if param.name.as_str().starts_with("impl ") {
493 return ControlFlow::Break(());
500 t.super_visit_with(self)
503 fn visit_const(&mut self, c: Const<'tcx>) -> ControlFlow<Self::BreakTy> {
505 ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => {}
508 | ConstKind::Bound(..)
509 | ConstKind::Placeholder(..)
510 | ConstKind::Error(..) => {
511 return ControlFlow::Break(());
516 c.super_visit_with(self)
520 pub struct MakeSuggestableFolder<'tcx> {
522 infer_suggestable: bool,
525 impl<'tcx> FallibleTypeFolder<'tcx> for MakeSuggestableFolder<'tcx> {
528 fn tcx(&self) -> TyCtxt<'tcx> {
532 fn try_fold_ty(&mut self, t: Ty<'tcx>) -> Result<Ty<'tcx>, Self::Error> {
533 let t = match *t.kind() {
534 Infer(InferTy::TyVar(_)) if self.infer_suggestable => t,
536 FnDef(def_id, substs) => {
537 self.tcx.mk_fn_ptr(self.tcx.fn_sig(def_id).subst(self.tcx, substs))
540 // FIXME(compiler-errors): We could replace these with infer, I guess.
544 | GeneratorWitness(..)
551 Alias(Opaque, AliasTy { def_id, .. }) => {
552 let parent = self.tcx.parent(def_id);
553 if let hir::def::DefKind::TyAlias | hir::def::DefKind::AssocTy = self.tcx.def_kind(parent)
554 && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) = *self.tcx.type_of(parent).kind()
555 && parent_opaque_def_id == def_id
564 // FIXME: It would be nice to make this not use string manipulation,
565 // but it's pretty hard to do this, since `ty::ParamTy` is missing
566 // sufficient info to determine if it is synthetic, and we don't
567 // always have a convenient way of getting `ty::Generics` at the call
568 // sites we invoke `IsSuggestable::is_suggestable`.
569 if param.name.as_str().starts_with("impl ") {
579 t.try_super_fold_with(self)
582 fn try_fold_const(&mut self, c: Const<'tcx>) -> Result<Const<'tcx>, ()> {
583 let c = match c.kind() {
584 ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => c,
587 | ConstKind::Bound(..)
588 | ConstKind::Placeholder(..)
589 | ConstKind::Error(..) => {
596 c.try_super_fold_with(self)