Skip to content

fix(set-iterator): brand check + done-flag for SetIteratorPrototype.next (§23.2.5.2.1)#441

Merged
dowdiness merged 8 commits into
mainfrom
fix/set-iterator-next-brand-check-done
Jun 24, 2026
Merged

fix(set-iterator): brand check + done-flag for SetIteratorPrototype.next (§23.2.5.2.1)#441
dowdiness merged 8 commits into
mainfrom
fix/set-iterator-next-brand-check-done

Conversation

@dowdiness

@dowdiness dowdiness commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Summary

Four commits fixing iterator prototype brand checks from issue #207.

Commit 1: SetIteratorPrototype.next — brand check + done-flag (§23.2.5.2.1)

Initial fix: switched from NativeCallable to make_method_func with class_name brand check, added done_ref. Superseded by commit 3 (slot-based design).

Commit 2: StringIteratorPrototype.next — brand check, class-name only (§22.1.5.2.1)

Object.create(stringIterator).next() → TypeError. Superseded by commit 4 (slot-based design).

Commit 3: SetIteratorPrototype.next — receiver internal slots

The commit 1 brand check was still insufficient: %SetIteratorPrototype% has class_name: "Set Iterator", so iter.next.call(Object.getPrototypeOf(iter)) passed the guard and advanced the closure-captured iter.

Migration to Map-iterator-style internal slots:

  • Added SET_ITERATOR_{NEXT_INDEX,TARGET,KIND}_SYMBOL_ID constants (IDs -133/-134/-135)
  • set_iterator_next(this_val) reads [[SetNextIndex]], [[IteratedSet]], [[SetIterationKind]] from this_val slots — missing slots → TypeError
  • make_set_iterator(realm_state, set_data, kind) stores state as hidden symbol slots
  • [[IteratedSet]] == Undefined (§step 12) replaces done_ref

SetIteratorPrototype/next: 6/20 → 20/20

Commit 4: StringIteratorPrototype.next — receiver internal slots

Same root cause as commit 3: %StringIteratorPrototype% has class_name: "String Iterator", so a.next.call(%StringIteratorPrototype%) passed the class-name guard and advanced a's closure state. And a.next.call(b) reads a's closure-captured index_ref/chars regardless of b's state.

Migration:

  • Added STRING_ITERATOR_{NEXT_INDEX,STRING}_SYMBOL_ID constants (IDs -136/-137)
  • string_iterator_next(this_val) reads [[StringIteratorNextIndex]] and [[IteratedString]] from receiver slots — missing STRING slot → TypeError (catches the prototype)
  • Factory stores the string and index 0 as slots; next() lives on %StringIteratorPrototype%
  • Uses unsafe_substring() for O(1) character slicing with correct surrogate-pair handling (previously O(n) to_array() per iterator construction)

StringIteratorPrototype/next (incl. RegExpStringIterator): 40/40

Test results

  • SetIteratorPrototype/next: 6/20 → 20/20
  • SetIteratorPrototype (full): 22/22 ✓
  • StringIteratorPrototype/next (incl. RegExpStringIterator): 40/40 ✓
  • moon test: 2183/2183 ✓

Closes part of #207 (slice 1 — iterator prototype brand checks + Set mutable iteration).

Test plan

  • make test262-filter FILTER="SetIteratorPrototype" → 22/22
  • make test262-filter FILTER="StringIteratorPrototype/next" → 40/40
  • Manual: iter.next.call(Object.getPrototypeOf(iter)) → TypeError for both Set and String iterators ✓
  • moon test → all 2183 pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Improved iterator handling for strings and sets.
    • Set iterators now support both value and entry iteration consistently.
    • String iteration now handles Unicode characters more reliably, including paired code points.

…ext (§23.2.5.2.1)

Two bugs fixed in make_set_values_iterator and make_set_entries_iterator:

1. this-not-object-throw: §23.2.5.2.1 step 2 requires TypeError when `this`
   is not an Object. Switched closure from NativeCallable to make_method_func
   (MethodCallable) so the next function receives this_val; added Object check
   and class_name "Set Iterator" brand check.

