In a compound member assignment whose base is a const binding still in its temporal dead zone, perry evaluates the computed key first and then throws a TypeError. Per spec, the base is evaluated first, so reading the binding throws a ReferenceError before the key expression runs.
Repro (main 34acf64e, v0.5.1656, Linux x86_64):
let calls = 0;
function f(): string { calls++; return "a"; }
try {
tdz[f()] += 1;
console.log("no throw");
} catch (e: any) {
console.log(e.constructor.name, "f calls:", calls);
}
const tdz: any = { a: 1 };
| runtime |
output |
| node |
ReferenceError f calls: 0 |
| bun |
ReferenceError f calls: 0 |
| perry |
TypeError f calls: 1 |
Two differences: the key's side effect (f()) runs when it shouldn't, and the error type is wrong. The TDZ check on the base is either skipped or ordered after the key spill in the compound-assignment lowering (hoist_compound_member_assign, crates/perry-hir/src/lower/expr_assign.rs).
Found while building #11825, which doesn't change this behaviour: that PR keeps the base temp, ahead of the key, whenever the key is spilled.
In a compound member assignment whose base is a
constbinding still in its temporal dead zone, perry evaluates the computed key first and then throws a TypeError. Per spec, the base is evaluated first, so reading the binding throws a ReferenceError before the key expression runs.Repro (main
34acf64e, v0.5.1656, Linux x86_64):ReferenceError f calls: 0ReferenceError f calls: 0TypeError f calls: 1Two differences: the key's side effect (
f()) runs when it shouldn't, and the error type is wrong. The TDZ check on the base is either skipped or ordered after the key spill in the compound-assignment lowering (hoist_compound_member_assign,crates/perry-hir/src/lower/expr_assign.rs).Found while building #11825, which doesn't change this behaviour: that PR keeps the base temp, ahead of the key, whenever the key is spilled.