B018: report useless unary and arithmetic operations - #579
Merged
Merged
Conversation
A statement such as '+c * d' left over from a broken continuation line does nothing. Shifts, bitwise operators and '@' are skipped because libraries overload them for side effects, as are operands that call, await, yield or assign, and operations directly in a try or with block, where tests run them to see them raise. Refs PyCQA#452.
cooperlees
approved these changes
Sep 28, 2026
cooperlees
left a comment
Collaborator
There was a problem hiding this comment.
This is muse (muse-spark), posting on behalf of @cooperlees via his coding agent.
Reviewed the diff and verified locally: checked out the PR head (29718a2, incl. the main-merge resolving the UNRELEASED changelog conflict with #578) and ran the full suite in an isolated venv in /tmp — 84 passed, and tests flagged no regressions in the neighboring B018/B908 eval files.
What I checked:
- The issue's case from #452 (
result = a * bfollowed by orphaned+c * d) is now reported as a uselessBinOp— good, this was a real gap in B018. - Operator scoping is well-judged: only arithmetic (
+ - * / // % **) count; shifts/bitwise/@are excluded since libraries overload them for side effects (Airflowtask1 >> task2, DSLs on|), and any non-arithmetic op anywhere in the expression tree vetoes the report. - Side-effect guard in
_is_useless_operation(Call/Await/Yield/YieldFrom/NamedExpranywhere in operands) correctly skipsf() + 1,-f(),(x := a) + 1. - The
try/withcarve-out works: I ran flake8 directly againsttests/eval_files/b908.pyand confirmed no new B018 fires on thewith raises: "1" + 1lines, only the pre-existing B017s. - Leaving out
Attribute(unlike the closed #540) is right — a bareobj.attrcan run a property getter. - New eval file
tests/eval_files/b018_operators.pyfires exactly the 12 expected lines (verified by running flake8 directly: lines 10-21); README note and UNRELEASED changelog entry are clear and accurate.
No changes requested. Approving for merge.
cooperlees
approved these changes
Sep 28, 2026
cooperlees
left a comment
Collaborator
There was a problem hiding this comment.
I agree with AI. Thanks for this!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #452.
B018 reported a statement that is only a literal, a tuple, or a call to a side-effect-free builtin. The case in the issue is an operation:
It was never reported. This adds the two node types @JelleZijlstra suggested there,
UnaryOpandBinOp, with the limits his comment pointed to:+ - * / // % **). Shifts, bitwise operators and@are left alone, because libraries overload them for their side effects: Airflow'stask1 >> task2, and pipeline or query DSLs built on|. An expression that contains any of them anywhere is skipped;f() + 1still runsf;tryorwithblock is skipped. That is where tests run one only to see it raise, as inwith pytest.raises(TypeError): "1" + 1.tests/eval_files/b908.pycontains exactly that, and it would otherwise start reporting.An earlier attempt, #540, was closed the same day with no review. It also added
Attribute, which this PR leaves out: a bareobj.attrcan run a property getter for its side effect.The message names the node like the other B018 cases:
Found useless BinOp expression/UnaryOp.Tests:
tests/eval_files/b018_operators.pyholds the 12 reported shapes (the issue's+c * d, each arithmetic operator, and-1,-a,+a,~a,not a) and the cases left alone: overloaded operators, side effects in operands, andtry/withblocks. Onmainit reports none of the 12. With the change,pytest testspasses on 3.12 (82 passed, 2 skipped) and on 3.10 (73 passed, 11 skipped).black --previewandflake8are clean, andmypy bugbear.pyreports the same 4 errors as onmain. There is also a README note and a changelog entry under UNRELEASED.