Skip to content

feat(ts)!: parts of arrays written where they are, without arrayView - #662

Merged
kixelated merged 15 commits into
mainfrom
quest/m3/ts-slice-object
Nov 8, 2026
Merged

kixelated merged 15 commits into
mainfrom
quest/m3/ts-slice-object

Conversation

@kixelated

@kixelated kixelated commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m3/ts-slice-object.md and deletes it.

Decisions

✅ Decided with the maintainer on 2026-11-07 at the round-3 design checkpoint: refuse, with a span, a shared reference or as_ref through a raw pointer to an array a MaybeUninit or ManuallyDrop holds (other than bytes), since the pointer may point into part of a bigger array, where the reference was a copy. That closes the assume_init_ref() route, where ptr::eq returned false and Rust returns true. It applies m3's unsafe policy: a silent mismatch is refused rather than recorded.

  • The reborrow &*p of the same shape already shared that copy on main (rvalue.rs), so it is refused too, for consistency.
  • The comparison refusal this PR had added for such pointers is gone, as no route to a copy remains.
  • slices::array_sources case 3's reborrow of a MaybeUninit<[[u8; 2]; 4]> through a raw pointer, from moq-dev/rust2#608, is dropped: it was the only use of the reborrow in the suite. Its borrowed Cow match stays.
  • Rejected: merging as is and moving the gap to Throw only where Rust panics (it stays a silent mismatch); tracking copy origins in a WeakMap (pointer metadata, which the policy refuses); dropping the PR.

Problem

The runtime's arrayView was a Proxy JS array over part of another, which rs.sliceView made wherever a &mut [T; N] to part of an array was taken whole as a JS array: by mem::replace and mem::swap, ptr::replace and ptr::swap, Cell::from_mut and UnsafeCell::from_mut, std mappings given one to write through, and a raw pointer's as_mut or Box::from_raw. Every pointer helper (arrayAsPtr, elemOf, sliceSame) also had to see through it.

Approach

Each construct now reads and writes the slice where it is:

  • mem::replace/mem::swap of a *r call rs.sliceReplaceAll(s, items, depth), and ptr::replace/ptr::swap copy out by rs.sliceCopy(s, depth). Both copy elements a write lands in to depth, as objectCopy does.
  • Cell::from_mut and UnsafeCell::from_mut of a &mut [T; N] make rs.cellOfSlice(s), an ArrayCell: a read gives a copy and a write lands in the slice. A cell's pointer (as_ptr, UnsafeCell::get, get_mut, as_ref_unchecked, as_mut_unchecked) is now rs.cellArray(cell, from): the slice the cell was made of, or else the slice of all of the array it holds. rs.cellOfArraySlice makes the same kind of cell.
  • ptr::as_ref/as_mut and NonNull::as_ref/as_mut of a pointer to an array return the pointer's slice as the reference, with no lend (mapping-land arrays) in between. For an array a MaybeUninit or ManuallyDrop holds, whose reference is the array, they follow the reborrow of *p: a byte view, and else refused.
  • Box::from_raw of a pointer to an array of elements calls rs.boxArrayFromRaw. It gives the array when the pointer covers all of it, as Box::into_raw made it, and otherwise aborts the way std's unsafe precondition(s) violated checks do. A box of an array of no bytes owns no allocation, so any pointer makes one: a new array of its length, as a dangling pointer's is.
  • arrayView, rs.sliceView, and the View class are gone. RoomView, the Proxy over a Vec's items and spare capacity, keeps its own traps without the start/part that only views of views needed.

