Repository navigation
feat(ts): translate writes into a Vec's spare capacity, committed by set_len, for any T - #623
Conversation
Quest-Claim: 53470320f13a36a5
…set_len, for any T Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…apacity # Conflicts: # ts/fixtures/reject/census.txt # ts/fixtures/reject/expected.txt # ts/fixtures/reject/ts.toml
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…apacity # Conflicts: # ts/fixtures/lang/panics/hook/expected/hooked.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou'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 22 minutes. View limit details
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. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head 0fbefa8 against base 324a7b7.
No actionable findings in the reviewed spare-capacity implementation. Room storage is keyed by the allocation's items array, and the raw-slice view keeps that array rather than following the original Vec wrapper after mem::take or swap. Pointer copies snapshot their source before writing, including slots past the logical length. set_len fixes capacity before changing length, saves the shortened tail without dropping it, and takes initialized slots back into items. The fixture exercises pointer writes/copies, raw slices, modeled drain/retain patterns, object-backed spare slots, moves, drop counts, and copying uninitialized MaybeUninit slots before writing them.
Direction: keep allocation identity and logical length separate as future Drain/BinaryHeap work builds on this. An explicit offset_from case between a spare-capacity pointer and the Vec's original pointer, plus a spare-slice array-view case, would further pin the View normalization used here. These are coverage suggestions, not identified defects. I did not treat regrowing removed elements after pop/truncate as a required preservation guarantee; that documented decision is distinct from the explicitly supported set_len-shortening path.
Limits: GitHub-only static review of the runtime, lowering, source fixtures, pointer tests, surrounding View/pointer/capacity machinery and documentation. No build, differential tests, random probes, benchmark, quest context, or CI executed, and the PR's reported validation was not independently reproduced. No claim of exhaustive coverage of every element type or unsafe program. This COMMENT is not merge approval.
…c-spare-capacity # Conflicts: # ts/README.md # ts/fixtures/lang/panics/hook/expected/hooked.ts # ts/fixtures/reject/census.txt # ts/fixtures/reject/expected.txt # ts/fixtures/reject/src/buffers.rs # ts/fixtures/reject/ts.toml # ts/runtime/rs/collections.ts

