]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_bools.rs
Merge commit 'd9ddce8a223cb9916389c039777b6966ea448dc8' into clippyup
[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     /// ```rust
22     /// struct S {
23     ///     is_pending: bool,
24     ///     is_processing: bool,
25     ///     is_finished: bool,
26     /// }
27     /// ```
28     ///
29     /// Use instead:
30     /// ```rust
31     /// enum S {
32     ///     Pending,
33     ///     Processing,
34     ///     Finished,
35     /// }
36     /// ```
37     #[clippy::version = "1.43.0"]
38     pub STRUCT_EXCESSIVE_BOOLS,
39     pedantic,
40     "using too many bools in a struct"
41 }
42
43 declare_clippy_lint! {
44     /// ### What it does
45     /// Checks for excessive use of
46     /// bools in function definitions.
47     ///
48     /// ### Why is this bad?
49     /// 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     /// ### Example
57     /// Bad:
58     /// ```rust,ignore
59     /// fn f(is_round: bool, is_hot: bool) { ... }
60     /// ```
61     ///
62     /// Good:
63     /// ```rust,ignore
64     /// enum Shape {
65     ///     Round,
66     ///     Spiky,
67     /// }
68     ///
69     /// enum Temperature {
70     ///     Hot,
71     ///     IceCold,
72     /// }
73     ///
74     /// fn f(shape: Shape, temperature: Temperature) { ... }
75     /// ```
76     #[clippy::version = "1.43.0"]
77     pub FN_PARAMS_EXCESSIVE_BOOLS,
78     pedantic,
79     "using too many bools in function parameters"
80 }
81
82 pub struct ExcessiveBools {
83     max_struct_bools: u64,
84     max_fn_params_bools: u64,
85 }
86
87 impl ExcessiveBools {
88     #[must_use]
89     pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
90         Self {
91             max_struct_bools,
92             max_fn_params_bools,
93         }
94     }
95
96     fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) {
97         match fn_sig.header.ext {
98             Extern::Implicit | Extern::Explicit(_) => return,
99             Extern::None => (),
100         }
101
102         let fn_sig_bools = fn_sig
103             .decl
104             .inputs
105             .iter()
106             .filter(|param| is_bool_ty(&param.ty))
107             .count()
108             .try_into()
109             .unwrap();
110         if self.max_fn_params_bools < fn_sig_bools {
111             span_lint_and_help(
112                 cx,
113                 FN_PARAMS_EXCESSIVE_BOOLS,
114                 span,
115                 &format!("more than {} bools in function parameters", self.max_fn_params_bools),
116                 None,
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         if let [name] = path.segments.as_slice() {
128             return name.ident.name == sym::bool;
129         }
130     }
131     false
132 }
133
134 impl EarlyLintPass for ExcessiveBools {
135     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
136         if item.span.from_expansion() {
137             return;
138         }
139         match &item.kind {
140             ItemKind::Struct(variant_data, _) => {
141                 if item.attrs.iter().any(|attr| attr.has_name(sym::repr)) {
142                     return;
143                 }
144
145                 let struct_bools = variant_data
146                     .fields()
147                     .iter()
148                     .filter(|field| is_bool_ty(&field.ty))
149                     .count()
150                     .try_into()
151                     .unwrap();
152                 if self.max_struct_bools < struct_bools {
153                     span_lint_and_help(
154                         cx,
155                         STRUCT_EXCESSIVE_BOOLS,
156                         item.span,
157                         &format!("more than {} bools in a struct", self.max_struct_bools),
158                         None,
159                         "consider using a state machine or refactoring bools into two-variant enums",
160                     );
161                 }
162             },
163             ItemKind::Impl(box Impl {
164                 of_trait: None, items, ..
165             })
166             | ItemKind::Trait(box Trait { items, .. }) => {
167                 for item in items {
168                     if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind {
169                         self.check_fn_sig(cx, sig, item.span);
170                     }
171                 }
172             },
173             ItemKind::Fn(box Fn { sig, .. }) => self.check_fn_sig(cx, sig, item.span),
174             _ => (),
175         }
176     }
177 }