Skip to content

Commit 7be3a32

Browse files
committed
Auto merge of rust-lang#6843 - Jarcho:match_on_same_arms_macro, r=Manishearth
Compare empty blocks for equality based on tokens fixes: rust-lang#1390 This only considers empty blocks for now, though we should also catch something like this: ```rust match 0 { 0 => { do_something(); trace!(0); 0 } 1 => { do_something(); trace!(1); 1 } x => x, } ``` As far as I can tell there aren't any negative effects on other lints. These blocks only happen to be the same for a given compilation, not all compilations. changelog: Fix `match_on_same_arms` and others. Only consider empty blocks equal if the tokens contained are the same.
2 parents 6680710 + 39c5e86 commit 7be3a32

File tree

4 files changed

+130
-8
lines changed

4 files changed

+130
-8
lines changed

clippy_utils/src/hir_utils.rs

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::consts::{constant_context, constant_simple};
2-
use crate::differing_macro_contexts;
2+
use crate::{differing_macro_contexts, snippet_opt};
33
use rustc_ast::ast::InlineAsmTemplatePiece;
44
use rustc_data_structures::fx::FxHashMap;
55
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
@@ -9,6 +9,7 @@ use rustc_hir::{
99
GenericArg, GenericArgs, Guard, HirId, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path,
1010
PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
1111
};
12+
use rustc_lexer::{tokenize, TokenKind};
1213
use rustc_lint::LateContext;
1314
use rustc_middle::ich::StableHashingContextProvider;
1415
use rustc_middle::ty::TypeckResults;
@@ -110,8 +111,54 @@ impl HirEqInterExpr<'_, '_, '_> {
110111

111112
/// Checks whether two blocks are the same.
112113
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
113-
over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r))
114-
&& both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
114+
match (left.stmts, left.expr, right.stmts, right.expr) {
115+
([], None, [], None) => {
116+
// For empty blocks, check to see if the tokens are equal. This will catch the case where a macro
117+
// expanded to nothing, or the cfg attribute was used.
118+
let (left, right) = match (
119+
snippet_opt(self.inner.cx, left.span),
120+
snippet_opt(self.inner.cx, right.span),
121+
) {
122+
(Some(left), Some(right)) => (left, right),
123+
_ => return true,
124+
};
125+
let mut left_pos = 0;
126+
let left = tokenize(&left)
127+
.map(|t| {
128+
let end = left_pos + t.len;
129+
let s = &left[left_pos..end];
130+
left_pos = end;
131+
(t, s)
132+
})
133+
.filter(|(t, _)| {
134+
!matches!(
135+
t.kind,
136+
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
137+
)
138+
})
139+
.map(|(_, s)| s);
140+
let mut right_pos = 0;
141+
let right = tokenize(&right)
142+
.map(|t| {
143+
let end = right_pos + t.len;
144+
let s = &right[right_pos..end];
145+
right_pos = end;
146+
(t, s)
147+
})
148+
.filter(|(t, _)| {
149+
!matches!(
150+
t.kind,
151+
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
152+
)
153+
})
154+
.map(|(_, s)| s);
155+
left.eq(right)
156+
},
157+
_ => {
158+
over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r))
159+
&& both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
160+
},
161+
}
115162
}
116163

117164
#[allow(clippy::similar_names)]
@@ -131,7 +178,10 @@ impl HirEqInterExpr<'_, '_, '_> {
131178
}
132179
}
133180

134-
let is_eq = match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) {
181+
let is_eq = match (
182+
&reduce_exprkind(self.inner.cx, &left.kind),
183+
&reduce_exprkind(self.inner.cx, &right.kind),
184+
) {
135185
(&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
136186
lb == rb && l_mut == r_mut && self.eq_expr(le, re)
137187
},
@@ -360,11 +410,30 @@ impl HirEqInterExpr<'_, '_, '_> {
360410
}
361411

362412
/// Some simple reductions like `{ return }` => `return`
363-
fn reduce_exprkind<'hir>(kind: &'hir ExprKind<'hir>) -> &ExprKind<'hir> {
413+
fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'hir ExprKind<'hir> {
364414
if let ExprKind::Block(block, _) = kind {
365415
match (block.stmts, block.expr) {
416+
// From an `if let` expression without an `else` block. The arm for the implicit wild pattern is an empty
417+
// block with an empty span.
418+
([], None) if block.span.is_empty() => &ExprKind::Tup(&[]),
366419
// `{}` => `()`
367-
([], None) => &ExprKind::Tup(&[]),
420+
([], None) => match snippet_opt(cx, block.span) {
421+
// Don't reduce if there are any tokens contained in the braces
422+
Some(snip)
423+
if tokenize(&snip)
424+
.map(|t| t.kind)
425+
.filter(|t| {
426+
!matches!(
427+
t,
428+
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
429+
)
430+
})
431+
.ne([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().cloned()) =>
432+
{
433+
kind
434+
},
435+
_ => &ExprKind::Tup(&[]),
436+
},
368437
([], Some(expr)) => match expr.kind {
369438
// `{ return .. }` => `return ..`
370439
ExprKind::Ret(..) => &expr.kind,

clippy_utils/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ extern crate rustc_errors;
1414
extern crate rustc_hir;
1515
extern crate rustc_hir_pretty;
1616
extern crate rustc_infer;
17+
extern crate rustc_lexer;
1718
extern crate rustc_lint;
1819
extern crate rustc_middle;
1920
extern crate rustc_mir;

tests/ui/match_same_arms2.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,35 @@ fn match_same_arms() {
120120
},
121121
}
122122

123+
// False positive #1390
124+
macro_rules! empty {
125+
($e:expr) => {};
126+
}
127+
match 0 {
128+
0 => {
129+
empty!(0);
130+
},
131+
1 => {
132+
empty!(1);
133+
},
134+
x => {
135+
empty!(x);
136+
},
137+
};
138+
139+
// still lint if the tokens are the same
140+
match 0 {
141+
0 => {
142+
empty!(0);
143+
},
144+
1 => {
145+
empty!(0);
146+
},
147+
x => {
148+
empty!(x);
149+
},
150+
}
151+
123152
match_expr_like_matches_macro_priority();
124153
}
125154

tests/ui/match_same_arms2.stderr

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,31 @@ LL | Ok(3) => println!("ok"),
141141
| ^^^^^
142142
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
143143

144+
error: this `match` has identical arm bodies
145+
--> $DIR/match_same_arms2.rs:144:14
146+
|
147+
LL | 1 => {
148+
| ______________^
149+
LL | | empty!(0);
150+
LL | | },
151+
| |_________^
152+
|
153+
note: same as this
154+
--> $DIR/match_same_arms2.rs:141:14
155+
|
156+
LL | 0 => {
157+
| ______________^
158+
LL | | empty!(0);
159+
LL | | },
160+
| |_________^
161+
help: consider refactoring into `0 | 1`
162+
--> $DIR/match_same_arms2.rs:141:9
163+
|
164+
LL | 0 => {
165+
| ^
166+
144167
error: match expression looks like `matches!` macro
145-
--> $DIR/match_same_arms2.rs:133:16
168+
--> $DIR/match_same_arms2.rs:162:16
146169
|
147170
LL | let _ans = match x {
148171
| ________________^
@@ -154,5 +177,5 @@ LL | | };
154177
|
155178
= note: `-D clippy::match-like-matches-macro` implied by `-D warnings`
156179

157-
error: aborting due to 8 previous errors
180+
error: aborting due to 9 previous errors
158181

0 commit comments

Comments
 (0)