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
6 changes: 6 additions & 0 deletions quest/m1/ffi-export/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,15 @@ Decided with the maintainer on 2026-11-02
arenas keep under Go's lent `Bytes` is the consumer's to fix with its global allocator, as
`ffi/README.md` documents. moq declares mimalloc in its exported crate, in its own tree.

Decided with the maintainer on 2026-11-04: the exporter is configless, with opinionated
names and defaults, and fails the export on a public item it cannot export.

## Required

- [moq-clock's C names once it moves into kio](/quest/m1/ffi-export/crates.md) - `moq_now` and `moq_origin` become the clock's, after moq moves it into `kio::clock`
- [C names and crates with no config](/quest/m1/ffi-export/names.md) - a collision-free naming rule, a facade crate, and the prefix from Cargo, leaving only `exclude` in `ffi.toml`
- [Sessions on sans-IO QUIC](/quest/m1/ffi-export/session-quic.md) - bindings move UDP datagrams and drive the session's poll themselves, over moq's runtime-free QUIC driver once it lands
- [C++ bindings on MSVC](/quest/m1/ffi-export/cpp.md) - the nightly's MSVC job passes the C++ checks
- [Generated Python type-checks](/quest/m1/ffi-export/python-types.md) - mypy and pyright pass on the generated Python
- [Every public item exports](/quest/m1/ffi-export/refusals.md) - the census is empty and `ffi.toml` is deleted, so the exporter reads no config
- [Waiting: moq-tokio within the recursion limit](/quest/m1/ffi-export/recursion-limit.md) - the exporter's moq pin moves past moq-dev/moq#7032 once moq-tokio's `Send` check fits the limit
5 changes: 2 additions & 3 deletions quest/m1/ffi-export/crates.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ moq's `main`.

## Plan

Then bump the moq pin and export `kio::clock` in moq-clock's place, in
[the config](/ffi/moq/ffi.toml) and `just ffi standin`, so the rule names its items
`moq_clock_*` and moq-clock's renames go.
Then bump the moq pin and re-export `kio::clock` in moq-clock's place in `just ffi
standin`, so the naming rule names its items `moq_clock_*`.

Public API: the C names moq's export emits.
39 changes: 39 additions & 0 deletions quest/m1/ffi-export/names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# [L] C names and crates with no config

## Goal

[rust2-ffi](/ffi/README.md) names every exported item by a collision-free rule and takes
its crate set and prefix from Cargo alone, so `ffi.toml` loses `package`, `prefix`,
`[crates.*]`, and every `rename`, leaving only `exclude` for
[the refusals](/quest/m1/ffi-export/refusals.md) to remove.

## Plan

Decided with the maintainer on 2026-11-04, after
[#390](https://github.com/moq-dev/rust2/pull/390) added moq-specific renames: the exporter
takes an arbitrary crate with no config, and a vanity rename, if ever supported, lives
inline in source next to the item.

- Naming rule: keep Rust's case. ✅ `moq_session_Stats` for the type and
`moq_Session_stats` for the method, so a crate that follows Rust's naming conventions
never collides. A residual clash, possible only with unconventional names, fails the
export with both spans. Rejected: snake case with `__` before members; mangled
(`moq_7session5Stats`), which is provably free but unreadable in C.
Case alone does not separate a field's getter and setter from a method of the same
name, which conventional Rust allows: moq's `Backoff::initial` field and method, and
the reject fixture's `Limit::max` setter beside `set_max`. The rule must name field
accessors apart from methods too, with no rename.
- Items of different crates are named by their path in the facade, so moq-tokio's
`Client` and moq-net's need the facade to re-export them under distinct paths.
- Crate set: one facade crate. ✅ The exporter exports one crate's `pub` API, following
its `pub use` re-exports, so moq-ffi re-exports what it ships, in moq's tree. Rejected:
crates listed on the command line, which only moves the list into a script.
- Prefix: the facade's Cargo lib name. ✅ `[lib] name` defaults to the package name;
moq-ffi sets it to `moq` in its own Cargo.toml for `moq_`. Rejected: the package name,
which would give `moq_ffi_`.

Every generator derives its names from the C ones, so each changes with them; regenerate
moq's export and the stand-in (`just ffi standin` becomes the facade's `pub use` list) and
update `ffi/README.md`'s naming and config sections.

Public API: every C name the exporter emits, and the `ffi.toml` keys it reads.
16 changes: 16 additions & 0 deletions quest/m1/ffi-export/python-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# [S] Generated Python type-checks

## Goal

The Python that [rust2-ffi](/ffi/README.md) generates passes mypy and pyright, checked in
CI on the api fixture and moq's export, so a typed consumer imports it
with no ignores.

## Plan

On `main` it fails both, at least on: classes based on the untyped `_n.Object`, enum
variants assigned as class attributes, `__hash__ = None` on unhashable classes, and the
extension loader's module spec. Fix the generator, not the checks; a construct no stub can
type is a gap in the generator's output, not a reason to ignore it.

Public API: the generated Python's types, not its behaviour.
25 changes: 25 additions & 0 deletions quest/m1/ffi-export/refusals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# [XL] Every public item exports

## Goal

[rust2-ffi](/ffi/README.md) exports every `pub` item of moq's crates, so `just ffi census`
is empty at the moq pin and `ffi.toml` is deleted: the exporter reads no config, and an
item it cannot export fails the export with a span.

## Plan

Decided with the maintainer on 2026-11-04: configless, and an unsupported public item
fails the export rather than being excluded. ✅ Split: [the naming rule](/quest/m1/ffi-export/names.md)
first, then this. Rejected: one quest that waits for the census to reach zero.

Today `ffi/moq/ffi.toml` excludes about 300 refused items across moq-net, moq-pattern,
moq-clock, moq-tokio, hang, moq-mux, and kio. ✅ Plan the children later: the first agent runs the
census and splits it by refusal kind into an epic through `/quest-plan`, ranked by what
moq-ffi's hand-written wrapper still covers. This quest's own remaining work, deleting
`ffi.toml` and the config reader, stays in that epic's README.

Public API: the exporter's command line and the C API of every item that exports.

## Required

- [C names and crates with no config](/quest/m1/ffi-export/names.md) - the collision-free naming rule and a facade crate, leaving only `exclude` in `ffi.toml`
4 changes: 4 additions & 0 deletions quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ generic std gap is planned here even when moq never calls it.
- [Writes through iter_mut without a Place per item](/quest/m3/ts-iter-mut-places.md) - a loop over `iter_mut()` of copy values writes the slice's array by index
- [rs.String](/quest/m3/ts-string-capacity.md) - `String`, `Box<str>`, and `&str` are a runtime type holding std's capacity
- [Slices not yet written](/quest/m3/ts-uninit-slices.md) - `Box::new_uninit_slice` and `write_clone_of_slice` translate
- [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md) - no runtime `rust2-ts:` error where native Rust goes on: an exact slower path, or a refusal at translation
- [rust2-ts with no ts.toml](/quest/m3/ts-configless.md) - a crate's whole `pub` API translates with no config and no bound items
- [Buf on a deque of bytes](/quest/m3/ts-vecdeque-buf.md) - `bytes`' `Buf` for a deque of bytes translates
- [A B-tree drops as std's does](/quest/m3/ts-btree-drop-order.md) - keys and values drop node by node, in std's order
16 changes: 16 additions & 0 deletions quest/m3/ts-btree-drop-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# [S] A B-tree drops as std's does

## Goal

[rust2-ts](/ts/README.md)'s `BTreeMap` and `BTreeSet` drop their keys and values in
std's order, node by node, proven by differential tests against native Rust that log
each drop.

## Plan

The runtime drops in key order, decided with the maintainer on 2026-10-30 for speed
while its tree was not std's. With std's node layout, ported in
[#577](https://github.com/moq-dev/rust2/pull/577), std's order costs little; measure it on
the TS bench's `btree/` workloads.

Public API: the translator's output.
29 changes: 29 additions & 0 deletions quest/m3/ts-configless.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# [XL] rust2-ts with no ts.toml

## Goal

[rust2-ts](/ts/README.md) translates a crate's whole `pub` API with no `ts.toml`: the
package is the crate it runs on, every `pub` item is a root, a `pub` type's `Display` and
`Debug` come along with it, and no item is bound to hand-written TypeScript.

## Plan

Decided with the maintainer on 2026-11-04: every tool is configless, with opinionated
names and defaults.

- ✅ `roots`, `package`, and `printable` go: the `pub` API is the roots, and printing
follows the impls. This waits on moq-net translating whole, which the
[model epic](/quest/m2/ts-model/README.md) drives toward.
- ✅ Each `bind` is a gap to close, not a platform boundary: a bound item becomes a
translation, or a mapping of that crate built into the runtime as std's are (moq's
model binds DEFLATE, its clock, and its task sets). Rejected: keeping `bind` as the one
config key.

Fixtures that name a few roots become crates whose `pub` API is those roots. Split
into an epic if the binds alone outgrow one PR.

Public API: the command line, and what a translation exports.

## Required

- [moq-net's model in TypeScript](/quest/m2/ts-model/README.md) - the model translates with no refusals
20 changes: 11 additions & 9 deletions quest/m3/ts-dyn.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ std type's class calling the mapping of its impl. Decided with the maintainer on
2026-10-27: a stock crate translates with no annotations or config, so trait objects move
from never to not yet.

`TypeId` has no JS value yet. The class of the value's own type stands for its type in a
`TypeId` has no JS value yet: the class of the value's own type stands for its type in a
downcast, but `TypeId::of` of a type no object holds has none, and a `&mut dyn Any` of a
value JS copies is of a second class. Open question for the maintainer, with options:
value JS copies is of a second class. Decided with the maintainer on 2026-11-04:

- Recommended: an `rs.u128` of rustc's `type_id_hash` of the type, which std's `TypeId`
is at run time, so equality, order, hash, and `Debug` reuse the `u128`'s; each object's
class returns its type's as a constant. Its value is native's only where the crate's
metadata hash is, so tests compare equality and order within a run, not `Debug`.
- The type's path as a string: readable, but its order and `Debug` are not native's.
- The class itself, as a downcast checks for: no new value, but order and hash need one
anyway, and a type no object holds would need a class of its own.
- ✅ rustc's 128-bit `type_id_hash` of the type, which std's `TypeId` holds at run time;
each object's class returns its type's as a constant. Its traits are std's `TypeId`
impls, not `u128`'s: `Hash` writes one `u64`, `Debug` prints `TypeId(0x…)`, and `Ord`
is derived over its words, so a test logs a `Hasher`'s calls and checks order and
`Debug`'s shape. Its value is native's only where the crate's metadata hash is, so tests
compare within a run rather than the exact bits.
- Rejected: the type's path as a string, whose order and `Debug` are not native's; the
class itself, which still needs a value for order and hash, and a class for a type no
object holds.

Public API: the translator's output.
37 changes: 37 additions & 0 deletions quest/m3/ts-runtime-throws.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# [L] Throw only where Rust panics

## Goal

Code [rust2-ts](/ts/README.md) generates fails at run time only where the Rust source
does: no `rust2-ts:` error the runtime throws where native Rust would go on. Each such
construct translates exactly, through a slower path if it must, or is refused at
translation with a span. Proven by differential tests against native Rust for every
construct that now translates.

## Plan

Decided with the maintainer on 2026-11-04: "we should not panic when Rust would not
panic", and runtime throws that copy Rust's behaviour are fine.

- ✅ A slower exact path, no flag: where one exists it is the translation, with the fast
path kept where the translator can prove it applies; refuse at translation only where
none exists. Rejected: refusing with no fallback; an inline opt-in.
- ✅ A throw that guards Rust UB (a read through a null or dangling pointer, `from_raw` of
a value already dropped, freeing memory `alloc` did not give) stays, as an abort worded
like std's `unsafe precondition(s) violated` check, since Rust defines nothing there.

The rest are gaps. At `main` they include a panic its hook cannot locate
([#556](https://github.com/moq-dev/rust2/pull/556)), a pointer cast to `()` that is not an
`Arc`'s or `Rc`'s (`rs.arcErase`, [#562](https://github.com/moq-dev/rust2/pull/562),
[#565](https://github.com/moq-dev/rust2/pull/565)), `offset_from` of one Rust allocation
JS keeps in two arrays, `Bytes::slice_ref` of other storage, `alloc`'s bytes read as
values, ranges stepped without their type, and fmt widths written as a placeholder.
`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 are
in scope too, through [addresses](/quest/m2/ts-model/addresses.md)' identities.

Public API: the translator's output, and which crates translate.

## Related

- [Unsafe code the translator can model](/quest/m3/ts-unsafe.md) - the raw-pointer model several of these throws guard
14 changes: 14 additions & 0 deletions quest/m3/ts-vecdeque-buf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# [S] Buf on a deque of bytes

## Goal

[rust2-ts](/ts/README.md) translates `bytes`' `impl Buf for VecDeque<u8>`, whose
`chunk` is the deque's front slice, proven by differential tests against native Rust
across a wrapped ring.

## Plan

Today it is refused at translation. The runtime already has the deque's two slices, as
std's `as_slices`, so the gap is the translator's mapping of `bytes`' impl onto them.

Public API: the translator's output.
1 change: 1 addition & 0 deletions quest/m4/go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ adds 324 KB to a Go binary where cgo adds 1.15 MB and a libc dependency.

- [Match cgo on the crate](/quest/m4/go/speed.md) - the translated crate within 2x of cgo on a group's stream end to end
- [moq-net beyond the codec](/quest/m4/go/coverage.md) - `Drop`, floats, 128-bit integers, trait objects, and collections, so moq-net translates whole
- [rust2-go with no go.toml](/quest/m4/go/configless.md) - a crate's whole `pub` API translates with no config
13 changes: 13 additions & 0 deletions quest/m4/go/configless.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# [S] rust2-go with no go.toml

## Goal

[rust2-go](/go/README.md) translates a crate's whole `pub` API with no `go.toml`: the
package is the crate's name and every `pub` item is a root.

## Plan

Decided with the maintainer on 2026-11-04: every tool is configless, with opinionated
names and defaults. Follow [rust2-ts's](/quest/m3/ts-configless.md) choices.

Public API: the command line, and what a translation exports.