extract supertrait collection to separate function

This commit is contained in:
y21 2024-02-11 00:17:21 +01:00
parent 28443e63fb
commit d1acbf576e

View File

@ -1,16 +1,17 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use rustc_errors::{Applicability, SuggestionStyle}; use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::{ use rustc_hir::{
Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, TraitBoundModifier, TraitItem, Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, OpaqueTy, TraitBoundModifier,
TraitItemKind, TyKind, TraitItem, TraitItemKind, TyKind, TypeBinding,
}; };
use rustc_hir_analysis::hir_ty_to_ty; use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, ClauseKind, Generics, Ty, TyCtxt}; use rustc_middle::ty::{self, ClauseKind, Generics, Ty, TyCtxt};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::Span; use rustc_span::Span;
declare_clippy_lint! { declare_clippy_lint! {
@ -50,20 +51,17 @@ declare_clippy_lint! {
} }
declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]); declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]);
#[allow(clippy::too_many_arguments)]
fn emit_lint( fn emit_lint(
cx: &LateContext<'_>, cx: &LateContext<'_>,
poly_trait: &rustc_hir::PolyTraitRef<'_>, poly_trait: &rustc_hir::PolyTraitRef<'_>,
opaque_ty: &rustc_hir::OpaqueTy<'_>, opaque_ty: &rustc_hir::OpaqueTy<'_>,
index: usize, index: usize,
// The bindings that were implied // The bindings that were implied, used for suggestion purposes since removing a bound with associated types
// means we might need to then move it to a different bound
implied_bindings: &[rustc_hir::TypeBinding<'_>], implied_bindings: &[rustc_hir::TypeBinding<'_>],
// The original bindings that `implied_bindings` are implied from bound: &ImplTraitBound<'_>,
implied_by_bindings: &[rustc_hir::TypeBinding<'_>],
implied_by_args: &[GenericArg<'_>],
implied_by_span: Span,
) { ) {
let implied_by = snippet(cx, implied_by_span, ".."); let implied_by = snippet(cx, bound.impl_trait_bound_span, "..");
span_lint_and_then( span_lint_and_then(
cx, cx,
@ -93,17 +91,17 @@ fn emit_lint(
// If we're going to suggest removing `Deref<..>`, we'll need to put `<Target = u8>` on `DerefMut` // If we're going to suggest removing `Deref<..>`, we'll need to put `<Target = u8>` on `DerefMut`
let omitted_assoc_tys: Vec<_> = implied_bindings let omitted_assoc_tys: Vec<_> = implied_bindings
.iter() .iter()
.filter(|binding| !implied_by_bindings.iter().any(|b| b.ident == binding.ident)) .filter(|binding| !bound.bindings.iter().any(|b| b.ident == binding.ident))
.collect(); .collect();
if !omitted_assoc_tys.is_empty() { if !omitted_assoc_tys.is_empty() {
// `<>` needs to be added if there aren't yet any generic arguments or bindings // `<>` needs to be added if there aren't yet any generic arguments or bindings
let needs_angle_brackets = implied_by_args.is_empty() && implied_by_bindings.is_empty(); let needs_angle_brackets = bound.args.is_empty() && bound.bindings.is_empty();
let insert_span = match (implied_by_args, implied_by_bindings) { let insert_span = match (bound.args, bound.bindings) {
([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(), ([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(),
([.., arg], []) => arg.span().shrink_to_hi(), ([.., arg], []) => arg.span().shrink_to_hi(),
([], [.., binding]) => binding.span.shrink_to_hi(), ([], [.., binding]) => binding.span.shrink_to_hi(),
([], []) => implied_by_span.shrink_to_hi(), ([], []) => bound.impl_trait_bound_span.shrink_to_hi(),
}; };
let mut associated_tys_sugg = if needs_angle_brackets { let mut associated_tys_sugg = if needs_angle_brackets {
@ -223,42 +221,64 @@ fn is_same_generics<'tcx>(
}) })
} }
struct ImplTraitBound<'tcx> {
/// The span of the bound in the `impl Trait` type
impl_trait_bound_span: Span,
/// The predicates defined in the trait referenced by this bound
predicates: &'tcx [(ty::Clause<'tcx>, Span)],
/// The `DefId` of the trait being referenced by this bound
trait_def_id: DefId,
/// The generic arguments on the `impl Trait` bound
args: &'tcx [GenericArg<'tcx>],
/// The associated types on this bound
bindings: &'tcx [TypeBinding<'tcx>],
}
/// Given an `impl Trait` type, gets all the supertraits from each bound ("implied bounds").
///
/// For `impl Deref + DerefMut + Eq` this returns `[Deref, PartialEq]`.
/// The `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`, and `PartialEq` comes from
/// `Eq`.
fn collect_supertrait_bounds<'tcx>(cx: &LateContext<'tcx>, opaque_ty: &OpaqueTy<'tcx>) -> Vec<ImplTraitBound<'tcx>> {
opaque_ty
.bounds
.iter()
.filter_map(|bound| {
if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound
&& let [.., path] = poly_trait.trait_ref.path.segments
&& poly_trait.bound_generic_params.is_empty()
&& let Some(trait_def_id) = path.res.opt_def_id()
&& let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates
// If the trait has no supertrait, there is no need to collect anything from that bound
&& !predicates.is_empty()
{
Some(ImplTraitBound {
predicates,
args: path.args.map_or([].as_slice(), |p| p.args),
bindings: path.args.map_or([].as_slice(), |p| p.bindings),
trait_def_id,
impl_trait_bound_span: bound.span(),
})
} else {
None
}
})
.collect()
}
fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) { fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
if let FnRetTy::Return(ty) = decl.output if let FnRetTy::Return(ty) = decl.output
&&let TyKind::OpaqueDef(item_id, ..) = ty.kind && let TyKind::OpaqueDef(item_id, ..) = ty.kind
&& let item = cx.tcx.hir().item(item_id) && let item = cx.tcx.hir().item(item_id)
&& let ItemKind::OpaqueTy(opaque_ty) = item.kind && let ItemKind::OpaqueTy(opaque_ty) = item.kind
// Very often there is only a single bound, e.g. `impl Deref<..>`, in which case // Very often there is only a single bound, e.g. `impl Deref<..>`, in which case
// we can avoid doing a bunch of stuff unnecessarily. // we can avoid doing a bunch of stuff unnecessarily.
&& opaque_ty.bounds.len() > 1 && opaque_ty.bounds.len() > 1
{ {
// Get all the (implied) trait predicates in the bounds. let supertraits = collect_supertrait_bounds(cx, opaque_ty);
// For `impl Deref + DerefMut` this will contain [`Deref`].
// The implied `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`.
// N.B. (G)ATs are fine to disregard, because they must be the same for all of its supertraits.
// Example:
// `impl Deref<Target = i32> + DerefMut<Target = u32>` is not allowed.
// `DerefMut::Target` needs to match `Deref::Target`.
let implied_bounds: Vec<_> = opaque_ty
.bounds
.iter()
.filter_map(|bound| {
if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound
&& let [.., path] = poly_trait.trait_ref.path.segments
&& poly_trait.bound_generic_params.is_empty()
&& let Some(trait_def_id) = path.res.opt_def_id()
&& let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates
&& !predicates.is_empty()
// If the trait has no supertrait, there is nothing to add.
{
Some((bound.span(), path, predicates, trait_def_id))
} else {
None
}
})
.collect();
// Lint all bounds in the `impl Trait` type that are also in the `implied_bounds` vec. // Lint all bounds in the `impl Trait` type that we've previously also seen in the set of
// supertraits of each of the bounds.
// This involves some extra logic when generic arguments are present, since // This involves some extra logic when generic arguments are present, since
// simply comparing trait `DefId`s won't be enough. We also need to compare the generics. // simply comparing trait `DefId`s won't be enough. We also need to compare the generics.
for (index, bound) in opaque_ty.bounds.iter().enumerate() { for (index, bound) in opaque_ty.bounds.iter().enumerate() {
@ -267,42 +287,26 @@ fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
&& let implied_args = path.args.map_or([].as_slice(), |a| a.args) && let implied_args = path.args.map_or([].as_slice(), |a| a.args)
&& let implied_bindings = path.args.map_or([].as_slice(), |a| a.bindings) && let implied_bindings = path.args.map_or([].as_slice(), |a| a.bindings)
&& let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id() && let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id()
&& let Some((implied_by_span, implied_by_args, implied_by_bindings)) = && let Some(bound) = supertraits.iter().find(|bound| {
implied_bounds bound.predicates.iter().any(|(clause, _)| {
.iter() if let ClauseKind::Trait(tr) = clause.kind().skip_binder()
.find_map(|&(span, implied_by_path, preds, implied_by_def_id)| { && tr.def_id() == def_id
let implied_by_args = implied_by_path.args.map_or([].as_slice(), |a| a.args); {
let implied_by_bindings = implied_by_path.args.map_or([].as_slice(), |a| a.bindings); is_same_generics(
cx.tcx,
preds.iter().find_map(|(clause, _)| { tr.trait_ref.args,
if let ClauseKind::Trait(tr) = clause.kind().skip_binder() bound.args,
&& tr.def_id() == def_id implied_args,
&& is_same_generics( bound.trait_def_id,
cx.tcx, def_id,
tr.trait_ref.args, )
implied_by_args, } else {
implied_args, false
implied_by_def_id, }
def_id, })
) })
{
Some((span, implied_by_args, implied_by_bindings))
} else {
None
}
})
})
{ {
emit_lint( emit_lint(cx, poly_trait, opaque_ty, index, implied_bindings, bound);
cx,
poly_trait,
opaque_ty,
index,
implied_bindings,
implied_by_bindings,
implied_by_args,
implied_by_span,
);
} }
} }
} }