-
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
if_same_then_else Should not warn for else if
blocks.
#3770
Comments
IMHO this lint is good as it is. If you have a situation where separating the if conditions makes the logic more readable you should allow this lint. From my experience this lint has in fact two purposes:
Example for 2.: if c1 {
// some code
} else if c2 {
// some other code
} else if c3 {
// same code as 1, because copy pasted, but forget to adapt that one line
} else {
// ...
} If we'd change the lint in a way that only consecutive branches would get linted, the second case would never be caught. And the second case can even lead to bugs, while the first one is just code style. TL;DR: I wouldn't change the lint, but add the second point to the "Why is this bad?" section of the documentation. |
In the example you gave I also think that it would have too many false positives. There are lots of cases where the actual value is trivial compared to the complexity of the condition so you wouldn't want to merge it. Additionally the more conditions that are between the two identical branches the more complicated the condition would have to become. The "correct" fix in the example you provided is: if c1 || (c3 && !c2) {
// some code
} else if c2 {
// some other code
} else {
// ...
} Unless the code in the statement body is quite complicated this is much harder to understand. And if the conditions have side effects it isn't even correct. Also I'm not proposing warning on any consecutive branches. I would only warn when the last if and the else branches are identical because in this case you could just omit the condition which will (almost) always result in simpler code. I guess it comes down to if you want to avoid false positives or false negatives. Maybe I can propose that this is split into two lints. One will warn when two consecutive branches are identical but not be in |
I think you misunderstood me there. The correct fix for my example would not be to merge the branches, but to change the code in the branch of condition 3, since it is copy pasted and should do something different (this happened to me in my last bigger project and saved me a lot of time). The problem with the suggestion is though, that the lint can't know if merging the branches is what the programmer wants or changing the code in one branch is the right thing to do. (*) Technically this isn't a false positive, since the if bodies are actually the same, so the lint does what it's supposed to do. For the merge of the two conditions in the example I provided, following should be enough: if c1 || c3 {
// some code
} else if c2 {
// some other code
} else {
// ...
} If (*) Maybe also add a note to the lint for this case, additionally to the suggestion. |
Well I guess we can agree to disagree. If you don't want to change it could we add another lint for the case I suggested? To be perfectly frank the lint has far too many false positives for me to use the way it is now but if it was broken in two then at least I could catch the cases I am interested in. |
I agree that this works in the simple case. However if I have to sprinkle lint ignores all over my code it becomes too distracting. I see that we also have a different definition of false positive. In my mind a false positive is "The linter bothered me, but the code could not be improved". This is a very different definition then the linter not having a bug. |
I've labeled this as
That would be possible
If you (completely) disagree with a lint, you can write |
I'd suggest that a user seeing this warning when doing error handling, eg: if stituation {
Err(...)
} else if second_situation {
Err(...)
} else {
Ok()
} That this may be a good place to think about creating a new class of errors. Even though there's no bug "as-is" in the logic, adding some fidelity to the return value improves the product. |
It may also be helpful to offer computed unions/intersections in the error report where possible, because mentally performing boolean operations between lots of different cases is sometimes tricky ( see the aforementioned You could always sit down and write a big ol' Karnaugh Map for this, but it seems viable a linter could just have the required logic in it to simplify the conditions automatically. |
Hi, I just want to provide a real example I encountered on this topic. I wrote a function that handles user input birthday: // User has already selected a month and also selected a day just now
// This functions adjust the month selection in case the combination is invalid
// There is also a special valid combination where month=0 and day=0,
// which means "Not provided"
fn adjust_birthmonth(day: u16, month: &mut u16) {
if day == 0 {
// month and day must be both zero for "not provided" combination
*month = 0;
} else if *month == 0 {
// if themonth is not provided but the day is, pick a valid month instead
*month = 1;
} else if day == 30 && *month == 2 {
// There is no Feb 30. Change to Jan instead
*month = 1;
// ^ clippy lint this on if_same_then_else.
// Technically these two blocks are indeed mergable,
// but their meanings are pretty different.
// So I really want to keep them separate to make the logic clear.
} else if day == 31 {
if let 2|4|6|9|12 = *month {
// There is no 31 for these months. Change to a valid month instead
*month -= 1;
}
}
} (I admit the overall UX design on this is not ideal, but that is a different topic) |
I agree that it makes sense to keep the arms separated here. But I think that in this case you should just allow this lint. I guess adding another lint or making this lint configurable is the way to go for this issue. |
In answer to @montrivo here, in particular this following excerpt of their comment:
The lint description says this:
I would argue that if the branches have wildly different bodies (when you take the comments into consideration), then it cannot possibly be a copy&paste error. And so, the lint’s design or implementation does not appear to be serving its initial purpose. Or that its purpose is misrepresented. Sure, you can |
Ha, I missed this remark. If I am reading this correctly you are asking me to change my correct code to incorrect code because the linter doesn't like it? My point is that this lint is flagging perfectly correct and readable code and incorrectly suggesting that it can be improved. This is what I call a false-positive and seems to be the thing that isn't supposed to exist in Another possible fix is to update the docs of |
Hi @kevincox I'm new to open source and would love to get started. Can I work on this issue? |
It's not my project. You would need to contact the maintainers. Hopefully they will see your interest and drop some starters here. Of course it seems that maintainers think this is working as intended ao I'm not sure they are interested in a patch at this point. |
@paridhi7 this is indeed not a good first issue, the label was added when the fix was thought to be just a documentation change. I think you could start with #5849, I will leave some mentoring instructions there in case you wish to tackle it. Don't hesitate to ask for support there or in Discord (#clippy channel or private message) if you get stuck. About this issue, just to give another opinion, I believe we should change the lint to be warn-by-default, as it has been shown that folks consider this useful sometimes, so it does not fit the It would be fun to try to implement @nagisa's suggestion :), but I would consider that heuristic experimental and not a candidate for a warn-by-default lint. |
Oh I thought I replied to this: I definitely see now that having this as a |
This lint is in my opinion there to avoid code duplication. Both this and issue #6285 trigger the lint as the statements and return of the blocks are the same. However, I agree that both are not direct cases of code duplication because they don't contain any logic and just a simple single expression. I would therefore suggest to only trigger this lint if the block contains actual statements. Single return statements like in the two issues would than be ignored. The lint help message could than suggest to either combine the conditions or to move the duplicate code into a local function. Some examples that are collapsed to keep it readable
There are two drawbacks that I see with this implementation:
What are your thoughts about this option? |
I don't think that is a problem. If you don't see it you don't know that it is there.
Maybe we could still lint on multiline return expressions. Generally I like your idea about skipping "simple" return expressions. |
What do you think about using cognitive complexity to determine if a return statement is "simple"? We could simply reuse the implementation of the |
Hmm, the cognitive complexity lint was abandoned for the reason that we are not able to actually decide how complex an expression is, because there are just to many factors. I'd like to here @felix91gr opinion on reusing stuff of the cognitive complexity implementation to determine the complexity of a single expression vs. a complete function (/a bigger scope). |
(Ohh. This is an interesting issue, @flip1995. Thanks for pinging me.) Okay so the reason why I stopped pursuing the cognitive complexity lint is because the approach I was using was just too incomplete and naïve. To keep a long story short:
So with the background out of the way, my thoughts are these: @xFrednet, I'd advise against it. I don't think the code I made is even remotely useful for this task. That said, open source is open source and I cannot tell you what you can do and cannot. But I think it can't be done with what I made, and you might encounter a lot of gratuitous hardship with no much reward if you try to use that implementation regardless 🙂 |
I wasn't aware of the discussions around the cognitive complexity lint that have already taken place. Thank you for clarifying this @flip1995 and @felix91gr. That was the main reason why I suggested it. So, back to the drawing board I guess 🙃 |
Here's my example: pub fn pc_degrade_announcement_target(ann_tar: u32, ann_age_blocks: u32) -> u32 {
if ann_age_blocks < ANN_WAIT_PERIOD {
} else if ann_age_blocks > 256 + ANN_WAIT_PERIOD {
} else if ann_age_blocks == ANN_WAIT_PERIOD {
return ann_tar;
} else {
let bn_ann_tar = bn_for_compact(ann_tar) << (ann_age_blocks - ANN_WAIT_PERIOD);
if bn_ann_tar.bits() < 256 {
let out = compact_for_bn(bn_ann_tar);
if out <= 0x207fffff {
return out;
}
}
}
0xffffffff
} I use this pattern of empty blocks often because or-chains become long and unreasonable and I don't think it should be considered an error. |
How about we allow // either there is an `else` here
else if a {
single_statement;
} else if b {
same_single_statement;
} else // ...or there is an `else` here |
Yeah, I think we can allow one-line similarities. We could use our |
A case that I just ran into that I think should be accepted: fn to_index(&self, x: usize, y: usize) -> Option<usize> {
if x < 0 || x >= self.w { // ignore for now that Clippy also complains about these
None
} else if y < 0 || y >= self.h {
None
} else {
Some(y * self.w + x)
}
} I think this is the clearest way to write the logic. The single-line heuristic would work great here. |
I also hit this. Since this is a deny-by-default lint, false positives are especially annoying. |
CC rust-lang#3770 From rust-lang#3770 (comment) (@flip1995): > Oh I thought I replied to this: I definitely see now that having this > as a correctness lint might be the wrong categorization. What we might > want to do is to just allow this lint, if there are comments in the > arm bodies. But a good first step would be to downgrade this lint to > style or complexity. I would vote for style since merging two arms is > not always less complex.
CC rust-lang#3770 From rust-lang#3770 (comment) (@flip1995): > Oh I thought I replied to this: I definitely see now that having this > as a correctness lint might be the wrong categorization. What we might > want to do is to just allow this lint, if there are comments in the > arm bodies. But a good first step would be to downgrade this lint to > style or complexity. I would vote for style since merging two arms is > not always less complex.
CC rust-lang#3770 From rust-lang#3770 (comment) (@flip1995): > Oh I thought I replied to this: I definitely see now that having this > as a correctness lint might be the wrong categorization. What we might > want to do is to just allow this lint, if there are comments in the > arm bodies. But a good first step would be to downgrade this lint to > style or complexity. I would vote for style since merging two arms is > not always less complex.
Change `if_same_then_else` to be a `style` lint CC #3770 From #3770 (comment) (`@flip1995):` > Oh I thought I replied to this: I definitely see now that having this > as a correctness lint might be the wrong categorization. What we might > want to do is to just allow this lint, if there are comments in the > arm bodies. But a good first step would be to downgrade this lint to > style or complexity. I would vote for style since merging two arms is > not always less complex. changelog: [`if_same_then_else`]: Change to be a `style` lint
I suppose the conclusion to this discussion would be:
Hopefully I didn't miss anything else, so... I'll adjusting the labels for this issue (ping me if wrong labels being added/removed) @rustbot label -A-documentation -A-lint +I-false-positive +good-first-issue |
FWIW I don't think one line is a great heuristic for this lint. But it would avoid all of the false-positives that I have personally run into. I also notice that "no false positives" has been removed from the description of So if that is the official judgement call I'm ok with it. |
There are many cases where code like this shouldn't be warned on. While you could do
condition1() || condition2()
often times it is more clear of the intent if they are separate. Especially if the conditions are non-trivial compared to the value being returned.I think the right time for this warning to warn is if the last
if
has the same condition as theelse
. For example the following two cases would warn.The text was updated successfully, but these errors were encountered: