blob: 1d18e194d15c609a91bf9b22ec17dcf87d24359c [file] [log] [blame]
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{get_parent_as_impl, has_repr_attr, is_bool};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, FnDecl, Item, ItemKind, TraitFn, TraitItem, TraitItemKind, Ty};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::LocalDefId;
use rustc_span::Span;
use rustc_target::spec::abi::Abi;
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 it's used as a state machine,
/// which is much better implemented as an enum.
/// If it's not the case, excessive bools usually benefit
/// from refactoring into two-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,
}
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
enum Kind {
Struct,
Fn,
}
impl ExcessiveBools {
#[must_use]
pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
Self {
max_struct_bools,
max_fn_params_bools,
}
}
fn too_many_bools<'tcx>(&self, tys: impl Iterator<Item = &'tcx Ty<'tcx>>, kind: Kind) -> bool {
if let Ok(bools) = tys.filter(|ty| is_bool(ty)).count().try_into() {
(if Kind::Fn == kind {
self.max_fn_params_bools
} else {
self.max_struct_bools
}) < bools
} else {
false
}
}
fn check_fn_sig(&self, cx: &LateContext<'_>, fn_decl: &FnDecl<'_>, span: Span) {
if !span.from_expansion() && self.too_many_bools(fn_decl.inputs.iter(), Kind::Fn) {
span_lint_and_help(
cx,
FN_PARAMS_EXCESSIVE_BOOLS,
span,
&format!("more than {} bools in function parameters", self.max_fn_params_bools),
None,
"consider refactoring bools into two-variant enums",
);
}
}
}
impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
impl<'tcx> LateLintPass<'tcx> for ExcessiveBools {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if item.span.from_expansion() {
return;
}
if let ItemKind::Struct(variant_data, _) = &item.kind {
if has_repr_attr(cx, item.hir_id()) {
return;
}
if self.too_many_bools(variant_data.fields().iter().map(|field| field.ty), Kind::Struct) {
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 == Abi::Rust
{
self.check_fn_sig(cx, fn_sig.decl, fn_sig.span);
}
}
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,
) {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
if let Some(fn_header) = fn_kind.header()
&& fn_header.abi == Abi::Rust
&& get_parent_as_impl(cx.tcx, hir_id).map_or(true, |impl_item| impl_item.of_trait.is_none())
{
self.check_fn_sig(cx, fn_decl, span);
}
}
}