Skip to content

Commit cd7027c

Browse files
committed
change to a structure for parsing assert macro
1 parent 8669d38 commit cd7027c

7 files changed

+307
-102
lines changed

clippy_lints/src/bool_assert_comparison.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use clippy_utils::{diagnostics::span_lint_and_sugg, higher, is_direct_expn_of, sugg::Sugg, ty::implements_trait};
1+
use clippy_utils::{
2+
diagnostics::span_lint_and_sugg, higher::AssertExpn, is_direct_expn_of, sugg::Sugg, ty::implements_trait,
3+
};
24
use rustc_ast::ast::LitKind;
35
use rustc_errors::Applicability;
46
use rustc_hir::{Expr, ExprKind, Lit};
@@ -79,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
7981
.chain(inverted_macros.iter().map(|el| (el, false)))
8082
{
8183
if let Some(span) = is_direct_expn_of(expr.span, mac) {
82-
if let Some(args) = higher::extract_assert_macro_args(expr) {
84+
if let Some(args) = AssertExpn::parse(expr).map(|v| v.argument_vector()) {
8385
if let [a, b, ref fmt_args @ ..] = args[..] {
8486
let (lit_value, other_expr) = match (bool_lit(a), bool_lit(b)) {
8587
(Some(lit), None) => (lit, b),
@@ -101,7 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
101103
}
102104

103105
let non_eq_mac = &mac[..mac.len() - 3];
104-
let mut applicability = Applicability::MaybeIncorrect;
106+
let mut applicability = Applicability::MachineApplicable;
105107
let sugg = Sugg::hir_with_applicability(cx, other_expr, "..", &mut applicability);
106108
let expr_string = if lit_value ^ is_eq {
107109
format!("!{}", sugg.maybe_par())
@@ -130,9 +132,9 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
130132
},
131133
};
132134
let suggestion = if let Some(spans) = arg_span {
133-
format!("{}!({}, {})", non_eq_mac, expr_string, spans)
135+
format!("{}!({}, {});", non_eq_mac, expr_string, spans)
134136
} else {
135-
format!("{}!({})", non_eq_mac, expr_string)
137+
format!("{}!({});", non_eq_mac, expr_string)
136138
};
137139
span_lint_and_sugg(
138140
cx,
@@ -141,7 +143,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
141143
&format!("used `{}!` with a literal bool", mac),
142144
"replace it with",
143145
suggestion,
144-
Applicability::MaybeIncorrect,
146+
applicability,
145147
);
146148
return;
147149
}

clippy_lints/src/eq_op.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then};
22
use clippy_utils::source::snippet;
33
use clippy_utils::ty::{implements_trait, is_copy};
4-
use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, in_macro, is_expn_of};
4+
use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher::AssertExpn, in_macro, is_expn_of};
55
use if_chain::if_chain;
66
use rustc_errors::Applicability;
77
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, StmtKind};
@@ -77,7 +77,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
7777
if_chain! {
7878
if is_expn_of(stmt.span, amn).is_some();
7979
if let StmtKind::Semi(matchexpr) = stmt.kind;
80-
if let Some(macro_args) = higher::extract_assert_macro_args(matchexpr);
80+
if let Some(macro_args) = AssertExpn::parse(matchexpr).map(|v| v.argument_vector());
8181
if macro_args.len() == 2;
8282
let (lhs, rhs) = (macro_args[0], macro_args[1]);
8383
if eq_expr_value(cx, lhs, rhs);

clippy_lints/src/mutable_debug_assertion.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint;
2-
use clippy_utils::{higher, is_direct_expn_of};
2+
use clippy_utils::{higher::AssertExpn, is_direct_expn_of};
33
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
44
use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
55
use rustc_lint::{LateContext, LateLintPass};
@@ -39,7 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
3939
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
4040
for dmn in &DEBUG_MACRO_NAMES {
4141
if is_direct_expn_of(e.span, dmn).is_some() {
42-
if let Some(macro_args) = higher::extract_assert_macro_args(e) {
42+
if let Some(macro_args) = AssertExpn::parse(e).map(|v| v.argument_vector()) {
4343
for arg in macro_args {
4444
let mut visitor = MutArgVisitor::new(cx);
4545
visitor.visit_expr(arg);

clippy_utils/src/higher.rs

+85-40
Original file line numberDiff line numberDiff line change
@@ -416,24 +416,73 @@ pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
416416
}
417417
}
418418

419-
/// Extract args from an assert-like macro.
420-
/// Currently working with:
421-
/// - `assert!`, `assert_eq!` and `assert_ne!`
422-
/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
423-
/// For example:
424-
/// `assert!(expr)` will return `Some([expr])`
425-
/// `debug_assert_eq!(a, b)` will return `Some([a, b])`
426-
pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
419+
/// A parsed
420+
/// assert!`, `assert_eq!` or `assert_ne!`,
421+
/// debug_assert!`, `debug_assert_eq!` or `debug_assert_ne!`
422+
/// macro.
423+
pub struct AssertExpn<'tcx> {
424+
/// the first agrument of the assret e.g. `var` in element `assert!(var, ...)`
425+
pub first_assert_argument: &'tcx Expr<'tcx>,
426+
/// second argument of the asset for case `assert_eq!`,
427+
/// `assert_ne!` etc ... Eg var_2 in `debug_assert_eq!(x, var_2,..)`
428+
pub second_assert_argument: Option<&'tcx Expr<'tcx>>,
429+
/// The format argument passed at the end of the macro
430+
pub format_arg: Option<FormatArgsExpn<'tcx>>,
431+
}
432+
433+
impl<'tcx> AssertExpn<'tcx> {
434+
/// Extract args from an assert-like macro.
435+
/// Currently working with:
436+
/// - `assert!`, `assert_eq!` and `assert_ne!`
437+
/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
438+
/// For example:
439+
/// `assert!(expr)` will return `Some(AssertExpn { first_assert_argument: expr,
440+
/// second_assert_argument: None, format_arg:None })` `debug_assert_eq!(a, b)` will return
441+
/// `Some(AssertExpn { first_assert_argument: a, second_assert_argument: Some(b),
442+
/// format_arg:None })`
443+
pub fn parse(e: &'tcx Expr<'tcx>) -> Option<Self> {
444+
if let ExprKind::Block(block, _) = e.kind {
445+
if block.stmts.len() == 1 {
446+
if let StmtKind::Semi(matchexpr) = block.stmts.get(0)?.kind {
447+
// macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
448+
if_chain! {
449+
if let Some(If { cond, .. }) = If::hir(matchexpr);
450+
if let ExprKind::Unary(UnOp::Not, condition) = cond.kind;
451+
then {
452+
return Some(Self {
453+
first_assert_argument: condition,
454+
second_assert_argument: None,
455+
format_arg: None, // FIXME actually parse the aguments
456+
});
457+
}
458+
}
459+
460+
// debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
461+
if_chain! {
462+
if let ExprKind::Block(matchblock,_) = matchexpr.kind;
463+
if let Some(matchblock_expr) = matchblock.expr;
464+
then {
465+
return Self::ast_matchblock(matchblock_expr);
466+
}
467+
}
468+
}
469+
} else if let Some(matchblock_expr) = block.expr {
470+
// macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
471+
return Self::ast_matchblock(matchblock_expr);
472+
}
473+
}
474+
None
475+
}
476+
427477
/// Try to match the AST for a pattern that contains a match, for example when two args are
428478
/// compared
429-
fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Vec<&Expr<'_>>> {
479+
fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Self> {
430480
if_chain! {
431481
if let ExprKind::Match(headerexpr, arms, _) = &matchblock_expr.kind;
432482
if let ExprKind::Tup([lhs, rhs]) = &headerexpr.kind;
433483
if let ExprKind::AddrOf(BorrowKind::Ref, _, lhs) = lhs.kind;
434484
if let ExprKind::AddrOf(BorrowKind::Ref, _, rhs) = rhs.kind;
435485
then {
436-
let mut vec_arg = vec![lhs, rhs];
437486
if_chain! {
438487
if !arms.is_empty();
439488
if let ExprKind::Block(Block{expr: Some(if_expr),..},_) = arms[0].body.kind;
@@ -444,47 +493,43 @@ pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx
444493
| StmtKind::Semi(call_assert_failed) = stmts_if_block[1].kind;
445494
if let ExprKind::Call(_, args_assert_failed) = call_assert_failed.kind;
446495
if args_assert_failed.len() >= 4;
447-
if let ExprKind::Call(_, args) = args_assert_failed[3].kind;
448-
if !args.is_empty();
449-
if let Some (mut format_arg_expn) = FormatArgsExpn::parse(&args[0]);
496+
if let ExprKind::Call(_, [arg, ..]) = args_assert_failed[3].kind;
497+
if let Some(format_arg_expn) = FormatArgsExpn::parse(&arg);
450498
then {
451-
vec_arg.push(format_arg_expn.format_string);
452-
vec_arg.append(&mut format_arg_expn.value_args);
499+
return Some(AssertExpn {
500+
first_assert_argument: lhs,
501+
second_assert_argument: Some(rhs),
502+
format_arg: Some(format_arg_expn)
503+
});
504+
}
505+
else {
506+
return Some(AssertExpn {
507+
first_assert_argument: lhs,
508+
second_assert_argument:
509+
Some(rhs),
510+
format_arg: None
511+
});
453512
}
454513
}
455-
return Some(vec_arg);
456514
}
457515
}
458516
None
459517
}
460518

461-
if let ExprKind::Block(block, _) = e.kind {
462-
if block.stmts.len() == 1 {
463-
if let StmtKind::Semi(matchexpr) = block.stmts.get(0)?.kind {
464-
// macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
465-
if_chain! {
466-
if let Some(If { cond, .. }) = If::hir(matchexpr);
467-
if let ExprKind::Unary(UnOp::Not, condition) = cond.kind;
468-
then {
469-
return Some(vec![condition]);
470-
}
471-
}
472-
473-
// debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
474-
if_chain! {
475-
if let ExprKind::Block(matchblock,_) = matchexpr.kind;
476-
if let Some(matchblock_expr) = matchblock.expr;
477-
then {
478-
return ast_matchblock(matchblock_expr);
479-
}
480-
}
519+
/// Gives the argument as a vector
520+
pub fn argument_vector(&self) -> Vec<&'tcx Expr<'tcx>> {
521+
let mut expr_vec = vec![self.first_assert_argument];
522+
if let Some(sec_agr) = self.second_assert_argument {
523+
expr_vec.push(sec_agr);
524+
}
525+
if let Some(ref format_arg) = self.format_arg {
526+
expr_vec.push(format_arg.format_string);
527+
for arg in &format_arg.value_args {
528+
expr_vec.push(arg)
481529
}
482-
} else if let Some(matchblock_expr) = block.expr {
483-
// macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
484-
return ast_matchblock(matchblock_expr);
485530
}
531+
expr_vec
486532
}
487-
None
488533
}
489534

490535
/// A parsed `format!` expansion

tests/ui/bool_assert_comparison.fixed

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::bool_assert_comparison)]
4+
5+
use std::ops::Not;
6+
7+
macro_rules! a {
8+
() => {
9+
true
10+
};
11+
}
12+
macro_rules! b {
13+
() => {
14+
true
15+
};
16+
}
17+
18+
// Implements the Not trait but with an output type
19+
// that's not bool. Should not suggest a rewrite
20+
#[derive(Debug, Copy, Clone)]
21+
#[allow(dead_code)]
22+
enum ImplNotTraitWithoutBool {
23+
VariantX(bool),
24+
VariantY(u32),
25+
}
26+
27+
impl PartialEq<bool> for ImplNotTraitWithoutBool {
28+
fn eq(&self, other: &bool) -> bool {
29+
match *self {
30+
ImplNotTraitWithoutBool::VariantX(b) => b == *other,
31+
_ => false,
32+
}
33+
}
34+
}
35+
36+
impl Not for ImplNotTraitWithoutBool {
37+
type Output = Self;
38+
39+
fn not(self) -> Self::Output {
40+
match self {
41+
ImplNotTraitWithoutBool::VariantX(b) => ImplNotTraitWithoutBool::VariantX(!b),
42+
ImplNotTraitWithoutBool::VariantY(0) => ImplNotTraitWithoutBool::VariantY(1),
43+
ImplNotTraitWithoutBool::VariantY(_) => ImplNotTraitWithoutBool::VariantY(0),
44+
}
45+
}
46+
}
47+
48+
// This type implements the Not trait with an Output of
49+
// type bool. Using assert!(..) must be suggested
50+
#[derive(Debug, Clone, Copy)]
51+
struct ImplNotTraitWithBool;
52+
53+
impl PartialEq<bool> for ImplNotTraitWithBool {
54+
fn eq(&self, _other: &bool) -> bool {
55+
false
56+
}
57+
}
58+
59+
impl Not for ImplNotTraitWithBool {
60+
type Output = bool;
61+
62+
fn not(self) -> Self::Output {
63+
true
64+
}
65+
}
66+
67+
fn main() {
68+
let a = ImplNotTraitWithoutBool::VariantX(true);
69+
let b = ImplNotTraitWithBool;
70+
71+
assert_eq!("a".len(), 1);
72+
assert!(!"a".is_empty());
73+
assert!("".is_empty());
74+
assert!("".is_empty());
75+
assert_eq!(a!(), b!());
76+
assert_eq!(a!(), "".is_empty());
77+
assert_eq!("".is_empty(), b!());
78+
assert_eq!(a, true);
79+
assert!(b);
80+
81+
assert_ne!("a".len(), 1);
82+
assert!("a".is_empty());
83+
assert!(!"".is_empty());
84+
assert!(!"".is_empty());
85+
assert_ne!(a!(), b!());
86+
assert_ne!(a!(), "".is_empty());
87+
assert_ne!("".is_empty(), b!());
88+
assert_ne!(a, true);
89+
assert!(!b);
90+
91+
debug_assert_eq!("a".len(), 1);
92+
debug_assert!(!"a".is_empty());
93+
debug_assert!("".is_empty());
94+
debug_assert!("".is_empty());
95+
debug_assert_eq!(a!(), b!());
96+
debug_assert_eq!(a!(), "".is_empty());
97+
debug_assert_eq!("".is_empty(), b!());
98+
debug_assert_eq!(a, true);
99+
debug_assert!(b);
100+
101+
debug_assert_ne!("a".len(), 1);
102+
debug_assert!("a".is_empty());
103+
debug_assert!(!"".is_empty());
104+
debug_assert!(!"".is_empty());
105+
debug_assert_ne!(a!(), b!());
106+
debug_assert_ne!(a!(), "".is_empty());
107+
debug_assert_ne!("".is_empty(), b!());
108+
debug_assert_ne!(a, true);
109+
debug_assert!(!b);
110+
111+
// assert with error messages
112+
assert_eq!("a".len(), 1, "tadam {}", 1);
113+
assert_eq!("a".len(), 1, "tadam {}", true);
114+
assert!(!"a".is_empty(), "tadam {}", 1);
115+
assert!(!"a".is_empty(), "tadam {}", true);
116+
assert!(!"a".is_empty(), "tadam {}", true);
117+
assert_eq!(a, true, "tadam {}", false);
118+
assert!("a".is_empty(), "tadam {} {}", false, 6);
119+
120+
debug_assert_eq!("a".len(), 1, "tadam {}", 1);
121+
debug_assert_eq!("a".len(), 1, "tadam {}", true);
122+
debug_assert!(!"a".is_empty(), "tadam {}", 1);
123+
debug_assert!(!"a".is_empty(), "tadam {}", true);
124+
debug_assert!(!"a".is_empty(), "tadam {}", true);
125+
debug_assert_eq!(a, true, "tadam {}", false);
126+
debug_assert!("a".is_empty(), "tadam {} {}", false, "b");
127+
128+
// Without ; at the end
129+
{
130+
debug_assert!(!"a".is_empty(), "tadam {}", true);
131+
};
132+
{
133+
assert_eq!("a".len(), 1, "tadam {}", 1)
134+
};
135+
{
136+
assert_ne!("a".len(), 1, "tadam {}", true)
137+
};
138+
}

0 commit comments

Comments
 (0)