|
1 | 1 | use clippy_utils::consts::{constant, Constant}; |
2 | 2 | use clippy_utils::diagnostics::span_lint_and_help; |
3 | | -use clippy_utils::higher; |
4 | | -use clippy_utils::source::snippet_opt; |
5 | | -use clippy_utils::{is_direct_expn_of, is_expn_of, match_panic_call, peel_blocks}; |
6 | | -use if_chain::if_chain; |
7 | | -use rustc_hir::{Expr, ExprKind, UnOp}; |
| 3 | +use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; |
| 4 | +use rustc_hir::Expr; |
8 | 5 | use rustc_lint::{LateContext, LateLintPass}; |
9 | 6 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
| 7 | +use rustc_span::sym; |
10 | 8 |
|
11 | 9 | declare_clippy_lint! { |
12 | 10 | /// ### What it does |
@@ -36,107 +34,39 @@ declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); |
36 | 34 |
|
37 | 35 | impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { |
38 | 36 | fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { |
39 | | - let lint_true = |is_debug: bool| { |
40 | | - span_lint_and_help( |
41 | | - cx, |
42 | | - ASSERTIONS_ON_CONSTANTS, |
43 | | - e.span, |
44 | | - if is_debug { |
45 | | - "`debug_assert!(true)` will be optimized out by the compiler" |
46 | | - } else { |
47 | | - "`assert!(true)` will be optimized out by the compiler" |
48 | | - }, |
49 | | - None, |
50 | | - "remove it", |
51 | | - ); |
| 37 | + let Some(macro_call) = root_macro_call_first_node(cx, e) else { return }; |
| 38 | + let is_debug = match cx.tcx.get_diagnostic_name(macro_call.def_id) { |
| 39 | + Some(sym::debug_assert_macro) => true, |
| 40 | + Some(sym::assert_macro) => false, |
| 41 | + _ => return, |
52 | 42 | }; |
53 | | - let lint_false_without_message = || { |
| 43 | + let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { return }; |
| 44 | + let Some((Constant::Bool(val), _)) = constant(cx, cx.typeck_results(), condition) else { return }; |
| 45 | + if val { |
54 | 46 | span_lint_and_help( |
55 | 47 | cx, |
56 | 48 | ASSERTIONS_ON_CONSTANTS, |
57 | | - e.span, |
58 | | - "`assert!(false)` should probably be replaced", |
| 49 | + macro_call.span, |
| 50 | + &format!( |
| 51 | + "`{}!(true)` will be optimized out by the compiler", |
| 52 | + cx.tcx.item_name(macro_call.def_id) |
| 53 | + ), |
59 | 54 | None, |
60 | | - "use `panic!()` or `unreachable!()`", |
| 55 | + "remove it", |
61 | 56 | ); |
62 | | - }; |
63 | | - let lint_false_with_message = |panic_message: String| { |
| 57 | + } else if !is_debug { |
| 58 | + let (assert_arg, panic_arg) = match panic_expn { |
| 59 | + PanicExpn::Empty => ("", ""), |
| 60 | + _ => (", ..", ".."), |
| 61 | + }; |
64 | 62 | span_lint_and_help( |
65 | 63 | cx, |
66 | 64 | ASSERTIONS_ON_CONSTANTS, |
67 | | - e.span, |
68 | | - &format!("`assert!(false, {})` should probably be replaced", panic_message), |
| 65 | + macro_call.span, |
| 66 | + &format!("`assert!(false{})` should probably be replaced", assert_arg), |
69 | 67 | None, |
70 | | - &format!("use `panic!({})` or `unreachable!({})`", panic_message, panic_message), |
| 68 | + &format!("use `panic!({})` or `unreachable!({0})`", panic_arg), |
71 | 69 | ); |
72 | | - }; |
73 | | - |
74 | | - if let Some(debug_assert_span) = is_expn_of(e.span, "debug_assert") { |
75 | | - if debug_assert_span.from_expansion() { |
76 | | - return; |
77 | | - } |
78 | | - if_chain! { |
79 | | - if let ExprKind::Unary(_, lit) = e.kind; |
80 | | - if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), lit); |
81 | | - if is_true; |
82 | | - then { |
83 | | - lint_true(true); |
84 | | - } |
85 | | - }; |
86 | | - } else if let Some(assert_span) = is_direct_expn_of(e.span, "assert") { |
87 | | - if assert_span.from_expansion() { |
88 | | - return; |
89 | | - } |
90 | | - if let Some(assert_match) = match_assert_with_message(cx, e) { |
91 | | - match assert_match { |
92 | | - // matched assert but not message |
93 | | - AssertKind::WithoutMessage(false) => lint_false_without_message(), |
94 | | - AssertKind::WithoutMessage(true) | AssertKind::WithMessage(_, true) => lint_true(false), |
95 | | - AssertKind::WithMessage(panic_message, false) => lint_false_with_message(panic_message), |
96 | | - }; |
97 | | - } |
98 | | - } |
99 | | - } |
100 | | -} |
101 | | - |
102 | | -/// Result of calling `match_assert_with_message`. |
103 | | -enum AssertKind { |
104 | | - WithMessage(String, bool), |
105 | | - WithoutMessage(bool), |
106 | | -} |
107 | | - |
108 | | -/// Check if the expression matches |
109 | | -/// |
110 | | -/// ```rust,ignore |
111 | | -/// if !c { |
112 | | -/// { |
113 | | -/// ::std::rt::begin_panic(message, _) |
114 | | -/// } |
115 | | -/// } |
116 | | -/// ``` |
117 | | -/// |
118 | | -/// where `message` is any expression and `c` is a constant bool. |
119 | | -fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> { |
120 | | - if_chain! { |
121 | | - if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr); |
122 | | - if let ExprKind::Unary(UnOp::Not, expr) = cond.kind; |
123 | | - // bind the first argument of the `assert!` macro |
124 | | - if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr); |
125 | | - let begin_panic_call = peel_blocks(then); |
126 | | - // function call |
127 | | - if let Some(arg) = match_panic_call(cx, begin_panic_call); |
128 | | - // bind the second argument of the `assert!` macro if it exists |
129 | | - if let panic_message = snippet_opt(cx, arg.span); |
130 | | - // second argument of begin_panic is irrelevant |
131 | | - // as is the second match arm |
132 | | - then { |
133 | | - // an empty message occurs when it was generated by the macro |
134 | | - // (and not passed by the user) |
135 | | - return panic_message |
136 | | - .filter(|msg| !msg.is_empty()) |
137 | | - .map(|msg| AssertKind::WithMessage(msg, is_true)) |
138 | | - .or(Some(AssertKind::WithoutMessage(is_true))); |
139 | 70 | } |
140 | 71 | } |
141 | | - None |
142 | 72 | } |
0 commit comments