Skip to content

Commit eaffd0e

Browse files
committed
Auto merge of #6167 - ThibsG:IdenticalArgumentsAssertEq3574, r=ebroto
Identical arguments on assert macro family Lint when identical args are used on `assert_eq!`, `debug_assert_eq!`, `assert_ne!` and `debug_assert_ne!` macros. Added to the lint `eq_op`. Common functions added to `utils/higher.rs` Fixes: #3574 Fixes: #4694 changelog: Lint on identical args when calling `assert_eq!`, `debug_assert_eq!`, `assert_ne!` and `debug_assert_ne!` macros
2 parents 74530ad + 16b5f37 commit eaffd0e

File tree

8 files changed

+254
-59
lines changed

8 files changed

+254
-59
lines changed

Diff for: clippy_lints/src/eq_op.rs

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::utils::{
2-
eq_expr_value, implements_trait, in_macro, is_copy, multispan_sugg, snippet, span_lint, span_lint_and_then,
2+
eq_expr_value, higher, implements_trait, in_macro, is_copy, is_expn_of, multispan_sugg, snippet, span_lint,
3+
span_lint_and_then,
34
};
5+
use if_chain::if_chain;
46
use rustc_errors::Applicability;
5-
use rustc_hir::{BinOp, BinOpKind, BorrowKind, Expr, ExprKind};
7+
use rustc_hir::{BinOp, BinOpKind, BorrowKind, Expr, ExprKind, StmtKind};
68
use rustc_lint::{LateContext, LateLintPass};
79
use rustc_session::{declare_lint_pass, declare_tool_lint};
810

@@ -23,6 +25,12 @@ declare_clippy_lint! {
2325
/// # let x = 1;
2426
/// if x + 1 == x + 1 {}
2527
/// ```
28+
/// or
29+
/// ```rust
30+
/// # let a = 3;
31+
/// # let b = 4;
32+
/// assert_eq!(a, a);
33+
/// ```
2634
pub EQ_OP,
2735
correctness,
2836
"equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)"
@@ -52,9 +60,34 @@ declare_clippy_lint! {
5260

5361
declare_lint_pass!(EqOp => [EQ_OP, OP_REF]);
5462

63+
const ASSERT_MACRO_NAMES: [&str; 4] = ["assert_eq", "assert_ne", "debug_assert_eq", "debug_assert_ne"];
64+
5565
impl<'tcx> LateLintPass<'tcx> for EqOp {
5666
#[allow(clippy::similar_names, clippy::too_many_lines)]
5767
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
68+
if let ExprKind::Block(ref block, _) = e.kind {
69+
for stmt in block.stmts {
70+
for amn in &ASSERT_MACRO_NAMES {
71+
if_chain! {
72+
if is_expn_of(stmt.span, amn).is_some();
73+
if let StmtKind::Semi(ref matchexpr) = stmt.kind;
74+
if let Some(macro_args) = higher::extract_assert_macro_args(matchexpr);
75+
if macro_args.len() == 2;
76+
let (lhs, rhs) = (macro_args[0], macro_args[1]);
77+
if eq_expr_value(cx, lhs, rhs);
78+
79+
then {
80+
span_lint(
81+
cx,
82+
EQ_OP,
83+
lhs.span.to(rhs.span),
84+
&format!("identical args used in this `{}!` macro call", amn),
85+
);
86+
}
87+
}
88+
}
89+
}
90+
}
5891
if let ExprKind::Binary(op, ref left, ref right) = e.kind {
5992
if e.span.from_expansion() {
6093
return;

Diff for: clippy_lints/src/mutable_debug_assertion.rs

+11-55
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use crate::utils::{is_direct_expn_of, span_lint};
2-
use if_chain::if_chain;
1+
use crate::utils::{higher, is_direct_expn_of, span_lint};
32
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
4-
use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability, StmtKind, UnOp};
3+
use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
54
use rustc_lint::{LateContext, LateLintPass};
65
use rustc_middle::hir::map::Map;
76
use rustc_middle::ty;
@@ -39,66 +38,23 @@ impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
3938
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
4039
for dmn in &DEBUG_MACRO_NAMES {
4140
if is_direct_expn_of(e.span, dmn).is_some() {
42-
if let Some(span) = extract_call(cx, e) {
43-
span_lint(
44-
cx,
45-
DEBUG_ASSERT_WITH_MUT_CALL,
46-
span,
47-
&format!("do not call a function with mutable arguments inside of `{}!`", dmn),
48-
);
49-
}
50-
}
51-
}
52-
}
53-
}
54-
55-
//HACK(hellow554): remove this when #4694 is implemented
56-
fn extract_call<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<Span> {
57-
if_chain! {
58-
if let ExprKind::Block(ref block, _) = e.kind;
59-
if block.stmts.len() == 1;
60-
if let StmtKind::Semi(ref matchexpr) = block.stmts[0].kind;
61-
then {
62-
// debug_assert
63-
if_chain! {
64-
if let ExprKind::Match(ref ifclause, _, _) = matchexpr.kind;
65-
if let ExprKind::DropTemps(ref droptmp) = ifclause.kind;
66-
if let ExprKind::Unary(UnOp::UnNot, ref condition) = droptmp.kind;
67-
then {
68-
let mut visitor = MutArgVisitor::new(cx);
69-
visitor.visit_expr(condition);
70-
return visitor.expr_span();
71-
}
72-
}
73-
74-
// debug_assert_{eq,ne}
75-
if_chain! {
76-
if let ExprKind::Block(ref matchblock, _) = matchexpr.kind;
77-
if let Some(ref matchheader) = matchblock.expr;
78-
if let ExprKind::Match(ref headerexpr, _, _) = matchheader.kind;
79-
if let ExprKind::Tup(ref conditions) = headerexpr.kind;
80-
if conditions.len() == 2;
81-
then {
82-
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref lhs) = conditions[0].kind {
41+
if let Some(macro_args) = higher::extract_assert_macro_args(e) {
42+
for arg in macro_args {
8343
let mut visitor = MutArgVisitor::new(cx);
84-
visitor.visit_expr(lhs);
44+
visitor.visit_expr(arg);
8545
if let Some(span) = visitor.expr_span() {
86-
return Some(span);
87-
}
88-
}
89-
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref rhs) = conditions[1].kind {
90-
let mut visitor = MutArgVisitor::new(cx);
91-
visitor.visit_expr(rhs);
92-
if let Some(span) = visitor.expr_span() {
93-
return Some(span);
46+
span_lint(
47+
cx,
48+
DEBUG_ASSERT_WITH_MUT_CALL,
49+
span,
50+
&format!("do not call a function with mutable arguments inside of `{}!`", dmn),
51+
);
9452
}
9553
}
9654
}
9755
}
9856
}
9957
}
100-
101-
None
10258
}
10359

10460
struct MutArgVisitor<'a, 'tcx> {

Diff for: clippy_lints/src/utils/higher.rs

+54
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::utils::{is_expn_of, match_def_path, paths};
77
use if_chain::if_chain;
88
use rustc_ast::ast;
99
use rustc_hir as hir;
10+
use rustc_hir::{BorrowKind, Expr, ExprKind, StmtKind, UnOp};
1011
use rustc_lint::LateContext;
1112

1213
/// Converts a hir binary operator to the corresponding `ast` type.
@@ -241,3 +242,56 @@ pub fn vec_macro<'e>(cx: &LateContext<'_>, expr: &'e hir::Expr<'_>) -> Option<Ve
241242

242243
None
243244
}
245+
246+
/// Extract args from an assert-like macro.
247+
/// Currently working with:
248+
/// - `assert!`, `assert_eq!` and `assert_ne!`
249+
/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
250+
/// For example:
251+
/// `assert!(expr)` will return Some([expr])
252+
/// `debug_assert_eq!(a, b)` will return Some([a, b])
253+
pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
254+
/// Try to match the AST for a pattern that contains a match, for example when two args are
255+
/// compared
256+
fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Vec<&Expr<'_>>> {
257+
if_chain! {
258+
if let ExprKind::Match(ref headerexpr, _, _) = &matchblock_expr.kind;
259+
if let ExprKind::Tup([lhs, rhs]) = &headerexpr.kind;
260+
if let ExprKind::AddrOf(BorrowKind::Ref, _, lhs) = lhs.kind;
261+
if let ExprKind::AddrOf(BorrowKind::Ref, _, rhs) = rhs.kind;
262+
then {
263+
return Some(vec![lhs, rhs]);
264+
}
265+
}
266+
None
267+
}
268+
269+
if let ExprKind::Block(ref block, _) = e.kind {
270+
if block.stmts.len() == 1 {
271+
if let StmtKind::Semi(ref matchexpr) = block.stmts[0].kind {
272+
// macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
273+
if_chain! {
274+
if let ExprKind::Match(ref ifclause, _, _) = matchexpr.kind;
275+
if let ExprKind::DropTemps(ref droptmp) = ifclause.kind;
276+
if let ExprKind::Unary(UnOp::UnNot, condition) = droptmp.kind;
277+
then {
278+
return Some(vec![condition]);
279+
}
280+
}
281+
282+
// debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
283+
if_chain! {
284+
if let ExprKind::Block(ref matchblock,_) = matchexpr.kind;
285+
if let Some(ref matchblock_expr) = matchblock.expr;
286+
then {
287+
return ast_matchblock(matchblock_expr);
288+
}
289+
}
290+
}
291+
} else if let Some(matchblock_expr) = block.expr {
292+
// macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
293+
return ast_matchblock(&matchblock_expr);
294+
}
295+
}
296+
None
297+
}

