]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/assertions_on_constants.rs
Rollup merge of #88707 - sylvestre:split_example, r=yaahc
[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     pub ASSERTIONS_ON_CONSTANTS,
30     style,
31     "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`"
32 }
33
34 declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]);
35
36 impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
37     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
38         let lint_true = |is_debug: bool| {
39             span_lint_and_help(
40                 cx,
41                 ASSERTIONS_ON_CONSTANTS,
42                 e.span,
43                 if is_debug {
44                     "`debug_assert!(true)` will be optimized out by the compiler"
45                 } else {
46                     "`assert!(true)` will be optimized out by the compiler"
47                 },
48                 None,
49                 "remove it",
50             );
51         };
52         let lint_false_without_message = || {
53             span_lint_and_help(
54                 cx,
55                 ASSERTIONS_ON_CONSTANTS,
56                 e.span,
57                 "`assert!(false)` should probably be replaced",
58                 None,
59                 "use `panic!()` or `unreachable!()`",
60             );
61         };
62         let lint_false_with_message = |panic_message: String| {
63             span_lint_and_help(
64                 cx,
65                 ASSERTIONS_ON_CONSTANTS,
66                 e.span,
67                 &format!("`assert!(false, {})` should probably be replaced", panic_message),
68                 None,
69                 &format!("use `panic!({})` or `unreachable!({})`", panic_message, panic_message),
70             );
71         };
72
73         if let Some(debug_assert_span) = is_expn_of(e.span, "debug_assert") {
74             if debug_assert_span.from_expansion() {
75                 return;
76             }
77             if_chain! {
78                 if let ExprKind::Unary(_, lit) = e.kind;
79                 if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), lit);
80                 if is_true;
81                 then {
82                     lint_true(true);
83                 }
84             };
85         } else if let Some(assert_span) = is_direct_expn_of(e.span, "assert") {
86             if assert_span.from_expansion() {
87                 return;
88             }
89             if let Some(assert_match) = match_assert_with_message(cx, e) {
90                 match assert_match {
91                     // matched assert but not message
92                     AssertKind::WithoutMessage(false) => lint_false_without_message(),
93                     AssertKind::WithoutMessage(true) | AssertKind::WithMessage(_, true) => lint_true(false),
94                     AssertKind::WithMessage(panic_message, false) => lint_false_with_message(panic_message),
95                 };
96             }
97         }
98     }
99 }
100
101 /// Result of calling `match_assert_with_message`.
102 enum AssertKind {
103     WithMessage(String, bool),
104     WithoutMessage(bool),
105 }
106
107 /// Check if the expression matches
108 ///
109 /// ```rust,ignore
110 /// if !c {
111 ///   {
112 ///     ::std::rt::begin_panic(message, _)
113 ///   }
114 /// }
115 /// ```
116 ///
117 /// where `message` is any expression and `c` is a constant bool.
118 fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> {
119     if_chain! {
120         if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
121         if let ExprKind::Unary(UnOp::Not, expr) = cond.kind;
122         // bind the first argument of the `assert!` macro
123         if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr);
124         // block
125         if let ExprKind::Block(block, _) = then.kind;
126         if block.stmts.is_empty();
127         if let Some(block_expr) = &block.expr;
128         // inner block is optional. unwrap it if it exists, or use the expression as is otherwise.
129         if let Some(begin_panic_call) = match block_expr.kind {
130             ExprKind::Block(inner_block, _) => &inner_block.expr,
131             _ => &block.expr,
132         };
133         // function call
134         if let Some(arg) = match_panic_call(cx, begin_panic_call);
135         // bind the second argument of the `assert!` macro if it exists
136         if let panic_message = snippet_opt(cx, arg.span);
137         // second argument of begin_panic is irrelevant
138         // as is the second match arm
139         then {
140             // an empty message occurs when it was generated by the macro
141             // (and not passed by the user)
142             return panic_message
143                 .filter(|msg| !msg.is_empty())
144                 .map(|msg| AssertKind::WithMessage(msg, is_true))
145                 .or(Some(AssertKind::WithoutMessage(is_true)));
146         }
147     }
148     None
149 }