| 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 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" | |
| } | |
| 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); | |
| } | |
| } | |
| } |