Differential tests against native Rust: slices::array_parts (replace and swap of numbers and strings; Cell/UnsafeCell of numbers, strings, and arrays of arrays through each read, write, and pointer; a Cell<[[T; N]; M]> pointer read after set; a pinned box's allocation kept across Cell::replace; std's Option/Vec holding a &mut [T; N]) and pointers::array_parts (as_mut, NonNull::as_mut, ptr::replace, ptr::swap, and Box::from_raw of arrays, including arrays of no bytes reboxed shorter, longer, and from part of a Vec, as_mut of arrays of arrays, and wrapped arrays lent by as_ref of bytes and by a cell's get_mut and as_mut_unchecked).

Fixed on the way, both reproduced against native first:

  • A Cell::from_mut of an array of arrays read a copy, so a write through its as_ptr landed in the copy. The new slices case gave 61 differences from native with main's pointer.
  • A Cell<[[T; N]; M]> was written whole by replacing its array, so a pointer taken earlier by as_ptr read the old one. Its writes now land in place, as other arrays' do.
  • A copy of arrays of arrays (mem::replace, Cell::replace, ptr::replace) copied every object element by objectCopy, which gave a pinned box a new allocation. It now copies only where a write lands in the object, in an array of one sliceIn or sliceOfPlace made, as sliceCopy and objectCopy already decide. Without that, the pinned-box case gives 32 differences from native.
  • ptr::as_ref of a pointer to an array a MaybeUninit holds gave undefined where its Option needs rs.None, which tsc rejected.
  • Lifted refusals: UnsafeCell::from_mut of an array of arrays, and as_mut/NonNull::as_mut of a raw pointer to an array of arrays.

Caused by

  • moq-dev/rust2#608 - rs.cellOfArraySlice read a copy, which Cell::as_ptr then pointed at
  • moq-dev/rust2#625 - a Cell of arrays of arrays written whole, leaving an earlier as_ptr on the old array

Impact

  • Breaking: runtime exports sliceView and the internal arrayView are removed. Translated code no longer calls them.
  • Added runtime exports: sliceCopy, sliceReplaceAll, boxArrayFromRaw, cellOfSlice, cellArray.
  • ArraySlice copies (arraySliceCopy, arraySliceReplace, arraySliceBy, cellOfArraySlice) apply an objectCopy element copy only in arrays of one sliceIn or sliceOfPlace made.
  • Translator output: Cell<[T; N]> and Cell<[[T; N]; M]> pointers read through rs.cellArray, and ptr::as_mut of an array pointer is the slice itself.
  • Newly refused, each with a span. None had a consumer: the full suite, including moq-net's codec, model, and census translations, passes.
    • std given a &mut [T; N] of non-bytes to write through as an array (only bytes have a JS view).
    • slice::from_mut/array::from_mut of a MaybeUninit<[T; N]> that is part of an array (the raw-pointer route to it is refused earlier already).
    • Every reborrow through a raw pointer of an array a MaybeUninit or ManuallyDrop holds, other than bytes: as_ref, as_mut, NonNull::as_ref/as_mut, and &*p (shared, which main translated as a copy where the pointer points into part of an array) and &mut *p (already refused). See Decisions.
  • Box::from_raw of an array of arrays stays refused, now saying why: arrays as_chunks split from one flat array have no array of their own to box.
    • Box::from_raw of an array of no bytes whose elements are more than structs and tuples (such as an enum of one variant), which the translator can't make anew, as ptr::dangling of one is refused already.
  • No wire impact.

Choices made without asking

  • The give path for a &mut [T; N] of non-bytes is refused rather than given a copy written back. No std call in any fixture or in moq-net reaches it, and a copy is only exact when the call keeps no borrow, which array_slice::give already has to refuse for slices of arrays.
  • The cell of a slice is one runtime class for both slices and array slices (ArrayCell). That replaced cellOfArraySlice's accessor place and fixed its pointer.
  • Box::from_raw of part of an array aborts as UB, per the runtime-throws decision that UB guards stay as precondition aborts.

Alternatives

  • Keep sliceView as a translator-only marker that sliceFrom cancels, and refuse any that survives. Rejected: every survivor had an exact form once the pointer mappings return the slice directly.
  • A cell holding a mirror array flushed back to the slice. Rejected: writes through as_ptr would land in the mirror with nothing to flush them.

Follow-ups

  • Plan: TryFrom<&mut [T]> for &mut [T; N] (<&mut [T; N]>::try_from(&mut s[..2])), Cell::as_array_of_cells, and convert::identity of a &mut [T; N] are unsupported calls today; the tests use first_chunk_mut instead. try_into of a slice into an array reference is common in parsers.
  • Drop: a raw pointer to [u32; N] anywhere in the slices fixture made its rest @ .. patterns fail with "a reference through which a raw pointer may be made into [u32; 4] in [u32; 5]". So the raw-pointer tests live in pointers. It is the raw-pointer model working as designed (Unsafe code the translator can model owns it).

Validation

  • quest gate nix develop --command just ci: passed at 3b9fefb.
  • Measurements: none; the change removes a Proxy from the paths it touches.

Models

Size: M
Implementer: Claude Code, claude-opus-5-5, high effort

🤖 Generated with Claude Code

(Written by Claude Opus 5.5 with high effort)

Review

Written by hand: quest review printed record skipped, as this session reviewed from a local branch named iterate/ts-slice-object (the PR's branch is checked out in another worktree).

  • Reviewer: Codex, gpt-6.1-sol, high effort
  • Session: 01a1b8a4-efc7-7fb0-86a2-4f882e71fca4, round 3 (new session after the design change)
  • Head: 3b9fefb
  • Base: origin/main at 2a0a7d4
  • Score: 10/10, The length regression is fixed, runtime checks and 24 targeted differential inputs pass, and no remaining defects were found; full suites were blocked by the read-only gate.
  • Findings: none
  • Earlier rounds:
    • Round 1 at 88d9999: fixed, medium, ts/runtime/rs.ts:923: [P2] Handle zero-sized arrays before enforcing whole-storage reboxing (a Box<[()]> reboxed as *mut [(); 2] aborted).
    • Round 2 at f9c6bcb: fixed, medium, ts/src/lower/pointer.rs:852: [P2] Preserve the requested length when reboxing zero-sized arrays (reboxed as *mut [(); 5], it had three elements).

@pullfrog

pullfrog Bot commented Nov 7, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

kixelated and others added 4 commits November 7, 2026 14:51
A `&mut [T; N]` to part of an array is read and written where its slice holds
it: `mem::replace` and `mem::swap` by `rs.sliceReplaceAll`, `ptr::replace` and
`ptr::swap` by `rs.sliceCopy`, `Cell::from_mut` and `UnsafeCell::from_mut` by a
cell of the slice whose pointer is the slice, `ptr::as_mut` and
`NonNull::as_mut` as the slice itself, and `Box::from_raw` as the array a box
owns all of. The runtime's `arrayView` and `rs.sliceView` go.

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>
@kixelated kixelated changed the title Parts of arrays without arrayView feat(ts)!: parts of arrays written where they are, without arrayView Nov 7, 2026
kixelated and others added 6 commits November 7, 2026 15:21
…oxes taken back

A reference from a raw pointer or a cell to an array a `MaybeUninit` holds is
the array, as a reborrow makes it, with a mutable one through a raw pointer
refused; an array of arrays' copy copies objects a write lands in only where
one does; and `Box::from_raw` takes back arrays `as_chunks` split from all of
a box's array.

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

A shared reference through a raw pointer to part of an array a `MaybeUninit`
holds is a copy, so comparing pointers to such arrays is refused; and
`Box::from_raw` of arrays of arrays stays refused, as arrays `as_chunks` split
from one flat array have none of their own to box.

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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shared reference or as_ref through a raw pointer to an array a MaybeUninit
or ManuallyDrop holds, of other than bytes, was a copy where the pointer points
into part of an array, so assume_init_ref compared unequal where Rust's is
equal. It is now refused, as the mutable one is, and pointer comparisons of
such arrays are no longer refused.

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

Copy link
Copy Markdown
Collaborator Author

Design change: per the maintainer's decision on 2026-11-07 at the round-3 checkpoint, a reborrow through a raw pointer of an array a MaybeUninit or ManuallyDrop holds (other than bytes) is now refused with a span, shared as well as mutable: ptr::as_ref/as_mut, NonNull::as_ref/as_mut, &*p, and &mut *p. The pointer may point into part of a bigger array, which has no JS array of its own, so a shared reference was a copy, and assume_init_ref() compared unequal where Rust's compares equal.

  • &*p of the same shape already made that copy on main. It is refused too, for consistency.
  • The comparison refusal this PR had added for such pointers is removed, since no route to a copy remains.
  • The reborrow in slices::array_sources case 3 (from feat(ts)!: rs.ArraySlice for slices of arrays, replacing the as_chunks_mut proxies #608), its only user, is dropped. The case's borrowed Cow match stays.
  • Rejected: merging as is with the gap moved to ts-runtime-throws, a WeakMap of copy origins, and dropping the PR.

The next review starts fresh with quest review --new.

(Written by Claude Opus 5.5 with high effort)

kixelated and others added 3 commits November 7, 2026 17:02
A box of a zero-sized array owns no allocation, so Rust takes any pointer
to one back as a box; it no longer aborts where the pointer covers part of
an array.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pointer to an array of no bytes may claim more elements than it points
at, so the box is a new array of its length, as a dangling pointer's is,
and refused where its elements are more than structs and tuples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 8, 2026 01:21
@coderabbitai

coderabbitai Bot commented Nov 8, 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 33 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: dd886d52-3eda-4f49-a933-8d0c3b1b14e8
Commits

Reviewing files that changed from the base of the PR and between 2a0a7d4 and 3b9fefb.

Files selected for processing: 29

  • quest/m3/README.md
  • quest/m3/ts-runtime-throws.md
  • quest/m3/ts-slice-object.md
  • ts/fixtures/lang/conversions/expected/conversions.ts
  • ts/fixtures/lang/objects/expected/objects.ts
  • ts/fixtures/lang/pointers/expected/core/option.ts
  • ts/fixtures/lang/pointers/expected/lib.ts
  • ts/fixtures/lang/pointers/expected/pointers.ts
  • ts/fixtures/lang/pointers/src/lib.rs
  • ts/fixtures/lang/pointers/src/pointers.rs
  • ts/fixtures/lang/slices/expected/core/option.ts
  • ts/fixtures/lang/slices/expected/lib.ts
  • ts/fixtures/lang/slices/expected/slices.ts
  • ts/fixtures/lang/slices/src/lib.rs
  • ts/fixtures/lang/slices/src/slices.rs
  • ts/fixtures/reject/census.txt
  • ts/fixtures/reject/expected.txt
  • ts/fixtures/reject/src/alloc.rs
  • ts/fixtures/reject/ts.toml
  • ts/fixtures/task/expected/drops/called.ts
  • ts/runtime/rs.ts
  • ts/runtime/rs/task.ts
  • ts/src/census.rs
  • ts/src/lower/lend.rs
  • ts/src/lower/library.rs
  • ts/src/lower/mir/call.rs
  • ts/src/lower/mir/rvalue.rs
  • ts/src/lower/pointer.rs
  • ts/src/lower/task.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

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Head: 3b9fefb
Base: 2a0a7d4

No actionable findings from this static review.

Reviewed the in-place slice replacement/copy paths, ArrayCell and cellArray pointer preservation, nested-array copy behavior, raw-pointer and cell reference lowering, wrapped-array refusals, zero-sized array reboxing, and RoomView changes against the surrounding implementation and Rust/generated TypeScript fixtures. The direction is consistent: references keep the original slice, whole writes land there, and unsupported non-byte wrapped-array borrows are rejected with a span rather than silently copied.

Validation limits: GitHub-only source review. I did not run builds, tests, the differential suite, or quest commands, and have not independently verified the reported CI results. This is a COMMENT review, not approval to merge.

(Written by review (OpenAI))

@kixelated
kixelated added this pull request to the merge queue Nov 8, 2026
Merged via the queue into main with commit 7a9926d Nov 8, 2026
7 checks passed
@kixelated
kixelated deleted the quest/m3/ts-slice-object branch November 8, 2026 01:40
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