Skip to content
Merged
3 changes: 1 addition & 2 deletions quest/m2/ts-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Decided with the maintainer on 2026-10-27, from a census of the model's 22 publi
- The end-to-end work stays here: the model's roots, its differential harness, and the
`frames.ts` workload. moq writes the binding files `model.toml` names, in its own tree.

The census at the pin on 2026-11-04: 72 refusals. `Timestamp` alone refuses nothing, and
The census at the pin on 2026-11-04: 69 refusals. `Timestamp` alone refuses nothing, and
the [model fixture](/ts/README.md#tests)'s harness runs it against native moq-net at the
pin; each case it adds as a type translates. Every other root reaches the rest of the
model, so each child below unblocks all of them together. The rest is moq's, for its
Expand All @@ -58,7 +58,6 @@ there, as does the `unsafe` slice of `model/frame.rs`'s `Filled`.
## Required

- [Iterators into a crate's own collections](/quest/m2/ts-model/from-iterator.md) - `collect`, `flat_map`, and `flatten` through moq-pattern's `Patterns`
- [std's impls through a reference](/quest/m2/ts-model/reference-impls.md) - `<&[u8] as AsRef<[u8]>>::as_ref`
- [Bytes from an owner that drops](/quest/m2/ts-model/bytes-owner.md) - `Bytes::from_owner` of a frame's segment
- [rangemap](/quest/m2/ts-model/rangemap.md) - B-tree ranges by a borrowed key, and std's iterators in a crate's fields
- [Addresses as identity](/quest/m2/ts-model/addresses.md) - `Arc::as_ptr(a) as usize` as a key, which needs a decision
Expand Down
13 changes: 0 additions & 13 deletions quest/m2/ts-model/reference-impls.md

This file was deleted.

4 changes: 4 additions & 0 deletions ts/fixtures/lang/conversions/expected/conversions.Logged.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export function partialCmp$cmp_PartialOrd(self: Logged, other: Logged): rs.Order
return cmp$cmp_Ord(self, other);
}

export function eq$cmp_PartialEq(self: Logged, other: Logged): boolean {
return self.id === other.id;
}

/** The value `data` makes from `at`. */
export function make$conversions_Value(data: rs.ByteSlice, at: rs.usize): Logged {
return new Logged(byte(data, at) & 127, ((a0: rs.Arc<rs.RefCell<rs.ByteVec>>): rs.Arc<rs.RefCell<rs.ByteVec>> => {
Expand Down
7 changes: 7 additions & 0 deletions ts/fixtures/lang/conversions/expected/conversions.Named.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ export function clone$Clone(self: Named): Named {
return { id: self.id, name: self.name };
}

export function eq$cmp_PartialEq(self: Named, other: Named): boolean {
if (!(self.id === other.id)) {
return false;
}
return self.name === other.name;
}

export function partialCmp$cmp_PartialOrd(self: Named, other: Named): rs.Ordering | undefined {
return cmp$cmp_Ord(self, other);
}
Expand Down
2,343 changes: 2,341 additions & 2 deletions ts/fixtures/lang/conversions/expected/conversions.ts

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions ts/fixtures/lang/conversions/expected/core/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ export function unwrapOr$1fr498u(self: rs.u8 | undefined, default_: rs.u8): rs.u
return self;
}

export function mapOr$14runhi(self: rs.Ordering | undefined, default_: rs.u8, f: (a0: rs.Ordering) => rs.u8): rs.u8 {
if (self === undefined) {
return default_;
}
return f(self);
}

export function clone$0wpatp7(self: rs.u16 | undefined | rs.None): rs.u16 | undefined | rs.None {
if (self === rs.None) {
return rs.None;
Expand Down
161 changes: 151 additions & 10 deletions ts/fixtures/lang/conversions/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
//! parameter; `binary_search`, by a closure and by a key, and `partition_point` over runs
//! of equal elements; `size_of_val` and `align_of_val`; slices, arrays, and cells of one
//! value, written through; arrays as slices by `AsRef`, `AsMut`, `Borrow`, and
//! `BorrowMut`, and `Vec`s by those and as themselves; `clone_from` of boxes; and raw
//! pointers of references. Each is generic over the value, run for numbers JS copies,
//! `BorrowMut`, and `Vec`s by those and as themselves; std's impls on references, which
//! `AsRef`, `AsMut`, and the comparisons forward to what they point at and `Deref` and
//! `Borrow` lend; `clone_from` of boxes; and raw pointers of references. Each is generic
//! over the value, run for numbers JS copies,
//! strings, `Option`s, arrays and `Vec`s of bytes and of numbers, a struct, and one that
//! logs its clones and drops.

use std::borrow::{Borrow, BorrowMut};
use std::cell::{Cell, RefCell};
use std::ops::Deref;
use std::rc::Rc;

/// A value made from the case's bytes, put back as bytes.
Expand Down Expand Up @@ -228,24 +231,47 @@ struct Held<T> {
}

/// Runs each conversion of the type `kind` picks, but a slice of one `u8`, whose `&mut`
/// no slice can view, and [`searched`] of those that order.
/// no slice can view, and [`searched`] of those that order; and [`referenced`] of one
/// type of each form JS keeps a value in.
pub fn conversions(kind: u8, data: &[u8], out: &mut Vec<u8>) {
match kind % 14 {
0 => ordered::<u32>(data, out),
1 => ordered::<u64>(data, out),
2 => ordered::<String>(data, out),
3 => ordered::<Option<u16>>(data, out),
0 => {
ordered::<u32>(data, out);
referenced::<u32>(data, out);
}
1 => {
ordered::<u64>(data, out);
referenced::<u64>(data, out);
}
2 => {
ordered::<String>(data, out);
referenced::<String>(data, out);
}
3 => {
ordered::<Option<u16>>(data, out);
referenced::<Option<u16>>(data, out);
}
4 => ordered::<[u8; 0]>(data, out),
5 => ordered::<[u8; 1]>(data, out),
6 => ordered::<[u8; 3]>(data, out),
6 => {
ordered::<[u8; 3]>(data, out);
referenced::<[u8; 3]>(data, out);
}
7 => ordered::<[u8; 40]>(data, out),
8 => ordered::<[u32; 2]>(data, out),
9 => ordered::<Named>(data, out),
9 => {
ordered::<Named>(data, out);
referenced::<Named>(data, out);
}
10 => ordered::<Vec<u32>>(data, out),
11 => ordered::<Vec<u8>>(data, out),
11 => {
ordered::<Vec<u8>>(data, out);
referenced::<Vec<u8>>(data, out);
}
12 => {
converted::<u8>(data, out);
searched::<u8>(data, out);
referenced::<u8>(data, out);
}
_ => {
// What an earlier case dropped at its end, which JS runs on after.
Expand All @@ -254,9 +280,11 @@ pub fn conversions(kind: u8, data: &[u8], out: &mut Vec<u8>) {
lent_one::<Logged>(data, out);
lent_through::<Logged>(data, out);
searched::<Logged>(data, out);
referenced::<Logged>(data, out);
}
}
measured(data, out);
referenced_bytes(data, out);
pointed(data, out);
boxed_kept(data, out);
}
Expand Down Expand Up @@ -622,3 +650,116 @@ fn pointed(data: &[u8], out: &mut Vec<u8>) {
let one = std::array::from_ref(&a[1]);
out.extend_from_slice(one);
}

/// std's impls on references, reached by generic functions: `AsRef` and `AsMut` of
/// slices, arrays, and `Vec`s through `&`, `&mut`, and a reference to either, which
/// forward to what each points at; `Deref`, `Borrow`, and `BorrowMut` of a `&` and a
/// `&mut`, which lend what it points at; and the comparisons of references to elements and to slices.
fn referenced<T: Value + Ord>(data: &[u8], out: &mut Vec<u8>) {
let mut array = [T::make(data, 13), T::make(data, 14), T::make(data, 15)];
let mut list = vec![T::make(data, 16), T::make(data, 17)];
read_as(&array[..], out);
read_as(&&array[1..], out);
read_as(&array, out);
read_as(&list, out);
read_as(&mut list, out);
read_as(&&mut list, out);
write_as(&mut array[..], T::make(data, 18));
write_as(&mut array, T::make(data, 19));
write_as(&mut list, T::make(data, 20));
write_as(&mut &mut list, T::make(data, 21));
deref_put(&array[0], out);
deref_put(&mut list[0], out);
borrow_put::<T, _>(&array[1], out);
borrow_put::<T, _>(&mut list[1], out);
borrow_write(&mut list[1], T::make(data, 24));
borrow_write(&mut array[1], T::make(data, 25));
// std's own impls for any `T`, and through a `&mut` to a `&mut`.
let mut whole = T::make(data, 26);
*BorrowMut::<T>::borrow_mut(&mut whole) = T::make(data, 27);
Borrow::<T>::borrow(&whole).put(out);
BorrowMut::<Vec<T>>::borrow_mut(&mut &mut list).push(T::make(data, 28));
let other = T::make(data, 29);
let mut r = &whole;
*BorrowMut::<&T>::borrow_mut(&mut r) = &other;
r.put(out);
compared(&array[0], &array[1], out).put(out);
compared(&&list[1], &&array[2], out).put(out);
compared(&mut T::make(data, 22), &mut T::make(data, 23), out).put(out);
let max = compared(&array[..], &list[..], out);
out.push(max.len() as u8);
for x in array.iter().chain(&list) {
x.put(out);
}
}

/// Bytes through `AsRef<[u8]>` and text through `AsRef<str>` of strings, slices, and
/// `Vec`s behind `&`, `&mut`, and a reference to one, as moq's buffers reach them.
fn referenced_bytes(data: &[u8], out: &mut Vec<u8>) {
let mut text: String = data.iter().take(4).map(|&b| char::from(b)).collect();
let mut bytes = data.to_vec();
read_bytes(text.as_str(), out);
read_bytes(&text, out);
read_bytes(&mut text, out);
read_bytes(&&*text, out);
read_bytes(data, out);
read_bytes(&data, out);
read_bytes(&bytes, out);
read_bytes(&mut bytes, out);
read_str(text.as_str(), out);
read_str(&text, out);
read_str(&&mut text, out);
}

/// What `b` reads as a slice: its length, then each element.
fn read_as<T: Value, B: AsRef<[T]>>(b: B, out: &mut Vec<u8>) {
let s = b.as_ref();
out.push(s.len() as u8);
for x in s {
x.put(out);
}
}

/// Writes `value` over the last element of what `b` lends as a slice.
fn write_as<T: Value, B: AsMut<[T]>>(mut b: B, value: T) {
if let Some(last) = b.as_mut().last_mut() {
*last = value;
}
}

/// What `b` derefs to, by `deref` and by `*`.
fn deref_put<T: Value, B: Deref<Target = T>>(b: B, out: &mut Vec<u8>) {
b.deref().put(out);
(*b).put(out);
}

/// What `b` lends by `borrow`.
fn borrow_put<T: Value, B: Borrow<T>>(b: B, out: &mut Vec<u8>) {
b.borrow().put(out);
}

/// Writes `value` over what `b` lends by `borrow_mut`.
fn borrow_write<T: Value, B: BorrowMut<T>>(mut b: B, value: T) {
*b.borrow_mut() = value;
}

/// Each comparison of `a` with `b`, and the greater.
fn compared<A: Ord>(a: A, b: A, out: &mut Vec<u8>) -> A {
let order = [a == b, a != b, a < b, a <= b, a > b, a >= b];
out.extend(order.map(u8::from));
out.push(a.cmp(&b) as u8);
out.push(a.partial_cmp(&b).map_or(9, |o| o as u8));
a.max(b)
}

/// The bytes `b` reads as.
fn read_bytes<B: AsRef<[u8]>>(b: B, out: &mut Vec<u8>) {
out.push(b.as_ref().len() as u8);
out.extend_from_slice(b.as_ref());
}

/// The text `s` reads as, as bytes.
fn read_str<S: AsRef<str>>(s: S, out: &mut Vec<u8>) {
out.push(s.as_ref().len() as u8);
out.extend_from_slice(s.as_ref().as_bytes());
}
2 changes: 2 additions & 0 deletions ts/fixtures/reject/census.txt
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ fixtures/reject/src/pointers.rs:pointers_apart+10:26: `offset_from` between a po
fixtures/reject/src/pointers.rs:pointers_apart+11:24: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays
fixtures/reject/src/pointers.rs:pointers_apart+12:23: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays
fixtures/reject/src/pointers.rs:pointers_apart+14:21: `offset_from` between a pointer into a shared slice JS copies and one into a value in no array, which JS keeps in different arrays
fixtures/reject/src/pointers.rs:pointers_ordered+2:2: comparing pointers to `u32`, each a place JS makes of its own
fixtures/reject/src/pointers.rs:pointers_ordered+2:2: ordering `*const u32` by its address, which JS has no counterpart of, in `std::cmp::max`
fixtures/reject/src/pointers.rs:put_replaced+2:10: a raw pointer to `(u32, u32)` in `pointers::Plain`, which the crate may put another object in place of
fixtures/reject/src/pointers.rs:range_of+1:2: a raw pointer to `(u32, u32)` in `pointers::Window`, which the crate may put another object in place of
fixtures/reject/src/pointers.rs:rc_raw_bytes+1:11: an `Rc`'s raw pointer to `[u8; 4]`, which JS makes anew each time
Expand Down
2 changes: 2 additions & 0 deletions ts/fixtures/reject/expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ fixtures/reject/src/pointers.rs:pointers_apart+10:26: `offset_from` between a po
fixtures/reject/src/pointers.rs:pointers_apart+11:24: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays
fixtures/reject/src/pointers.rs:pointers_apart+12:23: `offset_from` between a pointer into an array of this body's own and one into an array of this body's own, which JS keeps in different arrays
fixtures/reject/src/pointers.rs:pointers_apart+14:21: `offset_from` between a pointer into a shared slice JS copies and one into a value in no array, which JS keeps in different arrays
fixtures/reject/src/pointers.rs:pointers_ordered+2:2: comparing pointers to `u32`, each a place JS makes of its own
fixtures/reject/src/pointers.rs:pointers_ordered+2:2: ordering `*const u32` by its address, which JS has no counterpart of, in `std::cmp::max`
fixtures/reject/src/pointers.rs:put_replaced+2:10: a raw pointer to `(u32, u32)` in `pointers::Plain`, which the crate may put another object in place of
fixtures/reject/src/pointers.rs:range_of+1:2: a raw pointer to `(u32, u32)` in `pointers::Window`, which the crate may put another object in place of
fixtures/reject/src/pointers.rs:rc_raw_bytes+1:11: an `Rc`'s raw pointer to `[u8; 4]`, which JS makes anew each time
Expand Down
6 changes: 6 additions & 0 deletions ts/fixtures/reject/src/pointers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,3 +534,9 @@ pub fn rc_raw_bytes(p: *const [u8; 4]) -> u8 {
pub fn arc_from_borrow(n: &u32) -> u32 {
unsafe { *std::sync::Arc::from_raw(std::ptr::from_ref(n)) }
}

/// The greater of two pointers to numbers, which orders by their addresses.
pub fn pointers_ordered(a: &u32, b: &u32) -> bool {
let (p, q) = (std::ptr::from_ref(a), std::ptr::from_ref(b));
std::cmp::max(p, q) == p
}
1 change: 1 addition & 0 deletions ts/fixtures/reject/ts.toml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ roots = [
"pointers::pointer_pushed",
"pointers::pointer_returned",
"pointers::pointers_apart",
"pointers::pointers_ordered",
"pointers::put_replaced",
"pointers::range_of",
"pointers::range_replaced",
Expand Down
20 changes: 17 additions & 3 deletions ts/src/lower/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,30 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> {
if let Some(referent) = library::referent(tcx, def_id, gargs, instance) {
// An operand that was a reference to an `rs.Place`, as `&&mut u32` is, passes
// what the Place points at, and a `&mut` to a `&mut` or a box what the place it
// is holds, as JS holds a box as what it holds, but where that is a place too.
// is holds, as JS holds a box as what it holds, but where that is a place too. A
// mapping that takes what a `&mut` points at, as `pointees` says, was given the
// inner `&mut`, and takes what that points at.
let inputs = |gargs| self.t.sig(def_id, gargs).inputs().to_vec();
let pointees = |gargs| super::pointer::pointees(self.t, def_id, gargs);
let (given, taken) = (pointees(gargs), pointees(referent));
let args = std::iter::zip(args, std::iter::zip(inputs(gargs), inputs(referent)))
.map(|(arg, (was, now))| match was.kind() {
.enumerate()
.map(|(i, (arg, (was, now)))| match was.kind() {
_ if was == now => arg,
ty::Ref(_, inner, mutability) if inner.is_box() => match mutability {
ty::Mutability::Mut if self.t.place_pointee(now).is_none() => self.t.pointee_value(was, arg),
_ => arg,
},
ty::Ref(_, _, ty::Mutability::Mut) => self.t.pointee_value(was, arg),
ty::Ref(_, _, ty::Mutability::Mut) => {
let inner = match given[i] {
true => arg,
false => self.t.pointee_value(was, arg),
};
match taken[i] {
true => self.t.pointee_value(now, inner),
false => inner,
}
}
ty::Ref(_, inner, _) => self.t.pointee_value(*inner, arg),
_ => arg,
})
Expand Down
Loading