Files
scriptc/tests/corpus/5357-init-only-module-bindings.ts
T
Malte Ubl 6ee5463186 Read module let/var bindings that only their declaration writes as constants
A source module binding whose one write in the program is an unconditional
statement of the guarded synchronous initializer holds that value for every
later read, like a const; early reads already carry the frontend's init flag,
TDZ sentinel or undefined-admitting type. Marking it immutable hands it to
constant folding, borrowed reads and constant tables, so top-level `let N =
1024` no longer keeps index math in doubles behind global loads.
2026-10-16 06:38:44 -07:00

94 lines
2.5 KiB
TypeScript

// Module `let`/`var` bindings written only by their declaration read like
// constants; every other shape keeps observing writes and early reads.
function earlyLet(): number {
return LATE_LET + 1;
}
function earlyVar(): string {
return String(LATE_VAR);
}
try {
console.log("early let", earlyLet());
} catch (e) {
console.log("early let threw", (e as Error).name, (e as Error).message);
}
console.log("early var", earlyVar());
let LATE_LET = 41;
var LATE_VAR = 7;
console.log("late", earlyLet(), earlyVar());
// Init-only numbers bound loops in other functions.
let SIZE = 24;
var STRIDE = 3;
let LIMIT = Number(process.argv[2] ?? "97");
let SCALE = SIZE * STRIDE;
function grid(): number {
let total = 0;
for (let i = 0; i < SIZE; i++) {
for (let j = 0; j < SIZE; j += STRIDE) total = (total + i * SIZE + j) % LIMIT;
}
return total + SCALE;
}
console.log("grid", grid(), SIZE, STRIDE, LIMIT, SCALE);
// Written again later by a function: stays a live binding.
let counter = 10;
function bump(): number {
counter += 5;
return counter;
}
function readCounter(): number {
return counter;
}
console.log("counter", readCounter(), bump(), readCounter());
// Declared without a value and assigned once from a function.
let deferred: number;
function setDeferred(): void {
deferred = 99;
}
function readDeferred(): number {
return deferred;
}
setDeferred();
console.log("deferred", readDeferred());
// Assigned in a nested statement or a loop at module level.
let fromBlock = 1;
if (process.argv.length > 100) fromBlock = 2;
let fromLoop = 0;
for (let i = 0; i < 4; i++) fromLoop += i;
function readNested(): string {
return `${fromBlock}/${fromLoop}`;
}
console.log("nested", readNested());
// A redeclared var has two writes.
var twice = 1;
function readTwice(): number {
return twice;
}
console.log("twice before", readTwice());
var twice = 2;
console.log("twice after", readTwice());
// Objects held by init-only bindings still mutate through them.
let table: number[] = [];
function fill(n: number): number {
for (let i = 0; i < n; i++) table.push(i * SIZE);
return table.length;
}
console.log("table", fill(5), table.join(","));
let config = { mode: "fast", level: 1 };
function raise(): string {
config.level++;
return `${config.mode}:${config.level}`;
}
console.log("config", raise(), raise());
// Callbacks and timers read the settled value.
let OFFSET = 1000;
const mapped = [1, 2, 3].map((x) => x + OFFSET);
console.log("mapped", mapped.join(","));
setTimeout(() => console.log("timer", OFFSET + SIZE, LATE_LET), 0);