use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; use rustc_hir::def_id::{DefId, DefIdMap}; use rustc_hir::{GenericBound, Generics, PolyTraitRef, TraitBoundModifier, WherePredicate}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ClauseKind, PredicatePolarity}; use rustc_session::declare_lint_pass; use rustc_span::symbol::Ident; declare_clippy_lint! { /// ### What it does /// Lints `?Sized` bounds applied to type parameters that cannot be unsized /// /// ### Why is this bad? /// The `?Sized` bound is misleading because it cannot be satisfied by an /// unsized type /// /// ### Example /// ```rust /// // `T` cannot be unsized because `Clone` requires it to be `Sized` /// fn f(t: &T) {} /// ``` /// Use instead: /// ```rust /// fn f(t: &T) {} /// /// // or choose alternative bounds for `T` so that it can be unsized /// ``` #[clippy::version = "1.79.0"] pub NEEDLESS_MAYBE_SIZED, suspicious, "a `?Sized` bound that is unusable due to a `Sized` requirement" } declare_lint_pass!(NeedlessMaybeSized => [NEEDLESS_MAYBE_SIZED]); #[allow(clippy::struct_field_names)] struct Bound<'tcx> { /// The [`DefId`] of the type parameter the bound refers to param: DefId, ident: Ident, trait_bound: &'tcx PolyTraitRef<'tcx>, modifier: TraitBoundModifier, predicate_pos: usize, bound_pos: usize, } /// Finds all of the [`Bound`]s that refer to a type parameter and are not from a macro expansion fn type_param_bounds<'tcx>(generics: &'tcx Generics<'tcx>) -> impl Iterator> { generics .predicates .iter() .enumerate() .filter_map(|(predicate_pos, predicate)| { let WherePredicate::BoundPredicate(bound_predicate) = predicate else { return None; }; let (param, ident) = bound_predicate.bounded_ty.as_generic_param()?; Some( bound_predicate .bounds .iter() .enumerate() .filter_map(move |(bound_pos, bound)| match bound { &GenericBound::Trait(ref trait_bound, modifier) => Some(Bound { param, ident, trait_bound, modifier, predicate_pos, bound_pos, }), GenericBound::Outlives(_) | GenericBound::Use(..) => None, }) .filter(|bound| !bound.trait_bound.span.from_expansion()), ) }) .flatten() } /// Searches the supertraits of the trait referred to by `trait_bound` recursively, returning the /// path taken to find a `Sized` bound if one is found fn path_to_sized_bound(cx: &LateContext<'_>, trait_bound: &PolyTraitRef<'_>) -> Option> { fn search(cx: &LateContext<'_>, path: &mut Vec) -> bool { let trait_def_id = *path.last().unwrap(); if Some(trait_def_id) == cx.tcx.lang_items().sized_trait() { return true; } for (predicate, _) in cx.tcx.explicit_super_predicates_of(trait_def_id).iter_identity_copied() { if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() && trait_predicate.polarity == PredicatePolarity::Positive && !path.contains(&trait_predicate.def_id()) { path.push(trait_predicate.def_id()); if search(cx, path) { return true; } path.pop(); } } false } let mut path = vec![trait_bound.trait_ref.trait_def_id()?]; search(cx, &mut path).then_some(path) } impl LateLintPass<'_> for NeedlessMaybeSized { fn check_generics(&mut self, cx: &LateContext<'_>, generics: &Generics<'_>) { let Some(sized_trait) = cx.tcx.lang_items().sized_trait() else { return; }; let maybe_sized_params: DefIdMap<_> = type_param_bounds(generics) .filter(|bound| { bound.trait_bound.trait_ref.trait_def_id() == Some(sized_trait) && bound.modifier == TraitBoundModifier::Maybe }) .map(|bound| (bound.param, bound)) .collect(); for bound in type_param_bounds(generics) { if bound.modifier == TraitBoundModifier::None && let Some(sized_bound) = maybe_sized_params.get(&bound.param) && let Some(path) = path_to_sized_bound(cx, bound.trait_bound) { span_lint_and_then( cx, NEEDLESS_MAYBE_SIZED, sized_bound.trait_bound.span, "`?Sized` bound is ignored because of a `Sized` requirement", |diag| { let ty_param = sized_bound.ident; diag.span_note( bound.trait_bound.span, format!("`{ty_param}` cannot be unsized because of the bound"), ); for &[current_id, next_id] in path.array_windows() { let current = cx.tcx.item_name(current_id); let next = cx.tcx.item_name(next_id); diag.note(format!("...because `{current}` has the bound `{next}`")); } diag.span_suggestion_verbose( generics.span_for_bound_removal(sized_bound.predicate_pos, sized_bound.bound_pos), "change the bounds that require `Sized`, or remove the `?Sized` bound", "", Applicability::MaybeIncorrect, ); }, ); return; } } } }