Skip to content

vm, compiler: make global var bindings properties of the global object - #720

Merged
nooga merged 1 commit into
mainfrom
fix/72-global-var-bindings
Oct 17, 2026
Merged

nooga merged 1 commit into
mainfrom
fix/72-global-var-bindings

Conversation

@nooga

@nooga nooga commented Oct 17, 2026

Copy link
Copy Markdown
Owner

Part of #72 (group A: global var bindings and the global object).

Problems

  • Global var and function bindings lived only in heap slots, and the global object got copies only after an indirect eval. As a result, Object.getOwnPropertyDescriptor(globalThis, "x") returned undefined for a plain script var x.
  • hasOwnProperty answered true for top-level let/const/class.
  • var x; inside eval (direct or indirect) reset an existing global x to undefined.
  • Eval never ran the CanDeclareGlobalFunction or CanDeclareGlobalVar checks.

Fix

  • Var and function slots are now mirrored onto the global object as data properties. The heap's slot watch keeps them current; this is the same mechanism module namespaces use (pkg/vm/global_bindings.go).
    • Script declarations are non-configurable.
    • Eval declarations and implicit globals (zz = 1) are configurable.
    • Deleting a binding drops its mirror.
  • Top-level classes initialize their slot with OpSetGlobalInit, like let, so they are no longer mistaken for implicit globals.
  • A new OpDeclareGlobal implements CreateGlobalVarBinding and CreateGlobalFunctionBinding for eval code. In check mode it is emitted for every name before any binding is created, so a non-definable name throws a TypeError and declares nothing.
  • The blanket SyncHeapToGlobalObject after indirect eval is removed. It also copied lexical bindings onto the global object.

Module top-level bindings are not mirrored.

Testing

  • New smoke test tests/scripts/global_var_bindings_on_global_object.ts. TestScripts is green.
  • test262 (language + built-ins, vs main): +18, no regressions. This covers:
    • eval-code/{direct,indirect}/var-env-*-init-global-*
    • non-definable-*
    • global-code/decl-{func,lex,var}

🤖 Generated with Claude Code

Global var and function bindings lived only in heap slots. The global
object gained copies of them only after an indirect eval, so
getOwnPropertyDescriptor(globalThis, name) saw no script vars. On top of
that, hasOwnProperty answered true for let/const/class, and `var x;` in
eval reset an existing x to undefined.

Var and function slots are now mirrored onto the global object as data
properties, kept current through the heap's slot watch:
- script declarations are non-configurable
- eval declarations and implicit globals are configurable
Deleting a binding drops its mirror. Top-level classes initialize their
slot like let, so they are no longer mistaken for implicit globals.

Eval code declares globals through a new OpDeclareGlobal, which
implements CreateGlobalVarBinding and CreateGlobalFunctionBinding. It
first checks CanDeclareGlobalFunction and CanDeclareGlobalVar for every
name, so a conflict throws before any binding is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nooga
nooga force-pushed the fix/72-global-var-bindings branch from 8970902 to 2d62014 Compare October 17, 2026 21:26
@nooga
nooga merged commit e27edfd into main Oct 17, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant