Skip to content

feat(ts): translate Debug of std's iterators - #317

Merged
kixelated merged 5 commits into
mainfrom
quest/m3/ts-iter-debug
Oct 31, 2026
Merged

kixelated merged 5 commits into
mainfrom
quest/m3/ts-iter-debug

Conversation

@kixelated

@kixelated kixelated commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

format!("{it:?}") of std's iterators refused with unsupported call to <... as Debug>::fmt and a span (found by #312), while their collections' Debug translated. Each iterator writes its own text: Iter([1, 2]), Chars(['a']), Bytes(Copied { it: Iter([97]) }), a bare list of entries, or Range { iter: [(2, SetValZST)] }.

Reproduced on lang: 34 refusals of Debug across the iterators below.

Approach

  • The runtime's iterators behind these types gain remaining(): the items each has left, by value, without taking them (a place-lending iterator's values). rt.fmtIter writes them as a list inside the wrappers std writes, rt.fmtChars writes Chars(...), and rt.fmtSetVal writes a BTreeSet entry as std's internal map entry, (key, SetValZST).
  • fmt.rs maps each iterator's Debug by its std path to its wrapper and item type, as read from the pinned std source and confirmed natively:
    • Iter([..]): slice::Iter, binary_heap::Iter, btree_set::Iter; IterMut([..]): slice::IterMut; IntoIter([..]): vec, binary_heap, and vec_deque's; Drain([..]): vec::Drain; Drain { iter: Drain([..]) }: binary_heap::Drain.
    • Chars([..]) and Bytes(Copied { it: Iter([..]) }).
    • A bare list: btree_map's Iter, IterMut, Keys, Values, ValuesMut, Range, RangeMut, IntoIter, IntoKeys, IntoValues; hash_map's Iter, IterMut, Keys, Values, ValuesMut, IntoIter, IntoKeys, IntoValues, Drain; hash_set's Iter, IntoIter, Drain.
    • Range { iter: [(k, SetValZST), ..] } and IntoIter { iter: [..] }: btree_set::Range and IntoIter.
  • An iterator lending &[u8; N] as rt.ByteSlices was an iterMap; it is now rt.iterLend, whose remaining() reads through to the items as the collection holds them, so a slice's or collection's iterator of byte arrays prints too. values_mut of copied values is now its own pick of HashIter and BTreeIter rather than an iterMap over iter_mut, so it has remaining() too.
  • A hash iterator's remaining() walks a fresh iterator of the same map past what it took; the map cannot change while the iterator borrows or owns it.
  • A new fmt::iters case in lang runs differentially against native wasm32: every iterator above consumed from the front by nth and from the back by next_back (B-tree ones included), empty, {:#?}, {:x?}, width, Chars and Bytes over one- to four-byte chars and escaped chars, byte-array elements and keys, and elements with custom Debug. Hash iterators hold at most one entry, consumed or not, since std leaves their order unspecified. Breaking Chars::remaining and the SetValZST text made the differential fail with 186 differences.

Kept refused, with their span:

  • vec_deque::Iter and IterMut, and VecDeque::as_slices: they write the ring buffer's two halves, Iter([3], [1, 2]), and the runtime's deque splits elsewhere than std's (power-of-two growth that moves the head to 0, with_capacity ignored, item-by-item shifts in insert and remove). Recorded as A deque's two slices, after VecDeque capacity in std-rest.
  • vec_deque::Drain, which writes its internal counts, Drain(drain_len, idx, tail_len, remaining): same quest.
  • CharIndices: char_indices itself has no mapping, so nothing makes one.

Impact

  • The translator's output: {:?} of the iterators above translates instead of being refused. Iterators lending byte arrays are made by rt.iterLend instead of rt.iterMap.
  • Runtime: adds rt.fmtIter, rt.IterWrap, rt.fmtChars, rt.fmtSetVal, rt.iterLend, and Iter.remaining() on std's iterators that have it.

Alternatives

  • Cloning the iterator, as std's Debug impls do: the runtime's iterators have no clone, and a JS Map iterator can't be copied, so each kind reads its own state instead.
  • Unwrapping iterMap in fmtIter for byte arrays: iterLend keeps that read-through to the one wrapper it is right for.

Follow-ups

Choices made without asking: the quest named the listed iterators; I also covered each sibling the same mapping refused (IterMut, IntoIter, Drain, Range, Into*, *Mut). The quest's plan said btree_set::Iter writes a bare list; std writes Iter([..]), which this follows.

Review

Codex (quest review), gpt-6.1-sol, medium effort, against origin/main d1a076ef:

  • Round 1 on ac3946de: one finding, "Preserve bounded zip folds for borrowed iterators" (ts/src/lower/iter.rs:877). Refuted: it was #314's zip fold, on origin/main but not yet merged into that head, so the review saw it as reversed; this PR's only change to iter.rs is by_ref's iterMap to iterLend. Fixed by merging origin/main.
  • Round 2 on 517d782da7479f7ef6001de254ef6712bfd7df3f: no findings.

just ci passed locally on 517d782d.

🤖 Generated with Claude Code

(written by Claude Opus 5.5)

kixelated and others added 3 commits October 30, 2026 17:23
Quest-Claim: 46472d86c834193c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	ts/fixtures/lang/expected/harness.ts
#	ts/fixtures/lang/src/harness.rs
@kixelated kixelated changed the title std iterators' Debug feat(ts): translate Debug of std's iterators Oct 31, 2026
# Conflicts:
#	ts/fixtures/lang/expected/harness.ts
#	ts/fixtures/lang/src/harness.rs
# Conflicts:
#	ts/fixtures/lang/expected/harness.ts
#	ts/fixtures/lang/src/harness.rs
@kixelated
kixelated marked this pull request as ready for review October 31, 2026 01:38
@kixelated
kixelated merged commit 49f119b into main Oct 31, 2026
1 of 2 checks passed
@kixelated
kixelated deleted the quest/m3/ts-iter-debug branch October 31, 2026 01:39
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