2. iteration-mutable: §23.2.5.2.1 step 12 requires setting [[IteratedSet]] to
   undefined after exhaustion so subsequent next() calls return done=true even
   if items are added later. Added done_ref that is set on first exhaustion and
   checked on entry (step 7 analogue).

SetIteratorPrototype/next: 6/20 → 20/20.

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

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

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 56 minutes and 1 second. 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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c1728735-ee8b-4e25-8e35-97b3e3eee5a1

📥 Commits

Reviewing files that changed from the base of the PR and between 02c3e68 and 5538550.

📒 Files selected for processing (4)
  • interpreter/runtime/pkg.generated.mbti
  • interpreter/runtime/property.mbt
  • interpreter/runtime/value.mbt
  • interpreter/stdlib/builtins_map_set.mbt
📝 Walkthrough

Walkthrough

String Iterator and Set Iterator are refactored from per-instance closure-backed next functions into shared prototype-driven implementations. Hidden engine-private negative symbol IDs now store iteration state ([[StringIteratorNextIndex]], [[IteratedString]], [[SetNextIndex]], [[IteratedSet]], [[SetIterationKind]]) directly on the iterator object, which shared next functions read and write.

Changes

String and Set Iterator slot-based refactor

Layer / File(s) Summary
String Iterator slot IDs, next implementation, and prototype wiring
interpreter/runtime/iterators.mbt
Adds engine-private negative symbol IDs for [[StringIteratorNextIndex]] and [[IteratedString]], introduces string_iterator_type_error and string_iterator_next with surrogate-pair-aware index advancement, and rebinds StringIteratorPrototype.next to delegate to the new function.
String Iterator object construction
interpreter/runtime/iterators.mbt
Updates make_string_iterator_value_with_proto to initialize hidden symbol slot properties for next index and iterated string, removing the prior embedded per-instance next closure backed by captured character arrays.
Set Iterator slot IDs, kind constants, next implementation, and prototype wiring
interpreter/stdlib/builtins_map_set.mbt
Adds engine-private negative symbol IDs and values/entries kind constants for Set iterators, introduces set_iterator_type_error and set_iterator_next (tombstone-skipping, slot-reading/writing, kind-dispatching), and rebinds SetIteratorPrototype.next to the new function.
Shared Set Iterator constructor and factory rewiring
interpreter/stdlib/builtins_map_set.mbt
Introduces make_set_iterator(realm_state, set_data, kind) that builds a slot-initialized iterator object, and collapses make_set_values_iterator / make_set_entries_iterator into thin wrappers that call it with the appropriate kind.

Possibly related issues

Possibly related PRs

  • dowdiness/js_engine#12: Modifies string iteration via Symbol.iterator; this PR refactors the same String Iterator prototype next logic into a slot-based implementation.
  • dowdiness/js_engine#373: Modifies Set iterator yielding to skip tombstoned slots during forEach; this PR applies the same tombstone-skipping logic inside the newly unified set_iterator_next.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 Hop, hop, no more closures to keep,
The slots now hold what iterators seek.
String and Set, their state stored within,
Hidden symbols guard where next has been.
The prototype shares what once was alone—
Each iterator finally calls a shared home! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the Set iterator brand-check fix, though it omits the String iterator changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/set-iterator-next-brand-check-done

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.

…22.1.5.2.1)

Object.create(stringIterator) should throw TypeError when next() is called on
it — the created object inherits next from the instance (a closure) but has no
String Iterator internal slots. Switched make_string_iterator_value_with_proto
from NativeCallable (ignores this) to make_method_func (MethodCallable) and
added Object check + class_name "String Iterator" brand check per §22.1.5.2.1
steps 2-3. Also simplified result objects to create_iter_result.

StringIteratorPrototype/next: 46/48 → 48/48.

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: efd542c0c2

ℹ️ 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
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

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

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.394 ms startup/tiny_program 1.206 ms 0
frontend 7 0.929 ms pipeline/parse_heavy 0.518 ms 2
execution 26 14587.582 ms exec/fibonacci_30 13112.334 ms 2

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 13.771 ms 13.399 ms -2.7% 0.97x 12.0% 6.3% no
pipeline/bytecode/evaluate execution 8.844 ms 9.359 ms +5.8% 1.06x 1.3% 2.3% no
isolate/bytecode/call_frame execution 7.347 ms 7.527 ms +2.4% 1.02x 0.6% 5.7% no
isolate/bytecode/runtime_helpers execution 11.143 ms 10.869 ms -2.5% 0.98x 8.7% 1.4% no
isolate/bytecode/local_access execution 37.782 ms 35.212 ms -6.8% 0.93x 1.7% 2.3% no
isolate/bytecode/env_access execution 36.229 ms 34.699 ms -4.2% 0.96x 1.5% 1.3% no
isolate/bytecode/captured_access execution 37.027 ms 37.553 ms +1.4% 1.01x 1.0% 1.9% no
isolate/bytecode/dispatch_stack execution 22.754 ms 22.674 ms -0.4% 1.00x 2.0% 2.3% no

Base-vs-head comparison

benchmark stage base mean PR mean delta PR/base base CV PR CV noisy
startup/tiny_program startup 1.241 ms 1.206 ms -2.8% 0.97x 4.3% 4.8% no
lexer/small frontend 0.035 ms 0.035 ms -1.0% 0.99x 25.8% 24.0% base, PR
lexer/large frontend 0.303 ms 0.296 ms -2.2% 0.98x 0.8% 0.7% no
exec/fibonacci_30 execution 13463.083 ms 13112.334 ms -2.6% 0.97x 0.4% 1.4% no
exec/property_chain execution 15.751 ms 15.433 ms -2.0% 0.98x 10.0% 7.8% no
startup/phase/parse_tiny frontend 0.002 ms 0.002 ms +1.2% 1.01x 0.5% 0.5% no
startup/phase/new_interpreter startup 1.197 ms 1.188 ms -0.8% 0.99x 16.7% 7.0% base
startup/phase/execute_preparsed_tiny execution 0.001 ms 0.001 ms -8.6% 0.91x 2.0% 0.9% no
startup/phase/event_loop_drain_empty startup 0.000 ms 0.000 ms -0.7% 0.99x 0.7% 0.9% no
startup/phase/result_stringify_output execution 0.000 ms 0.000 ms +0.4% 1.00x 0.5% 0.3% no
exec/array_map_filter execution 19.885 ms 20.696 ms +4.1% 1.04x 20.4% 22.5% base, PR
exec/closure_factory execution 29.553 ms 29.357 ms -0.7% 0.99x 5.3% 7.3% no
baseline/closure_legacy/closure_factory execution 27.814 ms 27.663 ms -0.5% 0.99x 9.0% 8.5% no
baseline/bytecode/closure_factory execution 13.771 ms 13.399 ms -2.7% 0.97x 12.0% 6.3% no
isolate/bytecode/dispatch_stack execution 22.754 ms 22.674 ms -0.4% 1.00x 2.0% 2.3% no
isolate/bytecode/local_access execution 37.782 ms 35.212 ms -6.8% 0.93x 1.7% 2.3% no
isolate/bytecode/env_access execution 36.229 ms 34.699 ms -4.2% 0.96x 1.5% 1.3% no
isolate/bytecode/captured_access execution 37.027 ms 37.553 ms +1.4% 1.01x 1.0% 1.9% no
isolate/bytecode/call_frame execution 7.347 ms 7.527 ms +2.4% 1.02x 0.6% 5.7% no
isolate/bytecode/runtime_helpers execution 11.143 ms 10.869 ms -2.5% 0.98x 8.7% 1.4% no
isolate/bytecode/property_get execution 44.742 ms 44.081 ms -1.5% 0.99x 2.5% 3.4% no
isolate/bytecode/property_set execution 40.945 ms 41.577 ms +1.5% 1.02x 1.6% 1.2% no
isolate/bytecode/method_call execution 8.271 ms 8.490 ms +2.6% 1.03x 0.4% 0.6% no
isolate/bytecode/object_literal execution 13.429 ms 13.402 ms -0.2% 1.00x 0.9% 1.1% no
isolate/bytecode/array_literal execution 14.639 ms 14.575 ms -0.4% 1.00x 0.8% 1.4% no
exec/for_of execution 5.870 ms 5.679 ms -3.3% 0.97x 10.1% 4.3% no
exec/arithmetic_loop execution 991.721 ms 1022.584 ms +3.1% 1.03x 0.2% 0.3% no
exec/object_construction execution 7.385 ms 7.020 ms -4.9% 0.95x 6.9% 6.9% no
exec/string_ops execution 2.032 ms 2.150 ms +5.8% 1.06x 25.0% 27.5% base, PR
pipeline/exec/lex frontend 0.031 ms 0.030 ms -3.9% 0.96x 0.8% 1.3% no
pipeline/exec/parse frontend 0.027 ms 0.027 ms -0.5% 1.00x 2.9% 3.1% no
pipeline/exec/evaluate execution 26.413 ms 25.739 ms -2.6% 0.97x 11.9% 5.1% no
pipeline/closure_legacy/evaluate execution 24.939 ms 25.508 ms +2.3% 1.02x 5.2% 4.5% no
pipeline/bytecode/compile frontend 0.022 ms 0.022 ms -0.2% 1.00x 28.0% 33.1% base, PR
pipeline/bytecode/evaluate execution 8.844 ms 9.359 ms +5.8% 1.06x 1.3% 2.3% no
pipeline/parse_heavy frontend 0.509 ms 0.518 ms +1.8% 1.02x 5.4% 4.5% no

Mean-time chart (log scale)

benchmark stage mean chart
startup/tiny_program startup 1.206 ms ##
lexer/small frontend 0.035 ms ⚠ #
lexer/large frontend 0.296 ms #
exec/fibonacci_30 execution 13112.334 ms ##############################
exec/property_chain execution 15.433 ms ########
startup/phase/parse_tiny frontend 0.002 ms #
startup/phase/new_interpreter startup 1.188 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.696 ms ⚠ #########
exec/closure_factory execution 29.357 ms ##########
baseline/closure_legacy/closure_factory execution 27.663 ms ##########
baseline/bytecode/closure_factory execution 13.399 ms ########
isolate/bytecode/dispatch_stack execution 22.674 ms ##########
isolate/bytecode/local_access execution 35.212 ms ###########
isolate/bytecode/env_access execution 34.699 ms ###########
isolate/bytecode/captured_access execution 37.553 ms ###########
isolate/bytecode/call_frame execution 7.527 ms ######
isolate/bytecode/runtime_helpers execution 10.869 ms #######
isolate/bytecode/property_get execution 44.081 ms ############
isolate/bytecode/property_set execution 41.577 ms ###########
isolate/bytecode/method_call execution 8.490 ms #######
isolate/bytecode/object_literal execution 13.402 ms ########
isolate/bytecode/array_literal execution 14.575 ms ########
exec/for_of execution 5.679 ms ######
exec/arithmetic_loop execution 1022.584 ms #####################
exec/object_construction execution 7.020 ms ######
exec/string_ops execution 2.150 ms ⚠ ###
pipeline/exec/lex frontend 0.030 ms #
pipeline/exec/parse frontend 0.027 ms #
pipeline/exec/evaluate execution 25.739 ms ##########
pipeline/closure_legacy/evaluate execution 25.508 ms ##########
pipeline/bytecode/compile frontend 0.022 ms ⚠ #
pipeline/bytecode/evaluate execution 9.359 ms #######
pipeline/parse_heavy frontend 0.518 ms #

Closure-conversion comparison

  • unavailable

The previous brand check (class_name == Set Iterator) was insufficient:
SetIteratorPrototype itself carries that class_name, so calling
iter.next.call(Object.getPrototypeOf(iter)) passed the guard and advanced
the closure-captured iter instead of throwing for a receiver with no
iterator slots.

