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
8 changes: 2 additions & 6 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 @@ -29,7 +29,7 @@ Decided with the maintainer on 2026-10-27, from a census of the model's 22 publi
`cache::Pool`, `listing::Tracks`, `time::Timestamp`): 4,595 refusals over 1,112 items.
2,588 are calls into the workspace crates, and 424 items refuse nothing else.

- This is an epic; each group of refusals is a child below. moq must work, but nothing in
- Each group of refusals is its own quest. moq must work, but nothing in
the translator is specific to moq: dependencies follow general rules, and moq's own
changes (safe `cfg` twins of its `unsafe`, dropping or binding `hashlink`, `slab`, and
`smallvec`) are one research-tier quest in moq's tree.
Expand All @@ -46,7 +46,3 @@ Decided with the maintainer on 2026-10-27, from a census of the model's 22 publi
`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.

## Required

- [A crate's own iterator traits](/quest/m2/ts-model/iter-traits.md) - hashlink's double-ended, exact-size, and fused iterators, and their `size_hint`
23 changes: 0 additions & 23 deletions quest/m2/ts-model/iter-traits.md

This file was deleted.

1 change: 1 addition & 0 deletions quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ generic std gap is planned here even when moq never calls it.
- [A closure that drops given to a string's edit or search](/quest/m3/ts-string-closures.md) - `String::retain` and `str`'s searches by a closure that owns a value that drops translate
- [Waiting: a slice object for a mutable part of other values](/quest/m3/ts-slice-object.md) - waits on a moq profile showing the `Proxy` view's traps matter, then decides on an `rt.ByteSlice`-like form
- [Port the workflow guard to kixelated/quest](/quest/m3/quest-workflow-guard.md) - quest's environment guard parses YAML as this repository's does
- [Folds from the back, and a crate's own folds](/quest/m3/ts-iter-back-folds.md) - `rfold` and `try_rfold` translate, and so does a crate's iterator that overrides `fold` or `nth_back`
- [A comparison of two constants](/quest/m3/ts-constant-compares.md) - `size_of::<T>() == 1` in a generic function translates to TypeScript `tsc` accepts
- [A vector's exact capacity as it shrinks](/quest/m3/ts-vec-exact-capacity.md) - a deque made from a `Vec` splits its slices where std's does
- [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
Expand Down
30 changes: 30 additions & 0 deletions quest/m3/ts-iter-back-folds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# [M] Folds from the back, and a crate's own folds

## Goal

[rust2-ts](/ts/README.md) translates `DoubleEndedIterator::rfold` and `try_rfold` of any
iterator, and a crate's own iterator that overrides a provided method std's adaptors call
in place of `next` or `next_back`, such as `fold` or `nth_back`. Each runs where native's
does, as many times and in the same order, proven by differential tests against native
Rust.

## Plan

Found by [#436](https://github.com/moq-dev/rust2/pull/436), which translates a crate's own
`next_back`, `len`, and `size_hint`:

- `rfold` and `try_rfold` are an "unsupported call" for every iterator, std's included.
The runtime already folds from the back inside its adaptors (`Iter.rfold`, `folded`),
so the gap may be mostly the mapping.
- A crate's own `Iterator::fold` or `DoubleEndedIterator::nth_back` is refused as
"overriding `_`, which std's adaptors call in place of `next` or beside it". Each override
is a method std's adaptors and consumers call in place of the default: `fold` from
`for_each`, `count`, `last`, and `Map::fold`, `nth_back` from `Rev::nth`. `rt.iterFrom`
would call it there, as it now calls `next_back`.
- Open: whether overrides of the other provided methods (`nth`, `try_fold`, `rfold`,
`advance_by`, `count`, `last`, and more) land here too. Each is the same problem, and
the runtime's callers of each decide how far it reaches.
- `lang`'s `ends` fixture logs each method a crate's iterator is called through, and can
extend to these.

Public API: the translator's output.
6 changes: 4 additions & 2 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ compare the best and then the next, and a B-tree's iterators read their ends. `e
An iterator over a slice, array, or `Vec`, or a small range, that only its `next` and
moves read steps in place, as two variables: the array and an index into it, or the
range's bounds, with no `rt.Iter` made. The crate's own `impl Iterator` returns `rt.DONE` from its `next` too, and joins
std's adaptors through `rt.iterFrom`. A caller calls `next()` for one item, or iterates
std's adaptors through `rt.iterFrom`, which calls its own `size_hint`, `next_back`, and `len` where std's adaptors
call them, and `fuse` reads on past the end of one that is a `FusedIterator`, as std's does. A caller calls `next()` for one item, or iterates
an `rt.Iter` with `for ... of`. Each `rt.Iter`'s `sizeHint` is std's `size_hint`, its upper
bound `undefined` for `None`, and a `collect` or `extend` into a `Vec`, `VecDeque`,
`BinaryHeap`, B-tree, or hash collection reserves by it where std's does, panicking with
Expand Down Expand Up @@ -878,7 +879,8 @@ another type it borrows as through the crate's own `Borrow`, a `&mut` to a value
copies that std makes, other than a map's, or hands a closure, as `iter_mut` and
`and_modify` do, one into a `Result`'s payload passed on or kept, the adaptors
without a mapping in `src/lower/iter.rs` (such as `partition`), an
own `impl Iterator` with `next_back`, `len`, or another provided method of its own,
own `impl Iterator` or `DoubleEndedIterator` that overrides a provided method other than
`size_hint` and `len`, such as `fold` or `nth_back`,
a pointer's address (`{:p}`), `write!` with a placeholder into
the crate's own `fmt::Write`, a `str` pattern other than a
`char`, a `&str`, a predicate, or chars, a mutable `str` slice, a copy of a closure that changes
Expand Down
2 changes: 0 additions & 2 deletions ts/fixtures/crates/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ crate-type = ["lib", "cdylib"]
[features]
# A module of the dependency's name, whose files would be the dependency's.
shared = []
# The dependency's iterator runs from either end, which translation refuses.
rev = ["crates-dep/rev"]
# A type of the crate's own whose path a suffix spells as it does std's.
alike = []

Expand Down
4 changes: 0 additions & 4 deletions ts/fixtures/crates/dep/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,5 @@ edition.workspace = true
license.workspace = true
publish = false

[features]
# `Countdown` runs from either end.
rev = []

[lints]
workspace = true
1 change: 0 additions & 1 deletion ts/fixtures/crates/dep/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ impl Iterator for Countdown {
}
}

#[cfg(feature = "rev")]
impl DoubleEndedIterator for Countdown {
fn next_back(&mut self) -> Option<u8> {
if self.next == self.last {
Expand Down
8 changes: 8 additions & 0 deletions ts/fixtures/crates/expected/crates_dep.Countdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ export function next$iter_Iterator(self: Countdown): rt.u8 | rt.Done {
return rt.DONE;
}

export function nextBack$iter_DoubleEndedIterator(self: Countdown): rt.u8 | undefined {
if (!(self.next === self.last)) {
self.last = rt.u8Add(self.last, 1);
return self.last;
}
return undefined;
}

export { new_ as new };

//# sourceMappingURL=crates_dep.Countdown.ts.map
21 changes: 17 additions & 4 deletions ts/fixtures/crates/expected/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,27 @@ export function run(input: rt.ByteSlice): rt.ByteVec {
}
rt.byteVecPush(out, _143);
}
const _151: rt.Iter<rt.u8> = rt.iterRev(rt.iterFrom(Countdown.new(rt.min(input.length, 5) & 0xff), (it: Countdown.Countdown) => Countdown.next$iter_Iterator(it), undefined, undefined, (it: Countdown.Countdown) => rt.iterFromOption(Countdown.nextBack$iter_DoubleEndedIterator(it))));
rt.iterExtendBytes(out, _151, "next");
const both: Countdown.Countdown = Countdown.new(rt.min(input.length, 6) & 0xff);
while (true) {
const _160: rt.u8 | undefined = Countdown.nextBack$iter_DoubleEndedIterator(both);
if (_160 !== undefined) {
rt.byteVecPush(out, _160);
const _168: rt.u8 | undefined = rt.iterToOption(Countdown.next$iter_Iterator(both));
rt.iterExtendBytes(out, rt.iterOption(_168), undefined, true);
} else {
break;
}
}
const kept: rt.i16[] = [(input.length << 16) >> 16, 7];
const filled: rt.i16[] = copy$array_i16(kept);
Slots.fill(Slots.withBase(kept[0]), filled);
rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(kept[0] & 0xff, kept[1] & 0xff, filled[0] & 0xff, filled[1] & 0xff)), undefined, true);
const _181: rt.u8 = first(input.data, input.start, input.length);
rt.byteVecPush(out, _181);
const _184: rt.u8 = last.last(input);
rt.byteVecPush(out, _184);
const _202: rt.u8 = first(input.data, input.start, input.length);
rt.byteVecPush(out, _202);
const _205: rt.u8 = last.last(input);
rt.byteVecPush(out, _205);
log[Symbol.dispose]();
return out;
}
Expand Down
2 changes: 1 addition & 1 deletion ts/fixtures/crates/refused.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
unsupported call to `std::ptr::const_ptr::<impl *const u8>::byte_add` at fixtures/crates/dep/src/lib.rs:129:27
unsupported call to `std::ptr::const_ptr::<impl *const u8>::byte_add` at fixtures/crates/dep/src/lib.rs:128:27
1 change: 0 additions & 1 deletion ts/fixtures/crates/rev.txt

This file was deleted.

7 changes: 7 additions & 0 deletions ts/fixtures/crates/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ pub fn run(input: &[u8]) -> Vec<u8> {
for n in Countdown::new(input.len().min(4) as u8) {
out.push(n);
}
// The dependency's iterator, from the back and then from either end until they meet.
out.extend(Countdown::new(input.len().min(5) as u8).rev());
let mut both = Countdown::new(input.len().min(6) as u8);
while let Some(n) = both.next_back() {
out.push(n);
out.extend(both.next());
}
// Only the binding writes into a `[i16; 2]`, so the copy must not share it.
let kept: [i16; 2] = [input.len() as i16, 7];
let mut filled = kept;
Expand Down
13 changes: 12 additions & 1 deletion ts/fixtures/lang/caller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,17 @@ const capacityCases = [
...Array.from({ length: 8 * 6 }, (_, i) => Uint8Array.of(...op("capacity::collects"), i % 8, [0, 1, 2, 5, 9, 23][Math.floor(i / 8)], 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
];

/**
* `ends::ends`: each kind of iterator through every consumer and either end's bits, of
* runs empty, short, and long, whose hints are exact, loose, and lying, after none and a
* few steps through each of its methods.
*/
const endsCases = [0x00, 0x03, 0x0f, 0x15, 0x27, 0x36, 0x2f].flatMap((shape) =>
[[], [0x01, 0x00], [0x0a, 0x0d, 0x03], [0x05, 0x06, 0x04, 0x0f]].flatMap((steps) =>
Array.from({ length: 4 * 256 }, (_, i) => Uint8Array.of(...op("ends::ends"), i % 4, shape, i >> 2, ...steps)),
),
);

/**
* `rings::slices` of each item type, from a deque made by `new`, `with_capacity` of 5, and
* from a `Vec` of 7: pushed at both ends and popped at the front to wrap it, past a few
Expand Down Expand Up @@ -941,4 +952,4 @@ const sortCases = [
...Array.from({ length: 20 * 5 }, (_, i) => Uint8Array.of(...op("sorts::btrees"), i % 20, Math.floor(i / 20), 2, 9)),
];

export const cases: Uint8Array[] = [...loops, ...durations, ...ints(), ...utf8, ...floats, ...formats, ...parsed, ...singlesCases, ...singlesFormats, ...singlesParsed, ...dyns, ...cells, ...shared, ...locks, ...guards, ...maps, ...mappedLocks, ...relocks, ...tries, ...mutexMaps, ...printed, ...downgrades, ...leaks, ...placeCompares, ...rwlocks, ...onces, ...bounds, ...unsafeCells, ...edits, ...subslices, ...consumers, ...closures, ...elements, ...ranges, ...reducers, ...sliceEdits, ...drains, ...iters, ...splitEnds, ...dequeRanges, ...hinted, ...peekedOthers, ...hints, ...reserves, ...setOps, ...zeros, ...writes, ...floatsMulAdds, ...singlesMulAdds, ...unlikeEqs, ...ownNes, ...rangeFroms, ...compares, ...unlikeCompares, ...rangeMethods, ...wraps(), ...refOrders, ...saturates(), ...methods(), ...separated, ...asciiCases, ...gets, ...capacityCases, ...ringsCases, ...remadeCases, ...sortCases];
export const cases: Uint8Array[] = [...loops, ...durations, ...ints(), ...utf8, ...floats, ...formats, ...parsed, ...singlesCases, ...singlesFormats, ...singlesParsed, ...dyns, ...cells, ...shared, ...locks, ...guards, ...maps, ...mappedLocks, ...relocks, ...tries, ...mutexMaps, ...printed, ...downgrades, ...leaks, ...placeCompares, ...rwlocks, ...onces, ...bounds, ...unsafeCells, ...edits, ...subslices, ...consumers, ...closures, ...elements, ...ranges, ...reducers, ...sliceEdits, ...drains, ...iters, ...splitEnds, ...dequeRanges, ...hinted, ...peekedOthers, ...hints, ...reserves, ...setOps, ...zeros, ...writes, ...floatsMulAdds, ...singlesMulAdds, ...unlikeEqs, ...ownNes, ...rangeFroms, ...compares, ...unlikeCompares, ...rangeMethods, ...wraps(), ...refOrders, ...saturates(), ...methods(), ...separated, ...asciiCases, ...gets, ...capacityCases, ...ringsCases, ...remadeCases, ...sortCases, ...endsCases];
14 changes: 14 additions & 0 deletions ts/fixtures/lang/expected/core/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,13 @@ export function map$0gl6wke(self: [rt.usize, string] | undefined, f: (a0: [rt.us
return f(self);
}

export function map$1sj2ls1(self: [rt.usize, rt.u8] | undefined, f: (a0: [rt.usize, rt.u8]) => rt.u8): rt.u8 | undefined {
if (self === undefined) {
return undefined;
}
return f(self);
}

export function map$0a57co2(self: rt.u32 | undefined, f: (a0: rt.u32) => rt.u8): rt.u8 | undefined {
if (self === undefined) {
return undefined;
Expand Down Expand Up @@ -834,6 +841,13 @@ export function map$0cs071d(self: rt.u8 | undefined, f: (a0: rt.u8) => rt.u8): r
return f(self);
}

export function map$0vbopjd(self: rt.u8 | undefined, f: (a0: rt.u8) => rt.usize): rt.usize | undefined {
if (self === undefined) {
return undefined;
}
return f(self);
}

export function map$0at3a41(self: undefined | rt.None, f: (a0: undefined) => rt.u8 | undefined): rt.u8 | undefined | rt.None {
if (self === rt.None) {
return rt.None;
Expand Down
28 changes: 28 additions & 0 deletions ts/fixtures/lang/expected/ends.Counted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Generated by rust2-ts. Do not edit.

import * as rt from "./rt.ts";
import * as ends_Run from "./ends.Run.ts";

/** A `Run` whose `len` is its own, which reads the run's, as hashlink's `Values` does. */
export interface Counted {
_0: ends_Run.Run;
}

export function next$iter_Iterator(self: Counted): rt.u8 | rt.Done {
return ends_Run.next$iter_Iterator(self._0);
}

export function sizeHint$iter_Iterator(self: Counted): [rt.usize, rt.usize | undefined] {
return ends_Run.sizeHint$iter_Iterator(self._0);
}

export function nextBack$iter_DoubleEndedIterator(self: Counted): rt.u8 | undefined {
return ends_Run.nextBack$iter_DoubleEndedIterator(self._0);
}

export function len$iter_ExactSizeIterator(self: Counted): rt.usize {
ends_Run.logged(self._0, 108);
return rt.iterFrom(self._0, (it: ends_Run.Run) => ends_Run.next$iter_Iterator(it), undefined, (it: ends_Run.Run) => ends_Run.sizeHint$iter_Iterator(it), (it: ends_Run.Run) => rt.iterFromOption(ends_Run.nextBack$iter_DoubleEndedIterator(it))).len();
}

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

import * as rt from "./rt.ts";
import { thenSome$0d5am30 } from "./core/bool.ts";

/**
* Yields every other call to `next`, from either end, ending between: a `FusedIterator`
* that isn't, which std's `fuse` reads on from as it would one that is.
*/
export interface Flicker {
_0: rt.u8;
}

export function next$iter_Iterator(self: Flicker): rt.u8 | rt.Done {
self._0 = rt.u8WrappingAdd(self._0, 1);
return rt.iterFromOption(thenSome$0d5am30(rt.u8Rem(self._0, 2) === 0, self._0));
}

export function nextBack$iter_DoubleEndedIterator(self: Flicker): rt.u8 | undefined {
self._0 = rt.u8WrappingAdd(self._0, 3);
return thenSome$0d5am30(rt.u8Rem(self._0, 2) === 0, self._0);
}

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

import * as rt from "./rt.ts";
import * as Flicker from "./ends.Flicker.ts";

/** As `Flicker`, without its `FusedIterator`, so std's `fuse` ends it at its first `None`. */
export interface Lapse {
_0: Flicker.Flicker;
}

export function next$iter_Iterator(self: Lapse): rt.u8 | rt.Done {
return Flicker.next$iter_Iterator(self._0);
}

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

import * as rt from "./rt.ts";
import { map$0vbopjd as option_map$0vbopjd } from "./core/option.ts";

/**
* The items `front..back`, read from either end, logging each call of its methods: `n`,
* `b`, `h`, and `l`, each with how many items it has left.
*/
export interface Run {
front: rt.u8;
back: rt.u8;
/**
* What `size_hint` adds to its upper bound, or `None` for none: past 0, it lies, and
* std's `len` of it panics.
*/
over: rt.u8 | undefined;
log: rt.RefCell<rt.ByteVec>;
}

export function left(self: Run): rt.u8 {
return rt.u8Sub(self.back, self.front);
}

export function logged(self: Run, what: rt.u8): void {
const _6: rt.RefCell<rt.ByteVec> = rt.refCellBorrowMut(self.log);
const _7: Uint8Array = Uint8Array.of(what, left(self));
rt.iterExtendBytes(_6.value, rt.iterSlice(_7), undefined, true);
rt.refMutRelease(_6);
}

export function next$iter_Iterator(self: Run): rt.u8 | rt.Done {
logged(self, 110);
if (!(self.front === self.back)) {
self.front = rt.u8Add(self.front, 1);
return rt.u8Sub(self.front, 1);
}
return rt.DONE;
}

export function sizeHint$iter_Iterator(self: Run): [rt.usize, rt.usize | undefined] {
logged(self, 104);
const left_: rt.usize = left(self) as rt.usize;
const _5: rt.usize | undefined = option_map$0vbopjd(self.over, (over: rt.u8): rt.usize => {
return rt.usizeAdd(left_, over as rt.usize);
});
return [left_, _5] as [rt.usize, rt.usize | undefined];
}

export function nextBack$iter_DoubleEndedIterator(self: Run): rt.u8 | undefined {
logged(self, 98);
if (!(self.front === self.back)) {
self.back = rt.u8Sub(self.back, 1);
return self.back;
}
return undefined;
}

//# sourceMappingURL=ends.Run.ts.map
Loading