Finishes quest/m3/ts-unsafe/vec-spare-capacity.md.
Problem
rust2-ts refused
Vec::<T>::set_lenandspare_capacity_mutfor anyTbut a byte, and a write throughas_mut_ptr().add(len)threwa read or write through a pointer past the array JS keeps it inwhere Rust defines it. Anrs.Vec's items are a JS array exactly as long as theVec, so a write past the end would lengthen it, and nothing held the room past the length. Throw only where Rust panics makes that throw a gap.Approach
Vec's room past its length apart from its items, in anrs.Room(aMapby index), found from the items array through aWeakMap.Vec::as_ptrandas_mut_ptrare nowrs.vecAsPtr(v, size), which registers the room;AtIndexreads and writes,ptr::copy,slice::from_raw_parts, andslice::from_mutpast the length reach it, up to the capacity. A pointer past an array that is noVec's items, or into items a step since replaced, still throws, as before.spare_capacity_mutisrs.vecSpare(v, size, uninit): a slice whose array is a proxy view (aViewsubclass, asas_chunks_mut's runs are) of the items and then the room. A slot of a type JS keeps as an object is made byuniniton first read, soslot.write(v)copies into it as forBox::new_uninit_slice.set_lenisrs.vecSetLen(v, n, size, uninit): it panics past the capacity with std's precondition message, asbyteVecSetLendoes, fixes the capacity, moves what it gives up into the room and takes slots in from it. So std'sDrainandretainpatterns (shorten byset_len, read past the length through the pointer, lengthen again) are exact.MaybeUninitis (undefinedfor a value JS copies), which a copy keeps.spare::sparescase in thelang/elementsfixture against native wasm32:u32,String, andOption<u16>through pointers (by element,copy_nonoverlapping, a slice of the pointers, an insert that moves the tail into the room, a drain and a retain throughset_len), and those plus a drop-counting struct, a struct, and a tuple throughspare_capacity_mutslots (write, whole assignment,write_clone_of_slice,fill_with, read beforeset_len), and unwritten slots copied, including aVec<MaybeUninit<u16>>lengthened over them. The hooked panics fixture'sset_lenpast the capacity now covers aVec<u32>too, andtests/pointers.tsthe room's bounds.Impact
Vec::<T>::spare_capacity_mutandset_lentranslate for everyT, andVec::as_ptr/as_mut_ptrlower tors.vecAsPtrinstead ofrs.arrayAsPtr(v.items)(snapshots ofelements,slices, and the hookedpanicschange accordingly).vecAsPtr,vecSpare,vecSetLen,roomPast, and theRoomclass (runtime-internal, used bycollections.ts).rejectfixture losesbuffers::spare, which translates now; the census drops itsVec::spare_capacity_mutverbatim entry.Decisions
set_lenthat shortens a non-byteVec(pop,truncate,clear,remove,drain,retain, ...) leaves the room as it was. ✅ land as is, with no follow-up quest (rejected: throw on aset_lenthat regrows over such a slot; every shortening step saves what it gives up; reworkrs.Vecto keep its length apart from capacity-long storage). std'sVec::set_lenrequiresold_len..new_lento be initialized, and theVecdocs' Guarantees section says it "will not specifically overwrite any data that is removed from it, but also won't specifically preserve it. Its uninitialized memory is scratch space". So a slot a shortening step gave up is uninitialized, and lengthening over it breaks the contract (for a type with a destructor, it drops twice). Native's[1, 2]forlet mut v = vec![1u32, 2]; v.pop(); unsafe { v.set_len(2) }is an accident of today's std, and[1, undefined]is a valid result.ts/README.mdnow cites the contract instead of calling this a gap, and no test covers the pattern.Choices made without asking
Vecof objects stay refused.as_mut_ptrof aVec<T>whoseTJS keeps as an object (a struct, a tuple, or a type withDrop) is refused with a span, as the epic's 2026-11-01 decision keeps arithmetic on pointers to objects. ThoseVecs get their spare capacity throughspare_capacity_mutandset_len, which translate for everyT. The quest's "for anyT" holds for that path, and for the pointer path wherever the pointer model allows it.Mapbeside the items, not a separate length field onrs.Vec. Givingrs.Veca length apart fromitems.lengthwould touch everyVecoperation and the hot paths the capacity work kept free. A room costs aWeakMaplookup peras_ptr/as_mut_ptrcall and nothing elsewhere. No moq-net workload callsVec::as_ptr(the model fixture's snapshot is unchanged), so I took no bench numbers.Alternatives
spare_capacity_mutwould slice directly with no proxy: its pointers would then be in another array thanas_mut_ptr's, sooffset_frombetween them and arithmetic across the length would break.Vec, instead of theWeakMap: it saves a lookup, but it changes the hidden class of every array that had a pointer taken.Follow-ups
None.
Caused by
No single PR: the run-time throw was the decided behavior of moq-dev/rust2#400 until moq-dev/rust2#597's rule made it a gap.
Validation
just ci origin/main: passes on the head (ts lint, 89 fixture tests, 10 runtime tests, quest check).mem::takeandmem::swapof aVecwith a room, and a raw slice read through a pointer from before the move. Each is covered byspare::sparesand fails the probe without its fix.spare::spareskinds against the native build: no differences.mainafter review round 3 on 58e9b0b; the next commit rewords the README's line on shortening steps per the decision above.maintwice after review round 4 (mainlanded moq-dev/rust2#574's strings among others): conflicts ints/README.md,collections.ts's imports, and therejectfixture's sources resolved by hand, keeping both sides; therejectcensus, expected errors, the hookedpanicssnapshot, and theelementssnapshot (aStringis nowrs.String) regenerated withUPDATE_EXPECT=1.just ci origin/mainpasses (89 fixture tests, 10 runtime tests, quest check); review round 5 is clean.Models
Size: M
Implementer: Claude Code, claude-opus-5-5, high effort
(Written by Claude Opus 5.5 with high effort)
🤖 Generated with Claude Code
Review
Rewritten by
quest reviewafter each round.01a1b3c6-4f0e-7922-bd37-79f7bf42269a, round 5No defects found in the specified branch diff; runtime type-checking, pointer tests, targeted move probes, and 8,137 existing differential cases pass.