]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/excessive_bools.rs
New upstream version 1.75.0+dfsg1
[rustc.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::def_id::LocalDefId;
8 use rustc_span::Span;
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 /// ```no_run
26 /// struct S {
27 /// is_pending: bool,
28 /// is_processing: bool,
29 /// is_finished: bool,
30 /// }
31 /// ```
32 ///
33 /// Use instead:
34 /// ```no_run
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).map_or(true, |impl_item| impl_item.of_trait.is_none())
178 {
179 self.check_fn_sig(cx, fn_decl, span);
180 }
181 }
182 }