Skip to content

Commit 79db0f7

Browse files
committed
Fix match_single_binding for assign expressions
1 parent d422baa commit 79db0f7

File tree

5 files changed

+180
-64
lines changed

5 files changed

+180
-64
lines changed
Lines changed: 122 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1+
2+
13
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::source::{indent_of, snippet_block, snippet_with_applicability};
4+
use clippy_utils::macros::HirNode;
5+
use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability};
36
use clippy_utils::sugg::Sugg;
47
use clippy_utils::{get_parent_expr, is_refutable, peel_blocks};
58
use rustc_errors::Applicability;
6-
use rustc_hir::{Arm, Expr, ExprKind, Local, Node, PatKind};
9+
use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind};
710
use rustc_lint::LateContext;
11+
use rustc_span::Span;
812

913
use super::MATCH_SINGLE_BINDING;
1014

15+
enum AssignmentExpr {
16+
Assign { span: Span, match_span: Span },
17+
Local { span: Span, pat_span: Span },
18+
}
19+
1120
#[expect(clippy::too_many_lines)]
12-
pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) {
21+
pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'a>) {
1322
if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
1423
return;
1524
}
@@ -42,61 +51,59 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
4251
let mut applicability = Applicability::MaybeIncorrect;
4352
match arms[0].pat.kind {
4453
PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
45-
// If this match is in a local (`let`) stmt
46-
let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) {
47-
(
48-
parent_let_node.span,
54+
let (target_span, sugg) = match opt_parent_assign_span(cx, ex) {
55+
Some(AssignmentExpr::Assign { span, match_span }) => {
56+
let sugg = sugg_with_curlies(
57+
cx,
58+
(ex, expr),
59+
(bind_names, matched_vars),
60+
&*snippet_body,
61+
&mut applicability,
62+
Some(span),
63+
);
64+
65+
span_lint_and_sugg(
66+
cx,
67+
MATCH_SINGLE_BINDING,
68+
span.to(match_span),
69+
"this assignment could be simplified",
70+
"consider removing the `match` expression",
71+
sugg,
72+
applicability,
73+
);
74+
75+
return;
76+
},
77+
Some(AssignmentExpr::Local { span, pat_span }) => (
78+
span,
4979
format!(
5080
"let {} = {};\n{}let {} = {};",
5181
snippet_with_applicability(cx, bind_names, "..", &mut applicability),
5282
snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
5383
" ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
54-
snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability),
84+
snippet_with_applicability(cx, pat_span, "..", &mut applicability),
5585
snippet_body
5686
),
57-
)
58-
} else {
59-
// If we are in closure, we need curly braces around suggestion
60-
let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
61-
let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string());
62-
if let Some(parent_expr) = get_parent_expr(cx, expr) {
63-
if let ExprKind::Closure(..) = parent_expr.kind {
64-
cbrace_end = format!("\n{}}}", indent);
65-
// Fix body indent due to the closure
66-
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
67-
cbrace_start = format!("{{\n{}", indent);
68-
}
69-
}
70-
// If the parent is already an arm, and the body is another match statement,
71-
// we need curly braces around suggestion
72-
let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id);
73-
if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
74-
if let ExprKind::Match(..) = arm.body.kind {
75-
cbrace_end = format!("\n{}}}", indent);
76-
// Fix body indent due to the match
77-
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
78-
cbrace_start = format!("{{\n{}", indent);
79-
}
80-
}
81-
(
82-
expr.span,
83-
format!(
84-
"{}let {} = {};\n{}{}{}",
85-
cbrace_start,
86-
snippet_with_applicability(cx, bind_names, "..", &mut applicability),
87-
snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
88-
indent,
89-
snippet_body,
90-
cbrace_end
91-
),
92-
)
87+
),
88+
None => {
89+
let sugg = sugg_with_curlies(
90+
cx,
91+
(ex, expr),
92+
(bind_names, matched_vars),
93+
&*snippet_body,
94+
&mut applicability,
95+
None,
96+
);
97+
(expr.span, sugg)
98+
},
9399
};
100+
94101
span_lint_and_sugg(
95102
cx,
96103
MATCH_SINGLE_BINDING,
97104
target_span,
98105
"this match could be written as a `let` statement",
99-
"consider using `let` statement",
106+
"consider using a `let` statement",
100107
sugg,
101108
applicability,
102109
);
@@ -110,6 +117,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
110117
indent,
111118
snippet_body
112119
);
120+
113121
span_lint_and_sugg(
114122
cx,
115123
MATCH_SINGLE_BINDING,
@@ -135,15 +143,76 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
135143
}
136144
}
137145

