Skip to content

feat(stdlib): implement ES2024 Set methods (closes #236)#356

Merged
dowdiness merged 4 commits into
mainfrom
feat/set-methods-es2024
Jun 15, 2026
Merged

feat(stdlib): implement ES2024 Set methods (closes #236)#356
dowdiness merged 4 commits into
mainfrom
feat/set-methods-es2024

Conversation

@dowdiness

@dowdiness dowdiness commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • Implements all 7 ES2024 Set methods: union, intersection, difference, symmetricDifference, isSubsetOf, isSupersetOf, isDisjointFrom (closes Implement ES2024 Set methods #236)
  • Shared GetSetRecord helper per §7.4.2 (validates receiver is object, checks has/keys callable, normalises size via floor, rejects NaN/negative/infinite sizes)
  • Correct branching per spec: has-path vs keys-path based on size comparison for methods that define both
  • Manual index loops in all has-path iterations — MoonBit's for x in arr snapshots initial length; a has() callback that mutates the Set would cause out-of-bounds reads
  • Also fixes Set.prototype.forEach which had the same snapshot-length hazard introduced by an earlier refactor in this branch

Test plan

  • All 2092 unit tests pass
  • Set prototype test262: 692/714 (96.9%) — remaining 22 are 14× BigInt (skipped feature) and 8× pre-existing forEach/iterator bugs outside this scope
  • CI full suite

🤖 Generated with Claude Code

Add the 7 ES2024 Set prototype methods:
- union, intersection, difference, symmetricDifference
- isSubsetOf, isSupersetOf, isDisjointFrom

Implements GetSetRecord (§7.4.2) as a shared helper that validates the
argument is an object, normalises size via floor() (not parseInt or
Math.floor), and checks has/keys are callable.

Each method applies the spec-correct branching strategy based on size
comparison:
- has-path (thisSize ≤ otherSize): iterate this, call other.has()
- keys-path (thisSize > otherSize): iterate other.keys(), check this

Has-path loops use manual index loops (`while i < data.values.length()`)
instead of MoonBit's `for x in arr` for live-data semantics: the `has()`
callback may mutate data.values mid-iteration, and MoonBit's for-each
snapshots the length, causing out-of-bounds access when the array shrinks.

Fixes in intersection: keys-path now checks alreadyInResult before
adding to prevent duplicates; has-path checks element is still in
thisData after other.has() may have deleted it (§24.2.3.2 step 5.a.ii.2).

isSupersetOf and isDisjointFrom close the other.keys() iterator before
returning false on early exit.

Remove the "set-methods" entry from skip_features in test262_skip_metadata.json.

Test262 result: 692/714 Set prototype tests pass (96.9%, up from 96.1%).
Remaining 22 failures: 14 require BigInt support (no-BigInt engine
limitation), 4 are pre-existing forEach/keys/values bugs, 4 are
pre-existing iterator-mutation tests outside set-methods scope.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@dowdiness, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 24 minutes and 17 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7bf2607e-d948-44b4-bd31-f87188769db9

📥 Commits

Reviewing files that changed from the base of the PR and between 08b68d6 and 43c6e32.

📒 Files selected for processing (3)
  • interpreter/stdlib/builtins_map_set.mbt
  • interpreter/stdlib/builtins_weakmap_set.mbt
  • scripts/test262_skip_metadata.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/set-methods-es2024

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: faf225de72

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread interpreter/stdlib/builtins_map_set.mbt Outdated
Comment thread interpreter/stdlib/builtins_map_set.mbt Outdated
Comment thread interpreter/stdlib/builtins_map_set.mbt Outdated
let loc = @token.Loc::default()
let other = if args.length() > 0 { args[0] } else { Undefined }
let other_rec = get_set_record(interp, other, loc)
let result_values : Array[Value] = []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Base difference on a snapshot of the receiver

The has-path for difference should start with a copy of the receiver's SetData and remove entries whose other.has call is truthy. Starting from an empty result and walking live data.values lets other.has mutations change the answer, e.g. adding 2 while checking a single-element set causes difference to include the newly added 2, although it was not in the original receiver snapshot.

Useful? React with 👍 / 👎.

Comment thread interpreter/stdlib/builtins_map_set.mbt
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Run: https://github.com/dowdiness/js_engine/actions/runs/27531445160

startup/tiny_program is the PR #153 / issue #141 guardrail for built-in realm-stamping startup cost.

Stage summary

stage benchmarks total mean slowest benchmark slowest mean noisy rows
startup 3 2.408 ms startup/tiny_program 1.215 ms 0
frontend 7 0.862 ms pipeline/parse_heavy 0.492 ms 2
execution 25 15708.887 ms exec/fibonacci_30 14274.941 ms 1

Focused bytecode base-vs-head comparison

Base-vs-head deltas are reporting-only. Negative delta and PR/base < 1.00x mean the PR is faster; interpret high-CV or noisy rows cautiously.

benchmark stage base mean PR mean delta PR/base base CV PR CV noisy
baseline/bytecode/closure_factory execution 14.722 ms 15.994 ms +8.6% 1.09x 3.9% 4.3% no
pipeline/bytecode/evaluate execution 9.684 ms 9.963 ms +2.9% 1.03x 2.2% 2.4% no
isolate/bytecode/call_frame execution 16.132 ms 16.626 ms +3.1% 1.03x 0.6% 2.4% no
isolate/bytecode/runtime_helpers execution 20.016 ms 20.526 ms +2.5% 1.03x 0.9% 2.9% no
isolate/bytecode/local_access execution 36.878 ms 37.395 ms +1.4% 1.01x 1.5% 1.5% no
isolate/bytecode/env_access execution 36.820 ms 37.857 ms +2.8% 1.03x 1.4% 0.6% no
isolate/bytecode/captured_access execution 38.479 ms 38.130 ms -0.9% 0.99x 1.0% 2.6% no
isolate/bytecode/dispatch_stack execution 22.409 ms 22.575 ms +0.7% 1.01x 0.3% 0.7% no

Base-vs-head comparison

benchmark stage base mean PR mean delta PR/base base CV PR CV noisy
startup/tiny_program startup 1.307 ms 1.215 ms -7.0% 0.93x 5.2% 4.6% no
lexer/small frontend 0.031 ms 0.030 ms -3.4% 0.97x 29.1% 26.3% base, PR
lexer/large frontend 0.268 ms 0.262 ms -2.2% 0.98x 0.8% 1.7% no
exec/fibonacci_30 execution 14835.175 ms 14274.941 ms -3.8% 0.96x 0.6% 0.4% no
exec/property_chain execution 14.754 ms 14.272 ms -3.3% 0.97x 8.0% 10.7% no
startup/phase/parse_tiny frontend 0.002 ms 0.002 ms -0.4% 1.00x 0.5% 0.7% no
startup/phase/new_interpreter startup 1.245 ms 1.192 ms -4.2% 0.96x 6.4% 6.7% no
startup/phase/execute_preparsed_tiny execution 0.000 ms 0.001 ms +10.2% 1.10x 5.7% 0.6% no
startup/phase/event_loop_drain_empty startup 0.000 ms 0.000 ms -2.9% 0.97x 0.7% 1.5% no
startup/phase/result_stringify_output execution 0.000 ms 0.000 ms +2.2% 1.02x 0.9% 7.3% no
exec/array_map_filter execution 21.374 ms 20.553 ms -3.8% 0.96x 29.1% 17.8% base, PR
exec/closure_factory execution 28.062 ms 29.922 ms +6.6% 1.07x 6.6% 4.5% no
baseline/closure_legacy/closure_factory execution 26.891 ms 28.266 ms +5.1% 1.05x 10.4% 7.4% no
baseline/bytecode/closure_factory execution 14.722 ms 15.994 ms +8.6% 1.09x 3.9% 4.3% no
isolate/bytecode/dispatch_stack execution 22.409 ms 22.575 ms +0.7% 1.01x 0.3% 0.7% no
isolate/bytecode/local_access execution 36.878 ms 37.395 ms +1.4% 1.01x 1.5% 1.5% no
isolate/bytecode/env_access execution 36.820 ms 37.857 ms +2.8% 1.03x 1.4% 0.6% no
isolate/bytecode/captured_access execution 38.479 ms 38.130 ms -0.9% 0.99x 1.0% 2.6% no
isolate/bytecode/call_frame execution 16.132 ms 16.626 ms +3.1% 1.03x 0.6% 2.4% no
isolate/bytecode/runtime_helpers execution 20.016 ms 20.526 ms +2.5% 1.03x 0.9% 2.9% no
isolate/bytecode/property_get execution 45.626 ms 47.987 ms +5.2% 1.05x 1.8% 1.7% no
isolate/bytecode/property_set execution 41.213 ms 41.525 ms +0.8% 1.01x 3.9% 1.8% no
isolate/bytecode/method_call execution 17.176 ms 17.063 ms -0.7% 0.99x 0.7% 0.6% no
isolate/bytecode/object_literal execution 13.380 ms 13.500 ms +0.9% 1.01x 0.8% 3.2% no
isolate/bytecode/array_literal execution 13.505 ms 14.098 ms +4.4% 1.04x 1.4% 1.4% no
exec/arithmetic_loop execution 1015.446 ms 946.758 ms -6.8% 0.93x 1.3% 0.9% no
exec/object_construction execution 6.861 ms 6.970 ms +1.6% 1.02x 3.6% 6.1% no
exec/string_ops execution 1.950 ms 1.783 ms -8.6% 0.91x 15.4% 8.7% base
pipeline/exec/lex frontend 0.028 ms 0.027 ms -0.4% 1.00x 1.7% 0.8% no
pipeline/exec/parse frontend 0.026 ms 0.027 ms +3.6% 1.04x 3.0% 3.2% no
pipeline/exec/evaluate execution 26.054 ms 27.198 ms +4.4% 1.04x 12.2% 11.2% no
pipeline/closure_legacy/evaluate execution 23.454 ms 24.982 ms +6.5% 1.07x 4.2% 4.6% no
pipeline/bytecode/compile frontend 0.022 ms 0.022 ms -1.6% 0.98x 25.7% 28.8% base, PR
pipeline/bytecode/evaluate execution 9.684 ms 9.963 ms +2.9% 1.03x 2.2% 2.4% no
pipeline/parse_heavy frontend 0.487 ms 0.492 ms +1.1% 1.01x 6.0% 5.4% no

Mean-time chart (log scale)

benchmark stage mean chart
startup/tiny_program startup 1.215 ms ##
lexer/small frontend 0.030 ms ⚠ #
lexer/large frontend 0.262 ms #
exec/fibonacci_30 execution 14274.941 ms ##############################
exec/property_chain execution 14.272 ms ########
startup/phase/parse_tiny frontend 0.002 ms #
startup/phase/new_interpreter startup 1.192 ms ##
startup/phase/execute_preparsed_tiny execution 0.001 ms #
startup/phase/event_loop_drain_empty startup 0.000 ms #
startup/phase/result_stringify_output execution 0.000 ms #
exec/array_map_filter execution 20.553 ms ⚠ #########
exec/closure_factory execution 29.922 ms ##########
baseline/closure_legacy/closure_factory execution 28.266 ms ##########
baseline/bytecode/closure_factory execution 15.994 ms ########
isolate/bytecode/dispatch_stack execution 22.575 ms #########
isolate/bytecode/local_access execution 37.395 ms ###########
isolate/bytecode/env_access execution 37.857 ms ###########
isolate/bytecode/captured_access execution 38.130 ms ###########
isolate/bytecode/call_frame execution 16.626 ms ########
isolate/bytecode/runtime_helpers execution 20.526 ms #########
isolate/bytecode/property_get execution 47.987 ms ############
isolate/bytecode/property_set execution 41.525 ms ###########
isolate/bytecode/method_call execution 17.063 ms #########
isolate/bytecode/object_literal execution 13.500 ms ########
isolate/bytecode/array_literal execution 14.098 ms ########
exec/arithmetic_loop execution 946.758 ms #####################
exec/object_construction execution 6.970 ms ######
exec/string_ops execution 1.783 ms ###
pipeline/exec/lex frontend 0.027 ms #
pipeline/exec/parse frontend 0.027 ms #
pipeline/exec/evaluate execution 27.198 ms ##########
pipeline/closure_legacy/evaluate execution 24.982 ms ##########
pipeline/bytecode/compile frontend 0.022 ms ⚠ #
pipeline/bytecode/evaluate execution 9.963 ms #######
pipeline/parse_heavy frontend 0.492 ms #

Closure-conversion comparison

  • unavailable

- Set.prototype.forEach: restore manual index loop (the diff changed it
  to `for value in data.values` which snapshots the initial length;
  if the callback deletes an earlier element, the array shrinks past the
  snapshot length and the final iteration reads freed memory — same
  "illegal cast" bug fixed in isSubsetOf/isDisjointFrom)
- get_set_record: reject +Infinity and -Infinity sizes with RangeError
  per §7.4.2 step 6b; floor(+Infinity)=+Infinity was slipping past
  the < 0.0 guard and being accepted as a valid size

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 93a0fc218d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread interpreter/stdlib/builtins_map_set.mbt Outdated
Comment thread interpreter/stdlib/builtins_map_set.mbt Outdated
- get_set_record: use ToIntegerOrInfinity (truncation, not floor); allow +∞ as valid size
- union/symmetricDifference: call other.keys() before copying this.[[SetData]] (§24.2.3.1 step 4 / §24.2.3.4 step 4)
- intersection has-path: snapshot thisData before iterating; drop incorrect post-has re-check (§24.2.3.2 step 5)
- difference has-path: snapshot thisData before calling other.has() (§24.2.3.3 step 5)
- isSubsetOf/isDisjointFrom has-paths: snapshot thisData; skip elements deleted by has()
- isSupersetOf/isDisjointFrom keys-paths: propagate iterator.return() errors (remove swallowing catch)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c02eeb975

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread interpreter/stdlib/builtins_map_set.mbt Outdated
Comment thread interpreter/stdlib/builtins_map_set.mbt Outdated
Comment thread interpreter/stdlib/builtins_map_set.mbt Outdated
…ator close

- Set.prototype.forEach: don't advance index when callback deletes the current
  element (it compacts data.values; the successor shifts into the same slot)
- has-paths (intersection/difference/isSubsetOf/isDisjointFrom): replace snapshot
  with live index loop — spec iterates live [[SetData]], so elements added by
  other.has() must be visited; keep deletion-safe advancement for both paths
- intersection has-path/difference has-path: add SetDataHas dedup guard (§24.2.3.2/3)
- all keys-paths (union/intersection/difference/symmetricDifference/isSupersetOf/
  isDisjointFrom): wrap iterator_result_value in try-catch + iterator_close per
  IteratorStepValue §7.4.9 — if the value getter throws, close the iterator first

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43c6e32b69

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +983 to +985
if i < data.values.length() &&
same_value_zero(data.values[i], value) {
i = i + 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve re-added Set.forEach values

This cursor check treats an equal value at the same compacted index as the original entry, but when the callback does s.delete(value); s.add(value) on a single-element Set, the re-added value occupies data.values[i]. The spec says values deleted after being visited and re-added before completion are visited again, so new Set([1]).forEach(...) should call the callback twice in that scenario; this increments past the re-added entry and returns after one call.

Useful? React with 👍 / 👎.

@dowdiness dowdiness merged commit 2e3f64a into main Jun 15, 2026
15 checks passed
@dowdiness dowdiness deleted the feat/set-methods-es2024 branch June 15, 2026 08:08
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.

Implement ES2024 Set methods

1 participant