Skip to content

Commit f71d59e

Browse files
committed
Lint for type repetition in trait bounds.
This lint adds warning if types are redundantly repeated in trait bounds i.e. `T: Copy, T: Clone` instead of `T: Copy + Clone`. This is a late pass trait lint and has necessitated the addition of code to allow hashing of TyKinds without taking into account Span information.
1 parent f8e04ff commit f71d59e

File tree

5 files changed

+210
-4
lines changed

5 files changed

+210
-4
lines changed

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ pub mod swap;
265265
pub mod temporary_assignment;
266266
pub mod transmute;
267267
pub mod transmuting_null;
268+
pub mod trait_bounds;
268269
pub mod trivially_copy_pass_by_ref;
269270
pub mod try_err;
270271
pub mod types;
@@ -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,
@@ -868,6 +870,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
868870
transmute::USELESS_TRANSMUTE,
869871
transmute::WRONG_TRANSMUTE,
870872
transmuting_null::TRANSMUTING_NULL,
873+
trait_bounds::TYPE_REPETITION_IN_BOUNDS,
871874
trivially_copy_pass_by_ref::TRIVIALLY_COPY_PASS_BY_REF,
872875
try_err::TRY_ERR,
873876
types::ABSURD_EXTREME_COMPARISONS,

clippy_lints/src/trait_bounds.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use crate::utils::{in_macro, span_help_and_lint, SpanlessHash};
2+
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
3+
use rustc::{declare_tool_lint, lint_array};
4+
use rustc_data_structures::fx::FxHashMap;
5+
use rustc::hir::*;
6+
7+
declare_clippy_lint! {
8+
pub TYPE_REPETITION_IN_BOUNDS,
9+
complexity,
10+
"Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
11+
}
12+
13+
#[derive(Copy, Clone)]
14+
pub struct TraitBounds;
15+
16+
impl LintPass for TraitBounds {
17+
fn get_lints(&self) -> LintArray {
18+
lint_array!(TYPE_REPETITION_IN_BOUNDS)
19+
}
20+
21+
fn name(&self) -> &'static str {
22+
"TypeRepetitionInBounds"
23+
}
24+
}
25+
26+
27+
28+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TraitBounds {
29+
fn check_generics(&mut self, cx: &LateContext<'a, 'tcx>, gen: &'tcx Generics) {
30+
if in_macro(gen.span) {
31+
return;
32+
}
33+
let hash = | ty | -> u64 {
34+
let mut hasher = SpanlessHash::new(cx, cx.tables);
35+
hasher.hash_ty(ty);
36+
hasher.finish()
37+
};
38+
let mut map = FxHashMap::default();
39+
for bound in &gen.where_clause.predicates {
40+
if let WherePredicate::BoundPredicate(ref p) = bound {
41+
let h = hash(&p.bounded_ty);
42+
if let Some(ref v) = map.insert(h, p.bounds) {
43+
let mut hint_string = format!("consider combining the bounds: `{:?}: ", p.bounded_ty);
44+
for &b in v.iter() {
45+
hint_string.push_str(&format!("{:?}, ", b));
46+
}
47+
for &b in p.bounds.iter() {
48+
hint_string.push_str(&format!("{:?}, ", b));
49+
}
50+
hint_string.truncate(hint_string.len() - 2);
51+
hint_string.push('`');
52+
span_help_and_lint(
53+
cx,
54+
TYPE_REPETITION_IN_BOUNDS,
55+
p.span,
56+
"this type has already been used as a bound predicate",
57+
&hint_string,
58+
);
59+
}
60+
}
61+
}
62+
}
63+
}

