Files
scriptc/tests/corpus/in-array-runtime-index.ts
Malte Ubl 93b4994220 Lower runtime-key in over arrays and dictionaries to presence probes
`i in arr` with a non-literal number key boxed the typed array into the
checked-dynamic tree and materialized every element per evaluation, so
`for (...) if (i in arr)` loops were quadratic (1e4 elements: 5.3 s vs
Node 0.7 ms). A number key over an array receiver now lowers to arrayHas
(scr_arr_has), the query the literal fold and Object.hasOwn already use;
the key is stabilized first to keep JS evaluation order.

`k in dict` with a runtime string key over an index-signature record
snapshotted and scanned every overflow key per probe (1e4 keys: 436 ms,
1e5: timeout). The helper now ends in recordOvfHas (scr_map_has_str).
2026-10-08 15:44:26 -05:00

75 lines
2.6 KiB
TypeScript

// `i in arr` with a RUNTIME numeric key over a typed array answers with the
// array's own slot/property presence query (holes, length, ordinary numeric
// properties) instead of materializing the array per probe. The large loop
// at the end used to be quadratic.
const dense: number[] = [10, 20, 30];
const probes: number[] = [0, 1, 2, 3, -1, 1.5, NaN, Infinity, -Infinity, -0, 4294967294, 4294967295];
console.log(probes.map((k) => k in dense).join(","));
// Holes from `new Array(n)` and from writes past the end.
const holes = new Array<number>(6);
holes[1] = 1;
holes[4] = 4;
const present: number[] = [];
for (let i = -1; i < 8; i++) if (i in holes) present.push(i);
console.log(present.join(","), holes.length);
// Length truncation and pop remove presence; fill restores it.
const shrink: number[] = [1, 2, 3, 4, 5];
shrink.length = 2;
shrink.pop();
let alive = 0;
for (let i = 0; i < 5; i++) if (i in shrink) alive++;
console.log(alive, shrink.length);
shrink.length = 4;
console.log([0, 1, 2, 3].map((i) => i in shrink).join(","));
shrink.fill(9);
console.log([0, 1, 2, 3].map((i) => i in shrink).join(","));
// Ordinary (noncanonical) numeric property keys live beside the slots.
const withProps: number[] = [1, 2];
withProps[-1] = 5;
withProps[1.5] = 6;
console.log(-1 in withProps, 1.5 in withProps, -2 in withProps, 2.5 in withProps, withProps.length);
// String and object element arrays share the same query.
const names: string[] = ["a", "b"];
const objs: { v: number }[] = [{ v: 1 }];
let idx = 1;
console.log(idx in names, idx in objs, !(idx in objs));
// A key read from another array (number after the non-null assertion).
const keys: number[] = [0, 5, 2];
console.log(keys.map((_, j) => keys[j]! in dense).join(","));
// Sparse indices past the dense cutoff.
const sparse: number[] = [];
sparse[3000000] = 1;
sparse[5] = 2;
console.log(5 in sparse, 6 in sparse, 3000000 in sparse, 2999999 in sparse, sparse.length);
// JS evaluates the key before the receiver, and both exactly once.
const trace: string[] = [];
function key(k: number): number {
trace.push("key");
return k;
}
function recv(): number[] {
trace.push("recv");
return dense;
}
console.log(key(1) in recv(), key(7) in recv(), trace.join(","));
// Reassigning the receiver inside the key expression: the receiver is read
// after the key ran.
let swap: number[] = [1];
let j = 0;
console.log((swap = [1, 2, 3], j = 2, j) in swap);
// Large loop: linear in the array size.
const big: number[] = [];
for (let i = 0; i < 300000; i++) if (i % 3 !== 0) big[i] = i;
let count = 0;
for (let i = 0; i < 600000; i++) if (i in big) count++;
console.log(count, big.length);