From 15d25668c3fa7e8d04f4ecf1d505e3d084170c3e Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 4 Nov 2026 10:31:49 -0800 Subject: [PATCH 1/4] quest: plan configless tools, no needless runtime throws, and follow-ups Co-Authored-By: Claude Opus 5.5 --- quest/m1/ffi-export/README.md | 6 +++++ quest/m1/ffi-export/crates.md | 5 ++-- quest/m1/ffi-export/names.md | 33 +++++++++++++++++++++++++ quest/m1/ffi-export/python-types.md | 16 +++++++++++++ quest/m1/ffi-export/refusals.md | 25 +++++++++++++++++++ quest/m3/README.md | 4 ++++ quest/m3/ts-btree-drop-order.md | 20 ++++++++++++++++ quest/m3/ts-configless.md | 29 ++++++++++++++++++++++ quest/m3/ts-dyn.md | 18 +++++++------- quest/m3/ts-runtime-throws.md | 37 +++++++++++++++++++++++++++++ quest/m3/ts-vecdeque-buf.md | 14 +++++++++++ quest/m4/go/README.md | 1 + quest/m4/go/configless.md | 13 ++++++++++ 13 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 quest/m1/ffi-export/names.md create mode 100644 quest/m1/ffi-export/python-types.md create mode 100644 quest/m1/ffi-export/refusals.md create mode 100644 quest/m3/ts-btree-drop-order.md create mode 100644 quest/m3/ts-configless.md create mode 100644 quest/m3/ts-runtime-throws.md create mode 100644 quest/m3/ts-vecdeque-buf.md create mode 100644 quest/m4/go/configless.md diff --git a/quest/m1/ffi-export/README.md b/quest/m1/ffi-export/README.md index 5fb7f516b..0ad16fab3 100644 --- a/quest/m1/ffi-export/README.md +++ b/quest/m1/ffi-export/README.md @@ -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 diff --git a/quest/m1/ffi-export/crates.md b/quest/m1/ffi-export/crates.md index 461763eb4..13e032f04 100644 --- a/quest/m1/ffi-export/crates.md +++ b/quest/m1/ffi-export/crates.md @@ -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. diff --git a/quest/m1/ffi-export/names.md b/quest/m1/ffi-export/names.md new file mode 100644 index 000000000..de784d980 --- /dev/null +++ b/quest/m1/ffi-export/names.md @@ -0,0 +1,33 @@ +# [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. +- 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. diff --git a/quest/m1/ffi-export/python-types.md b/quest/m1/ffi-export/python-types.md new file mode 100644 index 000000000..d6ba2bc1b --- /dev/null +++ b/quest/m1/ffi-export/python-types.md @@ -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. diff --git a/quest/m1/ffi-export/refusals.md b/quest/m1/ffi-export/refusals.md new file mode 100644 index 000000000..e409ee385 --- /dev/null +++ b/quest/m1/ffi-export/refusals.md @@ -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-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` diff --git a/quest/m3/README.md b/quest/m3/README.md index 98c0d1b86..f087c7649 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -24,3 +24,7 @@ generic std gap is planned here even when moq never calls it. - [rs.String](/quest/m3/ts-string-capacity.md) - `String`, `Box`, and `&str` are a runtime type holding std's capacity - [A B-tree's searches call Ord as std's do](/quest/m3/ts-btree-search-order.md) - a B-tree's lookups and inserts call a key's own `Ord` in std's order and number - [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 diff --git a/quest/m3/ts-btree-drop-order.md b/quest/m3/ts-btree-drop-order.md new file mode 100644 index 000000000..429253ac6 --- /dev/null +++ b/quest/m3/ts-btree-drop-order.md @@ -0,0 +1,20 @@ +# [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 from +[the searches](/quest/m3/ts-btree-search-order.md), std's order costs little; measure it on +the TS bench's `btree/` workloads. + +Public API: the translator's output. + +## Required + +- [A B-tree's searches call Ord as std's do](/quest/m3/ts-btree-search-order.md) - std's node layout and search diff --git a/quest/m3/ts-configless.md b/quest/m3/ts-configless.md new file mode 100644 index 000000000..c30ce45fe --- /dev/null +++ b/quest/m3/ts-configless.md @@ -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 diff --git a/quest/m3/ts-dyn.md b/quest/m3/ts-dyn.md index 070ccd993..dfb5f3be7 100644 --- a/quest/m3/ts-dyn.md +++ b/quest/m3/ts-dyn.md @@ -13,16 +13,16 @@ 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. +- ✅ 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`. +- 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. diff --git a/quest/m3/ts-runtime-throws.md b/quest/m3/ts-runtime-throws.md new file mode 100644 index 000000000..5991b9505 --- /dev/null +++ b/quest/m3/ts-runtime-throws.md @@ -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 diff --git a/quest/m3/ts-vecdeque-buf.md b/quest/m3/ts-vecdeque-buf.md new file mode 100644 index 000000000..dc28c33a6 --- /dev/null +++ b/quest/m3/ts-vecdeque-buf.md @@ -0,0 +1,14 @@ +# [S] Buf on a deque of bytes + +## Goal + +[rust2-ts](/ts/README.md) translates `bytes`' `impl Buf for VecDeque`, 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. Its chunks follow the ring's split, so the runtime's +deque must expose its two slices as std's `as_slices` does. + +Public API: the translator's output. diff --git a/quest/m4/go/README.md b/quest/m4/go/README.md index e7fc3f09f..7dedf6b9f 100644 --- a/quest/m4/go/README.md +++ b/quest/m4/go/README.md @@ -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 diff --git a/quest/m4/go/configless.md b/quest/m4/go/configless.md new file mode 100644 index 000000000..95549a7f6 --- /dev/null +++ b/quest/m4/go/configless.md @@ -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. From 6caeb83017ffa63568a275410e7e21cb8c1271fb Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 4 Nov 2026 10:38:23 -0800 Subject: [PATCH 2/4] quest: name field accessors apart from methods, and keep TypeId's own traits Co-Authored-By: Claude Opus 5.5 --- quest/m1/ffi-export/names.md | 6 ++++++ quest/m3/ts-dyn.md | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/quest/m1/ffi-export/names.md b/quest/m1/ffi-export/names.md index de784d980..b8afcfdcc 100644 --- a/quest/m1/ffi-export/names.md +++ b/quest/m1/ffi-export/names.md @@ -19,6 +19,12 @@ inline in source next to the item. 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. diff --git a/quest/m3/ts-dyn.md b/quest/m3/ts-dyn.md index dfb5f3be7..6b4e524ae 100644 --- a/quest/m3/ts-dyn.md +++ b/quest/m3/ts-dyn.md @@ -17,10 +17,12 @@ from never to not yet. 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. Decided with the maintainer on 2026-11-04: -- ✅ 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`. +- ✅ 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. From b7184ce5d4a0924c0900ba12b0251b7db09c1fae Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 4 Nov 2026 10:40:32 -0800 Subject: [PATCH 3/4] quest: list moq-pattern's refusals, and place the deque Buf gap in the translator Co-Authored-By: Claude Opus 5.5 --- quest/m1/ffi-export/refusals.md | 4 ++-- quest/m3/ts-vecdeque-buf.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/quest/m1/ffi-export/refusals.md b/quest/m1/ffi-export/refusals.md index e409ee385..67c088c83 100644 --- a/quest/m1/ffi-export/refusals.md +++ b/quest/m1/ffi-export/refusals.md @@ -12,8 +12,8 @@ Decided with the maintainer on 2026-11-04: configless, and an unsupported public 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-clock, -moq-tokio, hang, moq-mux, and kio. ✅ Plan the children later: the first agent runs the +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. diff --git a/quest/m3/ts-vecdeque-buf.md b/quest/m3/ts-vecdeque-buf.md index dc28c33a6..9c7bf2452 100644 --- a/quest/m3/ts-vecdeque-buf.md +++ b/quest/m3/ts-vecdeque-buf.md @@ -8,7 +8,7 @@ across a wrapped ring. ## Plan -Today it is refused at translation. Its chunks follow the ring's split, so the runtime's -deque must expose its two slices as std's `as_slices` does. +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. From 19e6b239d256b29ab0ca3c7177aac55da9bb7a50 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 4 Nov 2026 10:46:18 -0800 Subject: [PATCH 4/4] quest: unblock the B-tree drop order now that #577 landed Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-btree-drop-order.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/quest/m3/ts-btree-drop-order.md b/quest/m3/ts-btree-drop-order.md index 429253ac6..4d102937d 100644 --- a/quest/m3/ts-btree-drop-order.md +++ b/quest/m3/ts-btree-drop-order.md @@ -9,12 +9,8 @@ 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 from -[the searches](/quest/m3/ts-btree-search-order.md), std's order costs little; measure it on +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. - -## Required - -- [A B-tree's searches call Ord as std's do](/quest/m3/ts-btree-search-order.md) - std's node layout and search