Diff for: tests/ui/auxiliary/proc_macro_derive.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#![crate_type = "proc-macro"]
55
#![feature(repr128, proc_macro_quote)]
6+
#![allow(clippy::eq_op)]
67

78
extern crate proc_macro;
89

Diff for: tests/ui/double_parens.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::double_parens)]
2-
#![allow(dead_code)]
2+
#![allow(dead_code, clippy::eq_op)]
33
#![feature(custom_inner_attributes)]
44
#![rustfmt::skip]
55

Diff for: tests/ui/eq_op_macros.rs

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#![warn(clippy::eq_op)]
2+
3+
// lint also in macro definition
4+
macro_rules! assert_in_macro_def {
5+
() => {
6+
let a = 42;
7+
assert_eq!(a, a);
8+
assert_ne!(a, a);
9+
debug_assert_eq!(a, a);
10+
debug_assert_ne!(a, a);
11+
};
12+
}
13+
14+
// lint identical args in assert-like macro invocations (see #3574)
15+
fn main() {
16+
assert_in_macro_def!();
17+
18+
let a = 1;
19+
let b = 2;
20+
21+
// lint identical args in `assert_eq!`
22+
assert_eq!(a, a);
23+
assert_eq!(a + 1, a + 1);
24+
// ok
25+
assert_eq!(a, b);
26+
assert_eq!(a, a + 1);
27+
assert_eq!(a + 1, b + 1);
28+
29+
// lint identical args in `assert_ne!`
30+
assert_ne!(a, a);
31+
assert_ne!(a + 1, a + 1);
32+
// ok
33+
assert_ne!(a, b);
34+
assert_ne!(a, a + 1);
35+
assert_ne!(a + 1, b + 1);
36+
37+
// lint identical args in `debug_assert_eq!`
38+
debug_assert_eq!(a, a);
39+
debug_assert_eq!(a + 1, a + 1);
40+
// ok
41+
debug_assert_eq!(a, b);
42+
debug_assert_eq!(a, a + 1);
43+
debug_assert_eq!(a + 1, b + 1);
44+
45+
// lint identical args in `debug_assert_ne!`
46+
debug_assert_ne!(a, a);
47+
debug_assert_ne!(a + 1, a + 1);
48+
// ok
49+
debug_assert_ne!(a, b);
50+
debug_assert_ne!(a, a + 1);
51+
debug_assert_ne!(a + 1, b + 1);
52+
53+
let my_vec = vec![1; 5];
54+
let mut my_iter = my_vec.iter();
55+
assert_ne!(my_iter.next(), my_iter.next());
56+
}

