Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(rewrite): assign walrus comparators to temps in chained comparisons
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>
  • Loading branch information
3 people committed May 8, 2026
commit ffdc372a3613faed3af8f6d9e86c030223d945ed
11 changes: 5 additions & 6 deletions src/_pytest/assertion/rewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,11 @@ def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]:
next_res, next_expl = self.visit(next_operand)
if isinstance(next_operand, ast.Compare | ast.BoolOp):
next_expl = f"({next_expl})"
# Assign NamedExpr comparators to a temp so each walrus evaluates
# exactly once — critical for chained comparisons where the same
# node would otherwise be re-evaluated as left_res next iteration.
if isinstance(next_res, ast.NamedExpr):
next_res = self.assign(next_res)
results.append(next_res)
sym = BINOP_MAP[op.__class__]
syms.append(ast.Constant(sym))
Expand All @@ -1103,12 +1108,6 @@ def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]:
res_expr = ast.copy_location(ast.Compare(left_res, [op], [next_res]), comp)
self.statements.append(ast.Assign([store_names[i]], res_expr))
left_res, left_expl = next_res, next_expl
# Replace NamedExpr entries in results with their target variable
# to avoid re-evaluating walrus operators in the explanation path.
results = [
ast.Name(r.target.id, ast.Load()) if isinstance(r, ast.NamedExpr) else r
for r in results
]
# Use pytest.assertion.util._reprcompare if that's available.
expl_call = self.helper(
"_call_reprcompare",
Expand Down
1 change: 0 additions & 1 deletion testing/test_assertrewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -1928,7 +1928,6 @@ def test_walrus_boolop():
result = pytester.runpytest()
assert result.ret == 0

@pytest.mark.xfail(reason="Chained compare re-evaluates walrus with same target")
def test_walrus_no_double_eval_chained_compare(self, pytester: Pytester) -> None:
"""Same walrus target in chained comparison must evaluate each once."""
pytester.makepyfile(
Expand Down
Loading