138-
/// Returns true if the `ex` match expression is in a local (`let`) statement
139-
fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> {
146+
/// Returns true if the `ex` match expression is in a local (`let`) or assign expression
147+
fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<AssignmentExpr> {
140148
let map = &cx.tcx.hir();
141-
if_chain! {
142-
if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id));
143-
if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id));
144-
then {
145-
return Some(parent_let_expr);
146-
}
149+
150+
if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)) {
151+
return match map.find(map.get_parent_node(parent_arm_expr.hir_id)) {
152+
Some(Node::Local(parent_let_expr)) => Some(AssignmentExpr::Local {
153+
span: parent_let_expr.span,
154+
pat_span: parent_let_expr.pat.span(),
155+
}),
156+
Some(Node::Expr(Expr {
157+
kind: ExprKind::Assign(parent_assign_expr, match_expr, _),
158+
..
159+
})) => Some(AssignmentExpr::Assign {
160+
span: parent_assign_expr.span,
161+
match_span: match_expr.span,
162+
}),
163+
_ => None,
164+
};
147165
}
166+
148167
None
149168
}
169+
170+
fn sugg_with_curlies<'a>(
171+
cx: &LateContext<'a>,
172+
(ex, match_expr): (&Expr<'a>, &Expr<'a>),
173+
(bind_names, matched_vars): (Span, Span),
174+
snippet_body: &str,
175+
applicability: &mut Applicability,
176+
assignment: Option<Span>,
177+
) -> String {
178+
let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
179+
180+
let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
181+
if let Some(parent_expr) = get_parent_expr(cx, match_expr) {
182+
if let ExprKind::Closure(..) = parent_expr.kind {
183+
cbrace_end = format!("\n{}}}", indent);
184+
// Fix body indent due to the closure
185+
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
186+
cbrace_start = format!("{{\n{}", indent);
187+
}
188+
}
189+
190+
// If the parent is already an arm, and the body is another match statement,
191+
// we need curly braces around suggestion
192+
let parent_node_id = cx.tcx.hir().get_parent_node(match_expr.hir_id);
193+
if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
194+
if let ExprKind::Match(..) = arm.body.kind {
195+
cbrace_end = format!("\n{}}}", indent);
196+
// Fix body indent due to the match
197+
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
198+
cbrace_start = format!("{{\n{}", indent);
199+
}
200+
}
201+
202+
let assignment_str = assignment.map_or_else(String::new, |span| {
203+
let mut s = snippet(cx, span, "..").to_string();
204+
s.push_str(" = ");
205+
s
206+
});
207+
208+
format!(
209+
"{}let {} = {};\n{}{}{}{}",
210+
cbrace_start,
211+
snippet_with_applicability(cx, bind_names, "..", applicability),
212+
snippet_with_applicability(cx, matched_vars, "..", applicability),
213+
indent,
214+
assignment_str,
215+
snippet_body,
216+
cbrace_end
217+
)
218+
}

tests/ui/match_single_binding.fixed

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,16 @@ fn main() {
111111
let x = 1;
112112
println!("Not an array index start");
113113
}
114+
115+
#[allow(dead_code)]
116+
fn issue_8723() {
117+
let (mut val, idx) = ("a b", 1);
118+
119+
let (pre, suf) = val.split_at(idx);
120+
val = {
121+
println!("{}", pre);
122+
suf
123+
};
124+
125+
let _ = val;
126+
}

tests/ui/match_single_binding.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,17 @@ fn main() {
126126
_ => println!("Not an array index start"),
127127
}
128128
}
129+
130+
#[allow(dead_code)]
131+
fn issue_8723() {
132+
let (mut val, idx) = ("a b", 1);
133+
134+
val = match val.split_at(idx) {
135+
(pre, suf) => {
136+
println!("{}", pre);
137+
suf
138+
},
139+
};
140+
141+
let _ = val;
142+
}

