Skip to content

Added support for WeakMap and WeakSet - #21

Merged
NetWin merged 3 commits into
incrementfrom
feature/weak-collections
Oct 24, 2026
Merged

NetWin merged 3 commits into
incrementfrom
feature/weak-collections

Conversation

@NetWin

@NetWin NetWin commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

What changes

Scripts can now use WeakMap and WeakSet, for example to cache results per object or to track which objects a traversal has already visited. Before this change, reading either name threw DISALLOWED_SYNTAX.

  • WeakMap supports get, set, has and delete. WeakSet supports add, has and delete.

  • Both behave as in JavaScript:

    • the constructors take an iterable and must be called with new
    • instanceof and String(...) give [object WeakMap] / [object WeakSet]
    • new WeakMap(iterable) reads each entry like entry[0] and entry[1], so { 0: key, 1: value } works as a pair
    • a primitive key or entry throws V8's TypeError: Invalid value used as weak map key / Invalid value used in weak set
    • get, has and delete with a primitive return undefined / false instead of throwing
  • Any object can be a key, including functions and built-ins such as Math. Scripts have no symbols, so nothing else can be.

  • At the boundary, a weak collection's entries can't be listed, so it can't be copied:

    • Passed in with data or globals, or returned from a host function, it is rejected like any other unsupported value.
    • Leaving the run, it becomes undefined.
    • Passed to a host function, it throws a catchable TypeError: A WeakMap cannot be passed to the host.

    In all three cases it is handled the way an iterator is.

No new divergence from JavaScript. As with every built-in, WeakMap.prototype and .constructor stay blocked.

The same entry reading fixes new Map(iterable), which used to throw for every entry that was not an array: new Map([{ 0: 'a', 1: 1 }]).get('a') now gives 1, as in JavaScript.

How

  • Wrappers: WeakMapWrapper and WeakSetWrapper hold a real host WeakMap / WeakSet, so entries keyed by throwaway objects can be garbage-collected during a long run. Scripts have no WeakRef or FinalizationRegistry, so they can't observe when that happens. Neither wrapper has a readOnly flag, because a weak collection can never arrive with frozen input.
  • Builtins: builtins/weak-map/ and builtins/weak-set/ follow the layout of map/ and set/. support/can-be-held-weakly.ts decides which values are valid keys.
  • Wiring: both builtins are registered in standard-globals.ts and method-dispatch.ts, and removed from HOST_ONLY_NAMES.
  • Constructor entries: new WeakMap and new Map read entry[0] and entry[1] through getMember, as Object.fromEntries does. Any object works as a pair, and properties a script added to Maple's own objects are seen. Before this, the Map constructor read the indexes off the host array directly.
  • Boundary: toHost drops both wrappers. In strict mode, which is used for host-function arguments, it throws UnconvertibleValueError instead, and host-bridge.ts reports that as a TypeError in the script.

Tests, parity and docs

  • Tests:
    • New weak-map.spec.ts and weak-set.spec.ts cover every method, the invalid-key messages, the constructor and its errors, and that set algebra refuses a WeakSet.
    • map.spec.ts and weak-map.spec.ts cover entries that are not arrays. Every expected value was checked against Node.
    • escapes.spec.ts checks that .constructor, __proto__ and WeakMap.prototype stay blocked.
    • to-host.spec.ts, from-host.spec.ts and host-functions.spec.ts cover the boundary in both directions.
  • Parity: none. js-interpreter follows ES5 and has no Map, Set, WeakMap or WeakSet, and the parity package only covers scripts it can run.
  • Docs: language.md (blocked names, the new allowlist, the instanceof count, the wrappers in the value model), a new "WeakMap and WeakSet" section and the standard-globals list in builtins.md, and a new row in the boundary.md table.
  • Benchmark: no measurable change on contact-summary.js or contact-ten-lines.js (measure-run, three alternating rounds).

WeakMap and WeakSet were rejected as host-only names. They are now
standard globals backed by host weak collections, with the spec surface
and V8 messages. Any object is a valid key. A weak collection cannot
enter a run, and leaves it as undefined, like an iterator.
@NetWin NetWin self-assigned this Oct 24, 2026
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

📋 Release Preview

Changelog

0.6.0 (2026-10-24)

🚀 Features

  • builtins: support WeakMap and WeakSet (4e1af2e)

🩹 Bug Fixes

  • map: read constructor entries as any object, not only arrays (91eb05e)

@NetWin
NetWin marked this pull request as ready for review October 24, 2026 13:38
Copilot AI lite review requested due to automatic review settings October 24, 2026 13:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

