Skip to content

Commit bead760

Browse files
committed
add excessive bools lints
1 parent c9a491d commit bead760

19 files changed

+427
-4
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,7 @@ Released 2018-09-13
11471147
[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
11481148
[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
11491149
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
1150+
[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
11501151
[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
11511152
[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation
11521153
[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map
@@ -1341,6 +1342,7 @@ Released 2018-09-13
13411342
[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars
13421343
[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes
13431344
[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
1345+
[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools
13441346
[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
13451347
[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting
13461348
[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting

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 351 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 353 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/excessive_bools.rs

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
use crate::utils::{attr_by_name, in_macro, match_path_ast, span_lint_and_help};
2+
use rustc_lint::{EarlyContext, EarlyLintPass};
3+
use rustc_session::{declare_tool_lint, impl_lint_pass};
4+
use rustc_span::Span;
5+
use syntax::ast::{AssocItemKind, Extern, FnSig, Item, ItemKind, Ty, TyKind};
6+
7+
use std::convert::TryInto;
8+
9+
declare_clippy_lint! {
10+
/// **What it does:** Checks for excessive
11+
/// use of bools in structs.
12+
///
13+
/// **Why is this bad?** Excessive bools in a struct
14+
/// is often a sign that it's used as a state machine,
15+
/// which is much better implemented as an enum.
16+
/// If it's not the case, excessive bools usually benefit
17+
/// from refactoring into two-variant enums for better
18+
/// readability and API.
19+
///
20+
/// **Known problems:** None.
21+
///
22+
/// **Example:**
23+
/// Bad:
24+
/// ```rust
25+
/// struct S {
26+
/// is_pending: bool,
27+
/// is_processing: bool,
28+
/// is_finished: bool,
29+
/// }
30+
/// ```
31+
///
32+
/// Good:
33+
/// ```rust
34+
/// enum S {
35+
/// Pending,
36+
/// Processing,
37+
/// Finished,
38+
/// }
39+
/// ```
40+
pub STRUCT_EXCESSIVE_BOOLS,
41+
pedantic,
42+
"using too many bools in a struct"
43+
}
44+
45+
declare_clippy_lint! {
46+
/// **What it does:** Checks for excessive use of
47+
/// bools in function definitions.
48+
///
49+
/// **Why is this bad?** Calls to such functions
50+
/// are confusing and error prone, because it's
51+
/// hard to remember argument order and you have
52+
/// no type system support to back you up. Using
53+
/// two-variant enums instead of bools often makes
54+
/// API easier to use.
55+
///
56+
/// **Known problems:** None.
57+
///
58+
/// **Example:**
59+
/// Bad:
60+
/// ```rust,ignore
61+
/// fn f(is_round: bool, is_hot: bool) { ... }
62+
/// ```
63+
///
64+
/// Good:
65+
/// ```rust,ignore
66+
/// enum Shape {
67+
/// Round,
68+
/// Spiky,
69+
/// }
70+
///
71+
/// enum Temperature {
72+
/// Hot,
73+
/// IceCold,
74+
/// }
75+
///
76+
/// fn f(shape: Shape, temperature: Temperature) { ... }
77+
/// ```
78+
pub FN_PARAMS_EXCESSIVE_BOOLS,
79+
pedantic,
80+
"using too many bools in function parameters"
81+
}
82+
83+
pub struct ExcessiveBools {
84+
max_struct_bools: u64,
85+
max_fn_params_bools: u64,
86+
}
87+
88+
impl ExcessiveBools {
89+
#[must_use]
90+
pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
91+
Self {
92+
max_struct_bools,
93+
max_fn_params_bools,
94+
}
95+
}
96+
97+
fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) {
98+
match fn_sig.header.ext {
99+
Extern::Implicit | Extern::Explicit(_) => return,
100+
Extern::None => (),
101+
}
102+
103+
if self.max_fn_params_bools
104+
< fn_sig
105+
.decl
106+
.inputs
107+
.iter()
108+
.filter(|param| is_bool_ty(&param.ty))
109+
.count()
110+
.try_into()
111+
.unwrap()
112+
{
113+
span_lint_and_help(
114+
cx,
115+
FN_PARAMS_EXCESSIVE_BOOLS,
116+
span,
117+
&format!("more than {} bools in function parameters", self.max_fn_params_bools),
118+
"consider refactoring bools into two-variant enums",
119+
);
120+
}
121+
}
122+
}
123+
124+
impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
125+
126+
fn is_bool_ty(ty: &Ty) -> bool {
127+
if let TyKind::Path(None, path) = &ty.kind {
128+
return match_path_ast(path, &["bool"]);
129+
}
130+
false
131+
}
132+
133+
impl EarlyLintPass for ExcessiveBools {
134+
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
135+
if in_macro(item.span) {
136+
return;
137+
}
138+
match &item.kind {
139+
ItemKind::Struct(variant_data, _) => {
140+
if attr_by_name(&item.attrs, "repr").is_some() {
141+
return;
142+
}
143+
144+
if self.max_struct_bools
145+
< variant_data
146+
.fields()
147+
.iter()
148+
.filter(|field| is_bool_ty(&field.ty))
149+
.count()
150+
.try_into()
151+
.unwrap()
152+
{
153+
span_lint_and_help(
154+
cx,
155+
STRUCT_EXCESSIVE_BOOLS,
156+
item.span,
157+
&format!("more than {} bools in a struct", self.max_struct_bools),
158+
"consider using a state machine or refactoring bools into two-variant enums",
159+
);
160+
}
161+
},
162+
ItemKind::Impl {
163+
of_trait: None, items, ..
164+
}
165+
| ItemKind::Trait(_, _, _, _, items) => {
166+
for item in items {
167+
if let AssocItemKind::Fn(fn_sig, _) = &item.kind {
168+
self.check_fn_sig(cx, fn_sig, item.span);
169+
}
170+
}
171+
},
172+
ItemKind::Fn(fn_sig, _, _) => self.check_fn_sig(cx, fn_sig, item.span),
173+
_ => (),
174+
}
175+
}
176+
}

clippy_lints/src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ pub mod erasing_op;
204204
pub mod escape;
205205
pub mod eta_reduction;
206206
pub mod eval_order_dependence;
207+
pub mod excessive_bools;
207208
pub mod excessive_precision;
208209
pub mod exit;
209210
pub mod explicit_write;
@@ -532,6 +533,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
532533
&eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
533534
&eval_order_dependence::DIVERGING_SUB_EXPRESSION,
534535
&eval_order_dependence::EVAL_ORDER_DEPENDENCE,
536+
&excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS,
537+
&excessive_bools::STRUCT_EXCESSIVE_BOOLS,
535538
&excessive_precision::EXCESSIVE_PRECISION,
536539
&exit::EXIT,
537540
&explicit_write::EXPLICIT_WRITE,
@@ -1001,6 +1004,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10011004
store.register_late_pass(|| box let_underscore::LetUnderscore);
10021005
store.register_late_pass(|| box atomic_ordering::AtomicOrdering);
10031006
store.register_early_pass(|| box single_component_path_imports::SingleComponentPathImports);
1007+
let max_fn_params_bools = conf.max_fn_params_bools;
1008+
let max_struct_bools = conf.max_struct_bools;
1009+
store.register_early_pass(move || box excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools));
10041010

10051011
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
10061012
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1055,6 +1061,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10551061
LintId::of(&enum_variants::MODULE_NAME_REPETITIONS),
10561062
LintId::of(&enum_variants::PUB_ENUM_VARIANT_NAMES),
10571063
LintId::of(&eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS),
1064+
LintId::of(&excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS),
1065+
LintId::of(&excessive_bools::STRUCT_EXCESSIVE_BOOLS),
10581066
LintId::of(&functions::MUST_USE_CANDIDATE),
10591067
LintId::of(&functions::TOO_MANY_LINES),
10601068
LintId::of(&if_not_else::IF_NOT_ELSE),

clippy_lints/src/utils/conf.rs

+4
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ define_Conf! {
154154
(array_size_threshold, "array_size_threshold", 512_000 => u64),
155155
/// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
156156
(vec_box_size_threshold, "vec_box_size_threshold", 4096 => u64),
157+
/// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
158+
(max_struct_bools, "max_struct_bools", 3 => u64),
159+
/// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
160+
(max_fn_params_bools, "max_fn_params_bools", 3 => u64),
157161
}
158162

159163
impl Default for Conf {

clippy_lints/src/utils/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,14 @@ pub fn is_no_std_crate(krate: &Crate<'_>) -> bool {
13551355
})
13561356
}
13571357

1358+
// Check if parent of a hir node is an item
1359+
// in a trait implementation block.
1360+
// For example, `f` in
1361+
// ```rust,ignore
1362+
// impl Trait for S {
1363+
// fn f() {}
1364+
// }
1365+
// ```
13581366
pub fn is_trait_impl_item(cx: &LateContext<'_, '_>, hir_id: HirId) -> bool {
13591367
if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
13601368
matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), .. })

src/lintlist/mod.rs

+15-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; 351] = [
9+
pub const ALL_LINTS: [Lint; 353] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -623,6 +623,13 @@ pub const ALL_LINTS: [Lint; 351] = [
623623
deprecation: None,
624624
module: "misc",
625625
},
626+
Lint {
627+
name: "fn_params_excessive_bools",
628+
group: "pedantic",
629+
desc: "using too many bools in function parameters",
630+
deprecation: None,
631+
module: "excessive_bools",
632+
},
626633
Lint {
627634
name: "fn_to_numeric_cast",
628635
group: "style",
@@ -1925,6 +1932,13 @@ pub const ALL_LINTS: [Lint; 351] = [
19251932
deprecation: None,
19261933
module: "strings",
19271934
},
1935+
Lint {
1936+
name: "struct_excessive_bools",
1937+
group: "pedantic",
1938+
desc: "using too many bools in a struct",
1939+
deprecation: None,
1940+
module: "excessive_bools",
1941+
},
19281942
Lint {
19291943
name: "suspicious_arithmetic_impl",
19301944
group: "correctness",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
max-fn-params-bools = 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#![warn(clippy::fn_params_excessive_bools)]
2+
3+
fn f(_: bool) {}
4+
fn g(_: bool, _: bool) {}
5+
6+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: more than 1 bools in function parameters
2+
--> $DIR/test.rs:4:1
3+
|
4+
LL | fn g(_: bool, _: bool) {}
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::fn-params-excessive-bools` implied by `-D warnings`
8+
= help: consider refactoring bools into two-variant enums
9+
10+
error: aborting due to previous error
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
max-struct-bools = 0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![warn(clippy::struct_excessive_bools)]
2+
3+
struct S {
4+
a: bool,
5+
}
6+
7+
struct Foo {}
8+
9+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error: more than 0 bools in a struct
2+
--> $DIR/test.rs:3:1
3+
|
4+
LL | / struct S {
5+
LL | | a: bool,
6+
LL | | }
7+
| |_^
8+
|
9+
= note: `-D clippy::struct-excessive-bools` implied by `-D warnings`
10+
= help: consider using a state machine or refactoring bools into two-variant enums
11+
12+
error: aborting due to previous error
13+
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `third-party` at line 5 column 1
1+
error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-struct-bools`, `max-fn-params-bools`, `third-party` at line 5 column 1
22

33
error: aborting due to previous error
44

tests/ui/fn_params_excessive_bools.rs

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#![warn(clippy::fn_params_excessive_bools)]
2+
3+
extern "C" {
4+
fn f(_: bool, _: bool, _: bool, _: bool);
5+
}
6+
7+
macro_rules! foo {
8+
() => {
9+
fn fff(_: bool, _: bool, _: bool, _: bool) {}
10+
};
11+
}
12+
13+
foo!();
14+
15+
#[no_mangle]
16+
extern "C" fn k(_: bool, _: bool, _: bool, _: bool) {}
17+
fn g(_: bool, _: bool, _: bool, _: bool) {}
18+
fn h(_: bool, _: bool, _: bool) {}
19+
fn e(_: S, _: S, _: Box<S>, _: Vec<u32>) {}
20+
fn t(_: S, _: S, _: Box<S>, _: Vec<u32>, _: bool, _: bool, _: bool, _: bool) {}
21+
22+
struct S {}
23+
trait Trait {
24+
fn f(_: bool, _: bool, _: bool, _: bool);
25+
fn g(_: bool, _: bool, _: bool, _: Vec<u32>);
26+
}
27+
28+
impl S {
29+
fn f(&self, _: bool, _: bool, _: bool, _: bool) {}
30+
fn g(&self, _: bool, _: bool, _: bool) {}
31+
#[no_mangle]
32+
extern "C" fn h(_: bool, _: bool, _: bool, _: bool) {}
33+
}
34+
35+
impl Trait for S {
36+
fn f(_: bool, _: bool, _: bool, _: bool) {}
37+
fn g(_: bool, _: bool, _: bool, _: Vec<u32>) {}
38+
}
39+
40+
fn main() {
41+
fn n(_: bool, _: u32, _: bool, _: Box<u32>, _: bool, _: bool) {
42+
fn nn(_: bool, _: bool, _: bool, _: bool) {}
43+
}
44+
}

0 commit comments

Comments
 (0)