]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_bools.rs
Auto merge of #7917 - Alexendoo:cargo-dev-lint, r=giraffate
[rust.git] / clippy_lints / src / excessive_bools.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use rustc_ast::ast::{AssocItemKind, Extern, FnKind, FnSig, ImplKind, Item, ItemKind, TraitKind, 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 use std::convert::TryInto;
8
9 declare_clippy_lint! {
10     /// ### What it does
11     /// Checks for excessive
12     /// use of bools in structs.
13     ///
14     /// ### Why is this bad?
15     /// 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     /// ### 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
47     /// Checks for excessive use of
48     /// bools in function definitions.
49     ///
50     /// ### Why is this bad?
51     /// 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     /// ### 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                 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 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 }