1
1
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 } ;
6
4
use rustc_errors:: Applicability ;
7
5
use rustc_lint:: { EarlyContext , EarlyLintPass } ;
8
6
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
9
- use rustc_span:: symbol:: kw:: { False , True } ;
10
7
11
8
declare_clippy_lint ! {
12
9
/// **What it does:** This lint warns about boolean comparisons in assert-like macros.
@@ -26,102 +23,53 @@ declare_clippy_lint! {
26
23
/// assert!(!"a".is_empty());
27
24
/// ```
28
25
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"
31
28
}
32
29
33
30
declare_lint_pass ! ( BoolAssertComparison => [ BOOL_ASSERT_COMPARISON ] ) ;
34
31
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 ( _) ,
39
37
..
40
- } ) ) => * i == True || * i == False ,
41
- _ => false ,
42
- }
38
+ } )
39
+ )
43
40
}
44
41
45
42
impl EarlyLintPass for BoolAssertComparison {
46
- fn check_mac ( & mut self , cx : & EarlyContext < ' _ > , mac : & MacCall ) {
43
+ fn check_expr ( & mut self , cx : & EarlyContext < ' _ > , e : & Expr ) {
47
44
let macros = [ "assert_eq" , "debug_assert_eq" ] ;
48
45
let inverted_macros = [ "assert_ne" , "debug_assert_ne" ] ;
49
46
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 ;
91
58
}
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
+ ) ;
92
70
}
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!
101
71
return ;
102
72
}
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
- ) ;
125
73
}
126
74
}
127
75
}
0 commit comments