Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b7e36f3
chore(quest): claim quest/m3/ts-dyn.md
kixelated Nov 6, 2026
7bdfc32
feat(ts): translate TypeId, TypeId::of, and Any::type_id of values an…
kixelated Nov 6, 2026
91536e8
quest: finish quest/m3/ts-dyn.md, as TypeId and Any::type_id translate
kixelated Nov 6, 2026
982f05f
Merge remote-tracking branch 'origin/main' into feat/ts-dynamic-types
kixelated Nov 6, 2026
d809c15
Merge remote-tracking branch 'origin/main' into HEAD
kixelated Nov 7, 2026
baa4c93
fix(ts): write TypeId's Debug in std's sequence of writes
kixelated Nov 7, 2026
b40e856
Merge remote-tracking branch 'origin/main' into HEAD
kixelated Nov 7, 2026
a57134e
test(ts): snapshot TypeId Debug's capped-writer case
kixelated Nov 7, 2026
862ded0
quest(m3): drop the spike's link to the finished ts-dyn quest
kixelated Nov 7, 2026
3d7e6ea
style(ts): format the TypeId capped-writer case
kixelated Nov 7, 2026
ad28c72
Merge remote-tracking branch 'origin/main' into HEAD
kixelated Nov 7, 2026
729a812
Merge remote-tracking branch 'origin/main' into HEAD
kixelated Nov 7, 2026
0b5b60f
fix(ts): a TypeId constant behind a reference, and a hook payload's type
kixelated Nov 7, 2026
375e5a0
Merge remote-tracking branch 'origin/main' into quest/m3/ts-dyn
kixelated Nov 7, 2026
339c7e1
Merge remote-tracking branch 'origin/main' into quest/m3/ts-dyn
kixelated Nov 7, 2026
33590bb
Merge branch 'base' into pr-605
Nov 7, 2026
a41df84
chore(ts): regenerate TypeId fixture snapshots after base merge
Nov 7, 2026
bc45441
Merge branch 'base' into pr-605
Nov 7, 2026
eca1fa7
test(ts): regenerate TypeId snapshots without incremental compilation
Nov 7, 2026
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
1 change: 0 additions & 1 deletion quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ generic std gap is planned here even when moq never calls it.
- [Retrial Cranelift on moq's tests](/quest/m3/cranelift-unwind.md) - waits on upstream: moq's tests pass under Cranelift, so its CI can weigh it again
- [Relink without rebuilding](/quest/m3/relink.md) - measure upstream's skip of dependency-only rebuilds on moq's edits once it reaches nightly
- [Unsafe code the translator can model](/quest/m3/ts-unsafe/README.md) - a dependency using only `unsafe` the translator can model translates from source
- [Trait objects' TypeId](/quest/m3/ts-dyn.md) - `Any::type_id` and `TypeId` translate with Rust's equality, order, and hash, so a consumer neither rewrites nor binds them
- [Waiting: drop the lang fixture's mul_add allowance](/quest/m3/ts-fma-allowance.md) - the differential fails on any `mul_add` difference again once wasm32's `fma` rounds exactly
- [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
- [rs.Slice](/quest/m3/ts-slice-object.md) - references to arrays of other values are a runtime slice too, and pointers compare by where they point
Expand Down
30 changes: 0 additions & 30 deletions quest/m3/ts-dyn.md

This file was deleted.

2 changes: 1 addition & 1 deletion quest/m3/ts-wasm-bindgen/spike.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ It also answers, for the imports quest:

- How a mapped `JsValue`, `JsCast`, and `Closure` meet the generic code around them, such
as `Closure::new` of a closure, which web-codecs' output and error callbacks need, given
that [trait objects](/quest/m3/ts-dyn.md) other than `TypeId` already translate.
that trait objects, `TypeId` included, already translate.
- How the differential test's native side runs a wasm-bindgen build: the wasm-bindgen CLI
in the dev shell at the fixture's pinned version, and a host with WebCodecs, which Bun
lacks, so likely a headless browser.
Expand Down
24 changes: 16 additions & 8 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ and each type a module of its own beside it, holding the type and its functions
from. `rs.ts` beside them is the
runtime: what std does that JS does not, with iterators in `rs/iter.ts`, `str`'s searches
in `rs/str.ts`, the ASCII methods of `u8`, `char`, `[u8]`, and `str` in `rs/ascii.ts`, `char`'s Unicode classes, case maps, escapes, and encodings, and `str`'s case maps and escapes, in `rs/char.ts`, collections in `rs/collections.ts`, hashing in `rs/hash.ts`, ownership, atomics, and async in
`rs/task.ts`, `f64`'s and `f32`'s methods in `rs/float.ts`, `Duration` in `rs/time.ts`, and `Layout` and memory from `alloc` in `rs/alloc.ts`. What translates from std's own MIR,
`rs/task.ts`, `f64`'s and `f32`'s methods in `rs/float.ts`, `Duration` in `rs/time.ts`, `TypeId` in `rs/any.ts`, and `Layout` and memory from `alloc` in `rs/alloc.ts`. What translates from std's own MIR,
`Option`'s, `Result`'s, and `Poll`'s methods, `==` of std's errors, `==` and ordering of a `Result` and a `Poll`, `cmp::max` and `min`, and the `str::parse` and
`from_str_radix` of an integer or a `NonZero` and `bool`'s `str::parse`, goes in a module under its crate's name, as
`core/option.ts` holds `map$1cf8u0y`. A dependency's modules go
Expand Down Expand Up @@ -122,13 +122,14 @@ crate translation reaches is refused, and so are two versions of one crate it re
| `PeekMut<T>` | `rs.PeekMut<T>`, the `rs.Place` of a heap's greatest element, which sifts it down as it drops once `DerefMut` lent it |
| `Ordering`, `DefaultHasher`, `RandomState`, `BuildHasherDefault<H>` | `rs.Ordering`, which is `-1`, `0`, or `1`; `rs.Hasher`; `rs.RandomState`, the keys of the `DefaultHasher` it builds, never changed once made; `undefined`, as `PhantomData` is, whose `build_hasher` is `H::default()` |
| `Duration` | `rs.Duration`: `{ secs, nanos }`, std's whole seconds as an `rs.u64` and the nanoseconds past them, never changed once made |
| `TypeId` | `rs.TypeId`: rustc's 128-bit hash of the type, which std's holds at run time, as 32 hex digits, equal by `===`; ordered as std's derive orders the hash's pointer-sized words, lowest first, hashed as its high 64 bits, and written as `TypeId(0x…)`, as std's are |
| `Layout`, `alloc::alloc` | `rs.Layout`: `{ size, align }`, never changed once made, checked as std's constructors check it; `alloc` gives an `rs.Block` of its bytes, which a cast of its pointer to `*mut T` the body shows came from `alloc` or `realloc` makes a JS array of `T`s, values JS copies, as an `rs.AtIndex` of its first; a cast back to `*mut u8` only as what `dealloc` or `realloc` takes, which free the array or copy it into a new one; its bytes, read, written, or cast to any other type, are refused, and fail at run time through a pointer from elsewhere |
| closure | an arrow function, with `[Symbol.dispose]` if it owns a value that drops, and what makes its clone if a clone can't be itself |
| `fn` pointer, function item as a value | the function, or std's an arrow that calls it as a written call does, as a closure is typed: a `()` result is `undefined`, where a declared function returns `void` |
| `&dyn Trait`, `&mut dyn Trait`, `Box<dyn Trait>`, `Arc<dyn Trait>`, `Rc<dyn Trait>`, `Weak<dyn Trait>`, `Pin<P>` of one | an object with the trait's methods, its supertraits' included: `shape.area$Shape()`; behind an `Arc` or an `Rc`, an `rs.ArcDyn` sharing the count of the `Arc` it was made from |
| a pointer to a struct whose last field unsizes to a trait object: `Rc<RefCell<dyn Trait>>`, `Arc<Mutex<dyn Trait>>`, `Arc<RwLock<dyn Trait>>`, `&mut Cell<dyn Trait>`, `Rc<UnsafeCell<dyn Trait>>`, `Arc<Mutex<dyn Future + Unpin>>`, the crate's own `&Tail<dyn Trait>` | `rs.tailDyn` of the struct it was made from: the same struct, whose other fields, borrows, and lock are that struct's own, and whose last field is the trait object a `&mut` to that field would be, made where it is read; a `dyn Future`'s polls the one future in place and drops it with the struct. A `ManuallyDrop<dyn Trait>` is the trait object of what it holds, which a box of it never drops |
| `dyn Error` | an object of its `fmt$Debug` and `fmt$Display`, and its `source` where a call dispatches to it: `e.source$Error()` |
| `dyn Any` | an instance of the class its value's type was wrapped in, which a downcast checks for: `a instanceof of$dyns_Square ? a.v : undefined` |
| `dyn Any` | an instance of the class its value's type was wrapped in, which a downcast checks for: `a instanceof of$dyns_Square ? a.v : undefined`, and whose `type_id` gives that type's: `a.typeId$Any()` |
| `dyn Fn`, `dyn FnMut`, `dyn FnOnce` | the closure or function it was made of: `(a0: rs.u8) => rs.u32` |
| `dyn Iterator`, `dyn DoubleEndedIterator`, `dyn ExactSizeIterator`, `dyn FusedIterator` | `rs.Iter<Item>`: `rs.iterDyn` of the iterator it was made of |
| constant | its value, inlined as Rust inlines it, so each use of one holding a cell is a cell of its own; one with no value tree, or holding a cell, read from the memory rustc evaluated |
Expand Down Expand Up @@ -651,9 +652,11 @@ mapping makes, such as a `Vec<u8>`'s `io::Write`, is refused where a call dispat
A `dyn Buf` holds no `chunks_vectored`, whose `IoSlice` has no JS value, so a call of it
fails.

A `dyn Any` is such an object with no methods, whose class `is`, `downcast_ref`,
A `dyn Any` is such an object of `type_id` alone, whose class `is`, `downcast_ref`,
`downcast_mut`, and `Box`'s, `Arc`'s, and `Rc`'s `downcast` check for: `a instanceof of$dyns_Square ?
a.v : undefined`. Its `Debug` writes `Any { .. }`, as std's does. A `&mut` one of a value JS copies is the class holding its `rs.Place`,
a.v : undefined`. Its `Debug` writes `Any { .. }`, as std's does. Its `type_id`, which it
holds once a call dispatches to it, and a crate's own trait over `Any` from the start, is a
constant of its class's type's `TypeId`, as `TypeId::of` and a value's `type_id` are. A `&mut` one of a value JS copies is the class holding its `rs.Place`,
or the class itself behind a `Box`, as a `&mut dyn Trait`'s is, and `Arc::downcast` gives
back the `Arc<T>` the object was made from, or, of one made from a box, an `rs.ArcDyn` of
the value sharing its count.
Expand Down Expand Up @@ -685,7 +688,7 @@ box drops the iterator it owns, and the last `Arc` the one they share. `Arc::get
`Arc` or `Weak` shares it, but a `dyn Any`'s, whose value `Arc::downcast` gives back.

A trait object of a trait with a const parameter, of a byte array, or behind any other
pointer, such as a struct's last field, is refused, and so is `Any::type_id`.
pointer, such as a struct's last field, is refused.

An `async fn` returns a future without running anything: an instance of a class of its
own (`sumFuture`), the state machine MIR's coroutine transform makes. Its fields are the
Expand Down Expand Up @@ -942,6 +945,11 @@ they need.
`rs.Borrowed`, so it clones and drops as std's does and only the borrowed side pays a
wrapper. Untagged, `to_mut` would clone on every call and a `match` would be refused;
tagged, every `Cow` would be wrapped.
- A `TypeId` is the hash std's holds, rather than the class of its type, which a `&mut dyn
Any` of a value JS copies has a second of and a type no object holds has none of, or
the type's path, whose order and `Debug` are not std's. It is the crate's metadata's, so
a translation and a native build of one crate agree on which types are equal but not on
the bits.
- A `Duration` keeps std's fields, whole seconds and nanoseconds, rather than one count
of nanoseconds, so its overflow, `checked_*`, and ordering are std's exactly, and a
lowering of its methods from core's MIR would read the same fields.
Expand Down Expand Up @@ -1109,9 +1117,9 @@ they need.
## Not yet

Byte reinterpretation, pointer addresses, pointer arithmetic on a value JS keeps as an object (other than wrapping arithmetic on a null or dangling pointer), memory from `alloc` other than an array of values JS copies, and unions whose
fields overlap in different types ([unsafe](/quest/m3/ts-unsafe/README.md)), and trait objects other than
`dyn Debug`, `dyn Display`, `dyn Future`, `dyn Any`, `dyn Error`, `dyn Fn*`, `dyn Buf`, `dyn Iterator` and its subtraits, and the translated crates' traits'
behind `&`, `&mut`, `Box`, `Arc`, `Rc`, `Weak`, or `Pin` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `Box<dyn Error>` made `From` a string, binds to
fields overlap in different types ([unsafe](/quest/m3/ts-unsafe/README.md)), and trait objects of a trait
with a const parameter, of an array, or behind a pointer other than `&`, `&mut`, `Box`, `Arc`, `Rc`,
`Weak`, or `Pin`, fail translation. Until they translate, an item that needs one, such as a `Box<dyn Error>` made `From` a string, binds to
hand-written TS ([bindings](#bindings)).

These fail translation: `f16` and `f128`, a `Poll` of a `Poll` of a `Poll`, a future whose output can be pending, a closure that owns a value
Expand Down
10 changes: 9 additions & 1 deletion ts/fixtures/lang/dyns/caller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@ const shared = [[], [1], [0, 0], [255, 255, 0, 1, 2, 3, 4, 5], [7, 0xff, 255, 25
Uint8Array.of(...op("dyns::shared"), 0, ...bytes),
);

export const cases: Uint8Array[] = [...dyns, ...shared];
/**
* `ids::ids` too short to run, and boxing each kind of value, the first two bytes picking
* ids at both ends of the list.
*/
const ids = [[], [1], [0, 12, 0, 1, 2], [12, 0, 255, 254, 253, 3]].map((bytes) =>
Uint8Array.of(...op("ids::ids"), 0, ...bytes),
);

export const cases: Uint8Array[] = [...dyns, ...shared, ...ids];
4 changes: 3 additions & 1 deletion ts/fixtures/lang/dyns/expected/core/any.Any.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Generated by rust2-ts. Do not edit.

export type Any = object;
import * as rs from "../rs.ts";

export type Any = { typeId$Any: () => rs.TypeId };
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

//# sourceMappingURL=any.Any.ts.map
27 changes: 27 additions & 0 deletions ts/fixtures/lang/dyns/expected/core/char/methods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Generated by rust2-ts. Do not edit.

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

export function toDigit$14z95r4(self: rs.char, radix: rs.u32): rs.u32 | undefined {
let value!: rs.u32;
if (radix >= 2) {
if (radix <= 36) {
b1: {
if (self > 57) {
if (radix > 10) {
value = rs.u32Add((rs.u32WrappingSub(self as rs.u32, 65 as rs.u32) & 4294967263) >>> 0, 10);
break b1;
}
}
value = rs.u32WrappingSub(self as rs.u32, 48 as rs.u32);
}
if (!(value < radix)) {
return undefined;
}
return value;
}
}
rs.panicFmt(["to_digit: invalid radix -- radix must be in the range 2 to 36 inclusive"]);
}

//# sourceMappingURL=methods.ts.map
181 changes: 181 additions & 0 deletions ts/fixtures/lang/dyns/expected/core/num.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Generated by rust2-ts. Do not edit.

import * as rs from "../rs.ts";
import { toDigit$14z95r4 } from "../core/char/methods.ts";

export function canNotOverflow$05izqtt(radix: rs.u32, isSignedTy: boolean, _digitsData: Uint8Array, _digitsStart: number, digitsLen: number): boolean {
if (!(radix <= 16)) {
return false;
}
return digitsLen <= rs.usizeSub(rs.usizeMul(16, 2), isSignedTy ? 1 : 0);
}

export function fromAsciiBytesRadixPanic(radix: rs.u32): never {
doPanic$1tugyvz(radix);
}

export function doPanic$1tugyvz(radix: rs.u32): never {
runtime$09k496w(radix);
}

export function runtime$09k496w(radix: rs.u32): never {
rs.panicFmt(["from_ascii_bytes_radix: radix must lie in the range `[2, 36]` - found ", (f: rs.Formatter) => rs.u32Display(radix, f)]);
}

export function fromAsciiBytesRadixImpl$0paozdl(src: rs.ByteSlice, radix: rs.u32): rs.Result<rs.u128, rs.ParseIntError> {
let _12_0!: boolean;
let _12_1Data!: Uint8Array;
let _12_1Start!: number;
let _12_1Len!: number;
if (!(2 > radix)) {
if (!(radix > 36)) {
if (!(src.length === 0)) {
const isSignedTy: boolean = rs.u128Gt(0, 0);
if (src.length === 1) {
const value7 = src.data[src.start];
if (value7 === 43 || value7 === 45) {
return rs.Err({ kind: "InvalidDigit" as rs.IntErrorKind });
}
}
b3: {
if (src.length >= 1) {
const value8 = src.data[src.start];
if (value8 === 43) {
_12_0 = true;
_12_1Data = src.data;
_12_1Start = src.start + 1;
_12_1Len = src.length - 1;
break b3;
}
if (value8 === 45) {
const rest2: rs.ByteSlice = rs.byteSliceRange(src, 1);
if (isSignedTy) {
_12_0 = false;
_12_1Data = rest2.data;
_12_1Start = rest2.start;
_12_1Len = rest2.length;
break b3;
}
}
}
_12_0 = true;
_12_1Data = src.data;
_12_1Start = src.start;
_12_1Len = src.length;
}
const isPositive: boolean = _12_0;
let digitsData: Uint8Array = _12_1Data;
let digitsStart: number = _12_1Start;
let digitsLen: number = _12_1Len;
let result: rs.u128 = 0;
b5: {
if (!canNotOverflow$05izqtt(radix, isSignedTy, digitsData, digitsStart, digitsLen)) {
if (!isPositive) {
while (true) {
if (!(digitsLen >= 1)) {
break b5;
}
const c4: rs.u8 = digitsData[digitsStart];
const restData5: Uint8Array = digitsData;
const restStart5: number = digitsStart + 1;
const restLen5: number = digitsLen - 1;
const mul2: rs.u128 | undefined = rs.u128CheckedMul(result, radix as rs.u128);
const _92: rs.u32 | undefined = toDigit$14z95r4(c4 as rs.char, radix);
if (_92 === undefined) {
return rs.Err({ kind: "InvalidDigit" as rs.IntErrorKind });
}
const x4: rs.u128 = _92 as rs.u128;
if (mul2 === undefined) {
return rs.Err({ kind: "NegOverflow" as rs.IntErrorKind });
}
result = mul2;
const _103: rs.u128 | undefined = rs.u128CheckedSub(result, x4);
if (_103 === undefined) {
return rs.Err({ kind: "NegOverflow" as rs.IntErrorKind });
}
result = _103;
digitsData = restData5;
digitsStart = restStart5;
digitsLen = restLen5;
}
}
while (true) {
if (!(digitsLen >= 1)) {
break b5;
}
const c3: rs.u8 = digitsData[digitsStart];
const restData4: Uint8Array = digitsData;
const restStart4: number = digitsStart + 1;
const restLen4: number = digitsLen - 1;
const mul: rs.u128 | undefined = rs.u128CheckedMul(result, radix as rs.u128);
const _67: rs.u32 | undefined = toDigit$14z95r4(c3 as rs.char, radix);
if (_67 === undefined) {
return rs.Err({ kind: "InvalidDigit" as rs.IntErrorKind });
}
const x3: rs.u128 = _67 as rs.u128;
if (mul === undefined) {
return rs.Err({ kind: "PosOverflow" as rs.IntErrorKind });
}
result = mul;
const _78: rs.u128 | undefined = rs.u128CheckedAdd(result, x3);
if (_78 === undefined) {
return rs.Err({ kind: "PosOverflow" as rs.IntErrorKind });
}
result = _78;
digitsData = restData4;
digitsStart = restStart4;
digitsLen = restLen4;
}
}
if (!isPositive) {
while (true) {
if (!(digitsLen >= 1)) {
break b5;
}
const c2: rs.u8 = digitsData[digitsStart];
const restData3: Uint8Array = digitsData;
const restStart3: number = digitsStart + 1;
const restLen3: number = digitsLen - 1;
result = rs.u128WrappingMul(result, radix as rs.u128);
const _49: rs.u32 | undefined = toDigit$14z95r4(c2 as rs.char, radix);
if (_49 === undefined) {
return rs.Err({ kind: "InvalidDigit" as rs.IntErrorKind });
}
result = rs.u128WrappingSub(result, _49 as rs.u128);
digitsData = restData3;
digitsStart = restStart3;
digitsLen = restLen3;
}
}
while (true) {
if (!(digitsLen >= 1)) {
break b5;
}
const c: rs.u8 = digitsData[digitsStart];
const restData2: Uint8Array = digitsData;
const restStart2: number = digitsStart + 1;
const restLen2: number = digitsLen - 1;
result = rs.u128WrappingMul(result, radix as rs.u128);
const _32: rs.u32 | undefined = toDigit$14z95r4(c as rs.char, radix);
if (_32 === undefined) {
return rs.Err({ kind: "InvalidDigit" as rs.IntErrorKind });
}
result = rs.u128WrappingAdd(result, _32 as rs.u128);
digitsData = restData2;
digitsStart = restStart2;
digitsLen = restLen2;
}
}
return rs.Ok(result);
}
return rs.Err({ kind: "Empty" as rs.IntErrorKind });
}
}
fromAsciiBytesRadixPanic(radix);
}

export function fromStrRadix$09i2k2a(src: string, radix: rs.u32): rs.Result<rs.u128, rs.ParseIntError> {
return fromAsciiBytesRadixImpl$0paozdl(rs.stringAsBytes(src), radix);
}

//# sourceMappingURL=num.ts.map
Loading