|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_then;
|
2 |
| -use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node}; |
| 2 | +use clippy_utils::macros::{find_assert_args, find_assert_eq_args, root_macro_call_first_node, MacroCall}; |
3 | 3 | use clippy_utils::sugg::Sugg;
|
4 | 4 | use clippy_utils::ty::{implements_trait, is_copy};
|
5 | 5 | use rustc_ast::ast::LitKind;
|
| 6 | +use rustc_ast::BinOpKind; |
6 | 7 | use rustc_errors::Applicability;
|
7 | 8 | use rustc_hir::{Expr, ExprKind, Lit};
|
8 | 9 | use rustc_lint::{LateContext, LateLintPass, LintContext};
|
9 | 10 | use rustc_middle::ty::{self, Ty};
|
10 | 11 | use rustc_session::declare_lint_pass;
|
| 12 | +use rustc_span::source_map::Spanned; |
11 | 13 | use rustc_span::symbol::Ident;
|
| 14 | +use rustc_span::Span; |
12 | 15 |
|
13 | 16 | declare_clippy_lint! {
|
14 | 17 | /// ### What it does
|
@@ -74,74 +77,115 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
|
74 | 77 | return;
|
75 | 78 | };
|
76 | 79 | let macro_name = cx.tcx.item_name(macro_call.def_id);
|
77 |
| - let eq_macro = match macro_name.as_str() { |
78 |
| - "assert_eq" | "debug_assert_eq" => true, |
79 |
| - "assert_ne" | "debug_assert_ne" => false, |
80 |
| - _ => return, |
81 |
| - }; |
82 |
| - let Some((a, b, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { |
83 |
| - return; |
| 80 | + let macro_name = macro_name.as_str(); |
| 81 | + match macro_name { |
| 82 | + "assert_eq" | "debug_assert_eq" => check_eq(cx, expr, ¯o_call, macro_name, true), |
| 83 | + "assert_ne" | "debug_assert_ne" => check_eq(cx, expr, ¯o_call, macro_name, false), |
| 84 | + "assert" | "debug_assert" => check(cx, expr, ¯o_call, macro_name), |
| 85 | + _ => {}, |
84 | 86 | };
|
| 87 | + } |
| 88 | +} |
85 | 89 |
|
86 |
| - let a_span = a.span.source_callsite(); |
87 |
| - let b_span = b.span.source_callsite(); |
88 |
| - |
89 |
| - let (lit_span, bool_value, non_lit_expr) = match (extract_bool_lit(a), extract_bool_lit(b)) { |
90 |
| - // assert_eq!(true/false, b) |
91 |
| - // ^^^^^^^^^^^^ |
92 |
| - (Some(bool_value), None) => (a_span.until(b_span), bool_value, b), |
93 |
| - // assert_eq!(a, true/false) |
94 |
| - // ^^^^^^^^^^^^ |
95 |
| - (None, Some(bool_value)) => (b_span.with_lo(a_span.hi()), bool_value, a), |
96 |
| - // If there are two boolean arguments, we definitely don't understand |
97 |
| - // what's going on, so better leave things as is... |
98 |
| - // |
99 |
| - // Or there is simply no boolean and then we can leave things as is! |
100 |
| - _ => return, |
101 |
| - }; |
| 90 | +fn check_eq<'tcx>( |
| 91 | + cx: &LateContext<'tcx>, |
| 92 | + expr: &'tcx Expr<'_>, |
| 93 | + macro_call: &MacroCall, |
| 94 | + macro_name: &str, |
| 95 | + eq_macro: bool, |
| 96 | +) { |
| 97 | + let Some((a, b, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { |
| 98 | + return; |
| 99 | + }; |
102 | 100 |
|
103 |
| - let non_lit_ty = cx.typeck_results().expr_ty(non_lit_expr); |
| 101 | + let a_span = a.span.source_callsite(); |
| 102 | + let b_span = b.span.source_callsite(); |
104 | 103 |
|
105 |
| - if !is_impl_not_trait_with_bool_out(cx, non_lit_ty) { |
106 |
| - // At this point the expression which is not a boolean |
107 |
| - // literal does not implement Not trait with a bool output, |
108 |
| - // so we cannot suggest to rewrite our code |
109 |
| - return; |
110 |
| - } |
| 104 | + let (lit_span, bool_value, non_lit_expr) = match (extract_bool_lit(a), extract_bool_lit(b)) { |
| 105 | + // assert_eq!(true/false, b) |
| 106 | + // ^^^^^^^^^^^^ |
| 107 | + (Some(bool_value), None) => (a_span.until(b_span), bool_value, b), |
| 108 | + // assert_eq!(a, true/false) |
| 109 | + // ^^^^^^^^^^^^ |
| 110 | + (None, Some(bool_value)) => (b_span.with_lo(a_span.hi()), bool_value, a), |
| 111 | + // If there are two boolean arguments, we definitely don't understand |
| 112 | + // what's going on, so better leave things as is... |
| 113 | + // |
| 114 | + // Or there is simply no boolean and then we can leave things as is! |
| 115 | + _ => return, |
| 116 | + }; |
111 | 117 |
|
112 |
| - if !is_copy(cx, non_lit_ty) { |
113 |
| - // Only lint with types that are `Copy` because `assert!(x)` takes |
114 |
| - // ownership of `x` whereas `assert_eq(x, true)` does not |
115 |
| - return; |
116 |
| - } |
| 118 | + let non_lit_ty = cx.typeck_results().expr_ty(non_lit_expr); |
117 | 119 |
|
118 |
| - let macro_name = macro_name.as_str(); |
119 |
| - let non_eq_mac = ¯o_name[..macro_name.len() - 3]; |
120 |
| - span_lint_and_then( |
121 |
| - cx, |
122 |
| - BOOL_ASSERT_COMPARISON, |
123 |
| - macro_call.span, |
124 |
| - format!("used `{macro_name}!` with a literal bool"), |
125 |
| - |diag| { |
126 |
| - // assert_eq!(...) |
127 |
| - // ^^^^^^^^^ |
128 |
| - let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); |
129 |
| - |
130 |
| - let mut suggestions = vec![(name_span, non_eq_mac.to_string()), (lit_span, String::new())]; |
131 |
| - |
132 |
| - if bool_value ^ eq_macro { |
133 |
| - let Some(sugg) = Sugg::hir_opt(cx, non_lit_expr) else { |
134 |
| - return; |
135 |
| - }; |
136 |
| - suggestions.push((non_lit_expr.span, (!sugg).to_string())); |
137 |
| - } |
138 |
| - |
139 |
| - diag.multipart_suggestion( |
140 |
| - format!("replace it with `{non_eq_mac}!(..)`"), |
141 |
| - suggestions, |
142 |
| - Applicability::MachineApplicable, |
143 |
| - ); |
144 |
| - }, |
145 |
| - ); |
| 120 | + if !is_impl_not_trait_with_bool_out(cx, non_lit_ty) { |
| 121 | + // At this point the expression which is not a boolean |
| 122 | + // literal does not implement Not trait with a bool output, |
| 123 | + // so we cannot suggest to rewrite our code |
| 124 | + return; |
| 125 | + } |
| 126 | + |
| 127 | + if !is_copy(cx, non_lit_ty) { |
| 128 | + // Only lint with types that are `Copy` because `assert!(x)` takes |
| 129 | + // ownership of `x` whereas `assert_eq(x, true)` does not |
| 130 | + return; |
| 131 | + } |
| 132 | + |
| 133 | + let non_eq_mac = ¯o_name[..macro_name.len() - 3]; |
| 134 | + span_lint_and_then( |
| 135 | + cx, |
| 136 | + BOOL_ASSERT_COMPARISON, |
| 137 | + macro_call.span, |
| 138 | + format!("used `{macro_name}!` with a literal bool"), |
| 139 | + |diag| { |
| 140 | + // assert_eq!(...) |
| 141 | + // ^^^^^^^^^ |
| 142 | + let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); |
| 143 | + |
| 144 | + let mut suggestions = vec![(name_span, non_eq_mac.to_string()), (lit_span, String::new())]; |
| 145 | + |
| 146 | + if bool_value ^ eq_macro { |
| 147 | + let Some(sugg) = Sugg::hir_opt(cx, non_lit_expr) else { |
| 148 | + return; |
| 149 | + }; |
| 150 | + suggestions.push((non_lit_expr.span, (!sugg).to_string())); |
| 151 | + } |
| 152 | + |
| 153 | + diag.multipart_suggestion( |
| 154 | + format!("replace it with `{non_eq_mac}!(..)`"), |
| 155 | + suggestions, |
| 156 | + Applicability::MachineApplicable, |
| 157 | + ); |
| 158 | + }, |
| 159 | + ); |
| 160 | +} |
| 161 | + |
| 162 | +/// Checks for `assert!(a == b)` and `assert!(a != b)` |
| 163 | +fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, macro_call: &MacroCall, macro_name: &str) { |
| 164 | + let Some((cond, _)) = find_assert_args(cx, expr, macro_call.expn) else { |
| 165 | + return; |
| 166 | + }; |
| 167 | + let ExprKind::Binary(Spanned { node, span }, lhs, rhs) = cond.kind else { |
| 168 | + return; |
| 169 | + }; |
| 170 | + // TODO: is `cond.span.from_expansion()` correct / needed? |
| 171 | + if (node != BinOpKind::Eq && node != BinOpKind::Ne) || cond.span.from_expansion() { |
| 172 | + return; |
146 | 173 | }
|
| 174 | + |
| 175 | + let new_name = format!("{macro_name}_{}", if node == BinOpKind::Eq { "eq" } else { "ne" }); |
| 176 | + let msg = format!("replace it with `{new_name}!(..)`"); |
| 177 | + span_lint_and_then( |
| 178 | + cx, |
| 179 | + BOOL_ASSERT_COMPARISON, |
| 180 | + macro_call.span, |
| 181 | + format!("used `{macro_name}!` with an equality comparison"), |
| 182 | + |diag| { |
| 183 | + let macro_name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); |
| 184 | + // TODO: should this be `cond.span`, expanded to include preceding whitespace? If so, how? |
| 185 | + let equality_span = Span::new(lhs.span.hi(), rhs.span.lo(), span.ctxt(), span.parent()); |
| 186 | + let suggestions = vec![(macro_name_span, new_name), (equality_span, ", ".to_string())]; |
| 187 | + |
| 188 | + diag.multipart_suggestion(msg, suggestions, Applicability::MachineApplicable); |
| 189 | + }, |
| 190 | + ); |
147 | 191 | }
|
0 commit comments