]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_bools.rs
Auto merge of #8786 - Alexendoo:identity-op-suggestions, r=dswij,xFrednet
[rust.git] / clippy_lints / src / excessive_bools.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use rustc_ast::ast::{AssocItemKind, Extern, Fn, FnSig, Impl, Item, ItemKind, Trait, Ty, TyKind};
3 use rustc_lint::{EarlyContext, EarlyLintPass};
4 use rustc_session::{declare_tool_lint, impl_lint_pass};
5 use rustc_span::{sym, Span};
6
7 declare_clippy_lint! {
8     /// ### What it does
9     /// Checks for excessive
10     /// use of bools in structs.
11     ///
12     /// ### Why is this bad?
13     /// 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     /// ### Example
21     /// Bad:
22     /// ```rust
23     /// struct S {
24     ///     is_pending: bool,
25     ///     is_processing: bool,
26     ///     is_finished: bool,
27     /// }
28     /// ```
29     ///
30     /// Good:
31     /// ```rust
32     /// enum S {
33     ///     Pending,
34     ///     Processing,
35     ///     Finished,
36     /// }
37     /// ```
38     #[clippy::version = "1.43.0"]
39     pub STRUCT_EXCESSIVE_BOOLS,
40     pedantic,
41     "using too many bools in a struct"
42 }
43
44 declare_clippy_lint! {
45     /// ### What it does
46     /// Checks for excessive use of
47     /// bools in function definitions.
48     ///
49     /// ### Why is this bad?
50     /// Calls to such functions
51     /// are confusing and error prone, because it's
52     /// hard to remember argument order and you have
53     /// no type system support to back you up. Using
54     /// two-variant enums instead of bools often makes
55     /// API easier to use.
56     ///
57     /// ### Example
58     /// Bad:
59     /// ```rust,ignore
60     /// fn f(is_round: bool, is_hot: bool) { ... }
61     /// ```
62     ///
63     /// Good:
64     /// ```rust,ignore
65     /// enum Shape {
66     ///     Round,
67     ///     Spiky,
68     /// }
69     ///
70     /// enum Temperature {
71     ///     Hot,
72     ///     IceCold,
73     /// }
74     ///
75     /// fn f(shape: Shape, temperature: Temperature) { ... }
76     /// ```
77     #[clippy::version = "1.43.0"]
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                 None,
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         if let [name] = path.segments.as_slice() {
129             return name.ident.name == sym::bool;
130         }
131     }
132     false
133 }
134
135 impl EarlyLintPass for ExcessiveBools {
136     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
137         if item.span.from_expansion() {
138             return;
139         }
140         match &item.kind {
141             ItemKind::Struct(variant_data, _) => {
142                 if item.attrs.iter().any(|attr| attr.has_name(sym::repr)) {
143                     return;
144                 }
145
146                 let struct_bools = variant_data
147                     .fields()
148                     .iter()
149                     .filter(|field| is_bool_ty(&field.ty))
150                     .count()
151                     .try_into()
152                     .unwrap();
153                 if self.max_struct_bools < struct_bools {
154                     span_lint_and_help(
155                         cx,
156                         STRUCT_EXCESSIVE_BOOLS,
157                         item.span,
158                         &format!("more than {} bools in a struct", self.max_struct_bools),
159                         None,
160                         "consider using a state machine or refactoring bools into two-variant enums",
161                     );
162                 }
163             },
164             ItemKind::Impl(box Impl {
165                 of_trait: None, items, ..
166             })
167             | ItemKind::Trait(box Trait { items, .. }) => {
168                 for item in items {
169                     if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind {
170                         self.check_fn_sig(cx, sig, item.span);
171                     }
172                 }
173             },
174             ItemKind::Fn(box Fn { sig, .. }) => self.check_fn_sig(cx, sig, item.span),
175             _ => (),
176         }
177     }
178 }