Skip to content

Support multiple assignment targets in Python frontend#937

Merged
olivier-aws merged 11 commits intomainfrom
support-multiple-assignment-targets
Apr 16, 2026
Merged

Support multiple assignment targets in Python frontend#937
olivier-aws merged 11 commits intomainfrom
support-multiple-assignment-targets

Conversation

@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator

@keyboardDrummer-bot keyboardDrummer-bot commented Apr 16, 2026

PR requested and agreed on by @olivier-aws

Desugar a = b = 5 (and a = b = c = 1, etc.) into individual assignments for each target, all sharing the same RHS expression. This removes the "Multiple assignment targets not yet supported" error.

Changes

  • Strata/Languages/Python/PythonToLaurel.lean: Replace the single-target guard with a loop over all targets, calling translateAssign for each one and threading the translation context through.
  • Test files: Move test_multi_assign.py and test_multiple_assign_targets.py from pending/ to the active test suite and add them to cbmc_expected.txt.

Verification

  • Lean build succeeds (lake build Strata.Languages.Python.PythonToLaurel ✅)
  • Full strata toIon pipeline succeeds for both test files ✅
  • All existing Python tests continue to pass (VerifyPythonTest, ToLaurelTest ✅)

Fixes #890

Desugar `a = b = 5` into individual assignments for each target,
all sharing the same RHS expression. This removes the
'Multiple assignment targets not yet supported' error.

Moves test_multi_assign.py and test_multiple_assign_targets.py
out of pending/ into the active test suite.

Fixes #890
CBMC cannot verify assertions in these tests because the mathematical
functions (PEq, Any_to_bool, normalize_any) are uninterpreted in the
GOTO model. The Strata pipeline translation is correct, but CBMC
reports VERIFICATION FAILED for the assert statements.
@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator Author

The CBMC CI failure was caused by incorrect expected outcomes in cbmc_expected.txt. The two new tests (test_multi_assign.py and test_multiple_assign_targets.py) were marked as PASS but CBMC reports VERIFICATION FAILED.

Root cause: The Python-to-GOTO pipeline correctly translates the multi-assignment code, but the generated GOTO model uses uninterpreted mathematical functions (PEq, Any_to_bool, normalize_any, etc.) for the assertion checks. CBMC cannot reason about the return values of these uninterpreted functions, so it reports verification failure for any test that contains assert statements.

This is consistent with the existing tests — all tests with assertions (test_arithmetic, test_comparisons, test_control_flow, test_strings) are already marked as FAIL. The only PASS test (test_composite_return) has no assertions at all.

Fix: Changed expected outcomes from PASS to FAIL in cbmc_expected.txt (commit f934f5f).

@olivier-aws
Copy link
Copy Markdown
Contributor

@keyboardDrummer-bot with this solution, it seems to me that the rhs of a multi-assignment would be evaluated multiple times. This is not correct if it has side effects, as the Python semantics is to evaluate the rhs only once and affect it then to all the lhs. Can you confirm?

@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator Author

@olivier-aws You're right. The current implementation calls translateAssign once per target, and translateAssign internally calls translateExpr ctx rhs each time (line ~1063), so the RHS expression gets translated — and would be evaluated at runtime — once per target.

In Python, a = b = f() evaluates f() exactly once and assigns the result to both a and b. The correct desugaring should be:

tmp = <rhs>
target_n = tmp
...
target_1 = tmp

(Note: Python assigns right-to-left across targets, but left-to-right within tuple unpacking.)

This is similar to the tuple unpacking pattern already in translateAssign (lines 1048–1060), which introduces a fresh temporary. I'll fix this by evaluating the RHS into a temporary variable first, then assigning that temporary to each target.

Introduce a temporary variable for the RHS expression when there are
multiple assignment targets (e.g. a = b = f()). This matches Python
semantics where the RHS is evaluated exactly once and then assigned
to each target.
@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator Author

Good catch — I've pushed a fix in bd44966.

The multi-assignment a = b = expr is now desugared as:

multi_assign_<offset> = expr   # evaluate RHS once
a = multi_assign_<offset>      # assign temp to each target
b = multi_assign_<offset>

This follows the same temporary-variable pattern already used for tuple unpacking. The single-target case (a = expr) is short-circuited to avoid the unnecessary temporary.

Verified: lake build Strata.Languages.Python.PythonToLaurel ✅, ToLaurelTest ✅, VerifyPythonTest ✅.

@olivier-aws
Copy link
Copy Markdown
Contributor

@keyboardDrummer-bot Looks great. Can you extend the tests for multi assignment to also check the case when the this has side effects?

Test that a = b = c.next() evaluates the RHS exactly once:
- New .py test file: test_multi_assign_side_effect.py
- Inline VerifyPythonTest: verifies the full pipeline handles it
- cbmc_expected.txt: added new test as SKIP (class methods)
@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator Author

Done — pushed eb86939 with a side-effect test for multi-assignment.

New test: test_multi_assign_side_effect.py uses a Counter class whose next() method increments self.count and returns it. The test does:

c: Counter = Counter()
a = b = c.next()
assert a == 1
assert b == 1
assert c.count == 1  # next() called exactly once

This would fail if the RHS were evaluated per-target (counter would be 2 for b, and c.count would be 2).

Also added an inline test in VerifyPythonTest.lean that runs the same program through the full Python → Laurel → Core → SMT pipeline.

Verified: lake build StrataTest.Languages.Python.VerifyPythonTest ✅, ToLaurelTest ✅.

@olivier-aws olivier-aws marked this pull request as ready for review April 16, 2026 14:39
@olivier-aws olivier-aws requested a review from a team April 16, 2026 14:39
@olivier-aws
Copy link
Copy Markdown
Contributor

@keyboardDrummer-bot resolve git conflicts

@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator Author

Resolved the merge conflict in StrataTest/Languages/Python/VerifyPythonTest.lean. Both sides added a new test at the end of the file — our branch added the multi-assign side-effect test and main added the print() keyword arguments test. I kept both.

Pushed as commit 32d9c6e.

Added comment regarding analysis imprecision affecting asserts.
Comment thread StrataTest/Languages/Python/tests/pending/test_multi_assign_side_effect.py Outdated
Comment thread StrataTest/Languages/Python/tests/cbmc_expected.txt Outdated
Comment thread Strata/Languages/Python/PythonToLaurel.lean
Replaced vague comment with a precise explanation of why the test is
in pending/ (CBMC cannot verify asserts due to uninterpreted functions
from Counter class methods) and what the test verifies (multi-assignment
evaluates RHS exactly once).
@olivier-aws olivier-aws enabled auto-merge April 16, 2026 19:55
@olivier-aws olivier-aws added this pull request to the merge queue Apr 16, 2026
Merged via the queue into main with commit f4715ae Apr 16, 2026
17 checks passed
@olivier-aws olivier-aws deleted the support-multiple-assignment-targets branch April 16, 2026 20:09
keyboardDrummer-bot added a commit that referenced this pull request Apr 17, 2026
Resolve 106 .expected file conflicts: take main's new assertion lines
(from #761 and #937) while removing Any_to_bool / ite_cond_calls_Any_to_bool
lines per this PR's truthiness changes, and recount DETAIL lines.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python frontend: multiple assignment targets not supported (2 tests)

5 participants