Skip to content

Commit 75e983a

Browse files
committed
Auto merge of #5125 - Areredify:too_many_bools, r=flip1995
Port mitsuhiko's excessive bools lints Closes #4 . changelog: add `struct_excessive_bools` and `fn_params_excessive_bools` lints. I moved is_trait_impl_item check because at first I implemented it as a late pass for some reason but then I realized it's actually an early lint. But it's a useful function to have, should I move it into a separate pr?
2 parents a3135ba + 338fb7a commit 75e983a

20 files changed

+436
-15
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
@@ -1342,6 +1343,7 @@ Released 2018-09-13
13421343
[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars
13431344
[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes
13441345
[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
1346+
[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools
13451347
[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
13461348
[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting
13471349
[`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 352 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 354 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

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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+
let fn_sig_bools = fn_sig
104+
.decl
105+
.inputs
106+
.iter()
107+
.filter(|param| is_bool_ty(&param.ty))
108+
.count()
109+
.try_into()
110+
.unwrap();
111+
if self.max_fn_params_bools < fn_sig_bools {
112+
span_lint_and_help(
113+
cx,
114+
FN_PARAMS_EXCESSIVE_BOOLS,
115+
span,
116+
&format!("more than {} bools in function parameters", self.max_fn_params_bools),
117+
"consider refactoring bools into two-variant enums",
118+
);
119+
}
120+
}
121+
}
122+
123+
impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
124+
125+
fn is_bool_ty(ty: &Ty) -> bool {
126+
if let TyKind::Path(None, path) = &ty.kind {
127+
return match_path_ast(path, &["bool"]);
128+
}
129+
false
130+
}
131+
132+
impl EarlyLintPass for ExcessiveBools {
133+
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
134+
if in_macro(item.span) {
135+
return;
136+
}
137+
match &item.kind {
138+
ItemKind::Struct(variant_data, _) => {
139+
if attr_by_name(&item.attrs, "repr").is_some() {
140+
return;
141+
}
142+
143+
let struct_bools = variant_data
144+
.fields()
145+
.iter()
146+
.filter(|field| is_bool_ty(&field.ty))
147+
.count()
148+
.try_into()
149+
.unwrap();
150+
if self.max_struct_bools < struct_bools {
151+
span_lint_and_help(
152+
cx,
153+
STRUCT_EXCESSIVE_BOOLS,
154+
item.span,
155+
&format!("more than {} bools in a struct", self.max_struct_bools),
156+
"consider using a state machine or refactoring bools into two-variant enums",
157+
);
158+
}
159+
},
160+
ItemKind::Impl {
161+
of_trait: None, items, ..
162+
}
163+
| ItemKind::Trait(_, _, _, _, items) => {
164+
for item in items {
165+
if let AssocItemKind::Fn(fn_sig, _) = &item.kind {
166+
self.check_fn_sig(cx, fn_sig, item.span);
167+
}
168+
}
169+
},
170+
ItemKind::Fn(fn_sig, _, _) => self.check_fn_sig(cx, fn_sig, item.span),
171+
_ => (),
172+
}
173+
}
174+
}

clippy_lints/src/functions.rs

+4-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use crate::utils::{
2-
attr_by_name, attrs::is_proc_macro, is_must_use_ty, iter_input_pats, match_def_path, must_use_attr, qpath_res,
3-
return_ty, snippet, snippet_opt, span_lint, span_lint_and_help, span_lint_and_then, trait_ref_of_method,
4-
type_is_unsafe_function,
2+
attr_by_name, attrs::is_proc_macro, is_must_use_ty, is_trait_impl_item, iter_input_pats, match_def_path,
3+
must_use_attr, qpath_res, return_ty, snippet, snippet_opt, span_lint, span_lint_and_help, span_lint_and_then,
4+
trait_ref_of_method, type_is_unsafe_function,
55
};
6-
use matches::matches;
76
use rustc::hir::map::Map;
87
use rustc::lint::in_external_macro;
98
use rustc::ty::{self, Ty};
@@ -195,20 +194,14 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Functions {
195194
span: Span,
196195
hir_id: hir::HirId,
197196
) {
198-
let is_impl = if let Some(hir::Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
199-
matches!(item.kind, hir::ItemKind::Impl{ of_trait: Some(_), .. })
200-
} else {
201-
false
202-
};
203-
204197
let unsafety = match kind {
205198
intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _, _) => unsafety,
206199
intravisit::FnKind::Method(_, sig, _, _) => sig.header.unsafety,
207200
intravisit::FnKind::Closure(_) => return,
208201
};
209202

210203
// don't warn for implementations, it's not their fault
211-
if !is_impl {
204+
if !is_trait_impl_item(cx, hir_id) {
212205
// don't lint extern functions decls, it's not their fault either
213206
match kind {
214207
intravisit::FnKind::Method(

clippy_lints/src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ pub mod erasing_op;
202202
pub mod escape;
203203
pub mod eta_reduction;
204204
pub mod eval_order_dependence;
205+
pub mod excessive_bools;
205206
pub mod excessive_precision;
206207
pub mod exit;
207208
pub mod explicit_write;
@@ -528,6 +529,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
528529
&eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
529530
&eval_order_dependence::DIVERGING_SUB_EXPRESSION,
530531
&eval_order_dependence::EVAL_ORDER_DEPENDENCE,
532+
&excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS,
533+
&excessive_bools::STRUCT_EXCESSIVE_BOOLS,
531534
&excessive_precision::EXCESSIVE_PRECISION,
532535
&exit::EXIT,
533536
&explicit_write::EXPLICIT_WRITE,
@@ -997,6 +1000,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
9971000
store.register_late_pass(|| box let_underscore::LetUnderscore);
9981001
store.register_late_pass(|| box atomic_ordering::AtomicOrdering);
9991002
store.register_early_pass(|| box single_component_path_imports::SingleComponentPathImports);
1003+
let max_fn_params_bools = conf.max_fn_params_bools;
1004+
let max_struct_bools = conf.max_struct_bools;
1005+
store.register_early_pass(move || box excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools));
10001006

10011007
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
10021008
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1051,6 +1057,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10511057
LintId::of(&enum_variants::MODULE_NAME_REPETITIONS),
10521058
LintId::of(&enum_variants::PUB_ENUM_VARIANT_NAMES),
10531059
LintId::of(&eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS),
1060+
LintId::of(&excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS),
1061+
LintId::of(&excessive_bools::STRUCT_EXCESSIVE_BOOLS),
10541062
LintId::of(&functions::MUST_USE_CANDIDATE),
10551063
LintId::of(&functions::TOO_MANY_LINES),
10561064
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": u64, 512_000),
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": u64, 4096),
157+
/// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
158+
(max_struct_bools, "max_struct_bools": u64, 3),
159+
/// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
160+
(max_fn_params_bools, "max_fn_params_bools": u64, 3),
157161
}
158162

159163
impl Default for Conf {

clippy_lints/src/utils/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -1354,3 +1354,18 @@ pub fn is_no_std_crate(krate: &Crate<'_>) -> bool {
13541354
}
13551355
})
13561356
}
1357+
1358+
/// Check if parent of a hir node is a trait implementation block.
1359+
/// For example, `f` in
1360+
/// ```rust,ignore
1361+
/// impl Trait for S {
1362+
/// fn f() {}
1363+
/// }
1364+
/// ```
1365+
pub fn is_trait_impl_item(cx: &LateContext<'_, '_>, hir_id: HirId) -> bool {
1366+
if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
1367+
matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), .. })
1368+
} else {
1369+
false
1370+
}
1371+
}

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; 352] = [
9+
pub const ALL_LINTS: [Lint; 354] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -623,6 +623,13 @@ pub const ALL_LINTS: [Lint; 352] = [
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",
@@ -1932,6 +1939,13 @@ pub const ALL_LINTS: [Lint; 352] = [
19321939
deprecation: None,
19331940
module: "strings",
19341941
},
1942+
Lint {
1943+
name: "struct_excessive_bools",
1944+
group: "pedantic",
1945+
desc: "using too many bools in a struct",
1946+
deprecation: None,
1947+
module: "excessive_bools",
1948+
},
19351949
Lint {
19361950
name: "suspicious_arithmetic_impl",
19371951
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

0 commit comments

Comments
 (0)