Skip to content

feat(ts): translate dyn Iterator and its subtraits as an rt.Iter - #437

Merged
kixelated merged 17 commits into
mainfrom
quest/m3/ts-dyn
Nov 3, 2026
Merged

kixelated merged 17 commits into
mainfrom
quest/m3/ts-dyn

Conversation

@kixelated

@kixelated kixelated commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

moq's model scans its live groups through a &mut dyn DoubleEndedIterator (model/track.rs), and its transports lend a lease's chunks as a Box<dyn Iterator<Item = &[u8]>> (transport/boxed.rs). Both are refused as "a trait object of another crate's trait", the most common trait-object refusal in moq's census.

Approach

A dyn Iterator, dyn DoubleEndedIterator, or dyn ExactSizeIterator is an rt.Iter of its items, as a dyn Future is an rt.Future. A coercion from &, &mut, or Box makes rt.iterDyn of the iterator it points at, std's or the crate's own (through rt.iterFrom). The runtime's new Dyn iterator forwards what std's vtable holds (next, size_hint, nth, advance_by, the back ends, len) and folds by next, as std's default methods read a trait object, so a last or count of a boxed Chain drops its items in the trait object's order and not in Chain's own. A box drops the iterator it owns. Borrowed (&mut I of a sized I) is now Dyn with std's fold specialization and no drop. An upcast between these traits is the same rt.Iter. A for loop over a Box<dyn Iterator> reads through the rt.Place that its &mut is.

Tests: dyns::iters in the lang fixture's differential test against native wasm32 covers std iterators, a range, and the crate's own iterator, borrowed and boxed. It runs each vtable method, std's adaptors over the borrow, moq's rev().find_map scan, an ExactSizeIterator's len with an upcast, the drop order of a boxed iterator's items and of a boxed Chain's last and count, and a Box<dyn Iterator> returned through the crate's own trait object, as moq's leases are. With rt.iterDyn returning its iterator unwrapped, the differential test fails on the Chain drop order (99 differences in 709,498 cases).

Census at the moq pin, before → after:

Refusals Trait objects of another crate's trait
moq-net 976 → 969 9 → 2 (dyn Error left)
model 232 → 229 3 → 0
kio 141 → 141 0 → 0

No construct gained refusals. The reject fixture's std trait object is now a dyn Hasher.

Review fixes: references and boxes in any order around an iterator, a shared one included, are read through in one pass to the iterator; a raw pointer to one as a dyn Iterator is refused; and a box's specializations are the box's, not what it holds. Earlier, a box of the crate's own iterator (Box<I>, as std's Iterator for Box<I> forwards) is now read as I in into_iter, iterator method calls, and next, so Box<Box<I>> as a dyn Iterator, &mut Box<I> and &mut Box<Box<I>> as a &mut dyn Iterator, &mut Box<dyn Iterator> reborrowed, and adaptors over a Box<I> translate. Each was reproduced as a tsc failure first and is covered in dyns::iters; this also fixes Box<I> of a crate iterator outside trait objects, which was broken before this PR.

Impact

  • The translator's output: these trait objects translate instead of failing. New runtime export rt.iterDyn.
  • No breaking change.

Alternatives

  • Pass the iterator itself, unwrapped: rejected, since its own folds and last then differ from what std's defaults do through the trait object (the differential test shows it).
  • An object of the trait's methods, as the crate's own traits get: std's adaptors and consumers all take an rt.Iter, so this would need a second protocol.

Follow-ups

  • Arc<dyn Iterator> and dyn FusedIterator stay refused: nothing in moq builds them, and nothing calls next through an Arc.
  • Iterator::by_ref of a &mut &mut I and mem::take passed as a function value are existing refusals, met while writing the fixture.
  • The quest keeps dyn Error's downcasts and the rest.

Review

Codex gpt-6.1-sol, medium effort, via quest review --base origin/main, on the merged branch:

  • Round 1 (a4b8a829): a boxed crate iterator reached rt.iterDyn as its raw object. Fixed in 191305a7. A second finding (ffi lending budget) was an artifact of a base the branch had not merged yet; e3ada274 merges it.
  • Round 2 (191305a7): &mut Box<dyn Iterator> reborrowed passed its place. Fixed in ef3bc43f.
  • Round 3 (ef3bc43f): a borrowed Box<Box<I>> read its place twice. Fixed in df1213d6.
  • Round 4 (84bd93ed): a shared &Box<I> as a &dyn Iterator read a place it never was, and a borrowed Box<&mut &mut I> kept its references, both from peeling references and boxes in two passes. The maintainer chose to unify the peel: peel_iter reads through &, &mut, and boxes in any order to one &mut I with that &mut's JS value, and dyn_iter passes a shared reference as it is. Fixed in 1d5bee91.
  • Round 5 (1d5bee91): a raw pointer to an iterator coerced to *mut dyn Iterator was relabeled a &mut. Now refused with a span, covered by the reject fixture's raw_dyn_iter, in 8231fa0d.
  • Round 6 (8231fa0d): peeling a box let collect see the held zip's TrustedLen, which a box lacks, so it folded where std reads by next. Specializations are now picked by the receiver's own type, and only next, next_back, nth_back, and into_iter use the peeled one, in 237cd7ec.
  • Round 7 (2207a73a, the same code before a just fix of the fixture): no findings.

Each round's case was reproduced first (a tsc failure, a missing refusal, or a differential mismatch) and is covered in dyns::iters or the reject fixture.

just ci passed on 749a2e10b7b14b5a36d117b0d44c0050ad81961d, which merges main through #468.

(written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 3 commits November 2, 2026 04:06
Quest-Claim: 63ea0c57a7775853
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Trait objects feat(ts): translate dyn Iterator and its subtraits as an rt.Iter Nov 2, 2026
@kixelated
kixelated marked this pull request as ready for review November 2, 2026 13:23
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 4 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d7503730-7eca-47b9-80c4-fb218376e001
Commits

Reviewing files that changed from the base of the PR and between 448555e and 749a2e1.

Files selected for processing: 22
  • quest/m3/README.md
  • quest/m3/ts-dyn.md
  • ts/README.md
  • ts/fixtures/lang/expected/core/option.ts
  • ts/fixtures/lang/expected/dyns.Chunks.ts
  • ts/fixtures/lang/expected/dyns.Countdown.ts
  • ts/fixtures/lang/expected/dyns.Lease.ts
  • ts/fixtures/lang/expected/dyns.Tagged.ts
  • ts/fixtures/lang/expected/dyns.ts
  • ts/fixtures/lang/expected/harness.ts
  • ts/fixtures/lang/src/dyns.rs
  • ts/fixtures/lang/src/harness.rs
  • ts/fixtures/reject/census.md
  • ts/fixtures/reject/expected.txt
  • ts/fixtures/reject/src/lib.rs
  • ts/fixtures/reject/ts.toml
  • ts/runtime/rt/iter.ts
  • ts/src/lower/dynamic.rs
  • ts/src/lower/iter.rs
  • ts/src/lower/mir/call.rs
  • ts/src/lower/mir/rvalue.rs
  • ts/src/lower/mod.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@moq-bot

moq-bot Bot commented Nov 2, 2026

Copy link
Copy Markdown

codex / Codex land fixer note:

The supplied failure log reports at line 1:

Conflicts with the base: quest/m3/README.md, quest/m3/ts-dyn.md, ts/README.md, ts/fixtures/lang/expected/harness.ts, ts/fixtures/lang/src/harness.rs, ts/fixtures/reject/census.md

I resolved the conflicts locally by merging base, keeping both sides' documentation and fixture cases, and regenerating the harness snapshot and rejection census. Runtime TypeScript checking, Rust formatting, Clippy, and census validation passed with the pinned nightly.

Validation command UPDATE_EXPECT=1 just ts test --test fixtures lang -- --test-threads=1 failed:

lang-fixture differs from native Rust:
709559 cases, 84408 panics, 125 aborts, 1 differences

Failing input: a6c09700bfff8568ff4f2c3440ff6311ffa818ff9f1fbf2ff710668f43bcffbade79a200e7ff0aff.

That input selects floats::mul_adds in the combined harness. A direct replay reproduced output index 25 as native 3f41e8176d8609aa versus translated 3f41e8176d8609ab, a one-ULP difference rejected by the existing comparison. This needs a reviewed behavior fix, outside the mechanical repair scope.

The broader test run was stopped after this deterministic failure. I aborted the trial merge and left pr-437 at its original pinned head.

(written by codex / Codex)

@moq-bot
moq-bot Bot disabled auto-merge November 2, 2026 18:34
@moq-bot

moq-bot Bot commented Nov 2, 2026

Copy link
Copy Markdown

codex / Codex land fixer: UPDATE_EXPECT=1 just ts test --test fixtures lang -- --test-threads=1 failed: '709559 cases, 84408 panics, 125 aborts, 1 differences'; floats::mul_adds differs by one ULP.. Recommendation: Re·solve the six base conflicts preserving both sides, then review and fi·x the f64 mul_add rounding mismatch before requeueing; it requires a behavior change.

Conflicts with the base: quest/m3/README.md, quest/m3/ts-dyn.md, ts/README.md, ts/fixtures/lang/expected/harness.ts, ts/fixtures/lang/src/harness.rs, ts/fixtures/reject/census.md

(written by codex / Codex)

kixelated and others added 13 commits November 2, 2026 14:21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	ts/README.md
#	ts/fixtures/lang/expected/dyns.ts
#	ts/fixtures/lang/expected/harness.ts
#	ts/fixtures/lang/src/dyns.rs
#	ts/fixtures/lang/src/harness.rs
#	ts/fixtures/reject/census.md
# Conflicts:
#	ts/fixtures/reject/census.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
std's Iterator for Box<I> forwards to I, which JS holds as itself: a boxed
crate iterator reached rt.iterDyn, adaptors, and next as its raw object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lace

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	ts/fixtures/lang/expected/harness.ts
#	ts/fixtures/lang/src/harness.rs
# Conflicts:
#	quest/m3/README.md
#	quest/m3/ts-dyn.md
#	ts/README.md
#	ts/fixtures/lang/expected/harness.ts
#	ts/fixtures/lang/src/harness.rs
#	ts/fixtures/reject/census.md
…order

A shared `&Box<I>` as a `&dyn Iterator` read a place it never was, and a
`Box<&mut &mut I>` borrowed kept its references, as references and boxes
were peeled in two passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A box of a `TrustedLen` zip collected through the zip's own fold, though
std reads a box by `next`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated enabled auto-merge November 3, 2026 02:40
@kixelated
kixelated added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit ccf8d33 Nov 3, 2026
7 checks passed
@kixelated
kixelated deleted the quest/m3/ts-dyn branch November 3, 2026 02:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant