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

New lint: uninformative_asserts #6258

Closed
wants to merge 3 commits into from
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1987,6 +1987,7 @@ Released 2018-09-13
[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
[`uninformative_asserts`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninformative_asserts
[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init
[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span:
let mut contains_initial_stars = false;
for line in doc.lines() {
let offset = line.as_ptr() as usize - doc.as_ptr() as usize;
debug_assert_eq!(offset as u32 as usize, offset);
debug_assert_eq!(offset as u32 as usize, offset, "doc comment too large");
contains_initial_stars |= line.trim_start().starts_with('*');
// +1 adds the newline, +3 skips the opening delimiter
sizes.push((line.len() + 1, span.with_lo(span.lo() + BytePos(3 + offset as u32))));
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/enum_variants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ fn to_camel_case(item_name: &str) -> String {
impl EarlyLintPass for EnumVariantNames {
fn check_item_post(&mut self, _cx: &EarlyContext<'_>, _item: &Item) {
let last = self.modules.pop();
assert!(last.is_some());
assert!(last.is_some(), "exited more items than entered");
}

#[allow(clippy::similar_names)]
Expand Down
4 changes: 4 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ mod try_err;
mod types;
mod undropped_manually_drops;
mod unicode;
mod uninformative_asserts;
mod unit_return_expecting_ord;
mod unnamed_address;
mod unnecessary_sort_by;
Expand Down Expand Up @@ -352,6 +353,7 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) {
store.register_pre_expansion_pass(|| box write::Write::default());
store.register_pre_expansion_pass(|| box attrs::EarlyAttributes);
store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro);
store.register_pre_expansion_pass(|| box uninformative_asserts::UninformativeAsserts::default());
Copy link
Member

@ebroto ebroto Oct 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just chiming in to say this because @llogiq may not be aware of it, but we try to avoid pre-expansion lints lately. If possible, this should target the expanded version.

See here and here for some background.

}

#[doc(hidden)]
Expand Down Expand Up @@ -874,6 +876,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&unicode::INVISIBLE_CHARACTERS,
&unicode::NON_ASCII_LITERAL,
&unicode::UNICODE_NOT_NFC,
&uninformative_asserts::UNINFORMATIVE_ASSERTS,
&unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD,
&unnamed_address::FN_ADDRESS_COMPARISONS,
&unnamed_address::VTABLE_ADDRESS_COMPARISONS,
Expand Down Expand Up @@ -1271,6 +1274,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&types::OPTION_OPTION),
LintId::of(&unicode::NON_ASCII_LITERAL),
LintId::of(&unicode::UNICODE_NOT_NFC),
LintId::of(&uninformative_asserts::UNINFORMATIVE_ASSERTS),
LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS),
LintId::of(&unused_self::UNUSED_SELF),
LintId::of(&wildcard_imports::ENUM_GLOB_USE),
Expand Down
5 changes: 4 additions & 1 deletion clippy_lints/src/needless_borrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow {

fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if item.attrs.iter().any(|a| a.has_name(sym!(automatically_derived))) {
debug_assert!(self.derived_item.is_none());
debug_assert!(
self.derived_item.is_none(),
"automatically-derived item contained another automatically-derived item"
);
self.derived_item = Some(item.hir_id);
}
}
Expand Down
5 changes: 4 additions & 1 deletion clippy_lints/src/non_expressive_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,10 @@ fn do_check(lint: &mut NonExpressiveNames, cx: &EarlyContext<'_>, attrs: &[Attri
/// Precondition: `a_name.chars().count() < b_name.chars().count()`.
#[must_use]
fn levenstein_not_1(a_name: &str, b_name: &str) -> bool {
debug_assert!(a_name.chars().count() < b_name.chars().count());
debug_assert!(
a_name.chars().count() < b_name.chars().count(),
"levenstein_not_1 precondition failed"
);
let mut a_chars = a_name.chars();
let mut b_chars = b_name.chars();
while let (Some(a), Some(b)) = (a_chars.next(), b_chars.next()) {
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/regex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ fn str_span(base: Span, c: regex_syntax::ast::Span, offset: u16) -> Span {
let offset = u32::from(offset);
let end = base.lo() + BytePos(u32::try_from(c.end.offset).expect("offset too large") + offset);
let start = base.lo() + BytePos(u32::try_from(c.start.offset).expect("offset too large") + offset);
assert!(start <= end);
assert!(start <= end, "span had negative length");
Span::new(start, end, base.ctxt())
}

Expand Down
109 changes: 109 additions & 0 deletions clippy_lints/src/uninformative_asserts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use crate::utils::span_lint_and_help;
use if_chain::if_chain;
use rustc_ast::ast::{Item, MacArgs, MacCall};
use rustc_ast::token;
use rustc_ast::tokenstream::TokenTree;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};

declare_clippy_lint! {
/// **What it does:**
/// Lint {debug_}assert{_eq,_ne}! without a custom panic message.
///
/// **Why is this bad?**
/// If the assertion fails, a custom message may make it easier to debug what went wrong.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// # fn some_condition(_x: u8) -> bool { true }
/// # fn bar(x: u8) -> u8 { x }
/// # let foo = 0u8;
/// # let a = 0u8;
/// # let b = 0u8;
/// assert!(some_condition(foo));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test should have a ///# #![allow(clippy::uninformative_assert)] line pretended. Otherwise clippy-checking the doctests would fail.

/// debug_assert_eq(a, bar(b));
/// ```
/// Use instead:
/// ```rust
/// # fn some_condition(_x: u8) -> bool { true }
/// # fn bar(x: u8) -> u8 { x }
/// # let foo = 0u8;
/// # let a = 0u8;
/// # let b = 0u8;
/// assert!(some_condition(foo), "foo failed some condition: foo = {}", foo);
/// debug_assert_eq!(a, bar(b), "failed to find inverse of bar at {}", a);
/// ```
pub UNINFORMATIVE_ASSERTS,
pedantic,
"using `assert!` without custom panic message"
}

#[derive(Default)]
pub struct UninformativeAsserts {
test_fns_deep: u32,
}

impl_lint_pass!(UninformativeAsserts => [UNINFORMATIVE_ASSERTS]);

const ONE_ARG_ASSERT: [&str; 2] = ["assert", "debug_assert"];
const TWO_ARG_ASSERT: [&str; 4] = ["assert_eq", "assert_ne", "debug_assert_eq", "debug_assert_ne"];

impl EarlyLintPass for UninformativeAsserts {
fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
if self.test_fns_deep > 0 {
return;
}
if let MacArgs::Delimited(_, _, ts) = &*mac.args {
let args_tts = ts.trees().collect::<Vec<_>>();
if let [seg] = &*mac.path.segments {
let mac_name = seg.ident.name.as_str();
let msg_arg_idx = if ONE_ARG_ASSERT.contains(&&*mac_name) {
1
} else if TWO_ARG_ASSERT.contains(&&*mac_name) {
2
} else {
return;
};
// this is a call to an `assert!`-family macro
// check if it has a custom panic message argument
let opt_msg = args_tts.split(is_comma).nth(msg_arg_idx);
if let None | Some([]) = opt_msg {
span_lint_and_help(
cx,
UNINFORMATIVE_ASSERTS,
mac.span(),
"`assert!` called without custom panic message",
None,
"consider adding a custom panic message",
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may even want a suggestion like {macro}({args}, "<your panic message here>") (with suitable snippets for macro and args). This would help people if they haven't added a message before.

}
}
}
}

fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) {
if item.attrs.iter().any(|attr| attr.has_name(sym!(test))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about #[cfg(test)]? I think we may want to check for that, too.

Perhaps that could be a fn is_test_related(&[Attribute]) -> bool helper function.

self.test_fns_deep = self.test_fns_deep.saturating_add(1);
}
}

fn check_item_post(&mut self, _: &EarlyContext<'_>, item: &Item) {
if item.attrs.iter().any(|attr| attr.has_name(sym!(test))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this check is duplicated here, it should really be a helper.

self.test_fns_deep = self.test_fns_deep.saturating_sub(1);
}
}
}

fn is_comma(tt: &TokenTree) -> bool {
if_chain! {
if let TokenTree::Token(token) = tt;
if let token::TokenKind::Comma = token.kind;
then {
return true;
}
}
false
}
6 changes: 4 additions & 2 deletions clippy_lints/src/utils/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub struct LimitStack {

impl Drop for LimitStack {
fn drop(&mut self) {
assert_eq!(self.stack.len(), 1);
assert_eq!(self.stack.len(), 1, "LimitStack was not empty when dropped");
}
}

Expand All @@ -47,7 +47,9 @@ impl LimitStack {
}
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
let stack = &mut self.stack;
parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
parse_attrs(sess, attrs, name, |val| {
assert_eq!(stack.pop(), Some(val), "popped non-matching attr value")
});
}
}

Expand Down
5 changes: 4 additions & 1 deletion clippy_lints/src/utils/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,10 @@ pub fn read(path: &Path) -> (Conf, Vec<Error>) {
Err(err) => return default(vec![err.into()]),
};

assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
assert!(
ERRORS.lock().expect("no threading -> mutex always safe").is_empty(),
"ERRORS was not empty at the start of toml parsing"
);
match toml::from_str(&content) {
Ok(toml) => {
let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
Expand Down
12 changes: 9 additions & 3 deletions clippy_lints/src/utils/numeric_literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl<'a> NumericLiteral<'a> {
}

pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) {
debug_assert!(group_size > 0);
debug_assert!(group_size > 0, "cannot group into groups of 0");

let mut digits = input.chars().filter(|&c| c != '_');

Expand Down Expand Up @@ -202,15 +202,21 @@ impl<'a> NumericLiteral<'a> {
}

fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
debug_assert!(lit_kind.is_numeric());
debug_assert!(
lit_kind.is_numeric(),
"non-numeric literal does not have numeric suffix"
);
lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| {
let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
(unsuffixed, Some(suffix))
})
}

fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
debug_assert!(lit_kind.is_numeric());
debug_assert!(
lit_kind.is_numeric(),
"non-numeric literal does not have numeric suffix"
);
let suffix = match lit_kind {
LitKind::Int(_, int_lit_kind) => match int_lit_kind {
LitIntType::Signed(int_ty) => Some(int_ty.name_str()),
Expand Down
7 changes: 7 additions & 0 deletions src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2468,6 +2468,13 @@ vec![
deprecation: None,
module: "panic_unimplemented",
},
Lint {
name: "uninformative_asserts",
group: "pedantic",
desc: "using `assert!` without custom panic message",
deprecation: None,
module: "uninformative_asserts",
},
Lint {
name: "uninit_assumed_init",
group: "correctness",
Expand Down
2 changes: 1 addition & 1 deletion tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn integration_test() {
])
.status()
.expect("unable to run git");
assert!(st.success());
assert!(st.success(), "git failed");

let root_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let target_dir = std::path::Path::new(&root_dir).join("target");
Expand Down
1 change: 1 addition & 0 deletions tests/ui/filter_map_next.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::uninformative_asserts)]

fn main() {
let a = ["1", "lol", "3", "NaN", "5"];
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/filter_map_next.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: called `filter_map(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find_map(p)` instead.
--> $DIR/filter_map_next.rs:6:32
--> $DIR/filter_map_next.rs:7:32
|
LL | let element: Option<i32> = a.iter().filter_map(|s| s.parse().ok()).next();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -8,7 +8,7 @@ LL | let element: Option<i32> = a.iter().filter_map(|s| s.parse().ok()).next
= note: replace `filter_map(|s| s.parse().ok()).next()` with `find_map(|s| s.parse().ok())`

error: called `filter_map(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find_map(p)` instead.
--> $DIR/filter_map_next.rs:10:26
--> $DIR/filter_map_next.rs:11:26
|
LL | let _: Option<u32> = vec![1, 2, 3, 4, 5, 6]
| __________________________^
Expand Down
3 changes: 2 additions & 1 deletion tests/ui/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
unused_variables,
clippy::manual_unwrap_or,
clippy::missing_docs_in_private_items,
clippy::single_match
clippy::single_match,
clippy::uninformative_asserts
)]

fn id<T>(x: T) -> T {
Expand Down
Loading