-
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
New Lint: branches_sharing_code
#6463
New Lint: branches_sharing_code
#6463
Conversation
r? @flip1995 (rust-highfive has picked a reviewer for you, use r? to override) |
if_shares_code_with_else
if_shares_code_with_else
511c888
to
4637f05
Compare
I'll have to fix some if statements in the project. This is a good example what I mean with "this lint is a bit aggressive". I'll do that in a separate commit tomorrow 🙃 |
if_shares_code_with_else
if_shares_code_with_else
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 like the concept of this lint. But I also think that this will have many FPs. So this should be allow-by-default.
tests/ui/shared_code_in_if_blocks.rs
Outdated
let _z = if x == 8 { | ||
println!("Branch 1"); | ||
let z = 10; | ||
foo!(); | ||
z | ||
} else { | ||
println!("Branch 2"); | ||
let z = 10; | ||
foo!(); | ||
z | ||
}; |
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.
What does the lint do, if you add a slightly different variant of this test:
let _z = if x == 8 {
println!("Branch 1");
let mut z = 1;
z += 10;
foo!();
z
} else {
println!("Branch 2");
let mut z = 2;
z += 10;
foo!();
z
};
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 lint would suggest to move the last three lines out of the blocks to prevent code duplication:
Lint output
error: All if blocks contain the same code at the end
--> $DIR/test_file.rs:174:9
|
LL | / z += 10;
LL | | foo!();
LL | | z
LL | | } else {
| |_____^
|
= help: Consider moving the code out of the if statement to prevent code duplication
This is an excellent example why this lint is aggressive and why the suggestions are not as automatically applicable. A possible rewrite would be:
let _z = {
let mut z = if x == 8 {
println!("Branch 1");
1
} else {
println!("Branch 2");
2
};
z += 10;
foo!();
z
}
This is not always the nicest way especially for simple statements like these. This can also be linked to the discussion in #3770 about the if_same_then_else
what could should actually be moved out. I sadly don't have a better suggestion right now and I believe that this is okay for now since the lint is allowed by default.
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.
So this case could be prevented, if we could make sure that:
- The definition of the assigned variable is before the if-else-block (aka not in the if-else-block) OR
- the definition of the assigned variable is contained in the
end
block this lint suggests to move out of the if-else-block.
1. implies 2. here. So collecting all of the definitions in the if-else and then check if the assigned to variable is in that collection should be a good heuristic.
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 hard part of implementing this was to decide what should be lined and what not. I see your suggestion and I'll try to implement it :)
tests/ui/shared_code_in_if_blocks.rs
Outdated
println!("End of block"); | ||
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.
I have a feeling that many tests are missing. Mainly tests where the returned value is present somewhere in the code that should be moved out. I think we should just not lint if the return value is used in the end stmts, but can lint if it is only used in the start stmts.
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 think that even these cases should be included in the lint since it can still be code duplication. My previous comment shows a rewrite where the return value is not a part of the linted code.
The main reason why I'm not in favor of your suggestion is the implementation. This has enabled me to reuse the provided SpanlessEq
implementation with very few changes. Adding the simple rule that the return value needs to be in the moved code would in my opinion add a lot of edge cases and limit the use of SpanlessEq
since it wasn't designed for value tracking. It can quickly end up in a lot of complex code which is difficult to maintain for little benefit.
That's my opinion with my current knowledge, but I might have overlooked something. I'm still fairly new to clippy and I still learn something every time I work on it 🙃.
I've implemented your other suggestions. Thank you for those, they were definitely helpful 👍. I'll wait on an answer on this comment before adding a bunch of tests (and probably separating them into different files to make it more readable) 🙃
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 think rewriting it like you suggested above makes it way harder to read and we shouldn't suggest that. Also this is not always possible. For example if z
is modified, but not returned. With my suggested change, I don't think you have to modify existing code, but rather just add a visitor and an additional check.
In general, I don't think a lint just saying "this is duplicated" without an easy suggestion how to improve it, is a good lint. We shouldn't say: "this is wrong, have fun rewriting it" without giving guidance on how to do it.
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.
Jup, the rewrite is definitely not pretty 😅.
The lint currently only suggests moving the code out of the block. Making a correct concrete suggestion seamed difficult as the lint didn't make sure that the moved values will also be available after the load. This should now be possible with your suggested change to make sure that the return value is included in the moved code or at least available in the outer scope. It will need some fine-tuning and probably some testing but should be possible 🙃.
How can I make it allow by default, do I just change the lint category to Your suggestions look god at a first glance I'll probably work on them tomorrow 🙃 Thank you for the review! |
Yes, just put it in the pedantic group. 👍 |
1ff5e48
to
04db249
Compare
if_shares_code_with_else
if_shares_code_with_else
Yes, I can definitely see that, and you don't have to provide an auto applicable suggestion here. My argument is, that if we can't even provide a suggestion, because it would be too hard, we also shouldn't lint. If you're stuck or finished with the thing I requested and want the next review, let me know by pinging me or requesting a review via the GitHub UI. |
☔ The latest upstream changes (presumably #6544) made this pull request unmergeable. Please resolve the merge conflicts. |
6b13418
to
d7195a8
Compare
☔ The latest upstream changes (presumably #6586) made this pull request unmergeable. Please resolve the merge conflicts. |
d2b051e
to
aa88925
Compare
Hey @flip1995, thank you for sending me back to the drawing board with the previous implementation of this lint. The implementation is now a bit more complex but way better in my opinion. It checks that all used values can be assessed in the new moved-to scope. It also adds some helpful notes if the suggestion is most likely not 100% correct. You can see some examples in the test output. This implementation is now up for review if the pipeline passes :) I changed the lint category to @rustbot label -S-waiting-on-author |
if_shares_code_with_else
if_shares_code_with_else
Thanks for putting all this work into this! Sadly I have to defer the review to another time (hopefully next weekend). I'm already reviewing for 4h and my brain is mush.
yeah sure. |
610535c
to
a6f54f5
Compare
The rebase was simple and the PR is ready for merge from my side. Thank you for the review! I know that it was quite a monster to begin with 😅 . The next ones will be smaller 🙃 |
@bors r+ thanks a lot! 😄 |
📌 Commit a6f54f5 has been approved by |
New Lint: `branches_sharing_code` This lint checks if all `if`-blocks contain some statements that are the same and can be moved out of the blocks to prevent code duplication. Here is an example: ```rust let _ = if ... { println!("Start"); // <-- Lint for code duplication let _a = 99; println!("End"); // <-- Lint for code duplication false } else { println!("Start"); let _b = 17; println!("End"); false }; ``` This could be written as: ```rust println!("Start"); let _ = if ... { let _a = 99; false } else { let _b = 17; false }; println!("End"); ``` --- This lint will get masked by the `IF_SAME_THEN_ELSE` lint. I think it makes more sense to only emit one lint per if block. This means that the folloing example: ```rust if ... { let _a = 17; } else { let _a = 17; } ``` Will only trigger the `IF_SAME_THEN_ELSE` lint and not the `SHARED_CODE_IN_IF_BLOCKS` lint. --- closes: #5234 changelog: Added a new lint: `branches_sharing_code` And hello to the one that is writing the changelog for this release :D
💥 Test timed out |
Homu seems to be stuck, another PR from Rust itself is also struck in the pending stage for the last hour 🤔 bors queue |
It only tests one PR at a time. @bors retry |
Yeah, this PR remained in the pending stage for the two hours. I've asked in the infra zulip stream if humo might be stuck. #6905 seems to be in the same state. It's also interesting that retry isn't working. |
New Lint: `branches_sharing_code` This lint checks if all `if`-blocks contain some statements that are the same and can be moved out of the blocks to prevent code duplication. Here is an example: ```rust let _ = if ... { println!("Start"); // <-- Lint for code duplication let _a = 99; println!("End"); // <-- Lint for code duplication false } else { println!("Start"); let _b = 17; println!("End"); false }; ``` This could be written as: ```rust println!("Start"); let _ = if ... { let _a = 99; false } else { let _b = 17; false }; println!("End"); ``` --- This lint will get masked by the `IF_SAME_THEN_ELSE` lint. I think it makes more sense to only emit one lint per if block. This means that the folloing example: ```rust if ... { let _a = 17; } else { let _a = 17; } ``` Will only trigger the `IF_SAME_THEN_ELSE` lint and not the `SHARED_CODE_IN_IF_BLOCKS` lint. --- closes: #5234 changelog: Added a new lint: `branches_sharing_code` And hello to the one that is writing the changelog for this release :D
💥 Test timed out |
@bors retry |
☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test |
This lint checks if all
if
-blocks contain some statements that are the same and can be moved out of the blocks to prevent code duplication. Here is an example:This could be written as:
This lint will get masked by the
IF_SAME_THEN_ELSE
lint. I think it makes more sense to only emit one lint per if block. This means that the folloing example:Will only trigger the
IF_SAME_THEN_ELSE
lint and not theSHARED_CODE_IN_IF_BLOCKS
lint.closes: #5234
changelog: Added a new lint:
branches_sharing_code
And hello to the one that is writing the changelog for this release :D