//! Diagnostics related methods for `Ty`.
-use crate::ty::subst::{GenericArg, GenericArgKind};
-use crate::ty::TyKind::*;
+use std::ops::ControlFlow;
+
use crate::ty::{
- ConstKind, DefIdTree, ExistentialPredicate, ExistentialProjection, ExistentialTraitRef,
- InferTy, ProjectionTy, Term, Ty, TyCtxt, TypeAndMut,
+ fold::TypeFoldable, Const, ConstKind, DefIdTree, ExistentialPredicate, InferTy,
+ PolyTraitPredicate, Ty, TyCtxt, TypeSuperFoldable, TypeVisitor,
};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::DefId;
use rustc_hir::WherePredicate;
use rustc_span::Span;
+use rustc_type_ir::sty::TyKind::*;
impl<'tcx> IntoDiagnosticArg for Ty<'tcx> {
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
_ => self.is_simple_ty(),
}
}
+}
- /// Whether the type can be safely suggested during error recovery.
- pub fn is_suggestable(self, tcx: TyCtxt<'tcx>) -> bool {
- fn generic_arg_is_suggestible<'tcx>(arg: GenericArg<'tcx>, tcx: TyCtxt<'tcx>) -> bool {
- match arg.unpack() {
- GenericArgKind::Type(ty) => ty.is_suggestable(tcx),
- GenericArgKind::Const(c) => const_is_suggestable(c.val()),
- _ => true,
- }
- }
-
- fn const_is_suggestable(kind: ConstKind<'_>) -> bool {
- match kind {
- ConstKind::Infer(..)
- | ConstKind::Bound(..)
- | ConstKind::Placeholder(..)
- | ConstKind::Error(..) => false,
- _ => true,
- }
- }
-
- // FIXME(compiler-errors): Some types are still not good to suggest,
- // specifically references with lifetimes within the function. Not
- //sure we have enough information to resolve whether a region is
- // temporary, so I'll leave this as a fixme.
+pub trait IsSuggestable<'tcx> {
+ /// 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.
+ fn is_suggestable(self, tcx: TyCtxt<'tcx>) -> bool;
+}
- match self.kind() {
- FnDef(..)
- | Closure(..)
- | Infer(..)
- | Generator(..)
- | GeneratorWitness(..)
- | Bound(_, _)
- | Placeholder(_)
- | Error(_) => false,
- Opaque(did, substs) => {
- let parent = tcx.parent(*did);
- if let hir::def::DefKind::TyAlias | hir::def::DefKind::AssocTy = tcx.def_kind(parent)
- && let Opaque(parent_did, _) = tcx.type_of(parent).kind()
- && parent_did == did
- {
- substs.iter().all(|a| generic_arg_is_suggestible(a, tcx))
- } else {
- false
- }
- }
- Dynamic(dty, _) => dty.iter().all(|pred| match pred.skip_binder() {
- ExistentialPredicate::Trait(ExistentialTraitRef { substs, .. }) => {
- substs.iter().all(|a| generic_arg_is_suggestible(a, tcx))
- }
- ExistentialPredicate::Projection(ExistentialProjection {
- substs, term, ..
- }) => {
- let term_is_suggestable = match term {
- Term::Ty(ty) => ty.is_suggestable(tcx),
- Term::Const(c) => const_is_suggestable(c.val()),
- };
- term_is_suggestable && substs.iter().all(|a| generic_arg_is_suggestible(a, tcx))
- }
- _ => true,
- }),
- Projection(ProjectionTy { substs: args, .. }) | Adt(_, args) => {
- args.iter().all(|a| generic_arg_is_suggestible(a, tcx))
- }
- Tuple(args) => args.iter().all(|ty| ty.is_suggestable(tcx)),
- Slice(ty) | RawPtr(TypeAndMut { ty, .. }) | Ref(_, ty, _) => ty.is_suggestable(tcx),
- Array(ty, c) => ty.is_suggestable(tcx) && const_is_suggestable(c.val()),
- _ => true,
- }
+impl<'tcx, T> IsSuggestable<'tcx> for T
+where
+ T: TypeFoldable<'tcx>,
+{
+ fn is_suggestable(self, tcx: TyCtxt<'tcx>) -> bool {
+ self.visit_with(&mut IsSuggestableVisitor { tcx }).is_continue()
}
}
-pub fn suggest_arbitrary_trait_bound(
+pub fn suggest_arbitrary_trait_bound<'tcx>(
+ tcx: TyCtxt<'tcx>,
generics: &hir::Generics<'_>,
err: &mut Diagnostic,
- param_name: &str,
- constraint: &str,
+ trait_pred: PolyTraitPredicate<'tcx>,
) -> bool {
+ if !trait_pred.is_suggestable(tcx) {
+ return false;
+ }
+
+ let param_name = trait_pred.skip_binder().self_ty().to_string();
+ let constraint = trait_pred.print_modifiers_and_trait_path().to_string();
let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
- match (param, param_name) {
- (Some(_), "Self") => return false,
- _ => {}
+
+ // 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.
- let (action, prefix) = if generics.has_where_clause {
- ("extending the", ", ")
- } else {
- ("introducing a", " where ")
- };
err.span_suggestion_verbose(
generics.tail_span_for_predicate_suggestion(),
&format!(
- "consider {} `where` bound, but there might be an alternative better way to express \
+ "consider {} `where` clause, but there might be an alternative better way to express \
this requirement",
- action,
+ if generics.where_clause_span.is_empty() { "introducing a" } else { "extending the" },
),
- format!("{}{}: {}", prefix, param_name, constraint),
+ format!("{} {}: {}", generics.add_where_or_trailing_comma(), param_name, constraint),
Applicability::MaybeIncorrect,
);
true
continue;
}
- let constraint = constraints.iter().map(|&(c, _)| c).collect::<Vec<_>>().join(" + ");
+ let mut constraint = constraints.iter().map(|&(c, _)| c).collect::<Vec<_>>();
+ constraint.sort();
+ constraint.dedup();
+ let constraint = constraint.join(" + ");
let mut suggest_restrict = |span, bound_list_non_empty| {
suggestions.push((
span,
continue;
}
- if generics.has_where_clause {
+ 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:
}
}
}
+
+pub struct IsSuggestableVisitor<'tcx> {
+ tcx: TyCtxt<'tcx>,
+}
+
+impl<'tcx> TypeVisitor<'tcx> for IsSuggestableVisitor<'tcx> {
+ type BreakTy = ();
+
+ fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+ match t.kind() {
+ FnDef(..)
+ | Closure(..)
+ | Infer(..)
+ | Generator(..)
+ | GeneratorWitness(..)
+ | Bound(_, _)
+ | Placeholder(_)
+ | Error(_) => {
+ return ControlFlow::Break(());
+ }
+
+ Opaque(did, _) => {
+ let parent = self.tcx.parent(*did);
+ if let hir::def::DefKind::TyAlias | hir::def::DefKind::AssocTy = self.tcx.def_kind(parent)
+ && let Opaque(parent_did, _) = self.tcx.type_of(parent).kind()
+ && parent_did == did
+ {
+ // Okay
+ } else {
+ return ControlFlow::Break(());
+ }
+ }
+
+ Dynamic(dty, _) => {
+ for pred in *dty {
+ match pred.skip_binder() {
+ ExistentialPredicate::Trait(_) | ExistentialPredicate::Projection(_) => {
+ // Okay
+ }
+ _ => return ControlFlow::Break(()),
+ }
+ }
+ }
+
+ Param(param) => {
+ // 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`.
+ if param.name.as_str().starts_with("impl ") {
+ return ControlFlow::Break(());
+ }
+ }
+
+ _ => {}
+ }
+
+ t.super_visit_with(self)
+ }
+
+ fn visit_const(&mut self, c: Const<'tcx>) -> ControlFlow<Self::BreakTy> {
+ match c.kind() {
+ ConstKind::Infer(..)
+ | ConstKind::Bound(..)
+ | ConstKind::Placeholder(..)
+ | ConstKind::Error(..) => {
+ return ControlFlow::Break(());
+ }
+ _ => {}
+ }
+
+ c.super_visit_with(self)
+ }
+}