diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index 85be6bb53..c94bfa802 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -2610,14 +2610,14 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): // A key whose literal type names only declared fields of one type // writes the field it names. JavaScript evaluates the receiver, then // the key, then the value; the dispatch reads the saved receiver and - // key once per named field. + // key once per named field. Locals are saved too, because the key or + // the value can reassign them. const named = litKey === null ? sameTypedFieldKeys(lowerer, shape, target.argumentExpression) : null; if (named && !shape.fields.every((f) => typeEquals(f.type, named.fieldType))) { const loc = locOf(expr); const body: IrStmt[] = []; const save = (value: IrExpr, name: string): IrExpr => { - if (value.kind === "varRef") return value; const temp = lowerer.declareHiddenLocal(name, value.type); body.push({ kind: "varDecl", localId: temp.id, init: value, loc }); return varRef(temp.id, temp.type, loc); diff --git a/tests/corpus/record-literal-key-unions.ts b/tests/corpus/record-literal-key-unions.ts index afe328e98..cb9336455 100644 --- a/tests/corpus/record-literal-key-unions.ts +++ b/tests/corpus/record-literal-key-unions.ts @@ -76,3 +76,23 @@ function only(s: Stats, key: "bytes"): number { return s[key]; } console.log(only(stats[3]!, "bytes")); + +// A key or value that reassigns the receiver or key local: the write goes +// to the receiver and key evaluated before them. +let target = stats[0]!; +let slot: Counter = "hits"; +target[slot] = ((slot = "misses"), 500); +console.log(stats[0]!.hits, stats[0]!.misses, slot); +target[slot] = ((target = stats[2]!), 600); +console.log(stats[0]!.misses, stats[2]!.misses, target === stats[2]); +function retarget(): Counter { + target = stats[3]!; + return "bytes"; +} +target = stats[1]!; +target[retarget()] = 700; +console.log(stats[1]!.bytes, stats[3]!.bytes === 700); +target = stats[0]!; +slot = "hits"; +target[slot] += ((slot = "bytes"), (target = stats[1]!), 1); +console.log(stats[0]!.hits, stats[0]!.bytes, stats[1]!.hits);