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