Skip to content

Split assert#16677

Draft
jcarey9149 wants to merge 22 commits intorust-lang:masterfrom
jcarey9149:split_assert
Draft

Split assert#16677
jcarey9149 wants to merge 22 commits intorust-lang:masterfrom
jcarey9149:split_assert

Conversation

@jcarey9149
Copy link

Please write a short comment explaining your change (or "none" for internal only changes)

changelog: ['assert_multiple']: Add an assert for asserts with mulitple boolean tests

@rustbot rustbot added the needs-fcp PRs that add, remove, or rename lints and need an FCP label Mar 6, 2026
@jcarey9149
Copy link
Author

Sadly, I accidentally deleted the branch connected to my previous PR, causing it to close. @ada4a

@github-actions
Copy link

github-actions bot commented Mar 6, 2026

Lintcheck changes for c38698e

Lint Added Removed Changed
clippy::assert_multiple 46 0 0

This comment will be updated if you push new changes

@ada4a
Copy link
Contributor

ada4a commented Mar 7, 2026

Sadly, I accidentally deleted the branch connected to my previous PR, causing it to close.

Happens to the best of us, don't worry...

Looking at Lintcheck, there seem to be some classes of cases where we don't really want to lint:

  • the assertion has a message – it usually presents information about everything that is asserted, so we shouldn't be splitting that up; and even if it didn't, we wouldn't know which of the newly assert we should move the message into
  • assertions of the form n < x && x < m – splitting these would look unnatural imo, but I guess that's the whole premise of the lint... not sure, let me know what you think

Copy link
Contributor

@ada4a ada4a left a comment

Choose a reason for hiding this comment

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

A bit more review.. I hope this will resolve the mystery of the missing suggestions

View changes since this review

// which is represented by `'v` (the HIR lifetime `'tcx` refers to the
// data inside the `LateContext`, not the borrow itself).
cx: &'v LateContext<'tcx>,
suggest_asserts: &'v mut Vec<String>,
Copy link
Contributor

Choose a reason for hiding this comment

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

at this point you might as well just own the vec -- would make lifetime management easier

@jcarey9149
Copy link
Author

Sadly, I accidentally deleted the branch connected to my previous PR, causing it to close.

Happens to the best of us, don't worry...

Looking at Lintcheck, there seem to be some classes of cases where we don't really want to lint:

  • the assertion has a message – it usually presents information about everything that is asserted, so we shouldn't be splitting that up; and even if it didn't, we wouldn't know which of the newly assert we should move the message into
  • assertions of the form n < x && x < m – splitting these would look unnatural imo, but I guess that's the whole premise of the lint... not sure, let me know what you think

I'm not sure I understand the comment about lintcheck. can you clarify please?

This is based on issue 1810.

@ada4a
Copy link
Contributor

ada4a commented Mar 7, 2026

Ah, sorry. Lintcheck is a thing where the PR is run on a bunch of popular crates, in order to see if the lint is doing the right thing, and catch false positives. You can see its results in #16677 (comment) by clicking on the clippy::assert_multiple thing

Comment on lines +35 to +37
// the visitor needs a mutable reference to a vector that lives
// only for the duration of a single `check_expr` invocation. we
// therefore introduce a separate lifetime `'v` for that borrow.
Copy link
Contributor

Choose a reason for hiding this comment

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

the comment is outdated now, feel free to remove/rephrase

impl<'tcx> LateLintPass<'tcx> for AssertMultiple {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) {
if let Some(macro_call) = root_macro_call_first_node(cx, e)
&& matches!(cx.tcx.get_diagnostic_name(macro_call.def_id), Some(sym::assert_macro))
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be left as future work, but I don't think there's anything stopping this lint from also working with debug_assert!?

self.suggests.push(tmptxt);
},

_ => {},
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 will end up eating the subexpressions which don't match any of the kinds above? For example, given a: i32 and is_foo: bool, the following code:

assert!(a > 0 && is_foo);

would become just

assert!(a > 0);

Right?

@ada4a
Copy link
Contributor

ada4a commented Mar 8, 2026

in the changelog message, the lint name should be in backticks (`), not in quotes (')

The wrong `TypeckResults` was used in the fallback equality function
passed by the `match_same_arms` and `filter_map` lints. Previously,
those fallback functions had no way of using the proper `TypeckResults`.
Those (one per expression being compared) are now passed to the
registered fallback function.
@jcarey9149
Copy link
Author

My latest update passes most tests, but the clippy test complains about collapsing my span_lint_and_then call into one (?) line. cargo dev fmt has a different idea.

How does this get resolved?

On the other front, it now supports debug_assert!() as well as assert!(). I also improved the handling of Or.

@ada4a
Copy link
Contributor

ada4a commented Mar 9, 2026

the clippy test complains about collapsing my span_lint_and_then call into one (?) line

Ah no, it also suggests switching to span_lint_and_sugg (instead of span_lint_and_then). The reason it fires is because the then closure only contains one operation. This is an internal lint I personally dislike, but that's a topic for another time. For now, you can copy its suggestion:

span_lint_and_sugg(cx, ASSERT_MULTIPLE, e.span, "multiple asserts combined into one", "consider writing", am_visitor.suggests.join("\n"), Applicability::MaybeIncorrect)

#[clippy::version = "1.95.0"]
pub ASSERT_MULTIPLE,
nursery,
"Splitting an assert using '&&' into separate asserts makes it clearer which is failing."
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"Splitting an assert using '&&' into separate asserts makes it clearer which is failing."
"Splitting an assert using `&&` into separate asserts makes it clearer which is failing."

Comment on lines +80 to +83
&& matches!(
cx.tcx.get_diagnostic_name(macro_call.def_id),
Some(sym::assert_macro | sym::debug_assert_macro)
)
Copy link
Contributor

@ada4a ada4a Mar 9, 2026

Choose a reason for hiding this comment

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

to avoid matching on the diagnostic name again below, you can do something like this:

Suggested change
&& matches!(
cx.tcx.get_diagnostic_name(macro_call.def_id),
Some(sym::assert_macro | sym::debug_assert_macro)
)
&& let assert_string = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
Some(sym::assert_macro) => "assert",
Some(sym::debug_assert_macro) => "debug_assert",
_ => return,
)

Note that this also uses &'static strs instead of owned Strings, since you don't really need ownership here.

"multiple asserts combined into one",
|diag| {
diag.span_suggestion(
e.span,
Copy link
Contributor

Choose a reason for hiding this comment

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

ohhhhh, I think I finally know why you haven't been getting any suggestions! I think it's because e here is a macro call, so its .span might be pointing to the definition of assert!, which is why it doesn't get shown. Try replacing this with macro_call.span

@ada4a
Copy link
Contributor

ada4a commented Mar 9, 2026

Also, I'm not sure how you ended up putting a bunch of other people's commits onto your branch?.. Try git pull --rebase && git rebase master or something

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-fcp PRs that add, remove, or rename lints and need an FCP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants