Skip to content

Commit

Permalink
Auto merge of rust-lang#9167 - aldhsu:fix-trait-duplication-false-pos…
Browse files Browse the repository at this point in the history
…, r=flip1995

Fixes [`trait_duplication_in_bounds`] false positives

Fixes rust-lang#9076 rust-lang#9151 rust-lang#8757.
Partially fixes rust-lang#8771.

changelog: [`trait_duplication_in_bounds`]: Reduce number of false positives.
  • Loading branch information
bors committed Aug 14, 2022
2 parents 4d5d191 + 8bae517 commit 84df61c
Show file tree
Hide file tree
Showing 7 changed files with 481 additions and 334 deletions.
82 changes: 46 additions & 36 deletions clippy_lints/src/trait_bounds.rs
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
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
@@ -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() {}

0 comments on commit 84df61c

Please sign in to comment.