-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
base: master
Are you sure you want to change the base?
Conversation
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 (
|
This comment has been minimized.
This comment has been minimized.
90bb498
to
98b1666
Compare
Manual checked sub
]manual_checked_sub
]
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.
which will try to do: |
This comment has been minimized.
This comment has been minimized.
9141494
to
414b39d
Compare
This comment has been minimized.
This comment has been minimized.
414b39d
to
5ef4f44
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple nits
There was a problem hiding this 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;
}
if let ExprKind::Lit(lit) = &expr.kind { | ||
if let LitKind::Int(_, LitIntType::Unsuffixed) = &lit.node { | ||
return true; | ||
} | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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;
}
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
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); |
There was a problem hiding this comment.
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
.
tests/ui/manual_checked_sub.fixed
Outdated
} | ||
|
||
// Decrementing inside an if condition | ||
if let Some(result) = a.checked_sub(0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks wrong.
} | ||
} | ||
false | ||
} |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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()
?
There was a problem hiding this comment.
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
clippy_utils/src/msrvs.rs
Outdated
@@ -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 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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>) { |
There was a problem hiding this comment.
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 (
suggession
→suggestion
) - 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)?, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra parenthesis in fix.
You should have a look at the output of lintcheck. Most of the suggestions are wrong because of |
Thanks for the comprehensive feedback, I will resolve them all and submit an update.
|
Hope you don't mind since you reviewed this and are now a part of the clippy team :). |
@rustbot author |
This comment has been minimized.
This comment has been minimized.
d6c0130
to
0bbdebb
Compare
@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 |
You could use a |
Suggest using checked_sub() instead of manual implementation for basic cases.
Partially fixes #12894
changelog: new lint: [manual_checked_sub]