use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::{get_parent_as_impl, has_repr_attr, is_bool}; use rustc_abi::ExternAbi; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl, Item, ItemKind, TraitFn, TraitItem, TraitItemKind, Ty}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::Span; use rustc_span::def_id::LocalDefId; declare_clippy_lint! { /// ### What it does /// Checks for excessive /// use of bools in structs. /// /// ### Why is this bad? /// Excessive bools in a struct is often a sign that /// the type is being used to represent a state /// machine, which is much better implemented as an /// enum. /// /// The reason an enum is better for state machines /// over structs is that enums more easily forbid /// invalid states. /// /// Structs with too many booleans may benefit from refactoring /// into multi variant enums for better readability and API. /// /// ### Example /// ```no_run /// struct S { /// is_pending: bool, /// is_processing: bool, /// is_finished: bool, /// } /// ``` /// /// Use instead: /// ```no_run /// enum S { /// Pending, /// Processing, /// Finished, /// } /// ``` #[clippy::version = "1.43.0"] pub STRUCT_EXCESSIVE_BOOLS, pedantic, "using too many bools in a struct" } declare_clippy_lint! { /// ### What it does /// Checks for excessive use of /// bools in function definitions. /// /// ### Why is this bad? /// Calls to such functions /// are confusing and error prone, because it's /// hard to remember argument order and you have /// no type system support to back you up. Using /// two-variant enums instead of bools often makes /// API easier to use. /// /// ### Example /// ```rust,ignore /// fn f(is_round: bool, is_hot: bool) { ... } /// ``` /// /// Use instead: /// ```rust,ignore /// enum Shape { /// Round, /// Spiky, /// } /// /// enum Temperature { /// Hot, /// IceCold, /// } /// /// fn f(shape: Shape, temperature: Temperature) { ... } /// ``` #[clippy::version = "1.43.0"] pub FN_PARAMS_EXCESSIVE_BOOLS, pedantic, "using too many bools in function parameters" } pub struct ExcessiveBools { max_struct_bools: u64, max_fn_params_bools: u64, } impl ExcessiveBools { pub fn new(conf: &'static Conf) -> Self { Self { max_struct_bools: conf.max_struct_bools, max_fn_params_bools: conf.max_fn_params_bools, } } } impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]); fn has_n_bools<'tcx>(iter: impl Iterator>, mut count: u64) -> bool { iter.filter(|ty| is_bool(ty)).any(|_| { let (x, overflow) = count.overflowing_sub(1); count = x; overflow }) } fn check_fn_decl(cx: &LateContext<'_>, decl: &FnDecl<'_>, sp: Span, max: u64) { if has_n_bools(decl.inputs.iter(), max) && !sp.from_expansion() { span_lint_and_help( cx, FN_PARAMS_EXCESSIVE_BOOLS, sp, format!("more than {max} bools in function parameters"), None, "consider refactoring bools into two-variant enums", ); } } impl<'tcx> LateLintPass<'tcx> for ExcessiveBools { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { if let ItemKind::Struct(_, _, variant_data) = &item.kind && variant_data.fields().len() as u64 > self.max_struct_bools && has_n_bools( variant_data.fields().iter().map(|field| field.ty), self.max_struct_bools, ) && !has_repr_attr(cx, item.hir_id()) && !item.span.from_expansion() { span_lint_and_help( cx, STRUCT_EXCESSIVE_BOOLS, item.span, format!("more than {} bools in a struct", self.max_struct_bools), None, "consider using a state machine or refactoring bools into two-variant enums", ); } } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'tcx>) { // functions with a body are already checked by `check_fn` if let TraitItemKind::Fn(fn_sig, TraitFn::Required(_)) = &trait_item.kind && fn_sig.header.abi == ExternAbi::Rust && fn_sig.decl.inputs.len() as u64 > self.max_fn_params_bools { check_fn_decl(cx, fn_sig.decl, fn_sig.span, self.max_fn_params_bools); } } fn check_fn( &mut self, cx: &LateContext<'tcx>, fn_kind: FnKind<'tcx>, fn_decl: &'tcx FnDecl<'tcx>, _: &'tcx Body<'tcx>, span: Span, def_id: LocalDefId, ) { if let Some(fn_header) = fn_kind.header() && fn_header.abi == ExternAbi::Rust && fn_decl.inputs.len() as u64 > self.max_fn_params_bools && get_parent_as_impl(cx.tcx, cx.tcx.local_def_id_to_hir_id(def_id)) .is_none_or(|impl_item| impl_item.of_trait.is_none()) { check_fn_decl(cx, fn_decl, span, self.max_fn_params_bools); } } }