Skip to content

B018: report useless unary and arithmetic operations - #579

Merged
cooperlees merged 2 commits into
PyCQA:mainfrom
Eljees:b018-unary-and-arithmetic
Sep 28, 2026
Merged

cooperlees merged 2 commits into
PyCQA:mainfrom
Eljees:b018-unary-and-arithmetic

Conversation

@Eljees

@Eljees Eljees commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

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:

result = a * b
+c * d  # meant as the continuation of the line above

It was never reported. This adds the two node types @JelleZijlstra suggested there, UnaryOp and BinOp, with the limits his comment pointed to:

  • only arithmetic operators count (+ - * / // % **). Shifts, bitwise operators and @ are left alone, because libraries overload them for their side effects: Airflow's task1 >> task2, and pipeline or query DSLs built on |. An expression that contains any of them anywhere is skipped;
  • an operation whose operands call, await, yield or assign is skipped, since f() + 1 still runs f;
  • an operation directly in a try or with block is skipped. That is where tests run one only to see it raise, as in with pytest.raises(TypeError): "1" + 1. tests/eval_files/b908.py contains 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 bare obj.attr can 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.py holds 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, and try/with blocks. On main it reports none of the 12. With the change, pytest tests passes on 3.12 (82 passed, 2 skipped) and on 3.10 (73 passed, 11 skipped). black --preview and flake8 are clean, and mypy bugbear.py reports the same 4 errors as on main. There is also a README note and a changelog entry under UNRELEASED.

Eljees and others added 2 commits September 25, 2026 11:17
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.
Keep both B018 (PyCQA#452) and B042 (PyCQA#538) entries (PR PyCQA#578 merged first).

@cooperlees cooperlees left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 * b followed by orphaned +c * d) is now reported as a useless BinOp — 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 (Airflow task1 >> 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/NamedExpr anywhere in operands) correctly skips f() + 1, -f(), (x := a) + 1.
  • The try/with carve-out works: I ran flake8 directly against tests/eval_files/b908.py and confirmed no new B018 fires on the with raises: "1" + 1 lines, only the pre-existing B017s.
  • Leaving out Attribute (unlike the closed #540) is right — a bare obj.attr can run a property getter.
  • New eval file tests/eval_files/b018_operators.py fires 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 cooperlees left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with AI. Thanks for this!

@cooperlees
cooperlees merged commit fa5fe9e into PyCQA:main Sep 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants