Skip to content

fix(rewrite): prevent walrus operator double evaluation in assertions#14447

Open
RonnyPfannschmidt wants to merge 5 commits intopytest-dev:mainfrom
RonnyPfannschmidt:ronny/fix-14445-assert-walrus
Open

fix(rewrite): prevent walrus operator double evaluation in assertions#14447
RonnyPfannschmidt wants to merge 5 commits intopytest-dev:mainfrom
RonnyPfannschmidt:ronny/fix-14445-assert-walrus

Conversation

@RonnyPfannschmidt
Copy link
Copy Markdown
Member

Summary

Fixes #14445 — assertion rewriting evaluated walrus operator (:=) expressions multiple times, causing incorrect test results when the expression had side effects.

Root cause: The variables_overwrite mechanism stored NamedExpr AST nodes and re-evaluated them in subsequent assertions, in _call_reprcompare's results tuple, and in explanation formatting.

Fix: Remove variables_overwrite entirely and instead:

  • Keep walrus expressions in their natural evaluation position (preserving left-to-right order)
  • Reference the target variable in explanations instead of re-evaluating
  • Freeze conflicting left operands via assign() when a comparator walrus targets the same name
  • Capture BoolOp conditions in stable temps for the explanation path

Test plan

  • Issue reproducer passes (both test_walrus_in_assertion_basic and test_walrus_running_counter)
  • All 19 existing walrus/namedexpr tests pass
  • Full test_assertrewrite.py suite passes (118 tests; only pre-existing subprocess env failures excluded)
  • Added 3 regression tests in TestIssue14445
  • Pre-commit hooks pass (ruff, mypy, codespell)

Made with Cursor

Fixes pytest-dev#14445 - assertion rewriting evaluated NamedExpr (:=) expressions
multiple times, causing side effects to fire repeatedly.

The root cause was the `variables_overwrite` mechanism which stored and
re-evaluated NamedExpr AST nodes in subsequent assertions, in
`_call_reprcompare`'s results tuple, and in explanation formatting.

The fix:
- visit_NamedExpr: reference the target variable in explanations instead
  of re-evaluating the full expression
- visit_Compare: assign left-side NamedExpr to a temp before right-side
  hoisting; freeze left_res when a comparator walrus targets the same
  name; replace NamedExpr entries in `results` with target variables
- visit_BoolOp: capture short-circuit condition in a stable temp for the
  explanation path; remove walrus target rename logic
- visit_Call: remove variables_overwrite substitution (walrus now properly
  assigns to user variables in its natural evaluation position)
- Remove variables_overwrite, scope tracking, Sentinel class

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Sonnet 4 <claude@anthropic.com>
Copilot AI review requested due to automatic review settings May 8, 2026 08:38
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes assertion rewriting so walrus (:=) expressions are not evaluated multiple times, preventing side effects from running twice and producing incorrect rewritten-assert behavior (per #14445).

Changes:

  • Removes the prior variables_overwrite/scope-tracking mechanism and adjusts NamedExpr handling to avoid re-evaluation in explanations.
  • Updates BoolOp/Compare rewriting to stabilize conditions/operands for explanation formatting.
  • Adds new regression tests for walrus side-effect/double-evaluation cases and updates existing expected assertion output.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/_pytest/assertion/rewrite.py Refactors assertion-rewrite AST generation around NamedExpr, BoolOp, and Compare to avoid walrus re-evaluation.
testing/test_assertrewrite.py Updates expected assertion output and adds regression tests for #14445 scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/_pytest/assertion/rewrite.py Outdated
Comment thread src/_pytest/assertion/rewrite.py Outdated
Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Sonnet 4 <claude@anthropic.com>
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label May 8, 2026
RonnyPfannschmidt and others added 3 commits May 8, 2026 13:04
Add tests for two remaining walrus double-evaluation scenarios:
- Bare NamedExpr as BoolOp operand evaluated twice via condition check
- Same walrus target in chained comparison evaluated multiple times

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Sonnet 4 <claude@anthropic.com>
Use the already-assigned res_var to build the short-circuit condition
instead of the raw visitor result, preventing bare NamedExpr operands
from being evaluated a second time when checking truthiness.

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Sonnet 4 <claude@anthropic.com>
In a chained comparison like `(x := f()) < (x := g()) < (x := h())`,
each NamedExpr comparator is now assigned to a temp variable so it
evaluates exactly once. Previously the raw NamedExpr node would be
reused as left_res in the next iteration, causing double evaluation.

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Sonnet 4 <claude@anthropic.com>
@RonnyPfannschmidt RonnyPfannschmidt added the backport 9.0.x apply to PRs at any point; backports the changes to the 9.0.x branch label May 8, 2026
@RonnyPfannschmidt RonnyPfannschmidt mentioned this pull request May 9, 2026
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport 9.0.x apply to PRs at any point; backports the changes to the 9.0.x branch bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Walrus expression duplicate evaluation failures with rewrite

2 participants