Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion src/decompiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21174,7 +21174,10 @@ let reopen = self
let group = std::mem::take(&mut self.pending_stores);
self.last_store_line = None;
self.pending_unpack_wrap.clear();
let all_same = group.windows(2).all(|w| expr_eq(&w[0].1, &w[1].1));
// COPY/DUP preserves the identity of one evaluated stack value.
// Structurally equal expressions can be independent evaluations:
// `left, right = read(), read()` must still call read twice.
let all_same = group.windows(2).all(|w| Rc::ptr_eq(&w[0].1, &w[1].1));
if all_same {
let value = group[0].1.clone();
let targets: Vec<ExprRef> = group.iter().map(|(t, _)| t.clone()).collect();
Expand Down
20 changes: 20 additions & 0 deletions tests/behavior/cases/b_assignment_identity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# MIN_VERSION: 3.9


def separate(read):
left, right = read(), read()
return left, right


def shared(read):
left = right = read()
return left, right


events = []
def read():
events.append("read")
return len(events)

print(separate(read), events)
print(shared(read), events)
20 changes: 20 additions & 0 deletions tests/fixtures/assignment_identity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Separate evaluations must not become one COPY-backed chained assignment.

def separate(read):
left, right = read(), read()
return left, right


def shared(read):
left = right = read()
return left, right


def separate_none():
left, right = None, None
return left, right


def shared_none():
right = left = None
return left, right
20 changes: 20 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ fn py310_basics() {
assert_contains(&src, &["while i < 10:", "v = 1 < x < 100"], "py3.10 basics");
}

#[test]
fn assignment_identity() {
for version in ["3.11", "3.12", "3.13", "3.14"] {
let fixture = format!("assignment_identity.{version}.pyc");
let (src, clean) = decompile_fixture(&fixture);
assert!(clean, "{fixture}: output marked incomplete:\n{src}");
assert_contains(
&src,
&[
"def separate(read):\n left, right = read(), read()\n return left, right",
"def shared(read):\n left = right = read()\n return left, right",
"def separate_none():\n left, right = None, None\n return left, right",
"def shared_none():\n right = left = None\n return left, right",
],
&fixture,
);
assert_eq!(src.matches("read()").count(), 3, "{fixture}:\n{src}");
}
}

#[test]
fn py311_comprehensions() {
let (src, clean) = decompile_fixture("comprehensions.3.11.pyc");
Expand Down
Binary file added tests/pyc/assignment_identity.3.11.pyc
Binary file not shown.
Binary file added tests/pyc/assignment_identity.3.12.pyc
Binary file not shown.
Binary file added tests/pyc/assignment_identity.3.13.pyc
Binary file not shown.
Binary file added tests/pyc/assignment_identity.3.14.pyc
Binary file not shown.