tests/ui/match_single_binding.stderr

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ LL | | }
99
| |_____^
1010
|
1111
= note: `-D clippy::match-single-binding` implied by `-D warnings`
12-
help: consider using `let` statement
12+
help: consider using a `let` statement
1313
|
1414
LL ~ let (x, y, z) = (a, b, c);
1515
LL + {
@@ -25,7 +25,7 @@ LL | | (x, y, z) => println!("{} {} {}", x, y, z),
2525
LL | | }
2626
| |_____^
2727
|
28-
help: consider using `let` statement
28+
help: consider using a `let` statement
2929
|
3030
LL ~ let (x, y, z) = (a, b, c);
3131
LL + println!("{} {} {}", x, y, z);
@@ -88,7 +88,7 @@ LL | | Point { x, y } => println!("Coords: ({}, {})", x, y),
8888
LL | | }
8989
| |_____^
9090
|
91-
help: consider using `let` statement
91+
help: consider using a `let` statement
9292
|
9393
LL ~ let Point { x, y } = p;
9494
LL + println!("Coords: ({}, {})", x, y);
@@ -102,7 +102,7 @@ LL | | Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1),
102102
LL | | }
103103
| |_____^
104104
|
105-
help: consider using `let` statement
105+
help: consider using a `let` statement
106106
|
107107
LL ~ let Point { x: x1, y: y1 } = p;
108108
LL + println!("Coords: ({}, {})", x1, y1);
@@ -116,7 +116,7 @@ LL | | ref r => println!("Got a reference to {}", r),
116116
LL | | }
117117
| |_____^
118118
|
119-
help: consider using `let` statement
119+
help: consider using a `let` statement
120120
|
121121
LL ~ let ref r = x;
122122
LL + println!("Got a reference to {}", r);
@@ -130,7 +130,7 @@ LL | | ref mut mr => println!("Got a mutable reference to {}", mr),
130130
LL | | }
131131
| |_____^
132132
|
133-
help: consider using `let` statement
133+
help: consider using a `let` statement
134134
|
135135
LL ~ let ref mut mr = x;
136136
LL + println!("Got a mutable reference to {}", mr);
@@ -144,7 +144,7 @@ LL | | Point { x, y } => x * y,
144144
LL | | };
145145
| |______^
146146
|
147-
help: consider using `let` statement
147+
help: consider using a `let` statement
148148
|
149149
LL ~ let Point { x, y } = coords();
150150
LL + let product = x * y;
@@ -159,7 +159,7 @@ LL | | unwrapped => unwrapped,
159159
LL | | })
160160
| |_________^
161161
|
162-
help: consider using `let` statement
162+
help: consider using a `let` statement
163163
|
164164
LL ~ .map(|i| {
165165
LL + let unwrapped = i.unwrap();
@@ -176,5 +176,25 @@ LL | | _ => println!("Not an array index start"),
176176
LL | | }
177177
| |_____^ help: consider using the match body instead: `println!("Not an array index start");`
178178

179-
error: aborting due to 12 previous errors
179+
error: this assignment could be simplified
180+
--> $DIR/match_single_binding.rs:134:5
181+
|
182+
LL | / val = match val.split_at(idx) {
183+
LL | | (pre, suf) => {
184+
LL | | println!("{}", pre);
185+
LL | | suf
186+
LL | | },
187+
LL | | };
188+
| |_____^
189+
|
190+
help: consider removing the `match` expression
191+
|
192+
LL ~ let (pre, suf) = val.split_at(idx);
193+
LL + val = {
194+
LL + println!("{}", pre);
195+
LL + suf
196+
LL ~ };
197+
|
198+
199+
error: aborting due to 13 previous errors
180200

tests/ui/match_single_binding2.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ LL | | },
88
| |_____________^
99
|
1010
= note: `-D clippy::match-single-binding` implied by `-D warnings`
11-
help: consider using `let` statement
11+
help: consider using a `let` statement
1212
|
1313
LL ~ Some((iter, _item)) => {
1414
LL + let (min, max) = iter.size_hint();
@@ -24,7 +24,7 @@ LL | | (a, b) => println!("a {:?} and b {:?}", a, b),
2424
LL | | }
2525
| |_____________^
2626
|
27-
help: consider using `let` statement
27+
help: consider using a `let` statement
2828
|
2929
LL ~ let (a, b) = get_tup();
3030
LL + println!("a {:?} and b {:?}", a, b);

0 commit comments

Comments
 (0)