Skip to content

Commit 92b124b

Browse files
committed
fix: match_single_binding wrongly handles scope
1 parent 97815d0 commit 92b124b

File tree

5 files changed

+206
-86
lines changed

5 files changed

+206
-86
lines changed

clippy_lints/src/matches/match_single_binding.rs

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::macros::HirNode;
3-
use clippy_utils::source::{indent_of, snippet, snippet_block_with_context, snippet_with_context};
3+
use clippy_utils::source::{
4+
RelativeIndent, indent_of, reindent_multiline_relative, snippet, snippet_block_with_context, snippet_with_context,
5+
};
46
use clippy_utils::{is_refutable, peel_blocks};
57
use rustc_errors::Applicability;
68
use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind, StmtKind};
@@ -50,7 +52,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
5052
cx,
5153
(ex, expr),
5254
(bind_names, matched_vars),
53-
&snippet_body,
55+
snippet_body,
5456
&mut app,
5557
Some(span),
5658
true,
@@ -83,7 +85,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
8385
cx,
8486
(ex, expr),
8587
(bind_names, matched_vars),
86-
&snippet_body,
88+
snippet_body,
8789
&mut app,
8890
None,
8991
true,
@@ -108,7 +110,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
108110
cx,
109111
(ex, expr),
110112
(bind_names, matched_vars),
111-
&snippet_body,
113+
snippet_body,
112114
&mut app,
113115
None,
114116
false,
@@ -161,47 +163,56 @@ fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<Ass
161163
None
162164
}
163165

164-
fn expr_parent_requires_curlies<'a>(cx: &LateContext<'a>, match_expr: &Expr<'a>) -> bool {
166+
fn expr_in_nested_block(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool {
167+
if let Node::Block(block) = cx.tcx.parent_hir_node(match_expr.hir_id) {
168+
return block
169+
.expr
170+
.map_or_else(|| matches!(block.stmts, [_]), |_| block.stmts.is_empty());
171+
}
172+
false
173+
}
174+
175+
fn expr_must_have_curlies(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool {
165176
let parent = cx.tcx.parent_hir_node(match_expr.hir_id);
166-
matches!(
167-
parent,
168-
Node::Expr(Expr {
169-
kind: ExprKind::Closure { .. },
170-
..
171-
}) | Node::AnonConst(..)
177+
if let Node::Expr(Expr {
178+
kind: ExprKind::Closure { .. },
179+
..
180+
})
181+
| Node::AnonConst(..) = parent
182+
{
183+
return true;
184+
}
185+
186+
if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id)
187+
&& let ExprKind::Match(..) = arm.body.kind
188+
{
189+
return true;
190+
}
191+
192+
false
193+
}
194+
195+
fn reindent_snippet_if_in_block(snippet_body: &str, has_assignment: bool) -> String {
196+
if has_assignment || !snippet_body.starts_with('{') {
197+
return reindent_multiline_relative(snippet_body, true, RelativeIndent::Add(0));
198+
}
199+
200+
reindent_multiline_relative(
201+
snippet_body.trim_start_matches('{').trim_end_matches('}').trim(),
202+
false,
203+
RelativeIndent::Sub(4),
172204
)
173205
}
174206

175207
fn sugg_with_curlies<'a>(
176208
cx: &LateContext<'a>,
177209
(ex, match_expr): (&Expr<'a>, &Expr<'a>),
178210
(bind_names, matched_vars): (Span, Span),
179-
snippet_body: &str,
211+
mut snippet_body: String,
180212
applicability: &mut Applicability,
181213
assignment: Option<Span>,
182214
needs_var_binding: bool,
183215
) -> String {
184-
let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
185-
186-
let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
187-
if expr_parent_requires_curlies(cx, match_expr) {
188-
cbrace_end = format!("\n{indent}}}");
189-
// Fix body indent due to the closure
190-
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
191-
cbrace_start = format!("{{\n{indent}");
192-
}
193-
194-
// If the parent is already an arm, and the body is another match statement,
195-
// we need curly braces around suggestion
196-
if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id)
197-
&& let ExprKind::Match(..) = arm.body.kind
198-
{
199-
cbrace_end = format!("\n{indent}}}");
200-
// Fix body indent due to the match
201-
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
202-
cbrace_start = format!("{{\n{indent}");
203-
}
204-
205216
let assignment_str = assignment.map_or_else(String::new, |span| {
206217
let mut s = snippet(cx, span, "..").to_string();
207218
s.push_str(" = ");
@@ -221,5 +232,15 @@ fn sugg_with_curlies<'a>(
221232
.to_string()
222233
};
223234

235+
let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
236+
let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
237+
if !expr_in_nested_block(cx, match_expr) && (needs_var_binding || expr_must_have_curlies(cx, match_expr)) {
238+
cbrace_end = format!("\n{indent}}}");
239+
// Fix body indent due to the closure
240+
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
241+
cbrace_start = format!("{{\n{indent}");
242+
snippet_body = reindent_snippet_if_in_block(&snippet_body, !assignment_str.is_empty());
243+
}
244+
224245
format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}")
225246
}

clippy_utils/src/source.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use rustc_span::{
1818
};
1919
use std::borrow::Cow;
2020
use std::fmt;
21-
use std::ops::{Deref, Index, Range};
21+
use std::ops::{Add, Deref, Index, Range};
2222

2323
pub trait HasSession {
2424
fn sess(&self) -> &Session;
@@ -476,6 +476,38 @@ pub fn position_before_rarrow(s: &str) -> Option<usize> {
476476
})
477477
}
478478

