Skip to content

Commit c38698e

Browse files
committed
more tests, support debug_assert!()
1 parent 0a96a61 commit c38698e

3 files changed

Lines changed: 73 additions & 30 deletions

File tree

clippy_lints/src/assert_multiple.rs

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use clippy_utils::diagnostics::span_lint_and_sugg;
1+
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::macros::{find_assert_args, root_macro_call_first_node};
33
use clippy_utils::source::snippet;
44
use rustc_errors::Applicability;
@@ -22,22 +22,20 @@ declare_clippy_lint! {
2222
/// ```no_run
2323
/// assert_eq!(a, b);
2424
/// assert_ne!(c,d);
25-
/// ...
25+
/// /* ... */
2626
/// ```
2727
#[clippy::version = "1.95.0"]
2828
pub ASSERT_MULTIPLE,
2929
nursery,
30-
"Splitting an assert using && into separate asserts makes it clearer which is failing."
30+
"Splitting an assert using '&&' into separate asserts makes it clearer which is failing."
3131
}
3232

3333
declare_lint_pass!(AssertMultiple => [ASSERT_MULTIPLE]);
3434

35-
// the visitor needs a mutable reference to a vector that lives
36-
// only for the duration of a single `check_expr` invocation. we
37-
// therefore introduce a separate lifetime `'v` for that borrow.
3835
struct AssertVisitor<'tcx, 'v> {
3936
cx: &'v LateContext<'tcx>,
4037
suggests: Vec<String>,
38+
assert_string: String,
4139
}
4240