Diff for: tests/ui/eq_op_macros.stderr

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
error: identical args used in this `assert_eq!` macro call
2+
--> $DIR/eq_op_macros.rs:7:20
3+
|
4+
LL | assert_eq!(a, a);
5+
| ^^^^
6+
...
7+
LL | assert_in_macro_def!();
8+
| ----------------------- in this macro invocation
9+
|
10+
= note: `-D clippy::eq-op` implied by `-D warnings`
11+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
12+
13+
error: identical args used in this `assert_ne!` macro call
14+
--> $DIR/eq_op_macros.rs:8:20
15+
|
16+
LL | assert_ne!(a, a);
17+
| ^^^^
18+
...
19+
LL | assert_in_macro_def!();
20+
| ----------------------- in this macro invocation
21+
|
22+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
23+
24+
error: identical args used in this `assert_eq!` macro call
25+
--> $DIR/eq_op_macros.rs:22:16
26+
|
27+
LL | assert_eq!(a, a);
28+
| ^^^^
29+
30+
error: identical args used in this `assert_eq!` macro call
31+
--> $DIR/eq_op_macros.rs:23:16
32+
|
33+
LL | assert_eq!(a + 1, a + 1);
34+
| ^^^^^^^^^^^^
35+
36+
error: identical args used in this `assert_ne!` macro call
37+
--> $DIR/eq_op_macros.rs:30:16
38+
|
39+
LL | assert_ne!(a, a);
40+
| ^^^^
41+
42+
error: identical args used in this `assert_ne!` macro call
43+
--> $DIR/eq_op_macros.rs:31:16
44+
|
45+
LL | assert_ne!(a + 1, a + 1);
46+
| ^^^^^^^^^^^^
47+
48+
error: identical args used in this `debug_assert_eq!` macro call
49+
--> $DIR/eq_op_macros.rs:9:26
50+
|
51+
LL | debug_assert_eq!(a, a);
52+
| ^^^^
53+
...
54+
LL | assert_in_macro_def!();
55+
| ----------------------- in this macro invocation
56+
|
57+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
58+
59+
error: identical args used in this `debug_assert_ne!` macro call
60+
--> $DIR/eq_op_macros.rs:10:26
61+
|
62+
LL | debug_assert_ne!(a, a);
63+
| ^^^^
64+
...
65+
LL | assert_in_macro_def!();
66+
| ----------------------- in this macro invocation
67+
|
68+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
69+
70+
error: identical args used in this `debug_assert_eq!` macro call
71+
--> $DIR/eq_op_macros.rs:38:22
72+
|
73+
LL | debug_assert_eq!(a, a);
74+
| ^^^^
75+
76+
error: identical args used in this `debug_assert_eq!` macro call
77+
--> $DIR/eq_op_macros.rs:39:22
78+
|
79+
LL | debug_assert_eq!(a + 1, a + 1);
80+
| ^^^^^^^^^^^^
81+
82+
error: identical args used in this `debug_assert_ne!` macro call
83+
--> $DIR/eq_op_macros.rs:46:22
84+
|
85+
LL | debug_assert_ne!(a, a);
86+
| ^^^^
87+
88+
error: identical args used in this `debug_assert_ne!` macro call
89+
--> $DIR/eq_op_macros.rs:47:22
90+
|
91+
LL | debug_assert_ne!(a + 1, a + 1);
92+
| ^^^^^^^^^^^^
93+
94+
error: aborting due to 12 previous errors
95+

Diff for: tests/ui/used_underscore_binding.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#![feature(rustc_private)]
55
#![warn(clippy::all)]
6-
#![allow(clippy::blacklisted_name)]
6+
#![allow(clippy::blacklisted_name, clippy::eq_op)]
77
#![warn(clippy::used_underscore_binding)]
88

99
#[macro_use]

0 commit comments

Comments
 (0)