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