blob: b2466bbd982d9b7992ef88fa70c274a7e7473859 [file] [log] [blame] [edit]
use super::OBFUSCATED_IF_ELSE;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, sym};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::Symbol;
#[expect(clippy::needless_pass_by_value)]
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
then_recv: &'tcx Expr<'_>,
then_arg: &'tcx Expr<'_>,
then_method_name: Symbol,
unwrap: Unwrap<'tcx>,
) {
let recv_ty = cx.typeck_results().expr_ty(then_recv);
if recv_ty.is_bool() {
let then_eager = switch_to_eager_eval(cx, then_arg);
let unwrap_eager = unwrap.arg().is_none_or(|arg| switch_to_eager_eval(cx, arg));
let mut applicability = if then_eager && unwrap_eager {
Applicability::MachineApplicable
} else {
Applicability::MaybeIncorrect
};
let if_then = match then_method_name {
sym::then if let ExprKind::Closure(closure) = then_arg.kind => {
let body = cx.tcx.hir_body(closure.body);
snippet_with_applicability(cx, body.value.span, "..", &mut applicability)
},
sym::then_some => snippet_with_applicability(cx, then_arg.span, "..", &mut applicability),
_ => return,
};
let els = match unwrap {
Unwrap::Or(arg) => snippet_with_applicability(cx, arg.span, "..", &mut applicability),
Unwrap::OrElse(arg) => match arg.kind {
ExprKind::Closure(closure) => {
let body = cx.tcx.hir_body(closure.body);
snippet_with_applicability(cx, body.value.span, "..", &mut applicability)
},
ExprKind::Path(_) => snippet_with_applicability(cx, arg.span, "_", &mut applicability) + "()",
_ => return,
},
Unwrap::OrDefault => "Default::default()".into(),
};
let sugg = format!(
"if {} {{ {} }} else {{ {} }}",
Sugg::hir_with_applicability(cx, then_recv, "..", &mut applicability),
if_then,
els
);
// To be parsed as an expression, the `if { … } else { … }` as the left operand of a binary operator
// requires parentheses.
let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let ExprKind::Binary(_, left, _) = parent_expr.kind
&& left.hir_id == expr.hir_id
{
format!("({sugg})")
} else {
sugg
};
span_lint_and_sugg(
cx,
OBFUSCATED_IF_ELSE,
expr.span,
"this method chain can be written more clearly with `if .. else ..`",
"try",
sugg,
applicability,
);
}
}
pub(super) enum Unwrap<'tcx> {
Or(&'tcx Expr<'tcx>),
OrElse(&'tcx Expr<'tcx>),
OrDefault,
}
impl<'tcx> Unwrap<'tcx> {
fn arg(&self) -> Option<&'tcx Expr<'tcx>> {
match self {
Self::Or(a) | Self::OrElse(a) => Some(a),
Self::OrDefault => None,
}
}
}