Migrate Set iterators to the same internal-slot design as Map iterators:

- Add negative-ID symbol constants SET_ITERATOR_{NEXT_INDEX,TARGET,KIND}_SYMBOL_ID
- set_iterator_next(this_val) reads slots from this_val; missing slots throw TypeError
- Iterator next lives on SetIteratorPrototype, not per-instance
- Collapse make_set_values_iterator + make_set_entries_iterator into make_set_iterator

Object.getPrototypeOf(iter).next() now correctly throws TypeError because the
prototype has no SET_ITERATOR_TARGET_SYMBOL_ID slot.

All 22 SetIteratorPrototype test262 tests pass; 2183 unit tests pass.

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: eab6ed1b6c

ℹ️ 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/runtime/iterators.mbt Outdated
dowdiness and others added 2 commits June 24, 2026 19:00
…state

%StringIteratorPrototype% has class_name "String Iterator", so the prior
class_name-only brand check accepted the prototype itself as |this|. That
allowed iter.next.call(Object.getPrototypeOf(iter)) to advance the
closure-captured state on the original iterator instead of throwing.

Migration to internal-slot design (STRING_ITERATOR_NEXT_INDEX_SYMBOL_ID=-136,
STRING_ITERATOR_STRING_SYMBOL_ID=-137):
- string_iterator_next() reads [[StringIteratorNextIndex]] and [[IteratedString]]
  from the receiver's hidden symbol slots; missing STRING slot -> TypeError.
- next() moves from per-instance closure to %StringIteratorPrototype%.next.
- Factory stores s and index 0 as slots; uses unsafe_substring() for O(1)
  character slicing with correct surrogate-pair handling.

StringIteratorPrototype/next (all): 40/40

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

Three changes from post-review:
- string_iterator_next: `if index >= len { return }` -> `guard` (AGENTS.md convention)
- string_iterator_next: cache `s[index+1].to_int()` as `next_code` (was evaluated twice)
- make_string_iterator_value_with_proto: call array_iterator_internal_slot_descriptor()
  instead of inlining the same PropDescriptor literal
