| //! Diagnostics related methods for `Ty`. | |
| use std::fmt::Write; | |
| use std::ops::ControlFlow; | |
| use rustc_data_structures::fx::FxIndexMap; | |
| use rustc_errors::{ | |
| Applicability, Diag, DiagArgValue, IntoDiagArg, into_diag_arg_using_display, listify, pluralize, | |
| }; | |
| use rustc_hir::def::{DefKind, Namespace}; | |
| use rustc_hir::def_id::DefId; | |
| use rustc_hir::{self as hir, AmbigArg, LangItem, PredicateOrigin, WherePredicateKind}; | |
| use rustc_span::{BytePos, Span}; | |
| use rustc_type_ir::TyKind::*; | |
| use crate::ty::{ | |
| self, AliasTy, Const, ConstKind, FallibleTypeFolder, InferConst, InferTy, Instance, Opaque, | |
| PolyTraitPredicate, Projection, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, | |
| TypeSuperVisitable, TypeVisitable, TypeVisitor, | |
| }; | |
| impl IntoDiagArg for Ty<'_> { | |
| fn into_diag_arg(self, path: &mut Option<std::path::PathBuf>) -> rustc_errors::DiagArgValue { | |
| ty::tls::with(|tcx| { | |
| let ty = tcx.short_string(self, path); | |
| DiagArgValue::Str(std::borrow::Cow::Owned(ty)) | |
| }) | |
| } | |
| } | |
| impl IntoDiagArg for Instance<'_> { | |
| fn into_diag_arg(self, path: &mut Option<std::path::PathBuf>) -> rustc_errors::DiagArgValue { | |
| ty::tls::with(|tcx| { | |
| let instance = tcx.short_string_namespace(self, path, Namespace::ValueNS); | |
| DiagArgValue::Str(std::borrow::Cow::Owned(instance)) | |
| }) | |
| } | |
| } | |
| into_diag_arg_using_display! { | |
| ty::Region<'_>, | |
| } | |
| impl<'tcx> Ty<'tcx> { | |
| /// Similar to `Ty::is_primitive`, but also considers inferred numeric values to be primitive. | |
| pub fn is_primitive_ty(self) -> bool { | |
| matches!( | |
| self.kind(), | |
| Bool | Char | |
| | Str | |
| | Int(_) | |
| | Uint(_) | |
| | Float(_) | |
| | Infer( | |
| InferTy::IntVar(_) | |
| | InferTy::FloatVar(_) | |
| | InferTy::FreshIntTy(_) | |
| | InferTy::FreshFloatTy(_) | |
| ) | |
| ) | |
| } | |
| /// Whether the type is succinctly representable as a type instead of just referred to with a | |
| /// description in error messages. This is used in the main error message. | |
| pub fn is_simple_ty(self) -> bool { | |
| match self.kind() { | |
| Bool | |
| | Char | |
| | Str | |
| | Int(_) | |
| | Uint(_) | |
| | Float(_) | |
| | Infer( | |
| InferTy::IntVar(_) | |
| | InferTy::FloatVar(_) | |
| | InferTy::FreshIntTy(_) | |
| | InferTy::FreshFloatTy(_), | |
| ) => true, | |
| Ref(_, x, _) | Array(x, _) | Slice(x) => x.peel_refs().is_simple_ty(), | |
| Tuple(tys) if tys.is_empty() => true, | |
| _ => false, | |
| } | |
| } | |
| /// Whether the type is succinctly representable as a type instead of just referred to with a | |
| /// description in error messages. This is used in the primary span label. Beyond what | |
| /// `is_simple_ty` includes, it also accepts ADTs with no type arguments and references to | |
| /// ADTs with no type arguments. | |
| pub fn is_simple_text(self) -> bool { | |
| match self.kind() { | |
| Adt(_, args) => args.non_erasable_generics().next().is_none(), | |
| Ref(_, ty, _) => ty.is_simple_text(), | |
| _ => self.is_simple_ty(), | |
| } | |
| } | |
| } | |
| pub trait IsSuggestable<'tcx>: Sized { | |
| /// Whether this makes sense to suggest in a diagnostic. | |
| /// | |
| /// We filter out certain types and constants since they don't provide | |
| /// meaningful rendered suggestions when pretty-printed. We leave some | |
| /// nonsense, such as region vars, since those render as `'_` and are | |
| /// usually okay to reinterpret as elided lifetimes. | |
| /// | |
| /// Only if `infer_suggestable` is true, we consider type and const | |
| /// inference variables to be suggestable. | |
| fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool; | |
| fn make_suggestable( | |
| self, | |
| tcx: TyCtxt<'tcx>, | |
| infer_suggestable: bool, | |
| placeholder: Option<Ty<'tcx>>, | |
| ) -> Option<Self>; | |
| } | |
| impl<'tcx, T> IsSuggestable<'tcx> for T | |
| where | |
| T: TypeVisitable<TyCtxt<'tcx>> + TypeFoldable<TyCtxt<'tcx>>, | |
| { | |
| #[tracing::instrument(level = "debug", skip(tcx))] | |
| fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool { | |
| self.visit_with(&mut IsSuggestableVisitor { tcx, infer_suggestable }).is_continue() | |
| } | |
| fn make_suggestable( | |
| self, | |
| tcx: TyCtxt<'tcx>, | |
| infer_suggestable: bool, | |
| placeholder: Option<Ty<'tcx>>, | |
| ) -> Option<T> { | |
| self.try_fold_with(&mut MakeSuggestableFolder { tcx, infer_suggestable, placeholder }).ok() | |
| } | |
| } | |
| pub fn suggest_arbitrary_trait_bound<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| generics: &hir::Generics<'_>, | |
| err: &mut Diag<'_>, | |
| trait_pred: PolyTraitPredicate<'tcx>, | |
| associated_ty: Option<(&'static str, Ty<'tcx>)>, | |
| ) -> bool { | |
| if !trait_pred.is_suggestable(tcx, false) { | |
| return false; | |
| } | |
| let param_name = trait_pred.skip_binder().self_ty().to_string(); | |
| let mut constraint = trait_pred.to_string(); | |
| if let Some((name, term)) = associated_ty { | |
| // FIXME: this case overlaps with code in TyCtxt::note_and_explain_type_err. | |
| // That should be extracted into a helper function. | |
| if let Some(stripped) = constraint.strip_suffix('>') { | |
| constraint = format!("{stripped}, {name} = {term}>"); | |
| } else { | |
| constraint.push_str(&format!("<{name} = {term}>")); | |
| } | |
| } | |
| let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name); | |
| // Skip, there is a param named Self | |
| if param.is_some() && param_name == "Self" { | |
| return false; | |
| } | |
| // Suggest a where clause bound for a non-type parameter. | |
| err.span_suggestion_verbose( | |
| generics.tail_span_for_predicate_suggestion(), | |
| format!( | |
| "consider {} `where` clause, but there might be an alternative better way to express \ | |
| this requirement", | |
| if generics.where_clause_span.is_empty() { "introducing a" } else { "extending the" }, | |
| ), | |
| format!("{} {constraint}", generics.add_where_or_trailing_comma()), | |
| Applicability::MaybeIncorrect, | |
| ); | |
| true | |
| } | |
| #[derive(Debug, Clone, Copy)] | |
| enum SuggestChangingConstraintsMessage<'a> { | |
| RestrictBoundFurther, | |
| RestrictType { ty: &'a str }, | |
| RestrictTypeFurther { ty: &'a str }, | |
| RemoveMaybeUnsized, | |
| ReplaceMaybeUnsizedWithSized, | |
| } | |
| fn suggest_changing_unsized_bound( | |
| generics: &hir::Generics<'_>, | |
| suggestions: &mut Vec<(Span, String, String, SuggestChangingConstraintsMessage<'_>)>, | |
| param: &hir::GenericParam<'_>, | |
| def_id: Option<DefId>, | |
| ) { | |
| // See if there's a `?Sized` bound that can be removed to suggest that. | |
| // First look at the `where` clause because we can have `where T: ?Sized`, | |
| // then look at params. | |
| for (where_pos, predicate) in generics.predicates.iter().enumerate() { | |
| let WherePredicateKind::BoundPredicate(predicate) = predicate.kind else { | |
| continue; | |
| }; | |
| if !predicate.is_param_bound(param.def_id.to_def_id()) { | |
| continue; | |
| }; | |
| let unsized_bounds = predicate | |
| .bounds | |
| .iter() | |
| .enumerate() | |
| .filter(|(_, bound)| { | |
| if let hir::GenericBound::Trait(poly) = bound | |
| && let hir::BoundPolarity::Maybe(_) = poly.modifiers.polarity | |
| && poly.trait_ref.trait_def_id() == def_id | |
| { | |
| true | |
| } else { | |
| false | |
| } | |
| }) | |
| .collect::<Vec<_>>(); | |
| if unsized_bounds.is_empty() { | |
| continue; | |
| } | |
| let mut push_suggestion = | |
| |sp, msg| suggestions.push((sp, "Sized".to_string(), String::new(), msg)); | |
| if predicate.bounds.len() == unsized_bounds.len() { | |
| // All the bounds are unsized bounds, e.g. | |
| // `T: ?Sized + ?Sized` or `_: impl ?Sized + ?Sized`, | |
| // so in this case: | |
| // - if it's an impl trait predicate suggest changing the | |
| // the first bound to sized and removing the rest | |
| // - Otherwise simply suggest removing the entire predicate | |
| if predicate.origin == PredicateOrigin::ImplTrait { | |
| let first_bound = unsized_bounds[0].1; | |
| let first_bound_span = first_bound.span(); | |
| if first_bound_span.can_be_used_for_suggestions() { | |
| let question_span = | |
| first_bound_span.with_hi(first_bound_span.lo() + BytePos(1)); | |
| push_suggestion( | |
| question_span, | |
| SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized, | |
| ); | |
| for (pos, _) in unsized_bounds.iter().skip(1) { | |
| let sp = generics.span_for_bound_removal(where_pos, *pos); | |
| push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized); | |
| } | |
| } | |
| } else { | |
| let sp = generics.span_for_predicate_removal(where_pos); | |
| push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized); | |
| } | |
| } else { | |
| // Some of the bounds are other than unsized. | |
| // So push separate removal suggestion for each unsized bound | |
| for (pos, _) in unsized_bounds { | |
| let sp = generics.span_for_bound_removal(where_pos, pos); | |
| push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized); | |
| } | |
| } | |
| } | |
| } | |
| /// Suggest restricting a type param with a new bound. | |
| /// | |
| /// If `span_to_replace` is provided, then that span will be replaced with the | |
| /// `constraint`. If one wasn't provided, then the full bound will be suggested. | |
| pub fn suggest_constraining_type_param( | |
| tcx: TyCtxt<'_>, | |
| generics: &hir::Generics<'_>, | |
| err: &mut Diag<'_>, | |
| param_name: &str, | |
| constraint: &str, | |
| def_id: Option<DefId>, | |
| span_to_replace: Option<Span>, | |
| ) -> bool { | |
| suggest_constraining_type_params( | |
| tcx, | |
| generics, | |
| err, | |
| [(param_name, constraint, def_id)].into_iter(), | |
| span_to_replace, | |
| ) | |
| } | |
| /// Suggest restricting a type param with a new bound. | |
| pub fn suggest_constraining_type_params<'a>( | |
| tcx: TyCtxt<'_>, | |
| generics: &hir::Generics<'_>, | |
| err: &mut Diag<'_>, | |
| param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>, | |
| span_to_replace: Option<Span>, | |
| ) -> bool { | |
| let mut grouped = FxIndexMap::default(); | |
| let mut unstable_suggestion = false; | |
| param_names_and_constraints.for_each(|(param_name, constraint, def_id)| { | |
| let stable = match def_id { | |
| Some(def_id) => match tcx.lookup_stability(def_id) { | |
| Some(s) => s.level.is_stable(), | |
| None => true, | |
| }, | |
| None => true, | |
| }; | |
| if stable || tcx.sess.is_nightly_build() { | |
| grouped.entry(param_name).or_insert(Vec::new()).push(( | |
| constraint, | |
| def_id, | |
| if stable { "" } else { "unstable " }, | |
| )); | |
| if !stable { | |
| unstable_suggestion = true; | |
| } | |
| } | |
| }); | |
| let mut applicability = Applicability::MachineApplicable; | |
| let mut suggestions = Vec::new(); | |
| for (param_name, mut constraints) in grouped { | |
| let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name); | |
| let Some(param) = param else { return false }; | |
| { | |
| let mut sized_constraints = constraints.extract_if(.., |(_, def_id, _)| { | |
| def_id.is_some_and(|def_id| tcx.is_lang_item(def_id, LangItem::Sized)) | |
| }); | |
| if let Some((_, def_id, _)) = sized_constraints.next() { | |
| applicability = Applicability::MaybeIncorrect; | |
| err.span_label(param.span, "this type parameter needs to be `Sized`"); | |
| suggest_changing_unsized_bound(generics, &mut suggestions, param, def_id); | |
| } | |
| } | |
| let bound_message = if constraints.iter().any(|(_, def_id, _)| def_id.is_none()) { | |
| SuggestChangingConstraintsMessage::RestrictBoundFurther | |
| } else { | |
| SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name } | |
| }; | |
| // in the scenario like impl has stricter requirements than trait, | |
| // we should not suggest restrict bound on the impl, here we double check | |
| // the whether the param already has the constraint by checking `def_id` | |
| let bound_trait_defs: Vec<DefId> = generics | |
| .bounds_for_param(param.def_id) | |
| .flat_map(|bound| { | |
| bound.bounds.iter().flat_map(|b| b.trait_ref().and_then(|t| t.trait_def_id())) | |
| }) | |
| .collect(); | |
| constraints | |
| .retain(|(_, def_id, _)| def_id.is_none_or(|def| !bound_trait_defs.contains(&def))); | |
| if constraints.is_empty() { | |
| continue; | |
| } | |
| let mut constraint = constraints.iter().map(|&(c, _, _)| c).collect::<Vec<_>>(); | |
| constraint.sort(); | |
| constraint.dedup(); | |
| let all_known = constraints.iter().all(|&(_, def_id, _)| def_id.is_some()); | |
| let all_stable = constraints.iter().all(|&(_, _, stable)| stable.is_empty()); | |
| let all_unstable = constraints.iter().all(|&(_, _, stable)| !stable.is_empty()); | |
| let post = if all_stable || all_unstable { | |
| // Don't redundantly say "trait `X`, trait `Y`", instead "traits `X` and `Y`" | |
| let mut trait_names = constraints | |
| .iter() | |
| .map(|&(c, def_id, _)| match def_id { | |
| None => format!("`{c}`"), | |
| Some(def_id) => format!("`{}`", tcx.item_name(def_id)), | |
| }) | |
| .collect::<Vec<_>>(); | |
| trait_names.sort(); | |
| trait_names.dedup(); | |
| let n = trait_names.len(); | |
| let stable = if all_stable { "" } else { "unstable " }; | |
| let trait_ = if all_known { format!("trait{}", pluralize!(n)) } else { String::new() }; | |
| let Some(trait_names) = listify(&trait_names, |n| n.to_string()) else { return false }; | |
| format!("{stable}{trait_} {trait_names}") | |
| } else { | |
| // We're more explicit when there's a mix of stable and unstable traits. | |
| let mut trait_names = constraints | |
| .iter() | |
| .map(|&(c, def_id, stable)| match def_id { | |
| None => format!("`{c}`"), | |
| Some(def_id) => format!("{stable}trait `{}`", tcx.item_name(def_id)), | |
| }) | |
| .collect::<Vec<_>>(); | |
| trait_names.sort(); | |
| trait_names.dedup(); | |
| match listify(&trait_names, |t| t.to_string()) { | |
| Some(names) => names, | |
| None => return false, | |
| } | |
| }; | |
| let constraint = constraint.join(" + "); | |
| let mut suggest_restrict = |span, bound_list_non_empty, open_paren_sp| { | |
| let suggestion = if span_to_replace.is_some() { | |
| constraint.clone() | |
| } else if constraint.starts_with('<') { | |
| constraint.clone() | |
| } else if bound_list_non_empty { | |
| format!(" + {constraint}") | |
| } else { | |
| format!(" {constraint}") | |
| }; | |
| if let Some(open_paren_sp) = open_paren_sp { | |
| suggestions.push((open_paren_sp, post.clone(), "(".to_string(), bound_message)); | |
| suggestions.push((span, post.clone(), format!("){suggestion}"), bound_message)); | |
| } else { | |
| suggestions.push((span, post.clone(), suggestion, bound_message)); | |
| } | |
| }; | |
| if let Some(span) = span_to_replace { | |
| suggest_restrict(span, true, None); | |
| continue; | |
| } | |
| // When the type parameter has been provided bounds | |
| // | |
| // Message: | |
| // fn foo<T>(t: T) where T: Foo { ... } | |
| // ^^^^^^ | |
| // | | |
| // help: consider further restricting this bound with `+ Bar` | |
| // | |
| // Suggestion: | |
| // fn foo<T>(t: T) where T: Foo { ... } | |
| // ^ | |
| // | | |
| // replace with: ` + Bar` | |
| // | |
| // Or, if user has provided some bounds, suggest restricting them: | |
| // | |
| // fn foo<T: Foo>(t: T) { ... } | |
| // --- | |
| // | | |
| // help: consider further restricting this bound with `+ Bar` | |
| // | |
| // Suggestion for tools in this case is: | |
| // | |
| // fn foo<T: Foo>(t: T) { ... } | |
| // -- | |
| // | | |
| // replace with: `T: Bar +` | |
| if let Some((span, open_paren_sp)) = generics.bounds_span_for_suggestions(param.def_id) { | |
| suggest_restrict(span, true, open_paren_sp); | |
| continue; | |
| } | |
| if generics.has_where_clause_predicates { | |
| // This part is a bit tricky, because using the `where` clause user can | |
| // provide zero, one or many bounds for the same type parameter, so we | |
| // have following cases to consider: | |
| // | |
| // When the type parameter has been provided zero bounds | |
| // | |
| // Message: | |
| // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... } | |
| // - help: consider restricting this type parameter with `where X: Bar` | |
| // | |
| // Suggestion: | |
| // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... } | |
| // - insert: `, X: Bar` | |
| suggestions.push(( | |
| generics.tail_span_for_predicate_suggestion(), | |
| post, | |
| constraints.iter().fold(String::new(), |mut string, &(constraint, _, _)| { | |
| write!(string, ", {param_name}: {constraint}").unwrap(); | |
| string | |
| }), | |
| SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name }, | |
| )); | |
| continue; | |
| } | |
| // Additionally, there may be no `where` clause but the generic parameter has a default: | |
| // | |
| // Message: | |
| // trait Foo<T=()> {... } | |
| // - help: consider further restricting this type parameter with `where T: Zar` | |
| // | |
| // Suggestion: | |
| // trait Foo<T=()> {... } | |
| // - insert: `where T: Zar` | |
| if matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) { | |
| // If we are here and the where clause span is of non-zero length | |
| // it means we're dealing with an empty where clause like this: | |
| // fn foo<X>(x: X) where { ... } | |
| // In that case we don't want to add another "where" (Fixes #120838) | |
| let where_prefix = if generics.where_clause_span.is_empty() { " where" } else { "" }; | |
| // Suggest a bound, but there is no existing `where` clause *and* the type param has a | |
| // default (`<T=Foo>`), so we suggest adding `where T: Bar`. | |
| suggestions.push(( | |
| generics.tail_span_for_predicate_suggestion(), | |
| post, | |
| format!("{where_prefix} {param_name}: {constraint}"), | |
| SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name }, | |
| )); | |
| continue; | |
| } | |
| // If user has provided a colon, don't suggest adding another: | |
| // | |
| // fn foo<T:>(t: T) { ... } | |
| // - insert: consider restricting this type parameter with `T: Foo` | |
| if let Some(colon_span) = param.colon_span { | |
| suggestions.push(( | |
| colon_span.shrink_to_hi(), | |
| post, | |
| format!(" {constraint}"), | |
| SuggestChangingConstraintsMessage::RestrictType { ty: param_name }, | |
| )); | |
| continue; | |
| } | |
| // If user hasn't provided any bounds, suggest adding a new one: | |
| // | |
| // fn foo<T>(t: T) { ... } | |
| // - help: consider restricting this type parameter with `T: Foo` | |
| let span = param.span.shrink_to_hi(); | |
| if span.can_be_used_for_suggestions() { | |
| suggestions.push(( | |
| span, | |
| post, | |
| format!(": {constraint}"), | |
| SuggestChangingConstraintsMessage::RestrictType { ty: param_name }, | |
| )); | |
| } | |
| } | |
| // FIXME: remove the suggestions that are from derive, as the span is not correct | |
| suggestions = suggestions | |
| .into_iter() | |
| .filter(|(span, _, _, _)| !span.in_derive_expansion()) | |
| .collect::<Vec<_>>(); | |
| let suggested = !suggestions.is_empty(); | |
| if suggestions.len() == 1 { | |
| let (span, post, suggestion, msg) = suggestions.pop().unwrap(); | |
| let msg = match msg { | |
| SuggestChangingConstraintsMessage::RestrictBoundFurther => { | |
| format!("consider further restricting this bound") | |
| } | |
| SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } | |
| | SuggestChangingConstraintsMessage::RestrictType { ty } | |
| if ty.starts_with("impl ") => | |
| { | |
| format!("consider restricting opaque type `{ty}` with {post}") | |
| } | |
| SuggestChangingConstraintsMessage::RestrictType { ty } => { | |
| format!("consider restricting type parameter `{ty}` with {post}") | |
| } | |
| SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => { | |
| format!("consider further restricting type parameter `{ty}` with {post}") | |
| } | |
| SuggestChangingConstraintsMessage::RemoveMaybeUnsized => { | |
| format!("consider removing the `?Sized` bound to make the type parameter `Sized`") | |
| } | |
| SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized => { | |
| format!("consider replacing `?Sized` with `Sized`") | |
| } | |
| }; | |
| err.span_suggestion_verbose(span, msg, suggestion, applicability); | |
| } else if suggestions.len() > 1 { | |
| let post = if unstable_suggestion { " (some of them are unstable traits)" } else { "" }; | |
| err.multipart_suggestion( | |
| format!("consider restricting type parameters{post}"), | |
| suggestions.into_iter().map(|(span, _, suggestion, _)| (span, suggestion)).collect(), | |
| applicability, | |
| ); | |
| } | |
| suggested | |
| } | |
| /// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for. | |
| pub(crate) struct TraitObjectVisitor<'tcx>(pub(crate) Vec<&'tcx hir::Ty<'tcx>>); | |
| impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> { | |
| fn visit_ty(&mut self, ty: &'v hir::Ty<'v, AmbigArg>) { | |
| match ty.kind { | |
| hir::TyKind::TraitObject(_, tagged_ptr) | |
| if let hir::Lifetime { | |
| kind: | |
| hir::LifetimeKind::ImplicitObjectLifetimeDefault | hir::LifetimeKind::Static, | |
| .. | |
| } = tagged_ptr.pointer() => | |
| { | |
| self.0.push(ty.as_unambig_ty()) | |
| } | |
| hir::TyKind::OpaqueDef(..) => self.0.push(ty.as_unambig_ty()), | |
| _ => {} | |
| } | |
| hir::intravisit::walk_ty(self, ty); | |
| } | |
| } | |
| pub struct IsSuggestableVisitor<'tcx> { | |
| tcx: TyCtxt<'tcx>, | |
| infer_suggestable: bool, | |
| } | |
| impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for IsSuggestableVisitor<'tcx> { | |
| type Result = ControlFlow<()>; | |
| fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { | |
| match *t.kind() { | |
| Infer(InferTy::TyVar(_)) if self.infer_suggestable => {} | |
| FnDef(..) | |
| | Closure(..) | |
| | Infer(..) | |
| | Coroutine(..) | |
| | CoroutineWitness(..) | |
| | Bound(_, _) | |
| | Placeholder(_) | |
| | Error(_) => { | |
| return ControlFlow::Break(()); | |
| } | |
| Alias(Opaque, AliasTy { def_id, .. }) => { | |
| let parent = self.tcx.parent(def_id); | |
| let parent_ty = self.tcx.type_of(parent).instantiate_identity(); | |
| if let DefKind::TyAlias | DefKind::AssocTy = self.tcx.def_kind(parent) | |
| && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) = | |
| *parent_ty.kind() | |
| && parent_opaque_def_id == def_id | |
| { | |
| // Okay | |
| } else { | |
| return ControlFlow::Break(()); | |
| } | |
| } | |
| Alias(Projection, AliasTy { def_id, .. }) | |
| if self.tcx.def_kind(def_id) != DefKind::AssocTy => | |
| { | |
| return ControlFlow::Break(()); | |
| } | |
| // FIXME: It would be nice to make this not use string manipulation, | |
| // but it's pretty hard to do this, since `ty::ParamTy` is missing | |
| // sufficient info to determine if it is synthetic, and we don't | |
| // always have a convenient way of getting `ty::Generics` at the call | |
| // sites we invoke `IsSuggestable::is_suggestable`. | |
| Param(param) if param.name.as_str().starts_with("impl ") => { | |
| return ControlFlow::Break(()); | |
| } | |
| _ => {} | |
| } | |
| t.super_visit_with(self) | |
| } | |
| fn visit_const(&mut self, c: Const<'tcx>) -> Self::Result { | |
| match c.kind() { | |
| ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => {} | |
| ConstKind::Infer(..) | |
| | ConstKind::Bound(..) | |
| | ConstKind::Placeholder(..) | |
| | ConstKind::Error(..) => { | |
| return ControlFlow::Break(()); | |
| } | |
| _ => {} | |
| } | |
| c.super_visit_with(self) | |
| } | |
| } | |
| pub struct MakeSuggestableFolder<'tcx> { | |
| tcx: TyCtxt<'tcx>, | |
| infer_suggestable: bool, | |
| placeholder: Option<Ty<'tcx>>, | |
| } | |
| impl<'tcx> FallibleTypeFolder<TyCtxt<'tcx>> for MakeSuggestableFolder<'tcx> { | |
| type Error = (); | |
| fn cx(&self) -> TyCtxt<'tcx> { | |
| self.tcx | |
| } | |
| fn try_fold_ty(&mut self, t: Ty<'tcx>) -> Result<Ty<'tcx>, Self::Error> { | |
| let t = match *t.kind() { | |
| Infer(InferTy::TyVar(_)) if self.infer_suggestable => t, | |
| FnDef(def_id, args) if self.placeholder.is_none() => { | |
| Ty::new_fn_ptr(self.tcx, self.tcx.fn_sig(def_id).instantiate(self.tcx, args)) | |
| } | |
| Closure(..) | |
| | CoroutineClosure(..) | |
| | FnDef(..) | |
| | Infer(..) | |
| | Coroutine(..) | |
| | CoroutineWitness(..) | |
| | Bound(_, _) | |
| | Placeholder(_) | |
| | Error(_) => { | |
| let Some(placeholder) = self.placeholder else { return Err(()) }; | |
| // We replace these with infer (which is passed in from an infcx). | |
| placeholder | |
| } | |
| Alias(Opaque, AliasTy { def_id, .. }) => { | |
| let parent = self.tcx.parent(def_id); | |
| let parent_ty = self.tcx.type_of(parent).instantiate_identity(); | |
| if let hir::def::DefKind::TyAlias | hir::def::DefKind::AssocTy = | |
| self.tcx.def_kind(parent) | |
| && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) = | |
| *parent_ty.kind() | |
| && parent_opaque_def_id == def_id | |
| { | |
| t | |
| } else { | |
| return Err(()); | |
| } | |
| } | |
| // FIXME: It would be nice to make this not use string manipulation, | |
| // but it's pretty hard to do this, since `ty::ParamTy` is missing | |
| // sufficient info to determine if it is synthetic, and we don't | |
| // always have a convenient way of getting `ty::Generics` at the call | |
| // sites we invoke `IsSuggestable::is_suggestable`. | |
| Param(param) if param.name.as_str().starts_with("impl ") => { | |
| return Err(()); | |
| } | |
| _ => t, | |
| }; | |
| t.try_super_fold_with(self) | |
| } | |
| fn try_fold_const(&mut self, c: Const<'tcx>) -> Result<Const<'tcx>, ()> { | |
| let c = match c.kind() { | |
| ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => c, | |
| ConstKind::Infer(..) | |
| | ConstKind::Bound(..) | |
| | ConstKind::Placeholder(..) | |
| | ConstKind::Error(..) => { | |
| return Err(()); | |
| } | |
| _ => c, | |
| }; | |
| c.try_super_fold_with(self) | |
| } | |
| } |