| 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 function declarations. |
| /// |
| /// ### Why is this bad? |
| /// Boolean parameters obscure meaning at the call site. The reader has to |
| /// remember what each `true` or `false` means and which position each flag |
| /// belongs in, with no help from the type system. |
| /// |
| /// The best replacement depends on what role the bool plays: |
| /// |
| /// - Use a two-variant enum when one parameter selects a mode. |
| /// - Split one function into two named operations when the choices are |
| /// distinct actions. |
| /// - Group multiple related flags into a struct or options type when they |
| /// travel together. |
| /// - Move the setting onto `self` or an existing config/context parameter |
| /// when it is really part of the surrounding state. |
| /// |
| /// This optimizes for readability of the calling code, which future readers |
| /// encounter more often than the function declaration. |
| /// |
| /// The `max-fn-params-bools` configuration accepts `0`, which lints on any |
| /// function with a bool parameter. |
| /// |
| /// ### Example |
| /// ```rust,ignore |
| /// fn render_document(show_hidden: bool, is_draft: bool) { ... } |
| /// ``` |
| /// |
| /// Use instead: |
| /// ```rust,ignore |
| /// enum Visibility { |
| /// ShowHidden, |
| /// HideHidden, |
| /// } |
| /// |
| /// enum DocumentKind { |
| /// Draft, |
| /// Final, |
| /// } |
| /// |
| /// fn render_document(visibility: Visibility, kind: DocumentKind) { ... } |
| /// |
| /// // or split the operation when the choices are distinct |
| /// fn render_draft() { ... } |
| /// fn render_final() { ... } |
| /// ``` |
| #[clippy::version = "1.43.0"] |
| pub FN_PARAMS_EXCESSIVE_BOOLS, |
| pedantic, |
| "using too many bools in function parameters" |
| } |
| |
| 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" |
| } |
| |
| impl_lint_pass!(ExcessiveBools => [ |
| FN_PARAMS_EXCESSIVE_BOOLS, |
| STRUCT_EXCESSIVE_BOOLS, |
| ]); |
| |
| 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, |
| } |
| } |
| } |
| |
| fn has_n_bools<'tcx>(iter: impl Iterator<Item = &'tcx Ty<'tcx>>, 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); |
| } |
| } |
| } |