Skip to content

feat(ts): translate std's conversions of a reference - #386

Merged
moq-bot[bot] merged 13 commits into
mainfrom
quest/m3/ts-ref-conversions
Nov 2, 2026
Merged

moq-bot[bot] merged 13 commits into
mainfrom
quest/m3/ts-ref-conversions

Conversation

@kixelated

@kixelated kixelated commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Completes quest/m3/ts-ref-conversions.md.

Problem

rust2-ts refused std's functions that take a reference and convert, copy, or measure what it points at, as "unsupported call", for every T (reproduced for u32, [u8; 2], [u32; 2], and a struct): ToOwned::to_owned and clone_into, Clone::clone_from, slice::binary_search, mem::size_of_val and align_of_val, slice::from_mut, array::from_ref and from_mut, an array's AsRef, AsMut, Borrow, and BorrowMut as a slice, Cell::from_mut, and ptr::from_ref and from_mut.

Approach

  • Copies. to_owned of a sized T is a call of Clone::clone. clone_from and clone_into lower in the MIR intercept as *self = source.clone(): clone, write through the borrowed place as mem::replace does, then drop the old value. A type whose clone_from is its own is refused, as slice::fill already does; LinkedList keeps its mapping.

  • binary_search. rt.sliceBinarySearch is std's loop: no early exit, so a run of equal elements gives std's Ok index, and Err the insertion point. It compares with cmp_fn, so a crate's own Ord is called on the same elements in the same order.

  • Sizes. size_of_val and align_of_val are wasm32's layout of T, or for a slice or str, its length times the element's size.

  • One-element slices and cells. slice::from_mut and array::from_mut give rt.sliceOfPlace, a length-1 Proxy over an rt.Place, so a whole write lands where the &mut points:

    • a value JS copies uses its rt.Place;
    • a [u8; N] uses rt.bytesPlace, which reads the view and writes the bytes in place;
    • an object uses the place of the slot its alias names.

    Cell::from_mut uses the same places through rt.cellOfPlace. An object no place names (a parameter) goes through rt.cellIn, which reads a copy and writes in place, a JS array taking the new length, since a Cell only moves or copies its value out.

  • from_ref and arrays as slices. array::from_ref maps like slice::from_ref. AsRef, AsMut, Borrow, and BorrowMut of an array as a slice are the array itself, and lend leaves them, to_owned, and the intercepted calls untouched.

  • Pointers. ptr::from_ref maps as NonNull::from does. A [u8; N] given as the view of its bytes becomes rt.byteSliceFrom of it, so it compares by buffer and offset. ptr::from_mut lowers as &raw mut *r of the place the alias names, through the same Rvalue::RawPtr lowering and checks, so a pointer to a Vec is the rt.Place of its slot. The pointer analysis treats both like NonNull::from.

  • Rebinding. pointer::rebinds now returns which arguments a call may give another value, not how many leading ones: clone_from and the three from_muts their first, clone_into its second. So a raw pointer to a variable that one of them replaces is refused, as one mem::replace replaces already is.

Still refused, each with a span:

  • slice::from_mut of a &mut u8: a &mut [u8] is an rt.ByteSlice, which can't view one u8's rt.Place.
  • slice::from_mut of a &mut to an object no place in the function names. A slice element write replaces the slot. Copying it into the object instead would make mem::replace(&mut s[0], v) return the object it just overwrote.
  • size_of_val of a trait object.
  • Cell::from_mut of a LazyCell, LazyLock, or LocalKey, and a whole write through a &mut to one. Their runtime classes keep state in private fields, which Object.assign and rt.cellIn don't copy, so Translator::write_in now refuses them as it already did rt.PollFn. The plain write through a &mut was silently wrong on main too.

Tests

conversions::conversions in the lang fixture runs differentially against native wasm32 for u8, u32, u64 past 2^53, String, Option<u16>, [u8; 0], [u8; 1], [u8; 3], [u8; 40], [u32; 2], Vec<u32> and Vec<u8> of 0 to 3 elements, a struct, and a type that logs its clones and drops. It covers:

  • each conversion through a local, a field, an element, and a parameter;
  • old values read out after whole writes through each one-element slice and cell, and writes through std's fill and iter_mut;
  • binary_search over sorted runs with duplicates, at lengths 0 to 24;
  • sizes and alignments of slices, a str past ASCII, usize, u128, a tuple with padding, Option<u32>, [u16; 3], a struct, a String, and ();
  • ptr::eq of from_ref pointers to byte arrays and objects.

The u8 case skips slice::from_mut, which is refused.

An early-exit binary_search in the runtime gives 103 differences. The reject fixture gains the three refusals, a clone_from of Vec<Reused>, a raw pointer outliving clone_from, clone_into, and Cell::from_mut of its variable, and whole writes into a LazyCell, a LazyLock, and a LocalKey.

