Skip to content

issue-2088 plan: a script-scope var reassigned via globalThis.name = … is invisible to subtreeContainsReassignmentOf #2634

Description

@carlos-alm

What

docs/plans/issue-2088.md's subtreeContainsReassignmentOf (WU-2, condition 4's identifier-resolution chain) detects a write to a module-level name via three AST shapes: a plain/augmented assignment or update expression whose target binds the bare identifier name (as of round 14, resolved through patternBindsName for a destructuring target too — see #2630 for the one shape, a parenthesized target, it still cannot see), and a declaration-less for...in/for...of loop-head target.

None of these shapes covers a write that reaches the same binding through the global object instead of through the identifier directly: in a non-module (classic script) file, a top-level var name = …; also creates a property on globalThis (var bindings at script scope are exposed as globalThis.name; let/const are not). globalThis.name = function () { return this.alpha(); }; (or global.name = … / self.name = … / window.name = … in the appropriate host) elsewhere in the same file reassigns the SAME binding findTopLevelFunctionNodeByName resolves, without ever writing to a bare identifier node at all — it is a member_expression assignment target, a shape subtreeContainsReassignmentOf does not look at, and one patternBindsName does not apply to either (member expressions are not a binding pattern).

This is a decidable, enumerable AST shape (an assignment_expression whose left is a member_expression with object globalThis/global/self/window and property name) — unlike eval/with, which are universal static-analysis limitations no tool can see through — so it sits closer to this design's other accepted, filed recall trade-offs (#2625, #2631, and the round-14 duplicate-declaration fail-safe) than to a Category-F "standard formula/platform limit" the plan already treats MAX_WALK_DEPTH truncation as.

Why this wasn't fixed inline

Recognising a globalThis/global/self/window-qualified write as a reassignment of the SAME module-level var binding requires threading in knowledge of which of several equivalent global-object aliases is in play (all resolve to the same object in a non-strict, non-module script; only globalThis is universally available across hosts and module types) and confirming the resolved declaration is even script-scoped in the first place (a let/const binding is never exposed this way, so the check would need to be conditioned on findTopLevelFunctionNodeByName having resolved through a variable_declaration, not a lexical_declaration) — additional scope for a resolution chain this plan has already found and fixed bugs in across seven rounds, for a pattern with no observed instance anywhere in this codebase today.

Suggested fix shape (not binding — decide at execute/fix time)

Add a fourth disjunct to subtreeContainsReassignmentOf, gated on the resolved declaration having come from a variable_declaration (var) specifically: an assignment_expression whose left is a member_expression with object.text in {'globalThis', 'global', 'self', 'window'} and property.text === name. Add a WU-10 escape-fallback case proving this shape is caught once implemented.

Where

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions