Files
scriptc/tests/corpus/7006-evolving-array-aliases.ts
T
Chris Tate 33cf76d36e Keep object and array literals that hold an evolving empty array on that array
An object or array literal that held an evolving `const xs = []` between
writes of different element types (`const box = { items: xs }` or
`const rows = [xs]` after `xs.push(1)`, before `xs.push("two")`) took the
element type the checker gives the array at that point, so it held a copy.
`box.items.length` and `box.items === xs` printed `1 false`, where Node
prints `2 true`. Such a literal now builds as a checked-dynamic literal that
keeps the array, and an unannotated binding of it takes the checked-dynamic
type. This covers shorthand properties (`{ xs }`), nested literals, spreads
of a literal holder, aliases of a holder and names destructured from one.
Element reads through an alias or holder (`rows[0]`, `alias[1]`) read the
checked-dynamic value instead of checking it against the checker's type,
which lists only the elements written before the alias or holder was taken.

A shorthand property now counts as a read of the array when its reads are
compared, so `{ xs }` before a write of another element type makes the
array checked-dynamic too.

Fixes #1641.
2026-10-22 21:23:57 -05:00

128 lines
3.5 KiB
TypeScript

// An unannotated `const xs = []` inside a function whose writes store values
// of more than one element type: an alias taken between the writes
// (`const alias = xs`) and an alias of that alias name the same array, so
// they see every later write, as in Node. A write of another element type
// after reads that agree on one type also keeps the array untyped.
function chain(): string {
const xs = [];
xs.push(1);
const a = xs;
const b = a;
xs.push("two");
b.push(3);
return `${xs.join(",")} ${a.length} ${b.length} ${a === xs} ${b === xs}`;
}
function viaAlias(): string {
const xs = [];
xs.push("a");
const alias = xs;
alias.push("b");
xs.push(2);
return `${xs.join(",")} ${alias.join(",")}`;
}
function letAlias(): string {
const xs = [];
xs.push(true);
let alias = xs;
xs.push("t");
const seen = alias.length;
alias = [];
return `${seen} ${alias.length} ${xs.length}`;
}
function nested(flag: boolean): string {
const xs = [];
xs.push(1);
if (flag) {
const inner = xs;
xs.push("n");
return `${inner.length} ${inner.join(":")}`;
}
return `${xs.length}`;
}
function elementWrite(): string {
const xs = [];
xs.push(1);
const alias = xs;
xs[1] = "one";
return `${alias.length} ${alias.join("+")}`;
}
function agreeing(): string {
const xs = [];
xs.push(1);
const alias = xs;
xs.push(2);
return `${alias.length} ${alias === xs} ${alias.reduce((sum, v) => sum + v, 0)}`;
}
function total(values: number[]): number {
return values.length;
}
function passed(): string {
const xs = [];
xs.push(1);
const alias = xs;
const n = total(alias);
xs.push("z");
return `${n} ${alias.length}`;
}
console.log(chain());
console.log(viaAlias(), "|", letAlias());
console.log(nested(true), "|", nested(false), "|", elementWrite());
console.log(agreeing(), "|", passed());
function spreadSame(): number {
const xs = [];
xs.push(1);
const more = [2, 3];
xs.push(...more);
return xs.reduce((sum, v) => sum + v, 0);
}
console.log(spreadSame());
// An object or array literal that holds the array between the writes
// (`{ items: xs }`, `[xs]`, a shorthand property, a nested literal), a
// binding of such a literal, and a name destructured from one hold the
// same array, so they see the later writes.
function objectHolder(): string {
const xs = [];
xs.push(1);
const box = { items: xs, label: "box" };
xs.push("two");
box.items.push(3);
return `${box.items.length} ${box.items === xs} ${box.items[1]} ${box.label} ${xs.join(",")}`;
}
function arrayHolder(): string {
const xs = [];
xs.push(1);
const rows = [xs, [5]];
xs.push("two");
let total = 0;
for (const row of rows) total += row.length;
return `${rows[0].length} ${rows[0] === xs} ${rows[0][1]} ${total}`;
}
function shorthandHolder(): string {
const xs = [];
xs.push(1);
const box = { xs, count: 3 };
xs.push("two");
return `${box.xs.length} ${box.xs === xs} ${box.count}`;
}
function nestedHolder(): string {
const xs = [];
xs.push(1);
const box = { inner: [xs], pair: [{ items: xs }] };
const outer = { box };
xs.push("two");
return `${box.inner[0].length} ${box.pair[0].items === xs} ${outer.box.inner[0][1]}`;
}
function holderAliases(): string {
const xs = [];
xs.push(1);
const box = { items: xs };
const other = box;
const { items } = box;
xs.push("two");
return `${other.items.length} ${items.length} ${items === xs} ${JSON.stringify(other)}`;
}
console.log(objectHolder(), "|", arrayHolder(), "|", shorthandHolder());
console.log(nestedHolder(), "|", holderAliases());