Skip to content

Commit cbb0039

Browse files
Switch from macro_expansion to early pass
1 parent 47fdfc8 commit cbb0039

File tree

5 files changed

+194
-134
lines changed

5 files changed

+194
-134
lines changed
Lines changed: 34 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::source::snippet;
3-
use rustc_ast::ast::{MacArgs, MacCall};
4-
use rustc_ast::token::{Token, TokenKind};
5-
use rustc_ast::tokenstream::TokenTree;
2+
use clippy_utils::{ast_utils, is_direct_expn_of};
3+
use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
64
use rustc_errors::Applicability;
75
use rustc_lint::{EarlyContext, EarlyLintPass};
86
use rustc_session::{declare_lint_pass, declare_tool_lint};
9-
use rustc_span::symbol::kw::{False, True};
107

118
declare_clippy_lint! {
129
/// **What it does:** This lint warns about boolean comparisons in assert-like macros.
@@ -26,102 +23,53 @@ declare_clippy_lint! {
2623
/// assert!(!"a".is_empty());
2724
/// ```
2825
pub BOOL_ASSERT_COMPARISON,
29-
pedantic,
30-
"Using a boolean as comparison value when there is no need"
26+
style,
27+
"Using a boolean as comparison value in an assert_* macro when there is no need"
3128
}
3229

3330
declare_lint_pass!(BoolAssertComparison => [BOOL_ASSERT_COMPARISON]);
3431

35-
fn is_bool(tt: Option<&TokenTree>) -> bool {
36-
match tt {
37-
Some(TokenTree::Token(Token {
38-
kind: TokenKind::Ident(i, _),
32+
fn is_bool_lit(e: &Expr) -> bool {
33+
matches!(
34+
e.kind,
35+
ExprKind::Lit(Lit {
36+
kind: LitKind::Bool(_),
3937
..
40-
})) => *i == True || *i == False,
41-
_ => false,
42-
}
38+
})
39+
)
4340
}
4441

4542
impl EarlyLintPass for BoolAssertComparison {
46-
fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
43+
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
4744
let macros = ["assert_eq", "debug_assert_eq"];
4845
let inverted_macros = ["assert_ne", "debug_assert_ne"];
4946

50-
if let MacArgs::Delimited(_, _, ts) = &*mac.args {
51-
let name;
52-
if let [seg] = &*mac.path.segments {
53-
name = seg.ident.name.as_str().to_string();
54-
if !macros.contains(&name.as_str()) && !inverted_macros.contains(&name.as_str()) {
55-
return;
56-
}
57-
} else {
58-
return;
59-
}
60-
let mut has_true = false;
61-
let mut has_false = false;
62-
let mut bool_span = None;
63-
let mut spans = Vec::new();
64-
let mut nb_comma = 0;
65-
let mut iter = ts.trees().peekable();
66-
while let Some(tt) = iter.next() {
67-
if let TokenTree::Token(ref token) = tt {
68-
match token.kind {
69-
TokenKind::Ident(i, _) => {
70-
// We only want to check the comparison arguments, nothing else!
71-
if nb_comma < 2 {
72-
if i == True {
73-
has_true = true;
74-
bool_span = Some(token.span);
75-
continue;
76-
} else if i == False {
77-
has_false = true;
78-
bool_span = Some(token.span);
79-
continue;
80-
}
81-
}
82-
},
83-
TokenKind::Comma => {
84-
nb_comma += 1;
85-
if nb_comma > 1 || !is_bool(iter.peek()) {
86-
spans.push((token.span, true));
87-
}
88-
continue;
89-
},
90-
_ => {},
47+
for mac in macros.iter().chain(inverted_macros.iter()) {
48+
if is_direct_expn_of(e.span, mac).is_some() {
49+
if let Some([a, b]) = ast_utils::extract_assert_macro_args(e) {
50+
let nb_bool_args = is_bool_lit(a) as usize + is_bool_lit(b) as usize;
51+
52+
if nb_bool_args != 1 {
53+
// If there are two boolean arguments, we definitely don't understand
54+
// what's going on, so better leave things as is...
55+
//
56+
// Or there is simply no boolean and then we can leave things as is!
57+
return;
9158
}
59+
60+
let non_eq_mac = &mac[..mac.len() - 3];
61+
span_lint_and_sugg(
62+
cx,
63+
BOOL_ASSERT_COMPARISON,
64+
e.span,
65+
&format!("used `{}!` with a literal bool", mac),
66+
"replace it with",
67+
format!("{}!", non_eq_mac),
68+
Applicability::HasPlaceholders,
69+
);
9270
}
93-
spans.push((tt.span(), false));
94-
}
95-
#[allow(clippy::if_same_then_else)] // It allows better code reability here.
96-
if has_false && has_true {
97-
// Don't know what's going on here, but better leave things as is...
98-
return;
99-
} else if !has_true && !has_false {
100-
// No problem detected!
10171
return;
10272
}
103-
let text = spans
104-
.into_iter()
105-
.map(|(s, needs_whitespace)| {
106-
let mut s = snippet(cx, s, "arg").to_string();
107-
if needs_whitespace {
108-
s.push(' ');
109-
}
110-
s
111-
})
112-
.collect::<String>();
113-
let is_cond_inverted = inverted_macros.contains(&name.as_str());
114-
let extra = (has_true && is_cond_inverted) || has_false;
115-
116-
span_lint_and_sugg(
117-
cx,
118-
BOOL_ASSERT_COMPARISON,
119-
bool_span.expect("failed to get the bool span"),
120-
"assert macro with a boolean comparison",
121-
"replace it with",
122-
format!("{}!({}{})", &name[..name.len() - 3], if extra { "!" } else { "" }, text),
123-
Applicability::MachineApplicable,
124-
);
12573
}
12674
}
12775
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,6 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) {
394394
store.register_pre_expansion_pass(|| box write::Write::default());
395395
store.register_pre_expansion_pass(|| box attrs::EarlyAttributes);
396396
store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro);
397-
store.register_pre_expansion_pass(|| box bool_assert_comparison::BoolAssertComparison);
398397
}
399398

400399
#[doc(hidden)]
@@ -1273,6 +1272,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12731272
store.register_late_pass(|| box from_str_radix_10::FromStrRadix10);
12741273
store.register_late_pass(|| box manual_map::ManualMap);
12751274
store.register_late_pass(move || box if_then_some_else_none::IfThenSomeElseNone::new(msrv));
1275+
store.register_early_pass(|| box bool_assert_comparison::BoolAssertComparison);
12761276

12771277
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
12781278
LintId::of(arithmetic::FLOAT_ARITHMETIC),
@@ -1335,7 +1335,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13351335
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
13361336
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
13371337
LintId::of(bit_mask::VERBOSE_BIT_MASK),
1338-
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
13391338
LintId::of(bytecount::NAIVE_BYTECOUNT),
13401339
LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS),
13411340
LintId::of(casts::CAST_LOSSLESS),
@@ -1738,6 +1737,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
17381737
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
17391738
LintId::of(blacklisted_name::BLACKLISTED_NAME),
17401739
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
1740+
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
17411741
LintId::of(casts::FN_TO_NUMERIC_CAST),
17421742
LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
17431743
LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),

clippy_utils/src/ast_utils.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
66

77
use crate::{both, over};
8+
use if_chain::if_chain;
89
use rustc_ast::ptr::P;
910
use rustc_ast::{self as ast, *};
1011
use rustc_span::symbol::Ident;
@@ -571,3 +572,32 @@ pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
571572
_ => false,
572573
}
573574
}
575+
576+
/// Extract args from an assert-like macro.
577+
/// Currently working with:
578+
/// - `assert!`, `assert_eq!` and `assert_ne!`
579+
/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
580+
/// For example:
581+
/// `assert!(expr)` will return Some([expr])
582+
/// `debug_assert_eq!(a, b)` will return Some([a, b])
583+
pub fn extract_assert_macro_args(mut expr: &Expr) -> Option<[&Expr; 2]> {
584+
if_chain! {
585+
if let ExprKind::If(_, ref block, _) = expr.kind;
586+
if let StmtKind::Semi(ref e) = block.stmts.get(0)?.kind;
587+
then {
588+
expr = e;
589+
}
590+
}
591+
if_chain! {
592+
if let ExprKind::Block(ref block, _) = expr.kind;
593+
if let StmtKind::Expr(ref expr) = block.stmts.get(0)?.kind;
594+
if let ExprKind::Match(ref match_expr, _) = expr.kind;
595+
if let ExprKind::Tup(ref tup) = match_expr.kind;
596+
if let (Some(ref a), Some(ref b)) = (tup.get(0), tup.get(1));
597+
if let (&ExprKind::AddrOf(_, _, ref a), &ExprKind::AddrOf(_, _, ref b)) = (&a.kind, &b.kind);
598+
then {
599+
return Some([&*a, &*b]);
600+
}
601+
}
602+
None
603+
}

