Skip to content

Commit c3e9136

Browse files
committed
Auto merge of #3766 - xd009642:issue-3764, r=flip1995
trait bounds lint - repeated types This PR is to tackle #3764 it's still a WIP and doesn't work but this is an initial stab. It builds though I haven't added any tests as I'm not sure where lint tests should go? Unfortunately, it seems id isn't tied to the type itself but I guess where it is in the AST? Looking at https://manishearth.github.io/rust-internals-docs/syntax/ast/struct.Ty.html I can't see any members that would let me tell if a type was repeated in multiple trait bounds. There may be other issues with how I've implemented this so any assistance is appreciated! changelog: Add new lint: `type_repetition_in_bounds`
2 parents e7d153a + 78ebcaa commit c3e9136

File tree

10 files changed

+234
-13
lines changed

10 files changed

+234
-13
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,7 @@ Released 2018-09-13
11381138
[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
11391139
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
11401140
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
1141+
[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
11411142
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
11421143
[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
11431144
[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are 308 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 309 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1212

clippy_lints/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ pub mod strings;
263263
pub mod suspicious_trait_impl;
264264
pub mod swap;
265265
pub mod temporary_assignment;
266+
pub mod trait_bounds;
266267
pub mod transmute;
267268
pub mod transmuting_null;
268269
pub mod trivially_copy_pass_by_ref;
@@ -588,6 +589,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
588589
reg.register_late_lint_pass(box checked_conversions::CheckedConversions);
589590
reg.register_late_lint_pass(box integer_division::IntegerDivision);
590591
reg.register_late_lint_pass(box inherent_to_string::InherentToString);
592+
reg.register_late_lint_pass(box trait_bounds::TraitBounds);
591593

592594
reg.register_lint_group("clippy::restriction", Some("clippy_restriction"), vec![
593595
arithmetic::FLOAT_ARITHMETIC,
@@ -858,6 +860,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
858860
swap::ALMOST_SWAPPED,
859861
swap::MANUAL_SWAP,
860862
temporary_assignment::TEMPORARY_ASSIGNMENT,
863+
trait_bounds::TYPE_REPETITION_IN_BOUNDS,
861864
transmute::CROSSPOINTER_TRANSMUTE,
862865
transmute::TRANSMUTE_BYTES_TO_STR,
863866
transmute::TRANSMUTE_INT_TO_BOOL,
@@ -1039,6 +1042,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
10391042
reference::REF_IN_DEREF,
10401043
swap::MANUAL_SWAP,
10411044
temporary_assignment::TEMPORARY_ASSIGNMENT,
1045+
trait_bounds::TYPE_REPETITION_IN_BOUNDS,
10421046
transmute::CROSSPOINTER_TRANSMUTE,
10431047
transmute::TRANSMUTE_BYTES_TO_STR,
10441048
transmute::TRANSMUTE_INT_TO_BOOL,

clippy_lints/src/trait_bounds.rs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use crate::utils::{in_macro, snippet, span_help_and_lint, SpanlessHash};
2+
use rustc::hir::*;
3+
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
4+
use rustc::{declare_tool_lint, impl_lint_pass};
5+
use rustc_data_structures::fx::FxHashMap;
6+
7+
#[derive(Copy, Clone)]
8+
pub struct TraitBounds;
9+
10+
declare_clippy_lint! {
11+
/// **What it does:** This lint warns about unnecessary type repetitions in trait bounds
12+
///
13+
/// **Why is this bad?** Repeating the type for every bound makes the code
14+
/// less readable than combining the bounds
15+
///
16+
/// **Example:**
17+
/// ```rust
18+
/// pub fn foo<T>(t: T) where T: Copy, T: Clone
19+
/// ```
20+
///
21+
/// Could be written as:
22+
///
23+
/// ```rust
24+
/// pub fn foo<T>(t: T) where T: Copy + Clone
25+
/// ```
26+
pub TYPE_REPETITION_IN_BOUNDS,
27+
complexity,
28+
"Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
29+
}
30+
31+
impl_lint_pass!(TraitBounds => [TYPE_REPETITION_IN_BOUNDS]);
32+
33+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TraitBounds {
34+
fn check_generics(&mut self, cx: &LateContext<'a, 'tcx>, gen: &'tcx Generics) {
35+
if in_macro(gen.span) {
36+
return;
37+
}
38+
let hash = |ty| -> u64 {
39+
let mut hasher = SpanlessHash::new(cx, cx.tables);
40+
hasher.hash_ty(ty);
41+
hasher.finish()
42+
};
43+
let mut map = FxHashMap::default();
44+
for bound in &gen.where_clause.predicates {
45+
if let WherePredicate::BoundPredicate(ref p) = bound {
46+
let h = hash(&p.bounded_ty);
47+
if let Some(ref v) = map.insert(h, p.bounds.iter().collect::<Vec<_>>()) {
48+
let mut hint_string = format!(
49+
"consider combining the bounds: `{}:",
50+
snippet(cx, p.bounded_ty.span, "_")
51+
);
52+
for b in v.iter() {
53+
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
54+
let path = &poly_trait_ref.trait_ref.path;
55+
hint_string.push_str(&format!(" {} +", path));
56+
}
57+
}
58+
for b in p.bounds.iter() {
59+
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
60+
let path = &poly_trait_ref.trait_ref.path;
61+
hint_string.push_str(&format!(" {} +", path));
62+
}
63+
}
64+
hint_string.truncate(hint_string.len() - 2);
65+
hint_string.push('`');
66+
span_help_and_lint(
67+
cx,
68+
TYPE_REPETITION_IN_BOUNDS,
69+
p.span,
70+
"this type has already been used as a bound predicate",
71+
&hint_string,
72+
);
73+
}
74+
}
75+
}
76+
}
77+
}

clippy_lints/src/utils/hir_utils.rs

+102-3
Original file line numberDiff line numberDiff line change
@@ -438,9 +438,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
438438
self.hash_expr(fun);
439439
self.hash_exprs(args);
440440
},
441-
ExprKind::Cast(ref e, ref _ty) | ExprKind::Type(ref e, ref _ty) => {
441+
ExprKind::Cast(ref e, ref ty) | ExprKind::Type(ref e, ref ty) => {
442442
self.hash_expr(e);
443-
// TODO: _ty
443+
self.hash_ty(ty);
444444
},
445445
ExprKind::Closure(cap, _, eid, _, _) => {
446446
match cap {
@@ -512,7 +512,10 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
512512
self.hash_expr(e);
513513
}
514514
},
515-
ExprKind::Tup(ref v) | ExprKind::Array(ref v) => {
515+
ExprKind::Tup(ref tup) => {
516+
self.hash_exprs(tup);
517+
},
518+
ExprKind::Array(ref v) => {
516519
self.hash_exprs(v);
517520
},
518521
ExprKind::Unary(lop, ref le) => {
@@ -574,4 +577,100 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
574577
},
575578
}
576579
}
580+
581+
pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
582+
std::mem::discriminant(&lifetime.name).hash(&mut self.s);
583+
if let LifetimeName::Param(ref name) = lifetime.name {
584+
std::mem::discriminant(name).hash(&mut self.s);
585+
match name {
586+
ParamName::Plain(ref ident) => {
587+
ident.name.hash(&mut self.s);
588+
},
589+
ParamName::Fresh(ref size) => {
590+
size.hash(&mut self.s);
591+
},
592+
ParamName::Error => {},
593+
}
594+
}
595+
}
596+
597+
pub fn hash_ty(&mut self, ty: &Ty) {
598+
self.hash_tykind(&ty.node);
599+
}
600+
601+
pub fn hash_tykind(&mut self, ty: &TyKind) {
602+
std::mem::discriminant(ty).hash(&mut self.s);
603+
match ty {
604+
TyKind::Slice(ty) => {
605+
self.hash_ty(ty);
606+
},
607+
TyKind::Array(ty, anon_const) => {
608+
self.hash_ty(ty);
609+
self.hash_expr(&self.cx.tcx.hir().body(anon_const.body).value);
610+
},
611+
TyKind::Ptr(mut_ty) => {
612+
self.hash_ty(&mut_ty.ty);
613+
mut_ty.mutbl.hash(&mut self.s);
614+
},
615+
TyKind::Rptr(lifetime, mut_ty) => {
616+
self.hash_lifetime(lifetime);
617+
self.hash_ty(&mut_ty.ty);
618+
mut_ty.mutbl.hash(&mut self.s);
619+
},
620+
TyKind::BareFn(bfn) => {
621+
bfn.unsafety.hash(&mut self.s);
622+
bfn.abi.hash(&mut self.s);
623+
for arg in &bfn.decl.inputs {
624+
self.hash_ty(&arg);
625+
}
626+
match bfn.decl.output {
627+
FunctionRetTy::DefaultReturn(_) => {
628+
().hash(&mut self.s);
629+
},
630+
FunctionRetTy::Return(ref ty) => {
631+
self.hash_ty(ty);
632+
},
633+
}
634+
bfn.decl.c_variadic.hash(&mut self.s);
635+
},
636+
TyKind::Tup(ty_list) => {
637+
for ty in ty_list {
638+
self.hash_ty(ty);
639+
}
640+
},
641+
TyKind::Path(qpath) => match qpath {
642+
QPath::Resolved(ref maybe_ty, ref path) => {
643+
if let Some(ref ty) = maybe_ty {
644+
self.hash_ty(ty);
645+
}
646+
for segment in &path.segments {
647+
segment.ident.name.hash(&mut self.s);
648+
}
649+
},
650+
QPath::TypeRelative(ref ty, ref segment) => {
651+
self.hash_ty(ty);
652+
segment.ident.name.hash(&mut self.s);
653+
},
654+
},
655+
TyKind::Def(_, arg_list) => {
656+
for arg in arg_list {
657+
match arg {
658+
GenericArg::Lifetime(ref l) => self.hash_lifetime(l),
659+
GenericArg::Type(ref ty) => self.hash_ty(&ty),
660+
GenericArg::Const(ref ca) => {
661+
self.hash_expr(&self.cx.tcx.hir().body(ca.value.body).value);
662+
},
663+
}
664+
}
665+
},
666+
TyKind::TraitObject(_, lifetime) => {
667+
self.hash_lifetime(lifetime);
668+
},
669+
TyKind::Typeof(anon_const) => {
670+
self.hash_expr(&self.cx.tcx.hir().body(anon_const.body).value);
671+
},
672+
TyKind::CVarArgs(lifetime) => self.hash_lifetime(lifetime),
673+
TyKind::Err | TyKind::Infer | TyKind::Never => {},
674+
}
675+
}
577676
}

src/lintlist/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 308] = [
9+
pub const ALL_LINTS: [Lint; 309] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1848,6 +1848,13 @@ pub const ALL_LINTS: [Lint; 308] = [
18481848
deprecation: None,
18491849
module: "types",
18501850
},
1851+
Lint {
1852+
name: "type_repetition_in_bounds",
1853+
group: "complexity",
1854+
desc: "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`",
1855+
deprecation: None,
1856+
module: "trait_bounds",
1857+
},
18511858
Lint {
18521859
name: "unicode_not_nfc",
18531860
group: "pedantic",

tests/ui/float_cmp.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ const ONE: f32 = ZERO + 1.0;
88

99
fn twice<T>(x: T) -> T
1010
where
11-
T: Add<T, Output = T>,
12-
T: Copy,
11+
T: Add<T, Output = T> + Copy,
1312
{
1413
x + x
1514
}

tests/ui/float_cmp.stderr

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
11
error: strict comparison of f32 or f64
2-
--> $DIR/float_cmp.rs:60:5
2+
--> $DIR/float_cmp.rs:59:5
33
|
44
LL | ONE as f64 != 2.0;
55
| ^^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(ONE as f64 - 2.0).abs() > error`
66
|
77
= note: `-D clippy::float-cmp` implied by `-D warnings`
88
note: std::f32::EPSILON and std::f64::EPSILON are available.
9-
--> $DIR/float_cmp.rs:60:5
9+
--> $DIR/float_cmp.rs:59:5
1010
|
1111
LL | ONE as f64 != 2.0;
1212
| ^^^^^^^^^^^^^^^^^
1313

1414
error: strict comparison of f32 or f64
15-
--> $DIR/float_cmp.rs:65:5
15+
--> $DIR/float_cmp.rs:64:5
1616
|
1717
LL | x == 1.0;
1818
| ^^^^^^^^ help: consider comparing them within some error: `(x - 1.0).abs() < error`
1919
|
2020
note: std::f32::EPSILON and std::f64::EPSILON are available.
21-
--> $DIR/float_cmp.rs:65:5
21+
--> $DIR/float_cmp.rs:64:5
2222
|
2323
LL | x == 1.0;
2424
| ^^^^^^^^
2525

2626
error: strict comparison of f32 or f64
27-
--> $DIR/float_cmp.rs:68:5
27+
--> $DIR/float_cmp.rs:67:5
2828
|
2929
LL | twice(x) != twice(ONE as f64);
3030
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(twice(x) - twice(ONE as f64)).abs() > error`
3131
|
3232
note: std::f32::EPSILON and std::f64::EPSILON are available.
33-
--> $DIR/float_cmp.rs:68:5
33+
--> $DIR/float_cmp.rs:67:5
3434
|
3535
LL | twice(x) != twice(ONE as f64);
3636
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/ui/type_repetition_in_bounds.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#[deny(clippy::type_repetition_in_bounds)]
2+
3+
pub fn foo<T>(_t: T)
4+
where
5+
T: Copy,
6+
T: Clone,
7+
{
8+
unimplemented!();
9+
}
10+
11+
pub fn bar<T, U>(_t: T, _u: U)
12+
where
13+
T: Copy,
14+
U: Clone,
15+
{
16+
unimplemented!();
17+
}
18+
19+
fn main() {}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: this type has already been used as a bound predicate
2+
--> $DIR/type_repetition_in_bounds.rs:6:5
3+
|
4+
LL | T: Clone,
5+
| ^^^^^^^^
6+
|
7+
note: lint level defined here
8+
--> $DIR/type_repetition_in_bounds.rs:1:8
9+
|
10+
LL | #[deny(clippy::type_repetition_in_bounds)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: consider combining the bounds: `T: Copy + Clone`
13+
14+
error: aborting due to previous error
15+

0 commit comments

Comments
 (0)