[P2] Interpreter: static blocks skip lexical reservation without var loop heads · Issue #2870 · nickna/SharpTS · GitHub
Skip to content

[P2] Interpreter: static blocks skip lexical reservation without var loop heads #2870

Closed

Description

@nickna

Class static blocks without a var loop head do not reserve their lexical bindings before creating closures. A closure can consequently read an outer binding during the inner binding's temporal dead zone.

var value = 99;
class Example {
    static {
        const read = () => value;
        try { read(); } catch (error: any) { console.log("before", error instanceof ReferenceError); }
        let value = 7;
        console.log("after", read());
    }
}
console.log("outer", value);

Node prints the complete output:

before true
after 7
outer 99

Checked SharpTS interpretation prints:

after 7
outer 99

Full strict TypeScript checking and both SharpTS no-emit profiles accept this exact source. The checked interpreter exits zero with empty stderr but omits the expected ReferenceError catch. The outer binding is deliberately var; its value must remain 99, while the inner lexical binding must be inaccessible until initialized.

Interpreter.ExecuteClassStaticBlock currently creates a block environment and calls ReserveLexicalDeclarations only when InterpreterVarDeclarations.CollectLoopHeads finds a var loop head. VariableResolver.VisitStaticBlock makes its scope and lexical preregistration conditional on the same test. This source has no loop. Ordinary blocks already reserve their lexical bindings at entry. A static block needs that protocol independently of whether it contains a loop.

Acceptance: reserve static-block lexical bindings in their own scope before any statement executes, with matching resolver scope depth. Preserve class/static this, source order, independent sibling scopes, nested lexical shadows, and existing block-owned loop-var behavior. This report does not require a new compiler storage policy.

This was reproduced using immutable runtime SHA-256 D4B4C417F6C992CEF3FD2FCF9B2A797DA7B1BE5DE59F71BFEE5D6B4DC8C40ED6, pin DCD4A3107ED2D1DAAD8D4350F503A7DEAF3C3AFA199EE3AF3B1E2A32DE01C340, on clean committed HEAD 19d4bf721f17fee071da207a407cc1e78c66ee61. The pin's historical build base is 2e44c4aca13bb71fd655910da4bb40290a9fe6da. The source and complete 20-file package remained unchanged; all child processes closed and drained. No introducing commit is claimed.

The compiler also omits the catch on this source, independently tracked as #2869: its lexical-name collector skips static initializers. That compiler metadata omission is separate from this interpreter/resolver scope-entry omission. #2806 changes checker environments and cannot repair this runtime entry path. Its retained whole static-block TDZ control contains for (var step of [1]) {}, which activates the existing reservation branch and explains why that interpreter control passes. Existing #2454 concerns class-expression compiler snapshots after initialization.

Ten fresh all-state searches returned 56 unique candidates without a capped result set. Reading the 12 closest full reports and all comments found no focused static-block reservation owner. Closed #2345 covers ordinary-block lexical reservation; #2706 covers classic-global loop-var publication, #2583 named-function self/body scope, and #2659 ordinary function-body var hoisting. This is a separate uncovered static-block entry path.

Exact source SHA-256: 2E7849D963AE498DDC1E62EF34F4624E8A283408A30B0ECAB908DEB312B6C419. Evidence: 2806-nominal-static-tdz-current-proof/baseline-summary.json (B028AAAF9F6F848765EF94140ECC530B84E61A15DC78F60606D3BD75DF96538A), raw results 329B2E221DE36732D930138D3D731A71F9E17D6FA056492098C95986CCC455F5, and static classification B678D4BE21AF5162DDB04C1AAD5141F9250CFDC6A162236C924B203498BC0A14.

Activity

  1. added
    bugSomething isn't working
    priority: P2Normal: scoped correctness/compatibility fixes and planned feature or tooling work.
    on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: P2Normal: scoped correctness/compatibility fixes and planned feature or tooling work.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions