From dc1d7c5f6449d6f68a94db16092f83015178cb71 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:06:19 -0700 Subject: [PATCH] chore(quest): plan the 2026-10-27 follow-ups and split the exporter into an epic Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HRfpGSuhrqBv8bAxEvpXi3 --- quest/m1/README.md | 4 +-- quest/m1/ffi-export.md | 54 ---------------------------- quest/m1/ffi-export/README.md | 45 +++++++++++++++++++++++ quest/m1/ffi-export/contract.md | 13 +++++++ quest/m1/ffi-export/cpp.md | 12 +++++++ quest/m1/ffi-export/crates.md | 17 +++++++++ quest/m1/ffi-export/driving.md | 17 +++++++++ quest/m1/ffi-export/instance-keys.md | 22 ++++++++++++ quest/m1/ffi-export/kotlin.md | 14 ++++++++ quest/m1/ffi-export/lent-closures.md | 19 ++++++++++ quest/m1/ffi-export/paths.md | 14 ++++++++ quest/m2/README.md | 4 ++- quest/m2/ts-drop.md | 5 +++ quest/m2/ts-harness-names.md | 24 +++++++++++++ quest/m2/ts-model/README.md | 4 ++- quest/m2/ts-model/std-places.md | 24 +++++++++++++ quest/m2/ts-model/std-rest.md | 25 +++++++++++++ quest/m2/ts-swap-parts.md | 24 +++++++++++++ quest/m3/README.md | 5 +++ quest/m3/ts-bind-gaps.md | 17 +++++++++ quest/m3/ts-bool-core.md | 17 +++++++++ quest/m3/ts-fold-guards.md | 21 +++++++++++ quest/m3/ts-partial-ord.md | 15 ++++++++ quest/m3/ts-range-fold.md | 14 ++++++++ 24 files changed, 372 insertions(+), 58 deletions(-) delete mode 100644 quest/m1/ffi-export.md create mode 100644 quest/m1/ffi-export/README.md create mode 100644 quest/m1/ffi-export/contract.md create mode 100644 quest/m1/ffi-export/cpp.md create mode 100644 quest/m1/ffi-export/crates.md create mode 100644 quest/m1/ffi-export/driving.md create mode 100644 quest/m1/ffi-export/instance-keys.md create mode 100644 quest/m1/ffi-export/kotlin.md create mode 100644 quest/m1/ffi-export/lent-closures.md create mode 100644 quest/m1/ffi-export/paths.md create mode 100644 quest/m2/ts-harness-names.md create mode 100644 quest/m2/ts-model/std-places.md create mode 100644 quest/m2/ts-model/std-rest.md create mode 100644 quest/m2/ts-swap-parts.md create mode 100644 quest/m3/ts-bind-gaps.md create mode 100644 quest/m3/ts-bool-core.md create mode 100644 quest/m3/ts-fold-guards.md create mode 100644 quest/m3/ts-partial-ord.md create mode 100644 quest/m3/ts-range-fold.md diff --git a/quest/m1/README.md b/quest/m1/README.md index aa42e8a01..f442f6c3a 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -11,11 +11,11 @@ Decided with the maintainer on 2026-10-25, from the [spike](https://github.com/kixelated/rust2/pull/41): the [exporter](/ffi/README.md), which turns a crate's `pub` API into bindings with no attributes, replaces the UniFFI fork. The fork stays in maintenance and keeps building moq's bindings until then; its feature quests -are folded into the [exporter's](/quest/m1/ffi-export.md). The +are folded into the [exporter's](/quest/m1/ffi-export/README.md). The [binding benchmark](/bench/ffi/README.md) continues as the exporter's baseline. ## Required - [Waiting: publish the UniFFI fork](/quest/m1/uniffi-release.md) - the fork publishes to crates.io under `rust2-*` names, once the maintainer decides it should -- [Exporter](/quest/m1/ffi-export.md) - moq's bindings exported from its crates' `pub` API, replacing the fork +- [Exporter](/quest/m1/ffi-export/README.md) - moq's bindings exported from its crates' `pub` API, replacing the fork - [Land main through quest land](/quest/m1/land.md) - every queued PR lands as one tested batch through the shared gate diff --git a/quest/m1/ffi-export.md b/quest/m1/ffi-export.md deleted file mode 100644 index 159a28807..000000000 --- a/quest/m1/ffi-export.md +++ /dev/null @@ -1,54 +0,0 @@ -# [XL] Exporter: moq's bindings from its crates' pub API - -## Goal - -[rust2-ffi](/ffi/README.md) replaces [the UniFFI fork](/uniffi/README.md) for moq: it exports -what moq's bindings need from moq's own crates, with no attributes and no hand-written wrapper -beyond what the census shows is genuinely new API, and generates the languages moq ships on its -C ABI. moq-ffi shrinks to that remainder. The fork stays in maintenance, building moq's bindings -until then. - -## Plan - -Decided with the maintainer on 2026-10-25, from the spike -([kixelated/rust2#41](https://github.com/kixelated/rust2/pull/41)): adopt the exporter, and fold -the fork's paused feature quests into it. Split into quests as work starts, ranked by what the -census refuses and what moq waits on: - -- **Paths without a lifetime**: [moq-dev/moq#6843](https://github.com/moq-dev/moq/pull/6843) - makes `Path` owned. Once it lands, bump the moq pin: the census then exports `Path::new`, - `strip_prefix`, and `Relative::new`, and drops `AsPath` and the `borrow` methods from - [the exclude list](/ffi/moq/ffi.toml), refusing only `Path::from_static`'s `'static` borrow. -- **Several crates**: moq-net, moq-pattern, moq-clock, moq-tokio, hang, and moq-mux export - into one library through an empty package standing in for moq-ffi's remainder, which - `just ffi standin` writes; moq-ffi takes its place once it shrinks to that remainder. - Left: - - moq-clock's C names (`moq_now`, `moq_origin`) wait for its merge into kio. - - A field and a method of one name, such as moq-tokio's `Backoff::initial`, claim one C - name that `rename` cannot split, so [the config](/ffi/moq/ffi.toml) excludes both. - - The generic types moq-ffi's media API uses export at their instances - ([kixelated/rust2#103](https://github.com/kixelated/rust2/pull/103)). Left: - `catalog::Producer`'s `json_*` and `binary_*` producers take a type parameter no rule - picks, `Producer::modify` returns a borrowing `Guard`, and `DEFAULT_NAME` sits on - `hang::Catalog<()>`, an instance the config does not name. -- **Driving**: sessions and origins run a `Driver` polled with a time and a `kio::Waiter`. - Decide between exporting that poll, a generated thread that drives it, or moq-tokio's - runtime helpers. -- **A contract symbol** per build, checked when bindings load, so a header and a library from - different builds refuse to mix. It replaces the fork's namespaced contract and per-method - checksums, which the export never had. -- **Generators** on the C ABI, measured on the [binding benchmark](/bench/ffi/README.md): - - C++ per standard (17, 20, 23) over the one C ABI, compiled with GCC, Clang, and MSVC, with - and without exceptions and RTTI; OBS builds at C17 and C++17. - - Kotlin through JNI or Panama rather than JNA: the fork measured 3.2 us per call against - 7 ns for upstream's JNI bindgen. - - Each keeps the C ABI's bulk and zero-copy payloads (no per-byte loop on either side) and - reports its generated size, raw and gzipped, against the fork's (Python 653 KiB, Kotlin - 914 KiB, Swift 684 KiB for moq-ffi). -- **Async in each language**: the C ABI's futures already take the result in the poll that - finishes them, so a cancel never loses a frame, cost three crossings when ready at once, - and reuse a repeated waker. Each language's native future around them must latch a wake - that lands between `PENDING` and parking, prove no loss on cancel in a test that forces that - window, and pick a semantic for concurrent pollers. Go's cancel contract (moq's - `go/wrapper/doc.go` ends the stream) is decided then. A hot read still makes a future handle - per read; measure it before adding a per-handle poll. diff --git a/quest/m1/ffi-export/README.md b/quest/m1/ffi-export/README.md new file mode 100644 index 000000000..d728049b2 --- /dev/null +++ b/quest/m1/ffi-export/README.md @@ -0,0 +1,45 @@ +# Exporter: moq's bindings from its crates' pub API + +## Goal + +[rust2-ffi](/ffi/README.md) replaces [the UniFFI fork](/uniffi/README.md) for moq: it exports +what moq's bindings need from moq's own crates, with no attributes and no hand-written wrapper +beyond what the census shows is genuinely new API, and generates the languages moq ships on its +C ABI. moq-ffi shrinks to that remainder. The fork stays in maintenance, building moq's bindings +until then. + +## Plan + +Decided with the maintainer on 2026-10-25, from the spike +([kixelated/rust2#41](https://github.com/kixelated/rust2/pull/41)): adopt the exporter, and fold +the fork's paused feature quests into it. + +Decided with the maintainer on 2026-10-27: this is an epic, with each item a child ranked by +what the census refuses and what moq waits on first, then the runtime, then the generators. +What stays here is the end-to-end: moq-ffi shrinks to its remainder and takes the stand-in +package's place. + +Every generator on the C ABI: + +- is measured on the [binding benchmark](/bench/ffi/README.md); +- keeps the C ABI's bulk and zero-copy payloads, with no per-byte loop on either side; +- reports its generated size, raw and gzipped, against the fork's (Python 653 KiB, Kotlin + 914 KiB, Swift 684 KiB for moq-ffi); +- wraps the C ABI's futures in the language's native future. Those futures already take the + result in the poll that finishes them, so a cancel never loses a frame, cost three crossings + when ready at once, and reuse a repeated waker. The native future must latch a wake that + lands between `PENDING` and parking, prove no loss on cancel in a test that forces that + window, and pick a semantic for concurrent pollers. A hot read still makes a future handle + per read; measure it before adding a per-handle poll. Decided with the maintainer on + 2026-10-27: async lands with each generator, not as its own quest. + +## Required + +- [Paths without a lifetime](/quest/m1/ffi-export/paths.md) - moq's owned `Path` exports, and its excludes go +- [Instances the config can't name yet](/quest/m1/ffi-export/instance-keys.md) - `&T` keys, `IntoIterator` as a `Vec`, `serde_json::Value`, and `Catalog<()>` +- [Closures lent for the call](/quest/m1/ffi-export/lent-closures.md) - a callback borrowed for one call, as `modify`'s `Guard` needs +- [One library from several crates](/quest/m1/ffi-export/crates.md) - the C names the several-crate export still can't give +- [Driving sessions and origins](/quest/m1/ffi-export/driving.md) - how a binding polls moq's `Driver` +- [A contract symbol](/quest/m1/ffi-export/contract.md) - a header and a library from different builds refuse to mix +- [C++ bindings](/quest/m1/ffi-export/cpp.md) - C++17, 20, and 23 over the C ABI, with native futures +- [Kotlin bindings](/quest/m1/ffi-export/kotlin.md) - JNI or Panama over the C ABI, with native futures diff --git a/quest/m1/ffi-export/contract.md b/quest/m1/ffi-export/contract.md new file mode 100644 index 000000000..30042d2dd --- /dev/null +++ b/quest/m1/ffi-export/contract.md @@ -0,0 +1,13 @@ +# [S] A contract symbol + +## Goal + +Each build exports a contract symbol that bindings check when they load, so a header and a +library from different builds refuse to mix. + +## Plan + +It replaces the fork's namespaced contract and per-method checksums, which the export never +had. + +Public API: one exported symbol per library. diff --git a/quest/m1/ffi-export/cpp.md b/quest/m1/ffi-export/cpp.md new file mode 100644 index 000000000..4e25b6d6f --- /dev/null +++ b/quest/m1/ffi-export/cpp.md @@ -0,0 +1,12 @@ +# [L] C++ bindings + +## Goal + +The exporter generates C++ over its one C ABI, per standard (17, 20, 23), compiled with +GCC, Clang, and MSVC, with and without exceptions and RTTI, including OBS's C17 and C++17 +builds, meeting the [epic's](/quest/m1/ffi-export/README.md) rules for every generator, +native futures included. + +## Plan + +Public API: a new generator. diff --git a/quest/m1/ffi-export/crates.md b/quest/m1/ffi-export/crates.md new file mode 100644 index 000000000..14cb63ba4 --- /dev/null +++ b/quest/m1/ffi-export/crates.md @@ -0,0 +1,17 @@ +# [M] One library from several crates + +## Goal + +moq-net, moq-pattern, moq-clock, moq-tokio, hang, and moq-mux export into one library +with every C name the bindings need, so nothing is excluded for a name clash. + +## Plan + +They export through an empty package standing in for moq-ffi's remainder, which +`just ffi standin` writes. Left: + +- moq-clock's C names (`moq_now`, `moq_origin`) wait for its merge into kio. +- A field and a method of one name, such as moq-tokio's `Backoff::initial`, claim one C + name that `rename` cannot split, so [the config](/ffi/moq/ffi.toml) excludes both. + +Public API: the exporter's config and the C names it emits. diff --git a/quest/m1/ffi-export/driving.md b/quest/m1/ffi-export/driving.md new file mode 100644 index 000000000..32778da71 --- /dev/null +++ b/quest/m1/ffi-export/driving.md @@ -0,0 +1,17 @@ +# [M] Driving sessions and origins + +## Goal + +A binding runs moq's sessions and origins: each runs a `Driver` polled with a time and a +`kio::Waiter`, and the exporter gives bindings a way to drive it that every generator can +use. + +## Plan + +Open: export that poll, generate a thread that drives it, or export moq-tokio's runtime +helpers. Go's cancel contract (moq's `go/wrapper/doc.go` ends the stream) is decided +with it. `time::run` at `origin::Driver` was left out of +[#126](https://github.com/kixelated/rust2/pull/126) because it runs on an ambient runtime +C does not have. + +Public API: the exporter's C ABI for drivers. diff --git a/quest/m1/ffi-export/instance-keys.md b/quest/m1/ffi-export/instance-keys.md new file mode 100644 index 000000000..10bd32b80 --- /dev/null +++ b/quest/m1/ffi-export/instance-keys.md @@ -0,0 +1,22 @@ +# [M] Instances the config can't name yet + +## Goal + +The exporter's `instances` key names the instances moq-ffi still wraps by hand: a `&T` +argument (`with_publisher` at `&origin::Producer`, as moq-ffi passes it), +`impl IntoIterator` exported as a `Vec` of `X`, `serde_json::Value` as the `T` of +`catalog::Producer`'s `json_snapshot` and `json_stream`, and `DEFAULT_NAME` on +`hang::Catalog<()>`. + +## Plan + +Left out of [#126](https://github.com/kixelated/rust2/pull/126), which exports generic +functions at config-named instances; decided with the maintainer on 2026-10-27 to plan +them all. + +- `IntoIterator` as a `Vec` needs `Vec` of records to map first. +- `serde_json::Value` crosses the C ABI as something every generator can read; decide + its shape (JSON text is the obvious one) when the quest starts. +- Report moq-ffi's coverage and the census before and after. + +Public API: the exporter's config and the C names it emits. diff --git a/quest/m1/ffi-export/kotlin.md b/quest/m1/ffi-export/kotlin.md new file mode 100644 index 000000000..85e5db806 --- /dev/null +++ b/quest/m1/ffi-export/kotlin.md @@ -0,0 +1,14 @@ +# [L] Kotlin bindings + +## Goal + +The exporter generates Kotlin over its C ABI through JNI or Panama, meeting the +[epic's](/quest/m1/ffi-export/README.md) rules for every generator, native futures +included. + +## Plan + +Not JNA: the fork measured 3.2 us per call through it against 7 ns for upstream's JNI +bindgen. + +Public API: a new generator. diff --git a/quest/m1/ffi-export/lent-closures.md b/quest/m1/ffi-export/lent-closures.md new file mode 100644 index 000000000..1dd945fab --- /dev/null +++ b/quest/m1/ffi-export/lent-closures.md @@ -0,0 +1,19 @@ +# [M] Closures lent for the call + +## Goal + +The exporter exports a function that takes a closure it calls only during the call, as a +C callback with a context pointer, so moq's `catalog::Producer` can be changed through a +lent closure (`mutate`) where `modify` returns a borrowing `Guard` the C ABI can't hold. + +## Plan + +Left out of [#126](https://github.com/kixelated/rust2/pull/126); decided with the +maintainer on 2026-10-27 to plan it. + +- A closure the callee keeps past the call stays refused; only a lent one exports. +- A panic or an error in the callback must not unwind across the C ABI. +- Decide whether `mutate` is moq's API or the exporter's rewrite of `modify`; moq's own + change belongs in moq's tree. + +Public API: the exporter's C ABI for callbacks. diff --git a/quest/m1/ffi-export/paths.md b/quest/m1/ffi-export/paths.md new file mode 100644 index 000000000..b7fbd8153 --- /dev/null +++ b/quest/m1/ffi-export/paths.md @@ -0,0 +1,14 @@ +# [S] Paths without a lifetime + +## Goal + +The exporter exports moq's owned `Path`: the census exports `Path::new`, `strip_prefix`, +and `Relative::new`, [the exclude list](/ffi/moq/ffi.toml) drops `AsPath` and the `borrow` +methods, and only `Path::from_static`'s `'static` borrow is refused. + +## Plan + +[moq-dev/moq#6843](https://github.com/moq-dev/moq/pull/6843), which makes `Path` owned, +merged on 2026-10-27. Bump the moq pin past it and report the census before and after. + +Public API: the C names moq's bindings get. diff --git a/quest/m2/README.md b/quest/m2/README.md index 9fbd0db2c..e901733a7 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -21,11 +21,13 @@ runtime such as Node or Bun calls Rust through [rust2-ffi](/ffi/README.md), so N Bun matter only as proxies for V8 and JavaScriptCore, and a speed call that splits them from the browsers goes to the browsers. -The fork's feature quests folded into the [exporter](/quest/m1/ffi-export.md); +The fork's feature quests folded into the [exporter](/quest/m1/ffi-export/README.md); [m1](/quest/m1/README.md#plan) records why. ## Required +- [A tuple's parts read after their variables change](/quest/m2/ts-swap-parts.md) - `(a, b) = (b, a)` swaps instead of copying one into both +- [Fixture cases by name](/quest/m2/ts-harness-names.md) - adding a harness case never renumbers another - [Byte slices without objects](/quest/m2/ts-byte-views/README.md) - reading through a translated cursor allocates no object per read - [Drop in the TypeScript translator](/quest/m2/ts-drop.md) - every value that drops in moq-net and kio drops where Rust drops it - [Async in the TypeScript translator](/quest/m2/ts-async.md) - moq-net's and kio's async code translates with Rust's lazy futures diff --git a/quest/m2/ts-drop.md b/quest/m2/ts-drop.md index bfcccf4d6..9e706f422 100644 --- a/quest/m2/ts-drop.md +++ b/quest/m2/ts-drop.md @@ -36,6 +36,11 @@ entries that drop, sorted by the crate's own `Ord`; a `collect` in place of a `z whose second iterator drops; `successors` of items that drop, which holds the next one; and `DebugList::entries` of items that drop. +Next, from [#115](https://github.com/kixelated/rust2/pull/115): `rt.iterFromFn` and +`rt.iterSuccessors` take droppers for their closure and for `successors`' pending item, +as std's fields drop them, which lifts both refusals and the reject fixture's second +refusal of dropping the `Take` around them. + Watch the cost: a function whose cleanup does something JS can see sets a variable before each call that can panic and wraps its body in a `try`; measure any change on the [TS benchmark](/ts/README.md#bench). diff --git a/quest/m2/ts-harness-names.md b/quest/m2/ts-harness-names.md new file mode 100644 index 000000000..75468bddb --- /dev/null +++ b/quest/m2/ts-harness-names.md @@ -0,0 +1,24 @@ +# [S] Fixture cases by name + +## Goal + +Adding a module to a TS fixture's differential harness never renumbers another's, so two +PRs that each add one merge without editing the dispatch, and a targeted run in +`caller.ts` can't land on the wrong module. + +## Plan + +Found on 2026-10-27: the lang fixture dispatches by hand-numbered `match op % N` in +`ts/fixtures/lang/src/harness.rs`, which `caller.ts` targets by number. Every concurrent +ts PR that day collided on the numbers and on `N`, and one collision routed +`durations`' targeted cases to another module, which only review caught. + +Decided with the maintainer on 2026-10-27: m2, after the swap fix, since it slows every +ts PR now. + +- Name each case, or derive the dispatch from one list, so the count follows it. +- Keep each case's share of the random run and the harness's coverage; a dispatch change + must not shrink what the differential run reaches. +- Apply it to every fixture with the same pattern. + +Public API: none. diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index db75e7f54..031ca9f36 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -38,7 +38,7 @@ Decided with the maintainer on 2026-10-27, from a census of the model's 22 publi - Each child measures this target by the census of `ts/moq/model.toml`, rooted at those types with the boundaries bound, which nightly reports beside the whole crate. - The end-to-end work stays here: the model's roots, its differential harness, and the - `frames.ts` workload. + `frames.ts` workload. moq writes the binding files `model.toml` names, in its own tree. Public API: the translator's output for moq-net's model. @@ -49,4 +49,6 @@ Public API: the translator's output for moq-net's model. - [Translate safe dependencies with the package](/quest/m2/ts-model/crates.md) - kio, moq-clock, moq-pattern, and rangemap translate as one program with moq-net - [Std gaps the model reaches](/quest/m2/ts-model/std-more.md) - ranges, wrappers, and `Vec`'s editing methods - [Options of values that can be undefined](/quest/m2/ts-model/nested-option.md) - `Option>` and `Option<()>` keep `None` apart from `Some(None)` +- [Std's mutable references to values JS copies](/quest/m2/ts-model/std-places.md) - `as_mut`, `get_or_insert`, `iter_mut`, and the rest give an `rt.Place` +- [The rest of std the model reaches](/quest/m2/ts-model/std-rest.md) - the std calls the census still refuses - [Async in the TypeScript translator](/quest/m2/ts-async.md) - kio's producers and consumers are futures diff --git a/quest/m2/ts-model/std-places.md b/quest/m2/ts-model/std-places.md new file mode 100644 index 000000000..9da313041 --- /dev/null +++ b/quest/m2/ts-model/std-places.md @@ -0,0 +1,24 @@ +# [M] Std's mutable references to values JS copies + +## Goal + +rust2-ts translates the std methods that hand out a `&mut` to a value JS copies (a +number, an `Option` of one, a `Box`), giving an `rt.Place` where +[#128](https://github.com/kixelated/rust2/pull/128)'s guard refuses them now, proven by +differential tests against native Rust. + +## Plan + +Found by #128 on 2026-10-27: its guard refused 74 std-made `&mut`s in moq-net, most from +`Option::as_mut`, `get_or_insert*`, `insert`, `slice::get_mut` and `last_mut`, +`iter_mut` and `values_mut`, `Entry::and_modify`, `retain_mut`, and `dedup_by_key`. +Also refused: an `Option<&mut T>` passed through `map`, `map_or`, `filter`, and +`and_then` closures, and comparing `Option`s or tuples of Places through the runtime's key +functions. + +- Rerun the model's census first: [#129](https://github.com/kixelated/rust2/pull/129) now + lowers `Option`'s combinators from core's MIR, so some of these may already translate. +- Code that needs no Place still gets none; measure on the + [TS benchmark](/ts/README.md#bench). + +Public API: the translator's output. diff --git a/quest/m2/ts-model/std-rest.md b/quest/m2/ts-model/std-rest.md new file mode 100644 index 000000000..1d5e288a9 --- /dev/null +++ b/quest/m2/ts-model/std-rest.md @@ -0,0 +1,25 @@ +# [M] The rest of std the model reaches + +## Goal + +rust2-ts translates the std calls the model's census still refuses after +[std gaps](/quest/m2/ts-model/std-more.md) and [core's MIR](https://github.com/kixelated/rust2/pull/129), +outside [Places](/quest/m2/ts-model/std-places.md), proven by differential tests against +native Rust. + +## Plan + +Found by [#124](https://github.com/kixelated/rust2/pull/124) and #129 on 2026-10-27: +iterator `min_by_key` and `max_by_key`, `slice::split_last`, integer `to_le_bytes`, +`Box::clone`, `retain` and `sort` of byte vectors, `Debug` of `Reverse` and `Bound`, +slicing by `..=`, and `Option`'s and `Result`'s `PartialEq` and `From` impls and the std +calls core's bodies reach (`Default::default`, a payload's `Clone`). + +- Rerun the model's census first, since #115, #128, and #129 moved it, and take what it + refuses in std rather than only this list. + +Public API: the translator's output. + +## Required + +- [Std gaps the model reaches](/quest/m2/ts-model/std-more.md) - ranges, wrappers, and `Vec`'s editing methods diff --git a/quest/m2/ts-swap-parts.md b/quest/m2/ts-swap-parts.md new file mode 100644 index 000000000..02e12e874 --- /dev/null +++ b/quest/m2/ts-swap-parts.md @@ -0,0 +1,24 @@ +# [S] A tuple's parts read after their variables change + +## Goal + +rust2-ts translates a tuple built from variables and read after one of them is +reassigned with the value it held when built, so `(a, b) = (b, a)` on two `&[u8]` in a +loop swaps them instead of translating to `a = b; b = a;`, proven by a differential test +against native Rust. + +## Plan + +Found by [#130](https://github.com/kixelated/rust2/pull/130) on 2026-10-27: a swap in its +`splits` fixture gave 74 differences in about 344k cases, so the committed fixture leaves +the pattern out. The cause is on main: the `var_parts` arm of `place_expr` in +`ts/src/lower/mir/place.rs` reads a once-built tuple's field as the operand it was built +from, even when that operand is a variable reassigned before the read. + +Decided with the maintainer on 2026-10-27: a silent miscompile, so it ranks first in m2. + +- Fix it where the parts are recorded or read, for every operand kind, not only byte + slices; check the other readers of `var_parts`. +- Put the swap back in the lang fixture, inside a loop. + +Public API: the translator's output. diff --git a/quest/m3/README.md b/quest/m3/README.md index 5a8e4f81c..77abab773 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -19,3 +19,8 @@ generic std gap is planned here even when moq never calls it. - [The rest of Duration](/quest/m3/ts-duration.md) - compound assignment, float conversions, and `Sum` on a `Duration` translate - [The rest of format specs](/quest/m3/ts-fmt-specs.md) - precision, argument widths, `+`, and the other radixes format as `core::fmt` does - [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library +- [Fold matches across tests that aren't variant switches](/quest/m3/ts-fold-guards.md) - a match an `if` or guard decided drops its dead arms +- [A crate's own comparison operators](/quest/m3/ts-partial-ord.md) - an overridden `lt`/`le`/`gt`/`ge` is called where std calls it +- [An inclusive range drained by a fold](/quest/m3/ts-range-fold.md) - a folded `RangeInclusive` ends in core's state +- [bool's combinators from core's MIR](/quest/m3/ts-bool-core.md) - `bool::then` lowers from core, retiring `rt.boolThen` +- [Binding gaps](/quest/m3/ts-bind-gaps.md) - bound types as keys, unit returns checked, renamed dependencies diff --git a/quest/m3/ts-bind-gaps.md b/quest/m3/ts-bind-gaps.md new file mode 100644 index 000000000..0c6506f3d --- /dev/null +++ b/quest/m3/ts-bind-gaps.md @@ -0,0 +1,17 @@ +# [S] Binding gaps + +## Goal + +rust2-ts's [bindings](/ts/README.md) cover the cases +[#127](https://github.com/kixelated/rust2/pull/127) refuses or checks loosely: a bound type +as a collection key through its binding's `Hash`, `Eq`, and `Ord`; a binding that returns +unit is checked against `=> undefined` rather than `=> void`, which accepts any return +value; and a path through a renamed dependency (`foo = { package = "bar" }`) resolves by +the name the package gives it. + +## Plan + +Found by #127 on 2026-10-27. Each fails with the Rust item's span today, apart from the +unit return, which `tsc` accepts unchecked. + +Public API: `ts.toml`'s bindings. diff --git a/quest/m3/ts-bool-core.md b/quest/m3/ts-bool-core.md new file mode 100644 index 000000000..31deeec14 --- /dev/null +++ b/quest/m3/ts-bool-core.md @@ -0,0 +1,17 @@ +# [XS] bool's combinators from core's MIR + +## Goal + +rust2-ts lowers `bool::then` and `then_some` from core's MIR, as +[#129](https://github.com/kixelated/rust2/pull/129) does `Option`'s and `Result`'s, and +deletes `rt.boolThen` and its dropper, with drops exact, proven by differential tests +against native Rust and the codec's bundle no larger. + +## Plan + +Found by [#115](https://github.com/kixelated/rust2/pull/115) on 2026-10-27: `rt.boolThen` +is the last runtime combinator that takes a closure dropper, and `rt.once` may go with it. +Decided with the maintainer on 2026-10-27 to plan it, since it deletes runtime code; +integer methods stay in the runtime, as core-mir decided. + +Public API: the translator's output and the runtime. diff --git a/quest/m3/ts-fold-guards.md b/quest/m3/ts-fold-guards.md new file mode 100644 index 000000000..8efdbede9 --- /dev/null +++ b/quest/m3/ts-fold-guards.md @@ -0,0 +1,21 @@ +# [S] Fold matches across tests that aren't variant switches + +## Goal + +rust2-ts folds a match that an enclosing test already decided when that test is an `if` +or a match guard, as [#110](https://github.com/kixelated/rust2/pull/110) does for a +switch on a variant, so the output drops the dead arms rather than widening them, proven +by differential tests against native Rust. + +## Plan + +Found by [#118](https://github.com/kixelated/rust2/pull/118) on 2026-10-27: the dataflow +in `ts/src/lower/mir/decided.rs` forgets what it knows at a switch on anything but a +variant, so a match decided before an `if` gets a type-only widening instead of folding. +`tsc` accepts it; this only shrinks the output. + +- Fix the doc comments in `ts/src/lower/mir/mod.rs` while there: `whole()` sits between + `own()`'s doc comment and `own()`. +- Measure the output size on the [TS benchmark](/ts/README.md#bench). + +Public API: the translator's output. diff --git a/quest/m3/ts-partial-ord.md b/quest/m3/ts-partial-ord.md new file mode 100644 index 000000000..afb206257 --- /dev/null +++ b/quest/m3/ts-partial-ord.md @@ -0,0 +1,15 @@ +# [S] A crate's own comparison operators + +## Goal + +rust2-ts translates `<`, `<=`, `>`, `>=`, sorts, and `Ord::max`, `min`, and `clamp` on a +type whose `PartialOrd` overrides `lt`, `le`, `gt`, or `ge`, calling the crate's override +where std does, proven by differential tests against native Rust. + +## Plan + +Found by [#124](https://github.com/kixelated/rust2/pull/124) on 2026-10-27: default sorts +and comparisons now go through `partial_cmp` in std's operand order, and a comparison that +reaches the crate's own `lt`, `le`, `gt`, or `ge` is refused with its span. + +Public API: the translator's output. diff --git a/quest/m3/ts-range-fold.md b/quest/m3/ts-range-fold.md new file mode 100644 index 000000000..643a16a0b --- /dev/null +++ b/quest/m3/ts-range-fold.md @@ -0,0 +1,14 @@ +# [XS] An inclusive range drained by a fold + +## Goal + +A `RangeInclusive` drained through `by_ref` by a fold (`any`, `find`, `for_each`) ends in +core's state, `exhausted` with `start == end`, so `start()`, `end_bound`, and `Debug` +read what std's do, proven by a differential test against native Rust. + +## Plan + +Found by [#124](https://github.com/kixelated/rust2/pull/124) on 2026-10-27: the runtime +folds through `next` and leaves `start` past `end`. + +Public API: the translator's output.