]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/excessive_bools.rs
Auto merge of #107843 - bjorn3:sync_cg_clif-2023-02-09, r=bjorn3
[rust.git] / src / tools / clippy / clippy_lints / src / excessive_bools.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use clippy_utils::{get_parent_as_impl, has_repr_attr, is_bool};
3 use rustc_hir::intravisit::FnKind;
4 use rustc_hir::{Body, FnDecl, Item, ItemKind, TraitFn, TraitItem, TraitItemKind, 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_span::def_id::LocalDefId;
9 use rustc_target::spec::abi::Abi;
10
11 declare_clippy_lint! {
12     /// ### What it does
13     /// Checks for excessive
14     /// use of bools in structs.
15     ///
16     /// ### Why is this bad?
17     /// Excessive bools in a struct
18     /// is often a sign that it's used as a state machine,
19     /// which is much better implemented as an enum.
20     /// If it's not the case, excessive bools usually benefit
21     /// from refactoring into two-variant enums for better
22     /// readability and API.
23     ///
24     /// ### Example
25     /// ```rust
26     /// struct S {
27     ///     is_pending: bool,
28     ///     is_processing: bool,
29     ///     is_finished: bool,
30     /// }
31     /// ```
32     ///
33     /// Use instead:
34     /// ```rust
35     /// enum S {
36     ///     Pending,
37     ///     Processing,
38     ///     Finished,
39     /// }
40     /// ```
41     #[clippy::version = "1.43.0"]
42     pub STRUCT_EXCESSIVE_BOOLS,
43     pedantic,
44     "using too many bools in a struct"
45 }
46
47 declare_clippy_lint! {
48     /// ### What it does
49     /// Checks for excessive use of
50     /// bools in function definitions.
51     ///
52     /// ### Why is this bad?
53     /// Calls to such functions
54     /// are confusing and error prone, because it's
55     /// hard to remember argument order and you have
56     /// no type system support to back you up. Using
57     /// two-variant enums instead of bools often makes
58     /// API easier to use.
59     ///
60     /// ### Example
61     /// ```rust,ignore
62     /// fn f(is_round: bool, is_hot: bool) { ... }
63     /// ```
64     ///
65     /// Use instead:
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 #[derive(Eq, PartialEq, Debug, Copy, Clone)]
91 enum Kind {
92     Struct,
93     Fn,
94 }
95
96 impl ExcessiveBools {
97     #[must_use]
98     pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
99         Self {
100             max_struct_bools,
101             max_fn_params_bools,
102         }
103     }
104
105     fn too_many_bools<'tcx>(&self, tys: impl Iterator<Item = &'tcx Ty<'tcx>>, kind: Kind) -> bool {
106         if let Ok(bools) = tys.filter(|ty| is_bool(ty)).count().try_into() {
107             (if Kind::Fn == kind {
108                 self.max_fn_params_bools
109             } else {
110                 self.max_struct_bools
111             }) < bools
112         } else {
113             false
114         }
115     }
116
117     fn check_fn_sig(&self, cx: &LateContext<'_>, fn_decl: &FnDecl<'_>, span: Span) {
118         if !span.from_expansion() && self.too_many_bools(fn_decl.inputs.iter(), Kind::Fn) {
119             span_lint_and_help(
120                 cx,
121                 FN_PARAMS_EXCESSIVE_BOOLS,
122                 span,
123                 &format!("more than {} bools in function parameters", self.max_fn_params_bools),
124                 None,
125                 "consider refactoring bools into two-variant enums",
126             );
127         }
128     }
129 }
130
131 impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
132
133 impl<'tcx> LateLintPass<'tcx> for ExcessiveBools {
134     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
135         if item.span.from_expansion() {
136             return;
137         }
138         if let ItemKind::Struct(variant_data, _) = &item.kind {
139             if has_repr_attr(cx, item.hir_id()) {
140                 return;
141             }
142
143             if self.too_many_bools(variant_data.fields().iter().map(|field| field.ty), Kind::Struct) {
144                 span_lint_and_help(
145                     cx,
146                     STRUCT_EXCESSIVE_BOOLS,
147                     item.span,
148                     &format!("more than {} bools in a struct", self.max_struct_bools),
149                     None,
150                     "consider using a state machine or refactoring bools into two-variant enums",
151                 );
152             }
153         }
154     }
155
156     fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'tcx>) {
157         // functions with a body are already checked by `check_fn`
158         if let TraitItemKind::Fn(fn_sig, TraitFn::Required(_)) = &trait_item.kind
159             && fn_sig.header.abi == Abi::Rust
160             {
161             self.check_fn_sig(cx, fn_sig.decl, fn_sig.span);
162         }
163     }
164
165     fn check_fn(
166         &mut self,
167         cx: &LateContext<'tcx>,
168         fn_kind: FnKind<'tcx>,
169         fn_decl: &'tcx FnDecl<'tcx>,
170         _: &'tcx Body<'tcx>,
171         span: Span,
172         def_id: LocalDefId,
173     ) {
174         let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
175         if let Some(fn_header) = fn_kind.header()
176             && fn_header.abi == Abi::Rust
177             && get_parent_as_impl(cx.tcx, hir_id)
178                 .map_or(true,
179                     |impl_item| impl_item.of_trait.is_none()
180                 )
181             {
182             self.check_fn_sig(cx, fn_decl, span);
183         }
184     }
185 }