clippy_lints/src/utils/hir_utils.rs

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::utils::differing_macro_contexts;
33
use rustc::hir::ptr::P;
44
use rustc::hir::*;
55
use rustc::lint::LateContext;
6-
use rustc::ty::TypeckTables;
6+
use rustc::ty::{Ty, TypeckTables};
77
use std::collections::hash_map::DefaultHasher;
88
use std::hash::{Hash, Hasher};
99
use syntax::ast::Name;
@@ -438,9 +438,11 @@ 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) => {
442+
let c: fn(_, _) -> _ = ExprKind::Cast;
443+
c.hash(&mut self.s);
442444
self.hash_expr(e);
443-
// TODO: _ty
445+
self.hash_ty(ty);
444446
},
445447
ExprKind::Closure(cap, _, eid, _, _) => {
446448
match cap {
@@ -512,9 +514,22 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
512514
self.hash_expr(e);
513515
}
514516
},
515-
ExprKind::Tup(ref v) | ExprKind::Array(ref v) => {
517+
ExprKind::Tup(ref tup) => {
518+
let c: fn(_) -> _ = ExprKind::Tup;
519+
c.hash(&mut self.s);
520+
self.hash_exprs(tup);
521+
},
522+
ExprKind::Array(ref v) => {
523+
let c: fn(_) -> _ = ExprKind::Array;
524+
c.hash(&mut self.s);
516525
self.hash_exprs(v);
517526
},
527+
ExprKind::Type(ref e, ref ty) => {
528+
let c: fn(_, _) -> _ = ExprKind::Type;
529+
c.hash(&mut self.s);
530+
self.hash_expr(e);
531+
self.hash_ty(ty);
532+
},
518533
ExprKind::Unary(lop, ref le) => {
519534
lop.hash(&mut self.s);
520535
self.hash_expr(le);
@@ -574,4 +589,100 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
574589
},
575590
}
576591
}
592+
593+
pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
594+
if let LifetimeName::Param(ref name) = lifetime.name {
595+
match name {
596+
ParamName::Plain(ref ident) => {
597+
ident.name.hash(&mut self.s);
598+
},
599+
ParamName::Fresh(ref size) => {
600+
size.hash(&mut self.s);
601+
},
602+
_ => {},
603+
}
604+
}
605+
}
606+
607+
pub fn hash_ty(&mut self, ty: &Ty) {
608+
std::mem::discriminant(&ty.node).hash(&mut self.s);
609+
match ty.node {
610+
Ty::Slice(ty) => {
611+
self.hash_ty(ty);
612+
},
613+
Ty::Array(ty, anon_const) => {
614+
self.hash_ty(ty);
615+
self.hash_expr(&self.cx.tcx.hir().body(anon_const.body).value);
616+
},
617+
Ty::Ptr(mut_ty) => {
618+
self.hash_ty(&mut_ty.ty);
619+
mut_ty.mutbl.hash(&mut self.s);
620+
},
621+
Ty::Rptr(lifetime, mut_ty) => {
622+
self.hash_lifetime(lifetime);
623+
self.hash_ty(&mut_ty.ty);
624+
mut_ty.mutbl.hash(&mut self.s);
625+
},
626+
Ty::BareFn(bfn) => {
627+
bfn.unsafety.hash(&mut self.s);
628+
bfn.abi.hash(&mut self.s);
629+
for arg in &bfn.decl.inputs {
630+
self.hash_ty(&arg);
631+
}
632+
match bfn.decl.output {
633+
FunctionRetTy::DefaultReturn(_) => {
634+
().hash(&mut self.s);
635+
},
636+
FunctionRetTy::Return(ref ty) => {
637+
self.hash_ty(ty);
638+
},
639+
}
640+
bfn.decl.c_variadic.hash(&mut self.s);
641+
},
642+
Ty::Tup(ty_list) => {
643+
for ty in ty_list {
644+
self.hash_ty(ty);
645+
}
646+
647+
},
648+
Ty::Path(qpath) => {
649+
match qpath {
650+
QPath::Resolved(ref maybe_ty, ref path) => {
651+
if let Some(ref ty) = maybe_ty {
652+
self.hash_ty(ty);
653+
}
654+
for segment in &path.segments {
655+
segment.ident.name.hash(&mut self.s);
656+
}
657+
},
658+
QPath::TypeRelative(ref ty, ref segment) => {
659+
self.hash_ty(ty);
660+
segment.ident.name.hash(&mut self.s);
661+
},
662+
}
663+
},
664+
Ty::Def(_, arg_list) => {
665+
for arg in arg_list {
666+
match arg {
667+
GenericArg::Lifetime(ref l) => self.hash_lifetime(l),
668+
GenericArg::Type(ref ty) => self.hash_ty(ty),
669+
GenericArg::Const(ref ca) => {
670+
self.hash_expr(&self.cx.tcx.hir().body(ca.value.body).value);
671+
},
672+
}
673+
}
674+
},
675+
Ty::TraitObject(_, lifetime) => {
676+
self.hash_lifetime(lifetime);
677+
678+
},
679+
Ty::Typeof(anon_const) => {
680+
self.hash_expr(&self.cx.tcx.hir().body(anon_const.body).value);
681+
},
682+
Ty::CVarArgs(lifetime) => {
683+
self.hash_lifetime(lifetime);
684+
},
685+
Ty::Err | Ty::Infer | Ty::Never => {},
686+
}
687+
}
577688
}

tests/ui/type_repetition_in_bounds.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#[deny(clippy::type_repetition_in_bounds)]
2+
3+
pub fn foo<T>(_t: T) where T: Copy, T: Clone {
4+
unimplemented!();
5+
}
6+
7+
pub fn bar<T, U>(_t: T, _u: U) where T: Copy, U: Clone {
8+
unimplemented!();
9+
}
10+
11+
12+
fn main() {
13+
14+
}
Lines changed: 15 additions & 0 deletions
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:3:37
3+
|
4+
LL | pub fn foo<T>(_t: T) where T: Copy, 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)