]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_bools.rs
Simplify `rustc_ast::visit::Visitor::visit_poly_trait_ref`.
[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     /// ```rust,ignore
58     /// fn f(is_round: bool, is_hot: bool) { ... }
59     /// ```
60     ///
61     /// Use instead:
62     /// ```rust,ignore
63     /// enum Shape {
64     ///     Round,
65     ///     Spiky,
66     /// }
67     ///
68     /// enum Temperature {
69     ///     Hot,
70     ///     IceCold,
71     /// }
72     ///
73     /// fn f(shape: Shape, temperature: Temperature) { ... }
74     /// ```
75     #[clippy::version = "1.43.0"]
76     pub FN_PARAMS_EXCESSIVE_BOOLS,
77     pedantic,
78     "using too many bools in function parameters"
79 }
80
81 pub struct ExcessiveBools {
82     max_struct_bools: u64,
83     max_fn_params_bools: u64,
84 }
85
86 impl ExcessiveBools {
87     #[must_use]
88     pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
89         Self {
90             max_struct_bools,
91             max_fn_params_bools,
92         }
93     }
94
95     fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) {
96         match fn_sig.header.ext {
97             Extern::Implicit(_) | Extern::Explicit(_, _) => return,
98             Extern::None => (),
99         }
100
101         let fn_sig_bools = fn_sig
102             .decl
103             .inputs
104             .iter()
105             .filter(|param| is_bool_ty(&param.ty))
106             .count()
107             .try_into()
108             .unwrap();
109         if self.max_fn_params_bools < fn_sig_bools {
110             span_lint_and_help(
111                 cx,
112                 FN_PARAMS_EXCESSIVE_BOOLS,
113                 span,
114                 &format!("more than {} bools in function parameters", self.max_fn_params_bools),
115                 None,
116                 "consider refactoring bools into two-variant enums",
117             );
118         }
119     }
120 }
121
122 impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
123
124 fn is_bool_ty(ty: &Ty) -> bool {
125     if let TyKind::Path(None, path) = &ty.kind {
126         if let [name] = path.segments.as_slice() {
127             return name.ident.name == sym::bool;
128         }
129     }
130     false
131 }
132
133 impl EarlyLintPass for ExcessiveBools {
134     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
135         if item.span.from_expansion() {
136             return;
137         }
138         match &item.kind {
139             ItemKind::Struct(variant_data, _) => {
140                 if item.attrs.iter().any(|attr| attr.has_name(sym::repr)) {
141                     return;
142                 }
143
144                 let struct_bools = variant_data
145                     .fields()
146                     .iter()
147                     .filter(|field| is_bool_ty(&field.ty))
148                     .count()
149                     .try_into()
150                     .unwrap();
151                 if self.max_struct_bools < struct_bools {
152                     span_lint_and_help(
153                         cx,
154                         STRUCT_EXCESSIVE_BOOLS,
155                         item.span,
156                         &format!("more than {} bools in a struct", self.max_struct_bools),
157                         None,
158                         "consider using a state machine or refactoring bools into two-variant enums",
159                     );
160                 }
161             },
162             ItemKind::Impl(box Impl {
163                 of_trait: None, items, ..
164             })
165             | ItemKind::Trait(box Trait { items, .. }) => {
166                 for item in items {
167                     if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind {
168                         self.check_fn_sig(cx, sig, item.span);
169                     }
170                 }
171             },
172             ItemKind::Fn(box Fn { sig, .. }) => self.check_fn_sig(cx, sig, item.span),
173             _ => (),
174         }
175     }
176 }