Skip to content

Fixes [trait_duplication_in_bounds] false positives #9167

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

Merged
merged 4 commits into from
Aug 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 46 additions & 36 deletions clippy_lints/src/trait_bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use clippy_utils::{SpanlessEq, SpanlessHash};
use core::hash::{Hash, Hasher};
use if_chain::if_chain;
use itertools::Itertools;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::unhash::UnhashMap;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
Expand Down Expand Up @@ -103,7 +103,6 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds {
fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) {
self.check_type_repetition(cx, gen);
check_trait_bound_duplication(cx, gen);
check_bounds_or_where_duplication(cx, gen);
}

fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
Expand Down Expand Up @@ -234,35 +233,61 @@ impl TraitBounds {
}

fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
if gen.span.from_expansion() || gen.params.is_empty() || gen.predicates.is_empty() {
if gen.span.from_expansion() {
return;
}

let mut map = FxHashMap::<_, Vec<_>>::default();
for predicate in gen.predicates {
// Explanation:
// fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z)
// where T: Clone + Default, { unimplemented!(); }
// ^^^^^^^^^^^^^^^^^^
// |
// collects each of these where clauses into a set keyed by generic name and comparable trait
// eg. (T, Clone)
let where_predicates = gen
.predicates
.iter()
.filter_map(|pred| {
if_chain! {
if pred.in_where_clause();
if let WherePredicate::BoundPredicate(bound_predicate) = pred;
if let TyKind::Path(QPath::Resolved(_, path)) = bound_predicate.bounded_ty.kind;
then {
return Some(
rollup_traits(cx, bound_predicate.bounds, "these where clauses contain repeated elements")
.into_keys().map(|trait_ref| (path.res, trait_ref)))
}
}
None
})
.flatten()
.collect::<FxHashSet<_>>();

// Explanation:
// fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) ...
// ^^^^^^^^^^^^^^^^^^ ^^^^^^^
// |
// compare trait bounds keyed by generic name and comparable trait to collected where
// predicates eg. (T, Clone)
for predicate in gen.predicates.iter().filter(|pred| !pred.in_where_clause()) {
if_chain! {
if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate;
if let WherePredicate::BoundPredicate(bound_predicate) = predicate;
if bound_predicate.origin != PredicateOrigin::ImplTrait;
if !bound_predicate.span.from_expansion();
if let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind;
if let Some(segment) = segments.first();
if let TyKind::Path(QPath::Resolved(_, path)) = bound_predicate.bounded_ty.kind;
then {
for (res_where, _, span_where) in bound_predicate.bounds.iter().filter_map(get_trait_info_from_bound) {
let trait_resolutions_direct = map.entry(segment.ident).or_default();
if let Some((_, span_direct)) = trait_resolutions_direct
.iter()
.find(|(res_direct, _)| *res_direct == res_where) {
let traits = rollup_traits(cx, bound_predicate.bounds, "these bounds contain repeated elements");
for (trait_ref, span) in traits {
let key = (path.res, trait_ref);
if where_predicates.contains(&key) {
span_lint_and_help(
cx,
TRAIT_DUPLICATION_IN_BOUNDS,
*span_direct,
span,
"this trait bound is already specified in the where clause",
None,
"consider removing this trait bound",
);
}
else {
trait_resolutions_direct.push((res_where, span_where));
);
}
}
}
Expand All @@ -273,23 +298,6 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
#[derive(PartialEq, Eq, Hash, Debug)]
struct ComparableTraitRef(Res, Vec<Res>);

fn check_bounds_or_where_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
if gen.span.from_expansion() {
return;
}

for predicate in gen.predicates {
if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate {
let msg = if predicate.in_where_clause() {
"these where clauses contain repeated elements"
} else {
"these bounds contain repeated elements"
};
rollup_traits(cx, bound_predicate.bounds, msg);
}
}
}

fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'a [PathSegment<'a>], Span)> {
if let GenericBound::Trait(t, tbm) = bound {
let trait_path = t.trait_ref.path;
Expand Down Expand Up @@ -331,7 +339,7 @@ fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
)
}

fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) {
fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) -> FxHashMap<ComparableTraitRef, Span> {
let mut map = FxHashMap::default();
let mut repeated_res = false;

Expand Down Expand Up @@ -373,4 +381,6 @@ fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) {
);
}
}

map
}
1 change: 0 additions & 1 deletion tests/compile-test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,6 @@ const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[
"single_component_path_imports_nested_first.rs",
"string_add.rs",
"toplevel_ref_arg_non_rustfix.rs",
"trait_duplication_in_bounds.rs",
"unit_arg.rs",
"unnecessary_clone.rs",
"unnecessary_lazy_eval_unfixable.rs",
Expand Down
112 changes: 112 additions & 0 deletions tests/ui/trait_duplication_in_bounds.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// run-rustfix
#![deny(clippy::trait_duplication_in_bounds)]
#![allow(unused)]

fn bad_foo<T: Clone + Copy, U: Clone + Copy>(arg0: T, argo1: U) {
unimplemented!();
}

fn bad_bar<T, U>(arg0: T, arg1: U)
where
T: Clone + Copy,
U: Clone + Copy,
{
unimplemented!();
}

fn good_bar<T: Clone + Copy, U: Clone + Copy>(arg0: T, arg1: U) {
unimplemented!();
}

fn good_foo<T, U>(arg0: T, arg1: U)
where
T: Clone + Copy,
U: Clone + Copy,
{
unimplemented!();
}

trait GoodSelfTraitBound: Clone + Copy {
fn f();
}

trait GoodSelfWhereClause {
fn f()
where
Self: Clone + Copy;
}

trait BadSelfTraitBound: Clone {
fn f();
}

trait BadSelfWhereClause {
fn f()
where
Self: Clone;
}

trait GoodTraitBound<T: Clone + Copy, U: Clone + Copy> {
fn f();
}

trait GoodWhereClause<T, U> {
fn f()
where
T: Clone + Copy,
U: Clone + Copy;
}

trait BadTraitBound<T: Clone + Copy, U: Clone + Copy> {
fn f();
}

trait BadWhereClause<T, U> {
fn f()
where
T: Clone + Copy,
U: Clone + Copy;
}

struct GoodStructBound<T: Clone + Copy, U: Clone + Copy> {
t: T,
u: U,
}

impl<T: Clone + Copy, U: Clone + Copy> GoodTraitBound<T, U> for GoodStructBound<T, U> {
// this should not warn
fn f() {}
}

struct GoodStructWhereClause;

impl<T, U> GoodTraitBound<T, U> for GoodStructWhereClause
where
T: Clone + Copy,
U: Clone + Copy,
{
// this should not warn
fn f() {}
}

fn no_error_separate_arg_bounds(program: impl AsRef<()>, dir: impl AsRef<()>, args: &[impl AsRef<()>]) {}

trait GenericTrait<T> {}

fn good_generic<T: GenericTrait<u64> + GenericTrait<u32>>(arg0: T) {
unimplemented!();
}

fn bad_generic<T: GenericTrait<u32> + GenericTrait<u64>>(arg0: T) {
unimplemented!();
}

mod foo {
pub trait Clone {}
}

fn qualified_path<T: Clone + foo::Clone>(arg0: T) {
unimplemented!();
}

fn main() {}
Loading