- Sound mode S-09, pre-alpha
- Design record 0007, Accepted
TSR0902
'{0}' may have changed since it was narrowed: it is assigned in a closure, and '{1}' on line {2} can run that closure.
A local variable that a closure assigns was narrowed, then a call that can run the closure came before its use. Copy it to a const first.
Rule S-09: Narrowings are dropped when they could be invalidated (level: sound). Reported by ts-r-check and ts-r-lsp in files that opt in to sound mode. See Sound mode.
What it means
A narrowing of a local let, var or parameter that is assigned inside another function or class body is dropped at every call, await and yield, because that call can run the closure. Locals never assigned from a closure keep TypeScript's behaviour.
Example
// A let assigned inside a closure can change when the closure runs.
function run(): number {
let cur: string | null = Math.random() > 0.5 ? "a" : null;
const reset = (): void => {
cur = null;
};
if (cur !== null) {
reset();
return cur.length; // @expect TSR0902
}
return 0;
}
export const n: number = run();
How to fix
- Copy the variable to a
constbefore the call and use the copy. - Or restructure so the closure doesn't assign the variable.
Sound mode is defined in design record 0007 (accepted 2026-10-15).