tests/ui/bool_assert_comparison.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,47 @@
11
#![warn(clippy::bool_assert_comparison)]
22

3+
macro_rules! a {
4+
() => { true }
5+
}
6+
macro_rules! b {
7+
() => { true }
8+
}
9+
310
fn main() {
411
assert_eq!("a".len(), 1);
512
assert_eq!("a".is_empty(), false);
613
assert_eq!("".is_empty(), true);
714
assert_eq!(true, "".is_empty());
15+
assert_eq!(a!(), b!());
816

917
assert_ne!("a".len(), 1);
1018
assert_ne!("a".is_empty(), false);
1119
assert_ne!("".is_empty(), true);
1220
assert_ne!(true, "".is_empty());
21+
assert_ne!(a!(), b!());
1322

1423
debug_assert_eq!("a".len(), 1);
1524
debug_assert_eq!("a".is_empty(), false);
1625
debug_assert_eq!("".is_empty(), true);
1726
debug_assert_eq!(true, "".is_empty());
27+
debug_assert_eq!(a!(), b!());
1828

1929
debug_assert_ne!("a".len(), 1);
2030
debug_assert_ne!("a".is_empty(), false);
2131
debug_assert_ne!("".is_empty(), true);
2232
debug_assert_ne!(true, "".is_empty());
33+
debug_assert_ne!(a!(), b!());
2334

2435
// assert with error messages
2536
assert_eq!("a".len(), 1, "tadam {}", 1);
2637
assert_eq!("a".len(), 1, "tadam {}", true);
2738
assert_eq!("a".is_empty(), false, "tadam {}", 1);
2839
assert_eq!("a".is_empty(), false, "tadam {}", true);
2940
assert_eq!(false, "a".is_empty(), "tadam {}", true);
41+
42+
debug_assert_eq!("a".len(), 1, "tadam {}", 1);
43+
debug_assert_eq!("a".len(), 1, "tadam {}", true);
44+
debug_assert_eq!("a".is_empty(), false, "tadam {}", 1);
45+
debug_assert_eq!("a".is_empty(), false, "tadam {}", true);
46+
debug_assert_eq!(false, "a".is_empty(), "tadam {}", true);
3047
}

0 commit comments

Comments
 (0)