1 //! Diagnostics related methods for `Ty`.
3 use std::ops::ControlFlow;
6 visit::TypeVisitable, AliasTy, Const, ConstKind, DefIdTree, InferConst, InferTy, Opaque,
7 PolyTraitPredicate, Projection, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor,
10 use rustc_data_structures::fx::FxHashMap;
11 use rustc_errors::{Applicability, Diagnostic, DiagnosticArgValue, IntoDiagnosticArg};
13 use rustc_hir::def::DefKind;
14 use rustc_hir::def_id::DefId;
15 use rustc_hir::WherePredicate;
17 use rustc_type_ir::sty::TyKind::*;
19 impl<'tcx> IntoDiagnosticArg for Ty<'tcx> {
20 fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
21 self.to_string().into_diagnostic_arg()
26 /// Similar to `Ty::is_primitive`, but also considers inferred numeric values to be primitive.
27 pub fn is_primitive_ty(self) -> bool {
37 | InferTy::FloatVar(_)
38 | InferTy::FreshIntTy(_)
39 | InferTy::FreshFloatTy(_)
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 {
56 | InferTy::FloatVar(_)
57 | InferTy::FreshIntTy(_)
58 | InferTy::FreshFloatTy(_),
60 Ref(_, x, _) | Array(x, _) | Slice(x) => x.peel_refs().is_simple_ty(),
61 Tuple(tys) if tys.is_empty() => true,
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 {
72 Adt(_, substs) => substs.non_erasable_generics().next().is_none(),
73 Ref(_, ty, _) => ty.is_simple_text(),
74 _ => self.is_simple_ty(),
79 pub trait IsSuggestable<'tcx> {
80 /// Whether this makes sense to suggest in a diagnostic.
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.
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;
92 impl<'tcx, T> IsSuggestable<'tcx> for T
94 T: TypeVisitable<'tcx>,
96 fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool {
97 self.visit_with(&mut IsSuggestableVisitor { tcx, infer_suggestable }).is_continue()
101 pub fn suggest_arbitrary_trait_bound<'tcx>(
103 generics: &hir::Generics<'_>,
104 err: &mut Diagnostic,
105 trait_pred: PolyTraitPredicate<'tcx>,
106 associated_ty: Option<(&'static str, Ty<'tcx>)>,
108 if !trait_pred.is_suggestable(tcx, false) {
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();
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);
121 constraint.push_str(&format!("<{} = {}>", name, term));
125 let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
127 // Skip, there is a param named Self
128 if param.is_some() && param_name == "Self" {
132 // Suggest a where clause bound for a non-type parameter.
133 err.span_suggestion_verbose(
134 generics.tail_span_for_predicate_suggestion(),
136 "consider {} `where` clause, but there might be an alternative better way to express \
138 if generics.where_clause_span.is_empty() { "introducing a" } else { "extending the" },
140 format!("{} {}: {}", generics.add_where_or_trailing_comma(), param_name, constraint),
141 Applicability::MaybeIncorrect,
147 enum SuggestChangingConstraintsMessage<'a> {
148 RestrictBoundFurther,
149 RestrictType { ty: &'a str },
150 RestrictTypeFurther { ty: &'a str },
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>,
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 {
167 if !predicate.is_param_bound(param.def_id.to_def_id()) {
171 for (pos, bound) in predicate.bounds.iter().enumerate() {
172 let hir::GenericBound::Trait(poly, hir::TraitBoundModifier::Maybe) = bound else {
175 if poly.trait_ref.trait_def_id() != def_id {
178 let sp = generics.span_for_bound_removal(where_pos, pos);
182 SuggestChangingConstraintsMessage::RemovingQSized,
188 /// Suggest restricting a type param with a new bound.
189 pub fn suggest_constraining_type_param(
191 generics: &hir::Generics<'_>,
192 err: &mut Diagnostic,
195 def_id: Option<DefId>,
197 suggest_constraining_type_params(
201 [(param_name, constraint, def_id)].into_iter(),
205 /// Suggest restricting a type param with a new bound.
206 pub fn suggest_constraining_type_params<'a>(
208 generics: &hir::Generics<'_>,
209 err: &mut Diagnostic,
210 param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>,
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))
217 let mut applicability = Applicability::MachineApplicable;
218 let mut suggestions = Vec::new();
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 };
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;
232 &format!("this type parameter needs to be `{}`", constraint),
234 suggest_removing_unsized_bound(generics, &mut suggestions, param, def_id);
238 if constraints.is_empty() {
242 let mut constraint = constraints.iter().map(|&(c, _)| c).collect::<Vec<_>>();
245 let constraint = constraint.join(" + ");
246 let mut suggest_restrict = |span, bound_list_non_empty| {
249 if bound_list_non_empty {
250 format!(" + {}", constraint)
252 format!(" {}", constraint)
254 SuggestChangingConstraintsMessage::RestrictBoundFurther,
258 // When the type parameter has been provided bounds
261 // fn foo<T>(t: T) where T: Foo { ... }
264 // help: consider further restricting this bound with `+ Bar`
267 // fn foo<T>(t: T) where T: Foo { ... }
270 // replace with: ` + Bar`
272 // Or, if user has provided some bounds, suggest restricting them:
274 // fn foo<T: Foo>(t: T) { ... }
277 // help: consider further restricting this bound with `+ Bar`
279 // Suggestion for tools in this case is:
281 // fn foo<T: Foo>(t: T) { ... }
284 // replace with: `T: Bar +`
285 if let Some(span) = generics.bounds_span_for_suggestions(param.def_id) {
286 suggest_restrict(span, true);
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:
295 // When the type parameter has been provided zero bounds
298 // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
299 // - help: consider restricting this type parameter with `where X: Bar`
302 // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
303 // - insert: `, X: Bar`
305 generics.tail_span_for_predicate_suggestion(),
308 .map(|&(constraint, _)| format!(", {}: {}", param_name, constraint))
309 .collect::<String>(),
310 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
315 // Additionally, there may be no `where` clause but the generic parameter has a default:
318 // trait Foo<T=()> {... }
319 // - help: consider further restricting this type parameter with `where T: Zar`
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`.
328 generics.tail_span_for_predicate_suggestion(),
329 format!(" where {}: {}", param_name, constraint),
330 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
335 // If user has provided a colon, don't suggest adding another:
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 {
341 colon_span.shrink_to_hi(),
342 format!(" {}", constraint),
343 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
348 // If user hasn't provided any bounds, suggest adding a new one:
350 // fn foo<T>(t: T) { ... }
351 // - help: consider restricting this type parameter with `T: Foo`
353 param.span.shrink_to_hi(),
354 format!(": {}", constraint),
355 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
359 // FIXME: remove the suggestions that are from derive, as the span is not correct
360 suggestions = suggestions
362 .filter(|(span, _, _)| !span.in_derive_expansion())
363 .collect::<Vec<_>>();
365 if suggestions.len() == 1 {
366 let (span, suggestion, msg) = suggestions.pop().unwrap();
369 let msg = match msg {
370 SuggestChangingConstraintsMessage::RestrictBoundFurther => {
371 "consider further restricting this bound"
373 SuggestChangingConstraintsMessage::RestrictType { ty } => {
374 s = format!("consider restricting type parameter `{}`", ty);
377 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => {
378 s = format!("consider further restricting type parameter `{}`", ty);
381 SuggestChangingConstraintsMessage::RemovingQSized => {
382 "consider removing the `?Sized` bound to make the type parameter `Sized`"
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(),
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>);
401 impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> {
402 fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
404 hir::TyKind::TraitObject(
408 hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static,
415 hir::TyKind::OpaqueDef(item_id, _, _) => {
417 let item = self.1.item(item_id);
418 hir::intravisit::walk_item(self, item);
422 hir::intravisit::walk_ty(self, ty);
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>);
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
433 self.0.push(lt.ident.span);
438 pub struct IsSuggestableVisitor<'tcx> {
440 infer_suggestable: bool,
443 impl<'tcx> TypeVisitor<'tcx> for IsSuggestableVisitor<'tcx> {
446 fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
448 Infer(InferTy::TyVar(_)) if self.infer_suggestable => {}
454 | GeneratorWitness(..)
458 return ControlFlow::Break(());
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
469 return ControlFlow::Break(());
473 Alias(Projection, AliasTy { def_id, .. }) => {
474 if self.tcx.def_kind(def_id) != DefKind::AssocTy {
475 return ControlFlow::Break(());
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(());
493 t.super_visit_with(self)
496 fn visit_const(&mut self, c: Const<'tcx>) -> ControlFlow<Self::BreakTy> {
498 ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => {}
501 | ConstKind::Bound(..)
502 | ConstKind::Placeholder(..)
503 | ConstKind::Error(..) => {
504 return ControlFlow::Break(());
509 c.super_visit_with(self)