178 lines
5.4 KiB
Rust
178 lines
5.4 KiB
Rust
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<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);
|
|
}
|
|
}
|
|
}
|