- make_set_iterator: call map_iterator_internal_slot_descriptor() instead of inlining

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@interpreter/stdlib/builtins_map_set.mbt`:
- Around line 1874-1922: `set_iterator_next` is using a raw array index cursor,
so Set mutations between `next()` calls can shift `set_data.values` and cause
skipped entries. Update the Set iterator logic in `set_iterator_next` to follow
the same stable-slot/tombstone approach used during live iteration, or otherwise
make the cursor track stable entry identities instead of array positions. Also
ensure `Set.prototype.delete` and `Set.prototype.clear` preserve iterator
correctness by not compacting in a way that invalidates existing
`SET_ITERATOR_NEXT_INDEX_SYMBOL_ID` state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf43179e-d239-4f69-8c26-363be364de2f

📥 Commits

Reviewing files that changed from the base of the PR and between b923e7c and 02c3e68.

📒 Files selected for processing (2)
  • interpreter/runtime/iterators.mbt
  • interpreter/stdlib/builtins_map_set.mbt

Comment thread interpreter/stdlib/builtins_map_set.mbt
set_iterator_next never incremented set_data.iteration_depth, so
delete/clear saw depth==0 during for..of and compacted (remove+shift)
instead of tombstoning. This caused the iterator cursor to skip elements
adjacent to deleted entries.

Fix: add SET_ITERATOR_ACTIVE_SYMBOL_ID=-138 slot (initialized false).
- First non-done next(): set ACTIVE=true, increment iteration_depth.
- Exhaustion: set ACTIVE=false, decrement iteration_depth; compact
  tombstones if depth reaches 0 (mirrors forEach cleanup at line 1107).

Abandoned iterators (for..of break/return) leave iteration_depth
elevated; there is no finalizer to decrement it.

SetIteratorPrototype: 22/22

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: 63542ee639

ℹ️ 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
data.bag.symbol_properties[SET_ITERATOR_ACTIVE_SYMBOL_ID] = Bool(
true,
)
set_data.iteration_depth += 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 Do not leave Set iteration_depth elevated

When a Set iterator is only partially consumed (for example const it = s.values(); it.next(); or a for...of loop that breaks), this increment is only undone if that same iterator is later exhausted. From then on delete/clear keep tombstones in data.values, and methods that are not tombstone-aware still use the raw backing array (e.g. isSubsetOf compares data.values.length()), so new Set([1,2]) after one next() and delete(1) can report isSubsetOf(new Set([2])) === false even though only 2 remains. Please avoid making the collection stay in iteration mode for abandoned iterators unless all later operations account for the persistent tombstones.

Useful? React with 👍 / 👎.

…terators

- Change delete/clear gate from `iteration_depth > 0` to `tombstones is Some(_)`
  so that compaction decisions are driven by whether tombstone mode is active,
  not by a depth counter that leaks when for..of loops are abandoned (break/return).

- make_set_iterator now enters tombstone mode (tombstones=Some({})) eagerly at
  iterator creation instead of lazily on first next() call. No ACTIVE sentinel slot.
  Remove SET_ITERATOR_ACTIVE_SYMBOL_ID constant and its per-iterator slot.

- set_iterator_next exhaustion compacts via compact_set_tombstones only when
  iteration_depth==0 (no forEach is currently walking the same set). If a forEach
  callback triggered the for..of, the forEach owns compaction on its own exit.

- forEach uses tombstones_pre_existed to decide who owns compaction: if tombstones
  was already Some (inherited from outer for..of), forEach leaves them intact.
  iteration_depth still counts active forEach calls to protect cursor stability
  when a nested for..of exhausts inside a forEach callback.

- Fix all tombstone-blind bulk methods: union, intersection, difference,
  isSubsetOf, isSupersetOf, isDisjointFrom, symmetricDifference now use
  set_data_effective_size() for the size comparison and skip/ignore tombstoned
  slots in scan loops and membership lookups via set_data_contains_live().

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: 5636e00864

ℹ️ 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 +2103 to +2104
if set_data.tombstones is None {
set_data.tombstones = Some(@set.Set::default())

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 Make Set size reads tombstone-aware

With this patch, merely calling s.values() puts the Set into tombstone mode, so a later delete or clear leaves removed entries in values until the iterator is exhausted. Normal s.size reads do not use the updated prototype getter: both Set size fast paths in interpreter/runtime/property.mbt still return set_data.values.length() (checked at lines 460 and 1181), so const s = new Set([1,2]); const it = s.values(); s.delete(1); s.size reports 2 instead of 1. Fresh evidence versus the earlier abandoned-iterator concern is that this new iterator-creation path still enables tombstones for ordinary property reads without updating those runtime fast paths.

Useful? React with 👍 / 👎.

Comment thread interpreter/stdlib/builtins_map_set.mbt Outdated
…exhaustion

- Add SetData::effective_size() method (values.length() - tombstoned count) and
  use it in both size fast paths in property.mbt (lines previously returned
  values.length() raw, reporting 2 for new Set([1,2]) after values()+delete(1)).

- Remove compaction from set_iterator_next exhaustion path. Compacting at
  exhaustion when multiple iterators share the same Set would shift values[]
  indices, corrupting other iterators' stored numeric cursor positions.
  Example: two iterators over [1,2,3]; delete(1) tombstones index 0; when the
  first iterator exhausts and compacts, the second (cursor=1) would skip to
  values[1]=3, missing element 2.

  Tombstone cleanup is now the exclusive responsibility of forEach when it
  exits tombstone mode (tombstones_pre_existed=false). Tombstones persist after
  iterator exhaustion — size is bounded by deleted-entry count (≤ values.length).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dowdiness dowdiness merged commit 2bfb17f into main Jun 24, 2026
15 checks passed
@dowdiness dowdiness deleted the fix/set-iterator-next-brand-check-done branch June 24, 2026 11:49
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