mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-12-23 19:43:20 +00:00
216 lines
7.8 KiB
Rust
216 lines
7.8 KiB
Rust
use clippy_utils::diagnostics::span_lint_and_sugg;
|
|
use clippy_utils::macros::HirNode;
|
|
use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability};
|
|
use clippy_utils::sugg::Sugg;
|
|
use clippy_utils::{get_parent_expr, is_refutable, peel_blocks};
|
|
use rustc_errors::Applicability;
|
|
use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind};
|
|
use rustc_lint::LateContext;
|
|
use rustc_span::Span;
|
|
|
|
use super::MATCH_SINGLE_BINDING;
|
|
|
|
enum AssignmentExpr {
|
|
Assign { span: Span, match_span: Span },
|
|
Local { span: Span, pat_span: Span },
|
|
}
|
|
|
|
#[expect(clippy::too_many_lines)]
|
|
pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'a>) {
|
|
if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
|
|
return;
|
|
}
|
|
|
|
let matched_vars = ex.span;
|
|
let bind_names = arms[0].pat.span;
|
|
let match_body = peel_blocks(arms[0].body);
|
|
let mut snippet_body = if match_body.span.from_expansion() {
|
|
Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
|
|
} else {
|
|
snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
|
|
};
|
|
|
|
// Do we need to add ';' to suggestion ?
|
|
match match_body.kind {
|
|
ExprKind::Block(block, _) => {
|
|
// macro + expr_ty(body) == ()
|
|
if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
|
|
snippet_body.push(';');
|
|
}
|
|
},
|
|
_ => {
|
|
// expr_ty(body) == ()
|
|
if cx.typeck_results().expr_ty(match_body).is_unit() {
|
|
snippet_body.push(';');
|
|
}
|
|
},
|
|
}
|
|
|
|
let mut applicability = Applicability::MaybeIncorrect;
|
|
match arms[0].pat.kind {
|
|
PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
|
|
let (target_span, sugg) = match opt_parent_assign_span(cx, ex) {
|
|
Some(AssignmentExpr::Assign { span, match_span }) => {
|
|
let sugg = sugg_with_curlies(
|
|
cx,
|
|
(ex, expr),
|
|
(bind_names, matched_vars),
|
|
&snippet_body,
|
|
&mut applicability,
|
|
Some(span),
|
|
);
|
|
|
|
span_lint_and_sugg(
|
|
cx,
|
|
MATCH_SINGLE_BINDING,
|
|
span.to(match_span),
|
|
"this assignment could be simplified",
|
|
"consider removing the `match` expression",
|
|
sugg,
|
|
applicability,
|
|
);
|
|
|
|
return;
|
|
},
|
|
Some(AssignmentExpr::Local { span, pat_span }) => (
|
|
span,
|
|
format!(
|
|
"let {} = {};\n{}let {} = {};",
|
|
snippet_with_applicability(cx, bind_names, "..", &mut applicability),
|
|
snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
|
|
" ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
|
|
snippet_with_applicability(cx, pat_span, "..", &mut applicability),
|
|
snippet_body
|
|
),
|
|
),
|
|
None => {
|
|
let sugg = sugg_with_curlies(
|
|
cx,
|
|
(ex, expr),
|
|
(bind_names, matched_vars),
|
|
&snippet_body,
|
|
&mut applicability,
|
|
None,
|
|
);
|
|
(expr.span, sugg)
|
|
},
|
|
};
|
|
|
|
span_lint_and_sugg(
|
|
cx,
|
|
MATCH_SINGLE_BINDING,
|
|
target_span,
|
|
"this match could be written as a `let` statement",
|
|
"consider using a `let` statement",
|
|
sugg,
|
|
applicability,
|
|
);
|
|
},
|
|
PatKind::Wild => {
|
|
if ex.can_have_side_effects() {
|
|
let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
|
|
let sugg = format!(
|
|
"{};\n{}{}",
|
|
snippet_with_applicability(cx, ex.span, "..", &mut applicability),
|
|
indent,
|
|
snippet_body
|
|
);
|
|
|
|
span_lint_and_sugg(
|
|
cx,
|
|
MATCH_SINGLE_BINDING,
|
|
expr.span,
|
|
"this match could be replaced by its scrutinee and body",
|
|
"consider using the scrutinee and body instead",
|
|
sugg,
|
|
applicability,
|
|
);
|
|
} else {
|
|
span_lint_and_sugg(
|
|
cx,
|
|
MATCH_SINGLE_BINDING,
|
|
expr.span,
|
|
"this match could be replaced by its body itself",
|
|
"consider using the match body instead",
|
|
snippet_body,
|
|
Applicability::MachineApplicable,
|
|
);
|
|
}
|
|
},
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
/// Returns true if the `ex` match expression is in a local (`let`) or assign expression
|
|
fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<AssignmentExpr> {
|
|
let map = &cx.tcx.hir();
|
|
|
|
if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)) {
|
|
return match map.find(map.get_parent_node(parent_arm_expr.hir_id)) {
|
|
Some(Node::Local(parent_let_expr)) => Some(AssignmentExpr::Local {
|
|
span: parent_let_expr.span,
|
|
pat_span: parent_let_expr.pat.span(),
|
|
}),
|
|
Some(Node::Expr(Expr {
|
|
kind: ExprKind::Assign(parent_assign_expr, match_expr, _),
|
|
..
|
|
})) => Some(AssignmentExpr::Assign {
|
|
span: parent_assign_expr.span,
|
|
match_span: match_expr.span,
|
|
}),
|
|
_ => None,
|
|
};
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn sugg_with_curlies<'a>(
|
|
cx: &LateContext<'a>,
|
|
(ex, match_expr): (&Expr<'a>, &Expr<'a>),
|
|
(bind_names, matched_vars): (Span, Span),
|
|
snippet_body: &str,
|
|
applicability: &mut Applicability,
|
|
assignment: Option<Span>,
|
|
) -> String {
|
|
let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
|
|
|
|
let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
|
|
if let Some(parent_expr) = get_parent_expr(cx, match_expr) {
|
|
if let ExprKind::Closure { .. } = parent_expr.kind {
|
|
cbrace_end = format!("\n{}}}", indent);
|
|
// Fix body indent due to the closure
|
|
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
|
|
cbrace_start = format!("{{\n{}", indent);
|
|
}
|
|
}
|
|
|
|
// If the parent is already an arm, and the body is another match statement,
|
|
// we need curly braces around suggestion
|
|
let parent_node_id = cx.tcx.hir().get_parent_node(match_expr.hir_id);
|
|
if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
|
|
if let ExprKind::Match(..) = arm.body.kind {
|
|
cbrace_end = format!("\n{}}}", indent);
|
|
// Fix body indent due to the match
|
|
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
|
|
cbrace_start = format!("{{\n{}", indent);
|
|
}
|
|
}
|
|
|
|
let assignment_str = assignment.map_or_else(String::new, |span| {
|
|
let mut s = snippet(cx, span, "..").to_string();
|
|
s.push_str(" = ");
|
|
s
|
|
});
|
|
|
|
format!(
|
|
"{}let {} = {};\n{}{}{}{}",
|
|
cbrace_start,
|
|
snippet_with_applicability(cx, bind_names, "..", applicability),
|
|
snippet_with_applicability(cx, matched_vars, "..", applicability),
|
|
indent,
|
|
assignment_str,
|
|
snippet_body,
|
|
cbrace_end
|
|
)
|
|
}
|