1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
2 use clippy_utils
::macros
::HirNode
;
3 use clippy_utils
::source
::{indent_of, snippet, snippet_block_with_context, snippet_with_applicability}
;
4 use clippy_utils
::{get_parent_expr, is_refutable, peel_blocks}
;
5 use rustc_errors
::Applicability
;
6 use rustc_hir
::{Arm, Expr, ExprKind, Node, PatKind, StmtKind}
;
7 use rustc_lint
::LateContext
;
10 use super::MATCH_SINGLE_BINDING
;
13 Assign { span: Span, match_span: Span }
,
14 Local { span: Span, pat_span: Span }
,
17 #[expect(clippy::too_many_lines)]
18 pub(crate) fn check
<'a
>(cx
: &LateContext
<'a
>, ex
: &Expr
<'a
>, arms
: &[Arm
<'_
>], expr
: &Expr
<'a
>) {
19 if expr
.span
.from_expansion() || arms
.len() != 1 || is_refutable(cx
, arms
[0].pat
) {
23 let matched_vars
= ex
.span
;
24 let bind_names
= arms
[0].pat
.span
;
25 let match_body
= peel_blocks(arms
[0].body
);
26 let mut app
= Applicability
::MaybeIncorrect
;
27 let mut snippet_body
= snippet_block_with_context(
38 // Do we need to add ';' to suggestion ?
39 if let Node
::Stmt(stmt
) = cx
.tcx
.hir().get_parent(expr
.hir_id
)
40 && let StmtKind
::Expr(_
) = stmt
.kind
41 && match match_body
.kind
{
42 // We don't need to add a ; to blocks, unless that block is from a macro expansion
43 ExprKind
::Block(block
, _
) => block
.span
.from_expansion(),
47 snippet_body
.push('
;'
);
50 match arms
[0].pat
.kind
{
51 PatKind
::Binding(..) | PatKind
::Tuple(_
, _
) | PatKind
::Struct(..) => {
52 let (target_span
, sugg
) = match opt_parent_assign_span(cx
, ex
) {
53 Some(AssignmentExpr
::Assign { span, match_span }
) => {
54 let sugg
= sugg_with_curlies(
57 (bind_names
, matched_vars
),
68 "this assignment could be simplified",
69 "consider removing the `match` expression",
76 Some(AssignmentExpr
::Local { span, pat_span }
) => (
79 "let {} = {};\n{}let {} = {snippet_body};",
80 snippet_with_applicability(cx
, bind_names
, "..", &mut app
),
81 snippet_with_applicability(cx
, matched_vars
, "..", &mut app
),
82 " ".repeat(indent_of(cx
, expr
.span
).unwrap_or(0)),
83 snippet_with_applicability(cx
, pat_span
, "..", &mut app
)
87 let sugg
= sugg_with_curlies(
90 (bind_names
, matched_vars
),
102 MATCH_SINGLE_BINDING
,
104 "this match could be written as a `let` statement",
105 "consider using a `let` statement",
111 if ex
.can_have_side_effects() {
112 let sugg
= sugg_with_curlies(
115 (bind_names
, matched_vars
),
124 MATCH_SINGLE_BINDING
,
126 "this match could be replaced by its scrutinee and body",
127 "consider using the scrutinee and body instead",
134 MATCH_SINGLE_BINDING
,
136 "this match could be replaced by its body itself",
137 "consider using the match body instead",
139 Applicability
::MachineApplicable
,
147 /// Returns true if the `ex` match expression is in a local (`let`) or assign expression
148 fn opt_parent_assign_span
<'a
>(cx
: &LateContext
<'a
>, ex
: &Expr
<'a
>) -> Option
<AssignmentExpr
> {
149 let map
= &cx
.tcx
.hir();
151 if let Some(Node
::Expr(parent_arm_expr
)) = map
.find_parent(ex
.hir_id
) {
152 return match map
.find_parent(parent_arm_expr
.hir_id
) {
153 Some(Node
::Local(parent_let_expr
)) => Some(AssignmentExpr
::Local
{
154 span
: parent_let_expr
.span
,
155 pat_span
: parent_let_expr
.pat
.span(),
157 Some(Node
::Expr(Expr
{
158 kind
: ExprKind
::Assign(parent_assign_expr
, match_expr
, _
),
160 })) => Some(AssignmentExpr
::Assign
{
161 span
: parent_assign_expr
.span
,
162 match_span
: match_expr
.span
,
171 fn sugg_with_curlies
<'a
>(
172 cx
: &LateContext
<'a
>,
173 (ex
, match_expr
): (&Expr
<'a
>, &Expr
<'a
>),
174 (bind_names
, matched_vars
): (Span
, Span
),
176 applicability
: &mut Applicability
,
177 assignment
: Option
<Span
>,
178 needs_var_binding
: bool
,
180 let mut indent
= " ".repeat(indent_of(cx
, ex
.span
).unwrap_or(0));
182 let (mut cbrace_start
, mut cbrace_end
) = (String
::new(), String
::new());
183 if let Some(parent_expr
) = get_parent_expr(cx
, match_expr
) {
184 if let ExprKind
::Closure { .. }
= parent_expr
.kind
{
185 cbrace_end
= format
!("\n{indent}}}");
186 // Fix body indent due to the closure
187 indent
= " ".repeat(indent_of(cx
, bind_names
).unwrap_or(0));
188 cbrace_start
= format
!("{{\n{indent}");
192 // If the parent is already an arm, and the body is another match statement,
193 // we need curly braces around suggestion
194 if let Node
::Arm(arm
) = &cx
.tcx
.hir().get_parent(match_expr
.hir_id
) {
195 if let ExprKind
::Match(..) = arm
.body
.kind
{
196 cbrace_end
= format
!("\n{indent}}}");
197 // Fix body indent due to the match
198 indent
= " ".repeat(indent_of(cx
, bind_names
).unwrap_or(0));
199 cbrace_start
= format
!("{{\n{indent}");
203 let assignment_str
= assignment
.map_or_else(String
::new
, |span
| {
204 let mut s
= snippet(cx
, span
, "..").to_string();
209 let scrutinee
= if needs_var_binding
{
212 snippet_with_applicability(cx
, bind_names
, "..", applicability
),
213 snippet_with_applicability(cx
, matched_vars
, "..", applicability
)
216 snippet_with_applicability(cx
, matched_vars
, "..", applicability
).to_string()
219 format
!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}")