Impact

  • Public API: the translator's output only. Calls refused before now translate. New runtime functions: rt.sliceBinarySearch, rt.sliceOfPlace, rt.bytesPlace, rt.cellOfPlace, and rt.cellIn. No breaking change, so no MIGRATE.md entry.
  • rt.sliceView's internal View now takes any indexable, so sliceOfPlace reuses it.

Alternatives

  • A Proxy slice that writes an opaque object in place (replaceIn): dropped, since mem::replace on its element reads the object, then overwrites it, returning the new value. Refused instead; see the follow-up.
  • A plain [view] for a [u8; N]: dropped, since a whole store into a &mut [[u8; N]] element replaces the slot, not the bytes.
  • Mapping binary_search_by, _by_key, and partition_point too: left for a follow-up to keep this to the quest's list.

Follow-ups

Quests added to m3:

  • ts-slice-of-one: slice::from_mut and array::from_mut of a &mut u8, and of an object no place names.
  • ts-binary-search-by: binary_search_by, binary_search_by_key, and partition_point through the same search.

Decisions

Settled by the maintainer on 2026-11-01, the first two as recommended:

  1. slice::from_mut of an object behind a parameter.

    • (a) Refuse it with a span, as done here. Recommended: exact or refused.
    • (b) Write in place with the aliasing caveat: wrong for mem::replace on the element.
    • (c) Make translated code write object elements of every slice in place: broad churn and a cost on every slice write.

    The follow-up quest records (c) as the path to lift it.

  2. The binary-search family.

    • (a) Leave binary_search_by, _by_key, and partition_point for their own quest, as done here. Recommended.
    • (b) Fold them in here.
  3. clone_from and clone_into of a vector, which std does into the allocation it has.

    • (a) Refuse a type that holds a capacity in clone_from_clones.
    • (b) Map each one's clone_from as std's body does, reusing the allocation.
    • (c) Fix clone_from_clones in its own PR first.
    • ✅ (d) Within the rule, no change: std documents no capacity for clone_from and clone_into beyond "avoids reallocation if possible", so under the 2026-11-01 "std's guarantees only" rule a capacity of at least the length is correct. A clone's exact capacity, 3 where native reads 10, is not a divergence. ts/README.md now says so, and a test that reads capacity after these checks it against std's guarantees.

Review

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

Local checks: just ci passed on 11030f84.

(written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 4 commits November 1, 2026 04:04
`to_owned`, `clone_into`, `clone_from`, `binary_search`, `size_of_val`,
`align_of_val`, `slice::from_mut`, `array::from_ref` and `from_mut`, an
array's `AsRef`, `AsMut`, `Borrow`, and `BorrowMut` as a slice,
`Cell::from_mut`, and `ptr::from_ref` and `from_mut`, for any `T` and for
`[u8; N]`.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, and assign a Vec through a cell whole

ptr::from_mut lowers as `&raw mut *r` of the place an alias names, so a
pointer to a Vec or another object JS points at by an rt.Place is made as
a borrow of it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title std's conversions of a reference feat(ts): translate std's conversions of a reference Nov 1, 2026
@kixelated
kixelated marked this pull request as ready for review November 1, 2026 15:06
@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

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: 67808a76-982f-48d1-8c63-35d44601466a
  • 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 and others added 3 commits November 1, 2026 07:19
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e is private

Object.assign and cellIn copy only enumerable fields, so a write through a
`&mut LazyCell` or `Cell::from_mut` of one left the old state in place.

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

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

moq-bot Bot commented Nov 1, 2026

Copy link
Copy Markdown

quest land removed this pull request from the queue: Conflicts with the base: ts/fixtures/lang/expected/harness.ts, ts/fixtures/lang/src/harness.rs, ts/fixtures/reject/census.md, ts/fixtures/reject/expected.txt
.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as draft November 1, 2026 18:40
@moq-bot

moq-bot Bot commented Nov 1, 2026

Copy link
Copy Markdown

Codex land fixer: PR cl·osed or head moved while fixing; inspect and pick it again. Recommendation: run /quest-iterate before picking it again.

quest land check: waiting for check launcher
quest land check: check launcher did not become ready within 3600 seconds

(written by Codex)

kixelated and others added 3 commits November 1, 2026 14:35
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 marked this pull request as ready for review November 1, 2026 23:19
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@moq-bot
moq-bot Bot merged commit a8bc81f into main Nov 2, 2026
@moq-bot
moq-bot Bot deleted the quest/m3/ts-ref-conversions branch November 2, 2026 01:36
kixelated added a commit that referenced this pull request Nov 2, 2026
Order the boxed-slice `clone_from` arm before #386's generic one, which
refused `Box<[T]>` and `Box<str>` as a type whose `clone_from` is its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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