479+
pub enum RelativeIndent {
480+
Add(usize),
481+
Sub(usize),
482+
}
483+
484+
impl Add<RelativeIndent> for usize {
485+
type Output = usize;
486+
487+
fn add(self, rhs: RelativeIndent) -> Self::Output {
488+
match rhs {
489+
RelativeIndent::Add(n) => self + n,
490+
RelativeIndent::Sub(n) => self.saturating_sub(n),
491+
}
492+
}
493+
}
494+
495+
/// Reindents a multiline string with possibility of ignoring the first line and relative
496+
/// indentation.
497+
pub fn reindent_multiline_relative(s: &str, ignore_first: bool, relative_indent: RelativeIndent) -> String {
498+
fn indent_of_string(s: &str) -> usize {
499+
s.find(|c: char| !c.is_whitespace()).unwrap_or(0)
500+
}
501+
502+
let mut indent = 0;
503+
if let Some(line) = s.lines().nth(usize::from(ignore_first)) {
504+
let line_indent = indent_of_string(line);
505+
indent = line_indent + relative_indent;
506+
}
507+
508+
reindent_multiline(s, ignore_first, Some(indent))
509+
}
510+
479511
/// Reindent a multiline string with possibility of ignoring the first line.
480512
pub fn reindent_multiline(s: &str, ignore_first: bool, indent: Option<usize>) -> String {
481513
let s_space = reindent_multiline_inner(s, ignore_first, indent, ' ');

tests/ui/match_single_binding.fixed

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ fn main() {
3030
let b = 2;
3131
let c = 3;
3232
// Lint
33-
let (x, y, z) = (a, b, c);
3433
{
34+
let (x, y, z) = (a, b, c);
3535
println!("{} {} {}", x, y, z);
3636
}
3737
// Lint
38-
let (x, y, z) = (a, b, c);
39-
println!("{} {} {}", x, y, z);
38+
{
39+
let (x, y, z) = (a, b, c);
40+
println!("{} {} {}", x, y, z);
41+
}
4042
// Ok
4143
foo!(a);
4244
// Ok
@@ -66,19 +68,27 @@ fn main() {
6668
}
6769
// Lint
6870
let p = Point { x: 0, y: 7 };
69-
let Point { x, y } = p;
70-
println!("Coords: ({}, {})", x, y);
71+
{
72+
let Point { x, y } = p;
73+
println!("Coords: ({}, {})", x, y);
74+
}
7175
// Lint
72-
let Point { x: x1, y: y1 } = p;
73-
println!("Coords: ({}, {})", x1, y1);
76+
{
77+
let Point { x: x1, y: y1 } = p;
78+
println!("Coords: ({}, {})", x1, y1);
79+
}
7480
// Lint
7581
let x = 5;
76-
let ref r = x;
77-
println!("Got a reference to {}", r);
82+
{
83+
let ref r = x;
84+
println!("Got a reference to {}", r);
85+
}
7886
// Lint
7987
let mut x = 5;
80-
let ref mut mr = x;
81-
println!("Got a mutable reference to {}", mr);
88+
{
89+
let ref mut mr = x;
90+
println!("Got a mutable reference to {}", mr);
91+
}
8292
// Lint
8393
let Point { x, y } = coords();
8494
let product = x * y;
@@ -120,10 +130,12 @@ fn main() {
120130
fn issue_8723() {
121131
let (mut val, idx) = ("a b", 1);
122132

123-
let (pre, suf) = val.split_at(idx);
124-
val = {
125-
println!("{}", pre);
126-
suf
133+
{
134+
let (pre, suf) = val.split_at(idx);
135+
val = {
136+
println!("{}", pre);
137+
suf
138+
}
127139
};
128140

129141
let _ = val;
@@ -139,14 +151,16 @@ fn issue_9575() {
139151
}
140152

141153
fn issue_9725(r: Option<u32>) {
142-
let x = r;
143-
match x {
144-
Some(_) => {
145-
println!("Some");
146-
},
147-
None => {
148-
println!("None");
149-
},
154+
{
155+
let x = r;
156+
match x {
157+
Some(_) => {
158+
println!("Some");
159+
},
160+
None => {
161+
println!("None");
162+
},
163+
}
150164
};
151165
}
152166

@@ -181,8 +195,10 @@ fn issue14634() {
181195
dbg!(3);
182196
println!("here");
183197
//~^^^ match_single_binding
184-
let id!(a) = dbg!(3);
185-
println!("found {a}");
198+
{
199+
let id!(a) = dbg!(3);
200+
println!("found {a}");
201+
}
186202
//~^^^ match_single_binding
187203
let id!(b) = dbg!(3);
188204
let id!(_a) = dbg!(b + 1);
@@ -204,3 +220,12 @@ mod issue14991 {
204220
}],
205221
}
206222
}
223+
224+
fn issue15018() {
225+
let a = 10;
226+
{
227+
let a = 11;
228+
println!("a = {a}")
229+
};
230+
println!("a = {a}");
231+
}

tests/ui/match_single_binding.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,12 @@ mod issue14991 {
267267
}],
268268
}
269269
}
270+
271+
fn issue15018() {
272+
let a = 10;
273+
match 11 {
274+
//~^ match_single_binding
275+
a => println!("a = {a}"),
276+
};
277+
println!("a = {a}");
278+
}

0 commit comments

Comments
 (0)