]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/assertions_on_constants.rs
Rollup merge of #91476 - m-ou-se:ferris-identifier, r=estebank
[rust.git] / src / tools / clippy / clippy_lints / src / assertions_on_constants.rs
1 use clippy_utils::consts::{constant, Constant};
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};
6 use if_chain::if_chain;
7 use rustc_hir::{Expr, ExprKind, UnOp};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_session::{declare_lint_pass, declare_tool_lint};
10
11 declare_clippy_lint! {
12     /// ### What it does
13     /// Checks for `assert!(true)` and `assert!(false)` calls.
14     ///
15     /// ### Why is this bad?
16     /// Will be optimized out by the compiler or should probably be replaced by a
17     /// `panic!()` or `unreachable!()`
18     ///
19     /// ### Known problems
20     /// None
21     ///
22     /// ### Example
23     /// ```rust,ignore
24     /// assert!(false)
25     /// assert!(true)
26     /// const B: bool = false;
27     /// assert!(B)
28     /// ```
29     #[clippy::version = "1.34.0"]
30     pub ASSERTIONS_ON_CONSTANTS,
31     style,
32     "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`"
33 }
34
35 declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]);
36
37 impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
38     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             );
52         };
53         let lint_false_without_message = || {
54             span_lint_and_help(
55                 cx,
56                 ASSERTIONS_ON_CONSTANTS,
57                 e.span,
58                 "`assert!(false)` should probably be replaced",
59                 None,
60                 "use `panic!()` or `unreachable!()`",
61             );
62         };
63         let lint_false_with_message = |panic_message: String| {
64             span_lint_and_help(
65                 cx,
66                 ASSERTIONS_ON_CONSTANTS,
67                 e.span,
68                 &format!("`assert!(false, {})` should probably be replaced", panic_message),
69                 None,
70                 &format!("use `panic!({})` or `unreachable!({})`", panic_message, panic_message),
71             );
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         // block
126         if let ExprKind::Block(block, _) = then.kind;
127         if block.stmts.is_empty();
128         if let Some(block_expr) = &block.expr;
129         // inner block is optional. unwrap it if it exists, or use the expression as is otherwise.
130         if let Some(begin_panic_call) = match block_expr.kind {
131             ExprKind::Block(inner_block, _) => &inner_block.expr,
132             _ => &block.expr,
133         };
134         // function call
135         if let Some(arg) = match_panic_call(cx, begin_panic_call);
136         // bind the second argument of the `assert!` macro if it exists
137         if let panic_message = snippet_opt(cx, arg.span);
138         // second argument of begin_panic is irrelevant
139         // as is the second match arm
140         then {
141             // an empty message occurs when it was generated by the macro
142             // (and not passed by the user)
143             return panic_message
144                 .filter(|msg| !msg.is_empty())
145                 .map(|msg| AssertKind::WithMessage(msg, is_true))
146                 .or(Some(AssertKind::WithoutMessage(is_true)));
147         }
148     }
149     None
150 }