Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add assert message to suggestion in lint assertions_on_constants #4635

Merged
merged 4 commits into from
Oct 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 126 additions & 28 deletions clippy_lints/src/assertions_on_constants.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use rustc::hir::{Expr, ExprKind};
use crate::consts::{constant, Constant};
use crate::utils::paths;
use crate::utils::{is_direct_expn_of, is_expn_of, match_def_path, snippet_opt, span_help_and_lint};
use if_chain::if_chain;
use rustc::hir::*;
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint};

use crate::consts::{constant, Constant};
use crate::utils::{is_direct_expn_of, is_expn_of, span_help_and_lint};
use syntax::ast::LitKind;

declare_clippy_lint! {
/// **What it does:** Checks for `assert!(true)` and `assert!(false)` calls.
Expand Down Expand Up @@ -31,39 +33,135 @@ declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AssertionsOnConstants {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
let lint_assert_cb = |is_debug_assert: bool| {
if let ExprKind::Unary(_, ref lit) = e.kind {
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.tables, lit) {
if is_true {
span_help_and_lint(
cx,
ASSERTIONS_ON_CONSTANTS,
e.span,
"`assert!(true)` will be optimized out by the compiler",
"remove it",
);
} else if !is_debug_assert {
span_help_and_lint(
cx,
ASSERTIONS_ON_CONSTANTS,
e.span,
"`assert!(false)` should probably be replaced",
"use `panic!()` or `unreachable!()`",
);
}
}
}
let lint_true = || {
span_help_and_lint(
cx,
ASSERTIONS_ON_CONSTANTS,
e.span,
"`assert!(true)` will be optimized out by the compiler",
"remove it",
);
};
let lint_false_without_message = || {
span_help_and_lint(
cx,
ASSERTIONS_ON_CONSTANTS,
e.span,
"`assert!(false)` should probably be replaced",
"use `panic!()` or `unreachable!()`",
);
};
let lint_false_with_message = |panic_message: String| {
span_help_and_lint(
cx,
ASSERTIONS_ON_CONSTANTS,
e.span,
&format!("`assert!(false, {})` should probably be replaced", panic_message),
&format!("use `panic!({})` or `unreachable!({})`", panic_message, panic_message),
)
};

if let Some(debug_assert_span) = is_expn_of(e.span, "debug_assert") {
if debug_assert_span.from_expansion() {
return;
}
lint_assert_cb(true);
if_chain! {
if let ExprKind::Unary(_, ref lit) = e.kind;
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.tables, lit);
if is_true;
then {
lint_true();
}
};
} else if let Some(assert_span) = is_direct_expn_of(e.span, "assert") {
if assert_span.from_expansion() {
return;
}
lint_assert_cb(false);
if let Some(assert_match) = match_assert_with_message(&cx, e) {
match assert_match {
// matched assert but not message
AssertKind::WithoutMessage(false) => lint_false_without_message(),
AssertKind::WithoutMessage(true) | AssertKind::WithMessage(_, true) => lint_true(),
AssertKind::WithMessage(panic_message, false) => lint_false_with_message(panic_message),
};
}
}
}
}

/// Result of calling `match_assert_with_message`.
enum AssertKind {
WithMessage(String, bool),
WithoutMessage(bool),
}

/// Check if the expression matches
///
/// ```rust,ignore
/// match { let _t = !c; _t } {
/// true => {
/// {
/// ::std::rt::begin_panic(message, _)
/// }
/// }
/// _ => { }
/// };
/// ```
///
/// where `message` is any expression and `c` is a constant bool.
fn match_assert_with_message<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) -> Option<AssertKind> {
if_chain! {
if let ExprKind::Match(ref expr, ref arms, _) = expr.kind;
// matches { let _t = expr; _t }
if let ExprKind::DropTemps(ref expr) = expr.kind;
if let ExprKind::Unary(UnOp::UnNot, ref expr) = expr.kind;
// bind the first argument of the `assert!` macro
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.tables, expr);
// arm 1 pattern
if let PatKind::Lit(ref lit_expr) = arms[0].pat.kind;
if let ExprKind::Lit(ref lit) = lit_expr.kind;
if let LitKind::Bool(true) = lit.node;
// arm 1 block
if let ExprKind::Block(ref block, _) = arms[0].body.kind;
if block.stmts.len() == 0;
if let Some(block_expr) = &block.expr;
if let ExprKind::Block(ref inner_block, _) = block_expr.kind;
if let Some(begin_panic_call) = &inner_block.expr;
// function call
if let Some(args) = match_function_call(cx, begin_panic_call, &paths::BEGIN_PANIC);
if args.len() == 2;
// bind the second argument of the `assert!` macro if it exists
if let panic_message = snippet_opt(cx, args[0].span);
// second argument of begin_panic is irrelevant
// as is the second match arm
then {
// an empty message occurs when it was generated by the macro
// (and not passed by the user)
return panic_message
.filter(|msg| !msg.is_empty())
.map(|msg| AssertKind::WithMessage(msg, is_true))
.or(Some(AssertKind::WithoutMessage(is_true)));
}
}
None
}

/// Matches a function call with the given path and returns the arguments.
///
/// Usage:
///
/// ```rust,ignore
/// if let Some(args) = match_function_call(cx, begin_panic_call, &paths::BEGIN_PANIC);
/// ```
fn match_function_call<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr, path: &[&str]) -> Option<&'a [Expr]> {
if_chain! {
if let ExprKind::Call(ref fun, ref args) = expr.kind;
if let ExprKind::Path(ref qpath) = fun.kind;
if let Some(fun_def_id) = cx.tables.qpath_res(qpath, fun.hir_id).opt_def_id();
if match_def_path(cx, fun_def_id, path);
then {
return Some(&args)
}
};
None
}
4 changes: 4 additions & 0 deletions tests/ui/assertions_on_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ fn main() {
assert!(true, "true message");
assert!(false, "false message");

let msg = "panic message";
assert!(false, msg.to_uppercase());

const B: bool = true;
assert!(B);

const C: bool = false;
assert!(C);
assert!(C, "C message");

debug_assert!(true);
// Don't lint this, since there is no better way for expressing "Only panic in debug mode".
Expand Down
28 changes: 22 additions & 6 deletions tests/ui/assertions_on_constants.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,54 @@ LL | assert!(true, "true message");
|
= help: remove it

error: `assert!(false)` should probably be replaced
error: `assert!(false, "false message")` should probably be replaced
--> $DIR/assertions_on_constants.rs:12:5
|
LL | assert!(false, "false message");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: use `panic!()` or `unreachable!()`
= help: use `panic!("false message")` or `unreachable!("false message")`

error: `assert!(true)` will be optimized out by the compiler
error: `assert!(false, msg.to_uppercase())` should probably be replaced
--> $DIR/assertions_on_constants.rs:15:5
|
LL | assert!(false, msg.to_uppercase());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: use `panic!(msg.to_uppercase())` or `unreachable!(msg.to_uppercase())`

error: `assert!(true)` will be optimized out by the compiler
--> $DIR/assertions_on_constants.rs:18:5
|
LL | assert!(B);
| ^^^^^^^^^^^
|
= help: remove it

error: `assert!(false)` should probably be replaced
--> $DIR/assertions_on_constants.rs:18:5
--> $DIR/assertions_on_constants.rs:21:5
|
LL | assert!(C);
| ^^^^^^^^^^^
|
= help: use `panic!()` or `unreachable!()`

error: `assert!(false, "C message")` should probably be replaced
--> $DIR/assertions_on_constants.rs:22:5
|
LL | assert!(C, "C message");
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: use `panic!("C message")` or `unreachable!("C message")`

error: `assert!(true)` will be optimized out by the compiler
--> $DIR/assertions_on_constants.rs:20:5
--> $DIR/assertions_on_constants.rs:24:5
|
LL | debug_assert!(true);
| ^^^^^^^^^^^^^^^^^^^^
|
= help: remove it
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error: aborting due to 7 previous errors
error: aborting due to 9 previous errors