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