Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor implied_bounds_in_impls lint #12269

Merged
merged 2 commits into from Feb 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
168 changes: 92 additions & 76 deletions clippy_lints/src/implied_bounds_in_impls.rs
@@ -1,16 +1,17 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
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::{
Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, TraitBoundModifier, TraitItem,
TraitItemKind, TyKind,
Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, OpaqueTy, TraitBoundModifier,
TraitItem, TraitItemKind, TyKind, TypeBinding,
};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, ClauseKind, Generics, Ty, TyCtxt};
use rustc_session::declare_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::Span;

declare_clippy_lint! {
Expand Down Expand Up @@ -50,20 +51,17 @@ declare_clippy_lint! {
}
declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]);

#[allow(clippy::too_many_arguments)]
fn emit_lint(
cx: &LateContext<'_>,
poly_trait: &rustc_hir::PolyTraitRef<'_>,
opaque_ty: &rustc_hir::OpaqueTy<'_>,
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<'_>],
// The original bindings that `implied_bindings` are implied from
implied_by_bindings: &[rustc_hir::TypeBinding<'_>],
implied_by_args: &[GenericArg<'_>],
implied_by_span: Span,
bound: &ImplTraitBound<'_>,
) {
let implied_by = snippet(cx, implied_by_span, "..");
let implied_by = snippet(cx, bound.span, "..");

span_lint_and_then(
cx,
Expand Down Expand Up @@ -93,17 +91,17 @@ fn emit_lint(
// If we're going to suggest removing `Deref<..>`, we'll need to put `<Target = u8>` on `DerefMut`
let omitted_assoc_tys: Vec<_> = implied_bindings
.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();

if !omitted_assoc_tys.is_empty() {
// `<>` 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 insert_span = match (implied_by_args, implied_by_bindings) {
let needs_angle_brackets = bound.args.is_empty() && bound.bindings.is_empty();
let insert_span = match (bound.args, bound.bindings) {
([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(),
([.., arg], []) => arg.span().shrink_to_hi(),
([], [.., binding]) => binding.span.shrink_to_hi(),
([], []) => implied_by_span.shrink_to_hi(),
([], []) => bound.span.shrink_to_hi(),
};

let mut associated_tys_sugg = if needs_angle_brackets {
Expand Down Expand Up @@ -223,42 +221,93 @@ fn is_same_generics<'tcx>(
})
}

struct ImplTraitBound<'tcx> {
/// The span of the bound in the `impl Trait` type
span: Span,
/// The predicates defined in the trait referenced by this bound. This also contains the actual
/// supertrait bounds
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,
span: bound.span(),
})
} else {
None
}
})
.collect()
}

/// Given a bound in an `impl Trait` type, looks for a trait in the set of supertraits (previously
/// collected in [`collect_supertrait_bounds`]) that matches (same trait and generic arguments).
fn find_bound_in_supertraits<'a, 'tcx>(
cx: &LateContext<'tcx>,
trait_def_id: DefId,
args: &'tcx [GenericArg<'tcx>],
bounds: &'a [ImplTraitBound<'tcx>],
) -> Option<&'a ImplTraitBound<'tcx>> {
bounds.iter().find(|bound| {
bound.predicates.iter().any(|(clause, _)| {
if let ClauseKind::Trait(tr) = clause.kind().skip_binder()
&& tr.def_id() == trait_def_id
{
is_same_generics(
cx.tcx,
tr.trait_ref.args,
bound.args,
args,
bound.trait_def_id,
trait_def_id,
)
} else {
false
}
})
})
}

fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
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 ItemKind::OpaqueTy(opaque_ty) = item.kind
// Very often there is only a single bound, e.g. `impl Deref<..>`, in which case
// we can avoid doing a bunch of stuff unnecessarily.
&& opaque_ty.bounds.len() > 1
{
// Get all the (implied) trait predicates in the bounds.
// 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();
let supertraits = collect_supertrait_bounds(cx, opaque_ty);

// 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
// 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() {
Expand All @@ -267,42 +316,9 @@ fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
&& 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 Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id()
&& let Some((implied_by_span, implied_by_args, implied_by_bindings)) =
implied_bounds
.iter()
.find_map(|&(span, implied_by_path, preds, implied_by_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);

preds.iter().find_map(|(clause, _)| {
if let ClauseKind::Trait(tr) = clause.kind().skip_binder()
&& tr.def_id() == def_id
&& is_same_generics(
cx.tcx,
tr.trait_ref.args,
implied_by_args,
implied_args,
implied_by_def_id,
def_id,
)
{
Some((span, implied_by_args, implied_by_bindings))
} else {
None
}
})
})
&& let Some(bound) = find_bound_in_supertraits(cx, def_id, implied_args, &supertraits)
{
emit_lint(
cx,
poly_trait,
opaque_ty,
index,
implied_bindings,
implied_by_bindings,
implied_by_args,
implied_by_span,
);
emit_lint(cx, poly_trait, opaque_ty, index, implied_bindings, bound);
}
}
}
Expand Down