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 new lint [manual_checked_sub] #14236

Open
wants to merge 22 commits into
base: master
Choose a base branch
from

Conversation

benacq
Copy link

@benacq benacq commented Feb 16, 2025

Suggest using checked_sub() instead of manual implementation for basic cases.

Partially fixes #12894

changelog: new lint: [manual_checked_sub]

@rustbot
Copy link
Collaborator

rustbot commented Feb 16, 2025

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @dswij (or someone else) some time within the next two weeks.

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 the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Feb 16, 2025
@rustbot

This comment has been minimized.

@rustbot rustbot added has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Feb 16, 2025
@benacq benacq force-pushed the manual_checked_sub branch from 90bb498 to 98b1666 Compare February 16, 2025 22:59
@rustbot rustbot removed has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Feb 16, 2025
@benacq benacq changed the title Add new lint [Manual checked sub] Add new lint [manual_checked_sub] Feb 16, 2025
@samueltardieu
Copy link
Contributor

a.checked_sub(b) will return an Option. The test cases seem to "work" because the results are always ignored.

The following code will fail if the proposed fix is applied:

    if a >= b {
        let _ = (a-b)+1;
    }

@benacq
Copy link
Author

benacq commented Feb 17, 2025

a.checked_sub(b) will return an Option. The test cases seem to "work" because the results are always ignored.

The following code will fail if the proposed fix is applied:

    if a >= b {
        let _ = (a-b)+1;
    }

Thanks for the feedback, i think get what you mean.
that will evaluate to

    a.checked_sub(b) + 1

which will try to do: Option<T> + 1, that will fail.
i will rectify and update the PR

@rustbot rustbot added has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Mar 1, 2025
@rustbot

This comment has been minimized.

@benacq benacq force-pushed the manual_checked_sub branch from 9141494 to 414b39d Compare March 1, 2025 23:20
@rustbot

This comment has been minimized.

@benacq benacq force-pushed the manual_checked_sub branch from 414b39d to 5ef4f44 Compare March 1, 2025 23:24
@rustbot rustbot removed has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Mar 1, 2025
Copy link
Contributor

@pitaj pitaj left a comment

Choose a reason for hiding this comment

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

Couple nits

Copy link
Contributor

@samueltardieu samueltardieu left a comment

Choose a reason for hiding this comment

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

I don't know what to think about the result name. It seems generic, and moreover it may exist. For example, the suggestion for this code will fail to compile:

let mut result = 0;
if a >= b {
    result = a - b;
}

because it will suggest

if let Some(result) = a.checked_sub(b) {
    result = result;
}

Comment on lines 180 to 184
if let ExprKind::Lit(lit) = &expr.kind {
if let LitKind::Int(_, LitIntType::Unsuffixed) = &lit.node {
return true;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

When can this part trigger? Wouldn't the expression be typed already? Is there a test which fails if we remove this?

Copy link
Author

Choose a reason for hiding this comment

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

Just realized i didn't include the test case for this in this commit, but this triggers for untyped integer literals that can be inferred as unsigned int.
example expression:

    if 10 >= 5 {
        let _ = 10 - 5;
    }

Copy link
Contributor

Choose a reason for hiding this comment

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

Just realized i didn't include the test case for this in this commit, but this triggers for untyped integer literals that can be inferred as unsigned int. example expression:

    if 10 >= 5 {
        let _ = 10 - 5;
    }

Does it? Because in are_literals_equal() it looks like you require literals to be suffixed in order to return true, so unsuffixed literals should not match.

I think the literal minus literal case can be forgotten about anyway, as I said, it makes no sense to suggest using .checked_sub() when the result can be determined at compilation time.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, that's right, i just added the test case locally and realized it still failed. However that was the intended use of that snippet.
I'm not sure if i get your last statement correctly, are you suggesting we don't trigger .checked_sub lint when we are dealing with literals?

Copy link
Author

Choose a reason for hiding this comment

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

Oh got it, never mind.

i missed this part of the review when i was going through it, just saw it, thanks again for feedback, will send and update soon.

return;
}

let applicability = Applicability::MachineApplicable;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why define a variable if its value cannot change? You can use the enum variant directly instead of this variable. In fact, since SubExprVisitor::applicability is not modified during the traversal either, this field can be dropped entirely, and made local to SubExprVisitor::build_suggession().

let applicability = Applicability::MachineApplicable;

if let ExprKind::If(drop_temp_expr, body_block, else_block) = expr.kind
&& let ExprKind::DropTemps(condition_expr) = drop_temp_expr.kind
Copy link
Contributor

Choose a reason for hiding this comment

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

By using clippy_utils::higher::If::hir(expr), you would not have to deal with DropTemps, especially if at one point the HIR changes, that would be covered by the higher module for every lint.

Comment on lines 70 to 95
SubExprVisitor {
cx,
if_expr: expr,
if_body_block: body_block,
else_block,
applicability,
condition_lhs: lhs,
condition_rhs: rhs,
}
.visit_expr(body_block);
}

if let BinOpKind::Gt = op.node {
if let ExprKind::Lit(lit) = &rhs.kind
&& let LitKind::Int(Pu128(0), _) = lit.node
{
SubExprVisitor {
cx,
if_expr: expr,
if_body_block: body_block,
else_block,
applicability,
condition_lhs: lhs,
condition_rhs: rhs,
}
.visit_expr(body_block);
Copy link
Contributor

Choose a reason for hiding this comment

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

Both code fragments seem identical, maybe they should be factored in. Also, is it right that this code will recognize if a >= b, but not if b <= a? Same question with a > 0 vs. a < 0.

}

// Decrementing inside an if condition
if let Some(result) = a.checked_sub(0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks wrong.

}
}
false
}
Copy link
Contributor

Choose a reason for hiding this comment

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

A blank line is missing after this line.

}
false
}
fn is_referencing_same_variable<'tcx>(expr1: &'tcx Expr<'_>, expr2: &'tcx Expr<'_>) -> bool {
Copy link
Contributor

@samueltardieu samueltardieu Mar 5, 2025

Choose a reason for hiding this comment

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

Couldn't this function be made simpler by using clippy_utils::path_to_local()?

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for mentioning this, looks super helpful, however, one limitation i noticed is that, it does not work for casted types. issue here

@@ -50,7 +50,7 @@ msrv_aliases! {
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
1,50,0 { BOOL_THEN, CLAMP, SLICE_FILL }
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST }
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST, MANUAL_CHECKED_SUB }
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
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST, MANUAL_CHECKED_SUB }
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST, CHECKED_SUB }

}

impl<'tcx> SubExprVisitor<'_, 'tcx> {
fn build_suggession(&mut self, expr: &Expr<'tcx>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The name has several issues:

  • Typo (suggessionsuggestion)
  • It doesn't build anything (a builder usually returns a value).

Maybe emit_lint() would be a better name.

RangeLimits::HalfOpen => upper_bound - lower_bound,
RangeLimits::Closed => (upper_bound - lower_bound).checked_add(1)?,
RangeLimits::HalfOpen => result,
RangeLimits::Closed => (result).checked_add(1)?,
Copy link
Contributor

Choose a reason for hiding this comment

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

Extra parenthesis in fix.

@samueltardieu
Copy link
Contributor

You should have a look at the output of lintcheck. Most of the suggestions are wrong because of .checked_sub(0), but once this is fixed, we should examine whether they are beneficial or not.

@benacq
Copy link
Author

benacq commented Mar 5, 2025

Thanks for the comprehensive feedback, I will resolve them all and submit an update.

You should have a look at the output of lintcheck. Most of the suggestions are wrong because of .checked_sub(0), but once this is fixed, we should examine whether they are beneficial or not.

@dswij
Copy link
Member

dswij commented Mar 27, 2025

r? @samueltardieu

Hope you don't mind since you reviewed this and are now a part of the clippy team :).

@rustbot rustbot assigned samueltardieu and unassigned dswij Mar 27, 2025
@samueltardieu
Copy link
Contributor

@rustbot author

@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Mar 27, 2025
@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) label Mar 27, 2025
@rustbot rustbot added the has-merge-commits PR has merge commits, merge with caution. label Apr 7, 2025
@rustbot

This comment has been minimized.

@benacq benacq force-pushed the manual_checked_sub branch from d6c0130 to 0bbdebb Compare April 7, 2025 17:50
@rustbot rustbot removed the has-merge-commits PR has merge commits, merge with caution. label Apr 7, 2025
@benacq
Copy link
Author

benacq commented Apr 7, 2025

I don't know what to think about the result name. It seems generic, and moreover it may exist. For example, the suggestion for this code will fail to compile:

let mut result = 0;
if a >= b {
    result = a - b;
}

because it will suggest

if let Some(result) = a.checked_sub(b) {
    result = result;
}

@samueltardieu I think this is the only feedback from your initial review that i have not fixed yet, i want to write a name generator function that checks the scope and append a counter to the name if it already exist but i am have a little challenge with checking the scope, could you please recommend some pointers on how i can go about this? or any existing implementation of this? thank you

@samueltardieu
Copy link
Contributor

i want to write a name generator function that checks the scope and append a counter to the name if it already exist but i am have a little challenge with checking the scope, could you please recommend some pointers on how i can go about this? or any existing implementation of this? thank you

You could use a Visitor to check what variable names are used within the if block. But the real challenge will be to choose a meaningful name IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add lints against more manual integer ops where direct methods exist
5 participants