-
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 manual_abs_diff
lint
#14482
add manual_abs_diff
lint
#14482
Conversation
df165b3
to
dfc3d13
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.
This should probably be restricted to standard integral types. This will be a false positive, with a suggestion causing a compilation error:
#[derive(Eq, PartialEq, PartialOrd)]
struct S(i32);
impl std::ops::Sub for S {
type Output = S;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
fn main() {
let (a, b) = (S(10), S(20));
let _ = if a < b { b - a } else { a - b };
}
Right, I'll fix that. |
ed0bacf
to
2332038
Compare
Since I started the review already and have more to say: |
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 is starting to be in a good shape. My issues right now are mostly with style, which feels a bit too verbose compared to most of the Clippy code. It is good not to have huge chunks of code, but creating intermediary types just for the sake of separating functions and using the types once seems a bit too much.
For example, the suggestions I make here should allow you to remove about 60 lines of code from this short lint (more than 35%) while keeping the intent clear and having the check_expr()
method go straight to the point.
clippy_lints/src/manual_abs_diff.rs
Outdated
if !expr.span.from_expansion() | ||
&& let Some(suggestion) = self.is_manual_abs_diff_pattern(cx, expr) | ||
{ | ||
emit_suggestion(cx, &suggestion); | ||
} |
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.
Given the relatively low complexity of the lint, separating the check and emitting phase does not bring a lot of clarity. Moreover, it makes you introduce one additional function, one additional method, and two intermediate Suggestion
and Input
types.
You can inline the calls, and rewrite them a bit to be more concise. For example, it may not be needed to match all names of the If
struct right away, I you can later refer to them by field access. Also, expansing the Spanned { OpKind, … }
is often not done, preferring to refer to the .node
attribute to get the operator kind later.
That could give something like:
if !expr.span.from_expansion() | |
&& let Some(suggestion) = self.is_manual_abs_diff_pattern(cx, expr) | |
{ | |
emit_suggestion(cx, &suggestion); | |
} | |
if !expr.span.from_expansion() | |
&& let Some(if_expr) = If::hir(expr) | |
&& let Some(r#else) = if_expr.r#else | |
&& let ExprKind::Binary(op, rhs, lhs) = if_expr.cond.kind | |
&& let (BinOpKind::Gt | BinOpKind::Ge, a, b) | (BinOpKind::Lt | BinOpKind::Le, b, a) = (op.node, rhs, lhs) | |
&& self.are_ty_eligible(cx, a, b) | |
&& Self::is_sub_expr(cx, if_expr.then, a, b) | |
&& Self::is_sub_expr(cx, r#else, b, a) | |
{ | |
let a = Sugg::hir(cx, a, "..").maybe_paren(); | |
let b = Sugg::hir(cx, b, ".."); | |
span_lint_and_sugg( | |
cx, | |
MANUAL_ABS_DIFF, | |
expr.span, | |
"manual absolute difference pattern without using `abs_diff`", | |
"replace with `abs_diff`", | |
format!("{a}.abs_diff({b})"), | |
Applicability::MachineApplicable, | |
); | |
} |
which is readable, and more inline with the code usually found in Clippy. You can then remove is_manual_abs_diff_pattern()
, emit_suggestion()
, and the Suggestion
and Input
types.
I'll comment about are_ty_eligible()
in another comment.
clippy_lints/src/manual_abs_diff.rs
Outdated
fn is_ty_applicable(&self, cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | ||
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs(); | ||
(matches!(expr_ty.kind(), ty::Uint(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF)) | ||
|| (is_type_diagnostic_item(cx, expr_ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF)) | ||
} |
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.
Rather than checking each type individually, you can first make sure that they are identical, then check for their eligibility for the lint:
fn is_ty_applicable(&self, cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | |
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs(); | |
(matches!(expr_ty.kind(), ty::Uint(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF)) | |
|| (is_type_diagnostic_item(cx, expr_ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF)) | |
} | |
fn are_ty_eligible(&self, cx: &LateContext<'_>, a: &Expr<'_>, b: &Expr<'_>) -> bool { | |
let a_ty = cx.typeck_results().expr_ty(a).peel_refs(); | |
a_ty == cx.typeck_results().expr_ty(b).peel_refs() | |
&& ((matches!(a_ty.kind(), ty::Uint(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF)) | |
|| (is_type_diagnostic_item(cx, a_ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF))) | |
} |
2332038
to
1df41b5
Compare
@samueltardieu applied all your suggestions, looks much better now, thanks! |
1df41b5
to
0f43428
Compare
Also got rid of matching against the destructuring of |
I'll have a new look later, thanks for the quick reaction. Can you also check for the presence of comments within the original expression? In this case, I think this is still a good idea to lint, but we should make the suggestion |
0f43428
to
191a1a8
Compare
Took comments into account, plus fixed the lint for literals (I hope...) |
191a1a8
to
a44e69f
Compare
a44e69f
to
d85ca8a
Compare
…errors Use `abs_diff` where applicable Very small cleanup, dogfooding a [new clippy lint](rust-lang/rust-clippy#14482) I'm trying to add
…errors Use `abs_diff` where applicable Very small cleanup, dogfooding a [new clippy lint](rust-lang/rust-clippy#14482) I'm trying to add
Rollup merge of rust-lang#139026 - yotamofek:pr/abs-diff, r=compiler-errors Use `abs_diff` where applicable Very small cleanup, dogfooding a [new clippy lint](rust-lang/rust-clippy#14482) I'm trying to add
Thanks for the review and thorough explanation, much appreciated! Just a small question about this:
wouldn't the compiler just error out because of the ambiguous type in that case, meaning the lint won't even run? |
c0a9541
to
f382f58
Compare
Unless we do the same complete type inference as the compiler, we will not see that some types may be certain. For example in fn f(_: &mut u32) {}
fn main() {
let mut b = 3;
f(&mut b);
let _ = if 5 < b {
5 - b
} else {
b - 5
};
} the compiler knows that |
IMO we should still suggest it in the face of likely inference errors, just as |
Agreed. I'll let just pass a few days to see if I have time to better inference detection, as I have some reservations in not putting a test with integers when I know it will fail. And:
|
New cases of |
As of today, the definition for Also, our testsuite applies |
In clippy as long as it produces valid syntax we use Our tests apply every suggestion no matter the applicability, suggestions that don't compile go in a separate file with rustfix disabled |
I'll wait a bit with this PR to see if @samueltardieu can fix the issues with #14492, because that's obviously the best solution to the problem with ambiguous types, |
This appears to be a can of worm: every time I add some new uncertainty resolution, it gets too conservative, and when I add some new, I find edge cases. I will continue working on this in the background, but it may take time. What I propose so that you can go forward is to check if the left hand side is an unsuffixed literal. If it is, and the right hand side is not, swap them. We may get some suggestions that will cause errors, but they should be very rare and easy to fix for the user, and then when the expr type certainty check is better, we may switch to it, as we could in many lints. Could you do the change, and let's see what the lintcheck run says? You may also want to rebase your commit, because of recent fixes in lintcheck handling in the CI.
I don't think this would be beneficial. |
In parallel, I've started the "final comment period" thread for this lint. |
@pitaj suggested in the Zulip thread to also check for @yotamofek would you be willing to add this as a second commit, so that it can be squashed if appropriate or separated into a future PR if not? If you are not interested in doing this, no problem, we can open a new issue for enhancing this lint. |
Sure, sounds good @samueltardieu ! Thanks!! re: |
Yes, it was a silly suggestion, forget about it. |
f382f58
to
52a3082
Compare
@samueltardieu I think this is ready for review, hope I didn't miss anything |
changelog: [
manual_abs_diff
]: Initial implementationHey, first time writing a new lint for clippy, hope I got it right. I think it's pretty self-explanatory!
Added a few
fixme
test cases, where the lint can be improved to catch more (probably rare) patterns, but opening a PR with this initial implementation to make sure I'm on the right track, and that this lint is acceptable at all.😁