2024-06-13 05:30:48 -05:00
|
|
|
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: Clone + ?Sized>(t: &T) {}
|
|
|
|
/// ```
|
|
|
|
/// Use instead:
|
|
|
|
/// ```rust
|
|
|
|
/// fn f<T: Clone>(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<Item = Bound<'tcx>> {
|
|
|
|
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,
|
|
|
|
}),
|
2024-06-05 15:18:52 -05:00
|
|
|
GenericBound::Outlives(_) | GenericBound::Use(..) => None,
|
2024-06-13 05:30:48 -05:00
|
|
|
})
|
|
|
|
.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<Vec<DefId>> {
|
|
|
|
fn search(cx: &LateContext<'_>, path: &mut Vec<DefId>) -> bool {
|
|
|
|
let trait_def_id = *path.last().unwrap();
|
|
|
|
|
|
|
|
if Some(trait_def_id) == cx.tcx.lang_items().sized_trait() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-08-28 23:17:40 -05:00
|
|
|
for (predicate, _) in cx.tcx.explicit_super_predicates_of(trait_def_id).iter_identity_copied() {
|
2024-06-13 05:30:48 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|