-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Added warning for unused arithmetic expressions #50149
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @aidanhs (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
src/libcore/internal_macros.rs
Outdated
@@ -38,6 +38,7 @@ macro_rules! forward_ref_binop { | |||
}; | |||
(impl $imp:ident, $method:ident for $t:ty, $u:ty, #[$attr:meta]) => { | |||
#[$attr] | |||
#[must_use] |
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.
Is the impl
the right place for this attribute? I thought the lint only looked at methods on trait definitions.
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.
Sorry, I didn't read the mentoring instructions before — this fix needs a bit of tweaking:
- Instead of adding
#[must_use]
to method definitions in impls, you need to add them to the trait definitions, e.g. here:Line 97 in 699eb95
fn add(self, rhs: RHS) -> Self::Output; - You don't need any of the
#[must_use]
in the macros, for the same reason.
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 fully understand what is going on in the impl
, though I'm a little unsure of where else I would put the attribute. The recurrence of #[$attr]
in each impl
made it seem as though the attributes need to be repeated before each possible combination of T
and U
states.
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, ok. I can go fix that. Thanks for the quick support. I also just learned that even though GitHub will automatically show new messages as they come in, I need to refresh to see edits.
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.
Take a look at PartialOrd
, which implements lt
(<
):
Lines 642 to 650 in 699eb95
#[inline] | |
#[must_use] | |
#[stable(feature = "rust1", since = "1.0.0")] | |
fn lt(&self, other: &Rhs) -> bool { | |
match self.partial_cmp(other) { | |
Some(Less) => true, | |
_ => false, | |
} | |
} |
The
#[must_use]
attribute is on the definition in trait PartialOrd
, but not on any of the impl PartialOrd
. The way #[must_use]
works at the moment, it's only the trait declaration which is important — everything else inherits from the trait. (That way, anyone who implements an operator will get the advantage of #[must_use]
without having to add it themselves.
So, to be clear, you can add the attribute just above the stability attribute here:
Lines 96 to 97 in 699eb95
#[stable(feature = "rust1", since = "1.0.0")] | |
fn add(self, rhs: RHS) -> Self::Output; |
(And the same for all the other operator-related traits.)
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.
Ah, that was my misunderstanding, sorry. Adding #[must_use]
to the trait methods affects all implementations downstream, not just arithmetic on numerical primitives.
If someone has #[deny(unused_must_use)]
this would be a breaking change. I don't know what we do for expanding the scopes of lints.
src/librustc_lint/unused.rs
Outdated
@@ -103,6 +103,11 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedResults { | |||
cx.span_lint(UNUSED_MUST_USE, expr.span, msg); | |||
op_warned = true; | |||
}, | |||
hir::BiAdd | hir::BiSub | hir::BiDiv | hir::BiMul | hir::BiRem => { |
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.
You might as well add the remaining binary operators here (https://github.com/rust-lang/rust/blob/master/src/librustc/hir/mod.rs#L1029-L1046) at the same 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.
I suggested keeping the match
to determine the error message.
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.
Yeah, different messages sound good 👍
We want the remaining operators too, though (the same message as the other non-comparison operators is fine).
src/librustc_lint/unused.rs
Outdated
@@ -103,6 +103,11 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedResults { | |||
cx.span_lint(UNUSED_MUST_USE, expr.span, msg); | |||
op_warned = true; | |||
}, | |||
hir::BiAdd | hir::BiSub | hir::BiDiv | hir::BiMul | hir::BiRem => { | |||
let msg = "unused arithmetic operation which must be used"; |
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.
- While you're here, maybe add the bitops too?
a^b;
giving a warning also makes sense to me - Unary ops too?
-x
seems to also fit "unused arithmetic operation which must be used" - At that point, is it just all ops? The only three left are
*
,&&
, and||
. (The short-circuiting ones I could see an argument for not having the lint , sincefoo() || panic!()
is possible, but...)
Edit: And @varkor beat me to at least one of the comments 😆
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 the logical and/or should still be covered by the lint as there is a result being ignored there. People should be preferring if
for conditional execution.
In fact for bonus points you could print a help message in this specific case, although I would check if the right side of the op isn't just a boolean literal or something.
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 agree with covering unary ops; just look at where this method is called and create an equivalent for ExprKind::UnaryOp
.
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'm working on the UnaryOp
pattern matching. While matching on binary operators, it matches on bin_op.node
. However, the UnOp
type doesn't have this field. I've tried to find the definition of this type but I've come up with nothing so far. Any suggestion as to what file I should check out?
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.
UnOp
is an enum defined here. BinOp
is an enum as well but it's wrapped in a struct that gives it span information, which I assume was considered unnecessary for unary ops.
All the hir::
types are defined in src/librust/hir.mod.rs
. For convenience all the enums have their variants reexported in this module as well.
I made the changes discussed. Warnings are now thrown whenever an operator is evaluated but not used. The warning is specific to the type of operator that went unused. The |
Putting |
src/librustc_lint/unused.rs
Outdated
}, | ||
} | ||
} | ||
if let hir::ExprUnary(un_op, ..) = expr.node { |
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 would combine this and the if let hir::ExprBinary
bit above into a single match
, along with deduplicating the cx.span lint(...); op_warned = true;
lines.
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 is the best practice for combining these two match statements? Combining them seems like it would require nested matches since ExprBinary
and ExprUnary
are different types. I have to match on op.node
for binary operators but only on op
for unary operators. Should I implement this nested system or is there a better way?
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 nested match is fine. It's mostly about eliminating the redundant code here.
src/librustc_lint/unused.rs
Outdated
hir::BiAdd | hir::BiSub | hir::BiDiv | hir::BiMul | hir::BiRem => { | ||
let msg = "unused arithmetic operation which must be used"; | ||
cx.span_lint(UNUSED_MUST_USE, expr.span, msg); | ||
op_warned = 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.
I would extract the above two lines out of each arm and just have each arm return its relevant message and then emit the lint at the end.
src/librustc_lint/unused.rs
Outdated
hir::BiAnd | hir::BiOr => { | ||
let msg = "unused logical operation which must be used"; | ||
cx.span_lint(UNUSED_MUST_USE, expr.span, msg); | ||
op_warned = 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.
For bonus points you could emit a help message here saying to use if (first_op) { second_op }
instead, but that's a bit involved. I might tack that on myself in a separate PR if you don't want to get into it.
I don't see a lot of UI tests for lints in general so I don't know how we want to test these additions. |
Note that
Given that RFC 1193 lint capping exists specifically to preven the breakage from spreading to dependent crates, it's probably OK sometimes (but is definitely OK here given
There's a src/test/ui/lint directory, but this would probably go with rust/src/test/ui/rfc_1940-must_use_on_functions/fn_must_use.rs (which should be moved to src/test/ui/lint/ on stabilization). |
@zackmdavis This change also affects the arithmetic, the logical/bitwise operators and the unary operators. Where would those lint tests go. |
new file |
I updated the match statements to reduce redundant code. Because I am not incredibly familiar with pattern matching, I introduced a bool that says whether to send the lint message. Without this check, it could throw empty warnings on expressions that were correct. |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
src/librustc_lint/unused.rs
Outdated
_ => {}, | ||
match expr.node { | ||
hir::ExprBinary(bin_op, ..) => { | ||
should_warn_op = 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.
let must_use_op = match expr.node {
hir::ExprBinary(bin_op, ..) => {
match bin_op.node {
hir::BiEq | hir::BiLt | hir::BiLe | hir::BiNe | hir::BiGe | hir::BiGt => {
Some("comparison")
},
hir::BiAdd | hir::BiSub | hir::BiDiv | hir::BiMul | hir::BiRem => {
Some("arithmetic operation")
},
hir::BiAnd | hir::BiOr => {
Some("logical operation")
},
hir::BiBitXor | hir::BiBitAnd | hir::BiBitOr | hir::BiShl | hir::BiShr => {
Some("bitwise operation")
},
}
},
hir::ExprUnary(..) => {
Some("unary operation")
},
_ => None
}
if let Some(must_use_op) {
cx.span_lint(UNUSED_MUST_USE, expr.span, format!("unused {} which must be used", must_use_op));
op_warned = true;
}
After seeing that, it seemed pretty obvious. I made the necessary changes and it is a lot cleaner than it was. Hopefully it all looks good now. |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
src/librustc_lint/unused.rs
Outdated
}, | ||
} | ||
}, | ||
hir::ExprUnary(..) => { |
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 just realized we can save two lines by writing this as hir::ExprUnary(..) => Some("unary operation"),
rather than with a block (obviously this is a criticism of my earlier review comment, not you)
Needs tests (probably a new UI test file at src/test/ui/lint/must_use_ops.rs or similar; don't forget the For the benefit of future software-archeologists, I argue that it's also worth squashing these 6+ commits down into 1. (GitHub truncates the commit message summary line at 72 characters, so something like "make must-use lint cover binary operations" (42 chars) is better than 12d227aff's 95 chars.) |
// Hardcoding the comparison operators here seemed more | ||
// expedient than the refactoring that would be needed to | ||
// look up the `#[must_use]` attribute which does exist on | ||
// the comparison trait methods |
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 comment (or a rephrasing of it; the word "comparison", at least, is no longer appropriate) should be preserved. (It was specifically requested by a reviewer.)
Should I include each operator or just one from each category, i.e., do I need to include an expected warning for |
I'd say all of them. (The marginal cost (in test run times) of additional bytes of source in a test file that would exist anyway is going to be very small.) |
ee8abeb
to
e8864ba
Compare
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
I rebased the commits into one that has a better name. This new commit also includes added tests. |
looks good to me (but I don't have the authority to approve PRs) |
(Standard practice is to start a new branch in your own repository, rather than working on master; this surprised me just now as I began to rebase #48925 off this.) |
That makes sense. I'll do that next time I submit a PR somewhere. Thank you for helping me out with all of this. |
@bors r+ |
📌 Commit 91aa267 has been approved by |
Added warning for unused arithmetic expressions The compiler now displays a warning when a binary arithmetic operation is evaluated but not used. This resolves #50124 by following the instructions outlined in the issue. The changes are as follows: - Added new pattern matching for unused arithmetic expressions in `src/librustc_lint/unused.rs` - Added `#[must_use]` attributes to the binary operation methods in `src/libcore/internal_macros.rs` - Added `#[must_use]` attributes to the non-assigning binary operators in `src/libcore/ops/arith.rs`
☀️ Test successful - status-appveyor, status-travis |
In the last Nightly I still don't see those warnings. Perhaps I am missing something? |
@leonardo-m You need |
The compiler now displays a warning when a binary arithmetic operation is evaluated but not used. This resolves #50124 by following the instructions outlined in the issue. The changes are as follows:
src/librustc_lint/unused.rs
#[must_use]
attributes to the binary operation methods insrc/libcore/internal_macros.rs
#[must_use]
attributes to the non-assigning binary operators insrc/libcore/ops/arith.rs