Skip to content

New lint: manual_next_multiple_of - #17517

Open
qdot3 wants to merge 23 commits into
rust-lang:masterfrom
qdot3:wip_manual_next_multiple_of
Open

New lint: manual_next_multiple_of#17517
qdot3 wants to merge 23 commits into
rust-lang:masterfrom
qdot3:wip_manual_next_multiple_of

Conversation

@qdot3

@qdot3 qdot3 commented Aug 7, 2026

Copy link
Copy Markdown

View all comments

Implementation of #16728 and part of #14543.

This lint suggests (checked_)next_multiple_of instead of manual reimplementation. These two patterns are supported:

let _ = a.div_ceil(b) * b;
let _ = a + (b - a % b) % b;

Note that (a + b - 1) / b * b is for manual_bit_width.

changelog:

changelog: new lint: [`manual_next_multiple_of`]

@rustbot rustbot added the S-waiting-on-community-reviews Status: This is awaiting for positive reviews from the community before a maintainer is assigned. label Aug 7, 2026
@rustbot

rustbot commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request, and welcome!

You should hear from one of our reviewers after this PR gets at least 2 reviews from the community.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@rustbot rustbot added needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Aug 7, 2026
@rustbot

This comment has been minimized.

@CommanderStorm CommanderStorm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

community review:
I have a few comments on the code, but mostly nits (should not do them this much likely).

There are a few cases where I think there might be a missing comment why not in a few cases (or a false negative), but that should be easy to clear up for you.

Overall, pretty clean and well done.
@rustbot author

View changes since this review

Comment thread tests/ui/manual_next_multiple_of.fixed Outdated
Comment thread clippy_lints/src/manual_next_multiple_of.rs Outdated
_ => return,
};

let mut app = Applicability::MaybeIncorrect;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

in what kind of cases might it be incorrect? 🤔

Have you done an lintcheck run?
What are the results/rationale here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

There is no way to detect no-op macro like this:

macro_rules! identity {
    ( $e:expr ) => {
        $e
    };
}

It leads to false positives because no-op macros must be modified in the futuer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If macros are the only source of fails, can we not lint on these macros then?

False positives are not ideal.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

False positives are not ideal.

I agree with you but they seem to be inevitable in this case. Please see #17472 (comment) for reference.

Comment thread clippy_lints/src/manual_next_multiple_of.rs Outdated
Comment thread clippy_lints/src/manual_next_multiple_of.rs Outdated
Comment on lines +148 to +157
let (a, b, x) = if let Some((lhs, rhs)) = unpack_bin_op(lhs1, BinOpKind::Rem) {
(rhs1, rhs, lhs)
} else
// lhs = a
// rhs = x % b
if let Some((lhs, rhs)) = unpack_bin_op(rhs1, BinOpKind::Rem) {
(lhs1, rhs, lhs)
} else {
return None;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is checked_rem not possible here?

@qdot3 qdot3 Aug 11, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

There are two patterns worth considering:

  1. if u2 > 0 { u1.div_ceil(u2) * u2 } else { /* do something */ }
    The ideal suggestion depends on the else block. IMO, this might be too much.

    • u1.checked_next_power_of_two(u2).unwrap_or(x) if the else expression has no side effects
    • u1.checked_next_power_of_two(u2).unwrap_or_else(|| foo()) if the else expression has side effects
    • normal suggestion if the else block is complex
  2. u1.checked_add((u2 - u1.checked_rem(u2)?).checked_rem(u2)?)
    I can implement this, but it will take some time.

Are there any other patterns we should consider?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment on lines +159 to +169
// x = b - a % b
if let Some((lhs, rhs)) = unpack_bin_op(x, BinOpKind::Sub)
&& eq_expr_value(cx, expr.span.ctxt(), lhs, b)
&& let Some((lhs, rhs)) = unpack_bin_op(rhs, BinOpKind::Rem)
&& eq_expr_value(cx, expr.span.ctxt(), lhs, a)
&& eq_expr_value(cx, expr.span.ctxt(), rhs, b)
{
Some((a, b))
} else {
None
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is checked_sub and checked_rem not possible here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Since a - b % a can never overflow, checked_sub should ideally be handled by a separate lint such as needless_checked_sub (which does not exist yet). In contrast, checked_rem should be considered.

Comment thread clippy_lints/src/manual_next_multiple_of.rs Outdated
Comment on lines +193 to +201
// `v = 2^k - 1`
if integer_const(cx, c, expr.span.ctxt()).is_some_and(|v| v & v.wrapping_add(1) == 0) {
if eq_expr_value(cx, expr.span.ctxt(), b, c) {
Some((a, c))
} else if eq_expr_value(cx, expr.span.ctxt(), a, c) {
Some((b, c))
} else {
None
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this doing what it should? Are you here not testing if v + 1 == 0?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread clippy_lints/src/manual_next_multiple_of.rs Outdated
@qdot3
qdot3 marked this pull request as draft August 10, 2026 04:49
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Aug 10, 2026
@qdot3
qdot3 force-pushed the wip_manual_next_multiple_of branch from 6193d70 to 58e12e2 Compare August 10, 2026 05:55
@qdot3
qdot3 marked this pull request as ready for review August 10, 2026 08:42
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Aug 10, 2026
@rustbot

rustbot commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@qdot3
qdot3 marked this pull request as draft August 11, 2026 03:56
@qdot3
qdot3 marked this pull request as ready for review August 11, 2026 11:33
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Aug 11, 2026
@qdot3
qdot3 marked this pull request as draft August 11, 2026 14:00
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Aug 11, 2026
@qdot3
qdot3 marked this pull request as ready for review August 11, 2026 14:14
@qdot3

qdot3 commented Aug 11, 2026

Copy link
Copy Markdown
Author

@rustbot review

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Aug 11, 2026

@CommanderStorm CommanderStorm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

community review:
LGTM, lets add the comments from the discussions as code comments, since this might not be clear for future readers.

Also, don't forget to sure to squash your changes into one commit 😉

View changes since this review

};

// x = b - a % b
if let Some((lhs, rhs)) = unpack_bin_op(x, BinOpKind::Sub)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lets document #17517 (comment)

Suggested change
if let Some((lhs, rhs)) = unpack_bin_op(x, BinOpKind::Sub)
// Since a - b % a can never overflow, checked_sub is not hanled and intentionally
if let Some((lhs, rhs)) = unpack_bin_op(x, BinOpKind::Sub)

Some(Self::DivCeil { a, b })
} else
// lhs = b
// rhs = a.div_ceil(b)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
// rhs = a.div_ceil(b)
// rhs = a.div_ceil(b)
// no handling of checked_div_ceil, because does not exist

$e
};
}
// This is an inevitable false positive

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

actually document the reason 😉
Future readers will thank you why this is an "inevitable" false positive

@rustbot

rustbot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes (possibly #17538) made this pull request unmergeable. Please resolve the merge conflicts.

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 S-waiting-on-community-reviews Status: This is awaiting for positive reviews from the community before a maintainer is assigned. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants