]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_bools.rs
Auto merge of #5139 - lzutao:linecount, r=llogiq
[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_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 }