4341
impl<'tcx> Visitor<'tcx> for AssertVisitor<'tcx, '_> {
@@ -48,15 +46,19 @@ impl<'tcx> Visitor<'tcx> for AssertVisitor<'tcx, '_> {
4846
rustc_hir::intravisit::walk_expr(self, lhs);
4947
rustc_hir::intravisit::walk_expr(self, rhs);
5048
},
49+
BinOpKind::Or => {
50+
let tmpstr = format!("{}!{};", self.assert_string, snippet(self.cx, e.span, ".."));
51+
self.suggests.push(tmpstr);
52+
},
5153
_ => {
52-
if let Some(x) = assert_from_op(self.cx, op.node, *lhs, *rhs) {
54+
if let Some(x) = assert_from_op(self, op.node, *lhs, *rhs) {
5355
self.suggests.push(x);
5456
}
5557
},
5658
},
5759
ExprKind::Call(_call, _args) => {
5860
let tmptxt = snippet(self.cx, e.span, "..");
59-
let tmpassrt = format!("assert!({tmptxt});");
61+
let tmpassrt = format!("{}!({tmptxt});", self.assert_string);
6062
self.suggests.push(tmpassrt);
6163
},
6264

@@ -75,44 +77,64 @@ impl<'tcx> Visitor<'tcx> for AssertVisitor<'tcx, '_> {
7577
impl<'tcx> LateLintPass<'tcx> for AssertMultiple {
7678
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) {
7779
if let Some(macro_call) = root_macro_call_first_node(cx, e)
78-
&& matches!(cx.tcx.get_diagnostic_name(macro_call.def_id), Some(sym::assert_macro))
80+
&& matches!(
81+
cx.tcx.get_diagnostic_name(macro_call.def_id),
82+
Some(sym::assert_macro | sym::debug_assert_macro)
83+
)
7984
&& let Some((condition, _)) = find_assert_args(cx, e, macro_call.expn)
8085
&& matches!(condition.kind, ExprKind::Binary(binop,_lhs,_rhs) if binop.node == BinOpKind::And)
8186
{
8287
let mut am_visitor = AssertVisitor {
8388
cx,
8489
suggests: Vec::new(),
90+
assert_string: if matches!(
91+
cx.tcx.get_diagnostic_name(macro_call.def_id),
92+
Some(sym::debug_assert_macro)
93+
) {
94+
"debug_assert".to_string()
95+
} else {
96+
"assert".to_string()
97+
},
8598
};
8699
rustc_hir::intravisit::walk_expr(&mut am_visitor, condition);
87100

88101
if !am_visitor.suggests.is_empty() {
89-
// build the suggestion string outside of the closure to avoid
90-
// borrowing `suggests` while the diag closure runs
91-
let text = am_visitor.suggests.join("\n");
92-
let applicability = Applicability::MaybeIncorrect;
93-
span_lint_and_sugg(
102+
span_lint_and_then(
94103
cx,
95104
ASSERT_MULTIPLE,
96105
e.span,
97-
"Multiple asserts combined into one",
98-
"consider writing",
99-
text.clone(),
100-
applicability,
106+
"multiple asserts combined into one",
107+
|diag| {
108+
diag.span_suggestion(
109+
e.span,
110+
"consider writing",
111+
am_visitor.suggests.join("\n"),
112+
Applicability::MaybeIncorrect,
113+
);
114+
},
101115
);
102116
}
103117
}
104118
}
105119
}
106120

107-
fn assert_from_op(cx: &LateContext<'_>, node: BinOpKind, lhs: Expr<'_>, rhs: Expr<'_>) -> Option<String> {
108-
let lhs_name = snippet(cx, lhs.span, "..");
109-
let rhs_name = snippet(cx, rhs.span, "..");
121+
fn assert_from_op(
122+
visitor: &mut AssertVisitor<'_, '_>,
123+
node: BinOpKind,
124+
lhs: Expr<'_>,
125+
rhs: Expr<'_>,
126+
) -> Option<String> {
127+
let cx = visitor.cx;
128+
let lhs_name = snippet(cx, lhs.span, "_");
129+
let rhs_name = snippet(cx, rhs.span, "_");
110130
match node {
111-
BinOpKind::Eq => Some(format!("assert_eq!({lhs_name}, {rhs_name});")),
112-
BinOpKind::Ne => Some(format!("assert_ne!({lhs_name}, {rhs_name});")),
113-
BinOpKind::Ge | BinOpKind::Gt | BinOpKind::Le | BinOpKind::Lt => {
114-
Some(format!("assert!({lhs_name} {} {rhs_name})", node.as_str()))
115-
},
131+
BinOpKind::Eq => Some(format!("{}_eq!({lhs_name}, {rhs_name});", visitor.assert_string)),
132+
BinOpKind::Ne => Some(format!("{}_ne!({lhs_name}, {rhs_name});", visitor.assert_string)),
133+
BinOpKind::Ge | BinOpKind::Gt | BinOpKind::Le | BinOpKind::Lt => Some(format!(
134+
"{}!({lhs_name} {} {rhs_name})",
135+
visitor.assert_string,
136+
node.as_str()
137+
)),
116138
_ => None,
117139
}
118140
}

tests/ui/assert_multiple.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,13 @@ fn main() {
4242
assert!(o == Vals::Owned || b == Vals::Other);
4343
assert!(o == Vals::Owned && b == Vals::Other);
4444
//~^ assert_multiple
45+
46+
debug_assert!(o == b);
47+
debug_assert!(o == b && other == Vals::Other);
48+
//~^ assert_multiple
49+
50+
assert!(o == b && (o == Vals::Owned || b == Vals::Other));
51+
//~^ assert_multiple
52+
// Next one we cannot split because of Or.
53+
assert!((o == b && o == Vals::Owned) || b == Vals::Other);
4554
}

tests/ui/assert_multiple.stderr

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: Multiple asserts combined into one
1+
error: multiple asserts combined into one
22
--> tests/ui/assert_multiple.rs:37:5
33
|
44
LL | assert!(myfunc1(1, "foo".to_string()) && b == Vals::Borrowed);
@@ -7,17 +7,29 @@ LL | assert!(myfunc1(1, "foo".to_string()) && b == Vals::Borrowed);
77
= note: `-D clippy::assert-multiple` implied by `-D warnings`
88
= help: to override `-D warnings` add `#[allow(clippy::assert_multiple)]`
99

10-
error: Multiple asserts combined into one
10+
error: multiple asserts combined into one
1111
--> tests/ui/assert_multiple.rs:40:5
1212
|
1313
LL | assert!(ms.myfunc(1, "foo".to_string()) && myfunc1(2, "bar".to_string()));
1414
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1515

16-
error: Multiple asserts combined into one
16+
error: multiple asserts combined into one
1717
--> tests/ui/assert_multiple.rs:43:5
1818
|
1919
LL | assert!(o == Vals::Owned && b == Vals::Other);
2020
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2121

22-
error: aborting due to 3 previous errors
22+
error: multiple asserts combined into one
23+
--> tests/ui/assert_multiple.rs:47:5
24+
|
25+
LL | debug_assert!(o == b && other == Vals::Other);
26+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27+
28+
error: multiple asserts combined into one
29+
--> tests/ui/assert_multiple.rs:50:5
30+
|
31+
LL | assert!(o == b && (o == Vals::Owned || b == Vals::Other));
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33+
34+
error: aborting due to 5 previous errors
2335

0 commit comments

Comments
 (0)