WeakMap object-entry handling must match JavaScript semantics, and the new wrapper types must be added to the documented value model.

1 open issue
What changed in this PR

Adds sandbox support for WeakMap and WeakSet, including JavaScript-like constructors and methods, weak-key validation, boundary handling, tests, parity coverage, and documentation.

Changes:

  • Adds weak collection wrappers, built-ins, methods, globals, and dispatch registration.
  • Prevents weak collections from crossing unsupported host boundaries.
  • Adds escape, boundary, parity, and documentation coverage.
  • Requires correction of WeakMap iterable entry handling and completion of wrapper-model documentation.
File Description
packages/​maple/​src/​values/​wrappers/​wrapper.ts Adds weak collection wrappers to the wrapper model.
packages/​maple/​src/​values/​wrappers/​weak-set-wrapper.ts Defines the sandbox WeakSet wrapper.
packages/​maple/​src/​values/​wrappers/​weak-map-wrapper.ts Defines the sandbox WeakMap wrapper.
packages/​maple/​src/​support/​can-be-held-weakly.ts Validates allowable weak collection keys and entries.
packages/​maple/​src/​host-functions.spec.ts Tests rejection of weak collections as host-function arguments.
packages/​maple/​src/​evaluator/​language/​references.ts Removes weak collection names from blocked globals.
packages/​maple/​src/​evaluator/​host-bridge.ts Reports weak collections as unconvertible host arguments.
packages/​maple/​src/​escapes.spec.ts Tests prototype and constructor escape protections.
packages/​maple/​src/​builtins/​weak-set/​weak-set.ts Implements the WeakSet constructor and branding.
packages/​maple/​src/​builtins/​weak-set/​weak-set.spec.ts Covers WeakSet behavior and errors.
packages/​maple/​src/​builtins/​weak-set/​weak-set-prototype.ts Registers WeakSet methods.
packages/​maple/​src/​builtins/​weak-set/​methods/​has.ts Implements WeakSet.has.
packages/​maple/​src/​builtins/​weak-set/​methods/​delete.ts Implements WeakSet.delete.
packages/​maple/​src/​builtins/​weak-set/​methods/​add.ts Implements WeakSet.add.
packages/​maple/​src/​builtins/​weak-map/​weak-map.ts Implements the WeakMap constructor and branding.
packages/​maple/​src/​builtins/​weak-map/​weak-map.spec.ts Covers WeakMap behavior and errors.
packages/​maple/​src/​builtins/​weak-map/​weak-map-prototype.ts Registers WeakMap methods.
packages/​maple/​src/​builtins/​weak-map/​methods/​set.ts Implements WeakMap.set.
packages/​maple/​src/​builtins/​weak-map/​methods/​has.ts Implements WeakMap.has.
packages/​maple/​src/​builtins/​weak-map/​methods/​get.ts Implements WeakMap.get.
packages/​maple/​src/​builtins/​weak-map/​methods/​delete.ts Implements WeakMap.delete.
packages/​maple/​src/​builtins/​standard-globals.ts Exposes WeakMap and WeakSet globally.
packages/​maple/​src/​builtins/​method-dispatch.ts Adds weak collection method dispatch.
packages/​maple/​src/​boundary/​to-host.ts Drops or rejects weak collections during output conversion.
packages/​maple/​src/​boundary/​to-host.spec.ts Tests weak collection output conversion.
packages/​maple/​src/​boundary/​from-host.spec.ts Tests rejection of host weak collections as input.
packages/​maple/​docs/​language.md Documents supported constructors and globals.
packages/​maple/​docs/​builtins.md Documents the new weak collection APIs.
packages/​maple/​docs/​boundary.md Documents weak collection boundary behavior.
packages/​maple-parity/​src/​divergences.spec.ts Adds parity coverage for weak collections.
packages/​maple-parity/​README.md Documents the interpreter parity divergence.

Review effort: Lite

Comment thread packages/maple/src/builtins/weak-map/weak-map.ts Outdated
new Map(iterable) refused every entry that was not an array, and
read entry[0] and entry[1] off the host array directly. It now reads
both through the member model, as Object.fromEntries does, so
{ 0: key, 1: value } and Maple's own objects work as in JavaScript.
@NetWin
NetWin merged commit e59c425 into increment Oct 24, 2026
8 checks passed
@NetWin
NetWin deleted the feature/weak-collections branch October 24, 2026 14:07
@NetWin NetWin mentioned this pull request Oct 25, 2026
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.6.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants