]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/excessive_bools.rs
refactor `has_repr_attr`
[rust.git] / clippy_lints / src / excessive_bools.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use clippy_utils::{has_repr_attr, is_bool};
3 use rustc_hir::intravisit::FnKind;
4 use rustc_hir::{Body, FnDecl, HirId, Item, ItemKind, Ty};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_session::{declare_tool_lint, impl_lint_pass};
7 use rustc_span::Span;
8 use rustc_target::spec::abi::Abi;
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     /// ```rust
25     /// struct S {
26     ///     is_pending: bool,
27     ///     is_processing: bool,
28     ///     is_finished: bool,
29     /// }
30     /// ```
31     ///
32     /// Use instead:
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     /// ```rust,ignore
61     /// fn f(is_round: bool, is_hot: bool) { ... }
62     /// ```
63     ///
64     /// Use instead:
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     #[clippy::version = "1.43.0"]
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 #[derive(Eq, PartialEq, Debug)]
90 enum Kind {
91     Struct,
92     Fn,
93 }
94
95 impl ExcessiveBools {
96     #[must_use]
97     pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
98         Self {
99             max_struct_bools,
100             max_fn_params_bools,
101         }
102     }
103
104     fn too_many_bools<'tcx>(&self, tys: impl Iterator<Item = &'tcx Ty<'tcx>>, kind: Kind) -> bool {
105         if let Ok(bools) = tys.filter(|ty| is_bool(ty)).count().try_into() {
106             (if Kind::Fn == kind {
107                 self.max_fn_params_bools
108             } else {
109                 self.max_struct_bools
110             }) < bools
111         } else {
112             false
113         }
114     }
115
116     fn check_fn_sig(&self, cx: &LateContext<'_>, fn_decl: &FnDecl<'_>, span: Span) {
117         if self.too_many_bools(fn_decl.inputs.iter(), Kind::Fn) {
118             span_lint_and_help(
119                 cx,
120                 FN_PARAMS_EXCESSIVE_BOOLS,
121                 span,
122                 &format!("more than {} bools in function parameters", self.max_fn_params_bools),
123                 None,
124                 "consider refactoring bools into two-variant enums",
125             );
126         }
127     }
128 }
129
130 impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
131
132 impl<'tcx> LateLintPass<'tcx> for ExcessiveBools {
133     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
134         if item.span.from_expansion() {
135             return;
136         }
137         if let ItemKind::Struct(variant_data, _) = &item.kind {
138             if has_repr_attr(cx, item.hir_id()) {
139                 return;
140             }
141
142             if self.too_many_bools(variant_data.fields().iter().map(|field| field.ty), Kind::Struct) {
143                 span_lint_and_help(
144                     cx,
145                     STRUCT_EXCESSIVE_BOOLS,
146                     item.span,
147                     &format!("more than {} bools in a struct", self.max_struct_bools),
148                     None,
149                     "consider using a state machine or refactoring bools into two-variant enums",
150                 )
151             }
152         }
153     }
154
155     fn check_fn(
156         &mut self,
157         cx: &LateContext<'tcx>,
158         fn_kind: FnKind<'tcx>,
159         fn_decl: &'tcx FnDecl<'tcx>,
160         _: &'tcx Body<'tcx>,
161         span: Span,
162         _: HirId,
163     ) {
164         if let Some(fn_header) = fn_kind.header()
165             && fn_header.abi == Abi::Rust
166             && !span.from_expansion() {
167             self.check_fn_sig(cx, fn_decl, span)
168         }
169     }
170 }