-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Prefer assert_eq!(.., false) to assert!(!..) #7990
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @giraffate (or someone else) soon. Please see the contribution instructions for more information. |
4bf19dc
to
3fc487b
Compare
Looks like the test failures are for |
I for one find |
I want to caution against introducing a new warn-by-default lint that is contrary to a current one. It is going to infuriate some users - see #7387 . There should be a compelling case for the change and this one already looks controversial. |
So from a pure process perspective: If we should introduce this new lint, we will have two contradicting lints, so at least one of them has to go into Personally, I also prefer Since you already mentioned in your first sentence of the PR, that people are indifferent about it, prefer the comparison to a literal, or, as you heard here from @llogiq and now me, prefer the shorter version, this is at best opinionated. So with your argument, we should not warn-by-default on both variants. I'm open to the argument that this lint is opinionated and should be allow-by-default. But looking at #7666, the numbers there don't really support that argument. So I would say, if this new lint gets added, it has to go into the For downgrading the existing lint, I'm not really convinced. |
After this conversation, I'm sympathetic to the idea that this new lint should not be warn-by-default, it's clearly more contentious than I realized. That also seems wise given that completely swapping the default would amount to lots of churn.
I now think that's the right approach. I don't see either of them being a slam dunk after hearing from you all. Importantly it's not a public API question and neither form is in itself an indicator of a bug (though I would argue that there's a potential to misread the inverted condition).
That table is a great data point, and can be very informative. But there are a few reasons why that's not the end of the story:
In particular, I would expect that published crates are significantly more likely to comply with the default (The below is an attempt to make the question less about individual style preference. Feel free to skip.) I've seen this exact question come up when helping to write and edit style guides at two different organizations in the past. Those style guides are unfortunately not public resources. I tried finding public style guides that comment on this question, but it is hard to find. Much more common is a discussion of avoiding comparison to literals in One interesting point is that many testing environments actually have a more specific semantic for this case. For instance, Perhaps the most directly-relevant public reference to the question is this accepted Stack Overflow answer which recommends asserting equality to false. But another (higher-voted, so yes it's contentious) answer cites PEP8's ban on literals in Rust is somewhat unique, in that the built-in test framework (particularly in combination with the expressive type system) is "good enough". It's not common to see custom assertion frameworks where a The other environment I've seen that has a similar context is Salesforce, where the built-in test framework is "good enough" and so custom assertions are not prevalent. There, as in Rust, the entire list of assertions is |
I think this is just one instance of a wider aversion to the One option is to split For the new lint added in the PR |
I guess I didn't explain clearly enough, because this is definitely not a general aversion to
There are cases where let mut state = InitialState;
state.transition(&input);
assert!(state.is_final()); There are cases where let deserialized: MyStruct = serde_json::deserialize(&input);
assert_eq!("foobar", deserialized.name);
assert_eq!(42, deserialized.count);
assert_eq!(false, deserialized.is_active); Both of these are assertions on boolean values, but they carry different semantics. |
This is the point where I say yes, that is a real downside, but this feels like a matter of personal preference.
I see what you're getting at here and this is a good point. You are checking a value rather than a condition. And from that standpoint, |
@couchand ping from triage. Should the discussion still continue? If you have any idea on this, feel free to comment here. |
@giraffate after the discussion above, I would agree with updating the new lint to
I'd agree, while recognizing that this form doesn't have the same readability hazard. |
☔ The latest upstream changes (presumably #8210) made this pull request unmergeable. Please resolve the merge conflicts. |
I'm fine with that 👍 |
@couchand ping from triage. Can you have any update on this? |
865e85d
to
fcde7de
Compare
@giraffate, I've rebased and updated per the most recent comments. |
fcde7de
to
00549b0
Compare
00549b0
to
ec3c227
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.
The tests fails, so can tests/ui/bool_assert_inverted.stderr
be updated?
/// | ||
/// ### Why is this bad? | ||
/// It is all too easy to misread the semantics of an assertion when the | ||
/// logic of the condition is reversed. Explicitly comparing to a boolean |
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.
nits
/// logic of the condition is reversed. Explicitly comparing to a boolean | |
/// logic of the condition is reversed. Explicitly comparing to a boolean |
macro_call.span, | ||
&format!("used `{}!` with an inverted condition", macro_name), | ||
"replace it with", | ||
format!("{}!(.., false, ..)", eq_mac), |
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.
Can the third argument be removed if there is no custom panic message?
☔ The latest upstream changes (presumably #8504) made this pull request unmergeable. Please resolve the merge conflicts. |
@couchand ping from triage. Can you have any questions to work on this? |
@couchand ping from triage. According the triage procedure, I'm closing this because 2 weeks have passed with no activity. If you have more time to work on this, feel free to reopen this. |
In numerous discussions over the years with developers at many different organizations, I've never heard a developer suggest that
assert!(!condition);
is better thanassert_eq!(condition, false);
. At best, developers are indifferent, but those who think deeply about such matters reliably prefer comparing against the explicit literal. It is easy to see why: the inversion of the condition is very likely to be missed when reading a list of assertions.This suggests that the
bool_assert_comparison
lint is backwards, at least in the case of comparing againstfalse
. When comparing againsttrue
, there are cases where a plainassert!()
is appropriate (such asassert!(maybe.is_none());
), and there are times when comparing against a literal is appropriate (e.g.assert_eq!(result, true);
), so there's no single right answer we should encourage.This PR adds a new lint
bool_assert_inverted
that checks to be sure you are not writing code of the formassert!(!condition);
where the inversion of the condition could be easily missed. It also moves the competingbool_assert_comparison
topedantic
to disable it by default. I'm not familiar enough with the clippy dev process to know how to remove it entirely, but that seems to be the right move, since the two lints cannot be enabled at the same time.changelog: Moves [
bool_assert_comparison
] topedantic
and introduces [bool_assert_inverted
] to check for easily-missed logic reversal in assertions.