Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 10 additions & 15 deletions quest/m2/ts-model/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Translate moq-net's model to TypeScript
# [L] Translate moq-net's model to TypeScript

## Goal

Expand Down Expand Up @@ -42,20 +42,19 @@ 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-06: 71 refusals. `Timestamp` alone refuses nothing, and
The census at the pin on 2026-11-07: 51 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
research quest: kio's `log.rs` reads bytes as its segments and retires them through
crossbeam-epoch, whose tagged pointers and inline closures (36 refusals) translation
refuses for good, and smallvec's union over a type parameter and raw parts (10) wait on
[unsafe](/quest/m3/ts-unsafe/README.md), so moq rewrites or drops them, as it does the
`unsafe` slice of `model/frame.rs`'s `Filled`.
model, all of it moq's, for its research quest: kio's `log.rs` reads bytes as its segments
and retires them through crossbeam-epoch, whose tagged pointers and inline closures (36
refusals) translation refuses for good, and smallvec's union over a type parameter and
raw parts (10) wait on [unsafe](/quest/m3/ts-unsafe/README.md), so moq rewrites or drops
them, as it does the `unsafe` slice of `model/frame.rs`'s `Filled`.

Decided with the maintainer on 2026-11-04, from a census at the bumped pin
(moq-dev/rust2#390): the refusals no quest owned became the children below. ✅ arrayvec's
`ArrayString` needs three of them (const generic types, `begin_panic`, `str::as_ptr`),
kept separate since each lands alone; rejected: one quest for `ArrayString`.
(moq-dev/rust2#390): the refusals no quest owned became child quests, since finished.
✅ arrayvec's `ArrayString` needs three of them (const generic types, `begin_panic`,
`str::as_ptr`), kept separate since each lands alone; rejected: one quest for `ArrayString`.

Decided with the maintainer on 2026-11-07, after three designs for addresses as identity
([#596](https://github.com/moq-dev/rust2/pull/596)) each left values whose ids were never freed
Expand All @@ -74,8 +73,4 @@ or pointers whose comparison disagreed with Rust after an address was reused:
starts, whether to rewrite one in safe Rust for both builds or wait for this translator.
Rejected: twins until [unsafe](/quest/m3/ts-unsafe/README.md) translates them.

## Required

- [A string's bytes through its pointer](/quest/m2/ts-model/str-as-ptr.md) - `str::as_ptr` reads the string's UTF-8

Public API: the translator's output for moq-net's model.
15 changes: 0 additions & 15 deletions quest/m2/ts-model/str-as-ptr.md

This file was deleted.

8 changes: 7 additions & 1 deletion quest/m3/ts-runtime-throws.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ The rest are gaps. At `main` they include a panic its hook cannot locate
[#565](https://github.com/moq-dev/rust2/pull/565)), `offset_from` of one Rust allocation
JS keeps in two arrays, a pointer moved past the array JS keeps it in (`AtIndex.reach`, such
as one kept in a field), `Bytes::slice_ref` of other storage, `alloc`'s bytes read as
values, ranges stepped without their type, fmt widths written as a placeholder, and a raw
values, ranges stepped without their type, fmt widths written as a placeholder, a read
through a substring's `str::as_ptr` outside its own bytes but inside its text's
(`s[..1].as_ptr().add(1)`, `s[1..].as_ptr().wrapping_sub(1)`), and a raw
slice of a byte in no array cast to `*const [u8; N]` by a route the body doesn't show,
which [a byte local in a buffer of its own](/quest/m3/ts-unsafe/lone-byte-buffers.md) owns
for locals.
The substring reads are UB under Stacked Borrows and allowed under Tree Borrows, so they
throw until Rust settles its memory model (decided 2026-11-07 in
[#634](https://github.com/moq-dev/rust2/pull/634)); if Rust rules them defined, a
substring's pointer carries its text's encoding and offset.
`git grep 'rust2-ts: ' ts/runtime` lists them; classify each before fixing. The pointer
comparisons [#584](https://github.com/moq-dev/rust2/pull/584) refuses at translation, where
JS may make two `rs.Place`s for one place, are out of scope: addresses were their route, and
Expand Down
7 changes: 7 additions & 0 deletions ts/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re
| `Vec<T>` | `rs.Vec<T>`: its items, the array `items`, and the capacity std's holds; `rs.vecFrom(items)` makes one of an array with room for exactly them. `rs.ByteVec` of bytes |
| `Cow<[T]>`, `Cow<str>` | `rs.Cow<T>`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows, `rs.Cow<T, rs.ArraySlice<S>>` holding the arrays of a slice of arrays; `rs.ByteCow` for bytes, an `rs.ByteVec` or an `rs.ByteSlice`; `rs.StrCow` for text, the `rs.String` it owns or an `rs.Borrowed` holding the string it borrows, whose text `rs.strCowStr` reads |
| `&T`, `&mut T`, `Box<T>`, `Reverse<T>`, `NonZero<T>`, `AssertUnwindSafe<T>` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rs.Place<T>`, which for what a box holds is the place holding the box |
| `*const T`, `*mut T`, `NonNull<T>` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place<T>` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. A pointer to a slice cast to one to an array of its elements is the `rs.Slice` or `rs.ByteSlice` of that many from its first, read through only where Rust reads. A `*const ()` is an identity, such as a waker's data, and `rs.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rs.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rs.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rs` class such as `rs.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place |
| `*const T`, `*mut T`, `NonNull<T>` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place<T>` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. A pointer to a slice cast to one to an array of its elements is the `rs.Slice` or `rs.ByteSlice` of that many from its first, read through only where Rust reads. A `*const ()` is an identity, such as a waker's data, and `rs.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rs.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rs.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rs` class such as `rs.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place |
| `MaybeUninit<T>` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined`, as `Box::new_uninit` and each of `Box::new_uninit_slice`'s is; a slice's `write_clone_of_slice` clones into its slots without dropping what they held. `Box::new_zeroed`'s holds the value of zero bytes in each part they are one of, such as a number, `false`, `None`, a null pointer, or an enum's variant of a tag or niche of zero, and `undefined` in each part they are none of, such as a reference, for a write to fill before a read |
| `ManuallyDrop<T>` | `T`, which nothing drops but its own `drop`; its `Debug`, comparisons, and constants are refused |
| `union` | an object of slots named by offset, `at0` and `at8`, each holding the field part kept there; fields must overlap in the same type, or in a non-null pointer and an `Option` of it |
Expand Down Expand Up @@ -1215,7 +1215,8 @@ so adding, removing, or reordering a case changes no other case's inputs.
script of spawns, sends, polls, and drops, and logs every poll, wake, and drop, as the
fixture's native executor does. The logs must match byte for byte.
- `stock` calls `bytes`, `hashbrown`, `rand`, and `tracing` with no config, `hashlink`
over `hashbrown`, and `rangemap` over std's B-tree, against native Rust, whose
over `hashbrown`, `rangemap` over std's B-tree, and arrayvec's `ArrayString` over raw
pointers, against native Rust, whose
`rand::rng()` reads the fixture's own getrandom backend on wasm32. It checks `Bytes`'s
slices and panics, how each is made and printed, that no event evaluates a field, and
that each random value is in its range, every value of a small range drawn. It splits,
Expand All @@ -1230,7 +1231,8 @@ so adding, removing, or reordering a case changes no other case's inputs.
that collide and that pass 2^53, checks their capacity by std's guarantees, and writes a
`hashlink::LinkedHashMap` in its own order. It inserts, removes, looks up, overlaps, and
gaps `rangemap`'s maps and sets, which search their B-trees by what each key lends by
rangemap's own `Borrow`. `caller.ts` names the inputs that read a `BytesMut` past
rangemap's own `Borrow`. It pushes, pops, truncates, and removes text of each UTF-8
width in an `ArrayString` until it is full. `caller.ts` names the inputs that read a `BytesMut` past
where `bytes` shares a vector's storage, 128 MiB, drop each pick of a storage's
handles before reserving, and read a deque across where its ring wraps and past its end.
- `bind` binds `buf::Blob`, a renamed dependency's type with `unsafe` inside, and a function and a type of its own that read through raw
Expand Down
4 changes: 2 additions & 2 deletions ts/fixtures/lang/strs/expected/lib.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Generated by rust2-ts. Do not edit.

import * as rs from "./rs.ts";
import { strs, slices, edits, parses, iters, drains, hinted, gets, unicode, utf16 } from "./strs.ts";
import { strs, slices, edits, parses, iters, drains, hinted, gets, unicode, utf16, pointers } from "./strs.ts";

/**
* Each case's name and what it runs. The first two bytes, big-endian, pick one modulo
Expand All @@ -12,7 +12,7 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By
edits(a0, a1.data, a1.start, a1.length, a2);
}] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::parses", parses as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::iters", iters as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::drains", (a02: rs.u8, a12: rs.ByteSlice, a22: rs.ByteVec): undefined => {
drains(a02, a12.data, a12.start, a12.length, a22);
}] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::hinted", hinted as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::gets", gets as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::unicode", unicode as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::utf16", utf16 as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]);
}] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::hinted", hinted as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::gets", gets as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::unicode", unicode as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::utf16", utf16 as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::pointers", pointers as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]);
}

//# sourceMappingURL=lib.ts.map
32 changes: 32 additions & 0 deletions ts/fixtures/lang/strs/expected/strs.Fixed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Generated by rust2-ts. Do not edit.

import * as rs from "./rs.ts";

/** Text in a fixed buffer, pushed as arrayvec's `ArrayString` pushes it. */
export interface Fixed {
xs: Uint8Array;
len: rs.usize;
}

function new_(): Fixed {
return { xs: rs.bytes(12).fill((0 as unknown) as rs.u8), len: 0 };
}

export function tryPushStr(self: Fixed, s: string): rs.Result<undefined, string> {
if (!(rs.stringLen(s) > rs.usizeSub(rs.byteSliceFrom(self.xs).length, self.len))) {
const dst: rs.Place<rs.u8> = rs.ptrAdd(rs.byteSliceAsPtr(rs.byteSliceFrom(self.xs)), self.len);
rs.ptrCopy(rs.strAsPtr(s), dst, rs.stringLen(s));
const _20: rs.usize = rs.stringLen(s);
self.len = rs.usizeAdd(self.len, _20);
return rs.OK;
}
return rs.Err(s);
}

export function asStr(self: Fixed): string {
return rs.strFromUtf8Unchecked(rs.ptrByteSlice(rs.byteSliceAsPtr(rs.byteSliceFrom(self.xs)), self.len, true));
}

export { new_ as new };

//# sourceMappingURL=strs.Fixed.ts.map
Loading