From 276244f6dbcccbce91c162aa6775f36848a17817 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 13:28:27 -0800 Subject: [PATCH 1/7] chore(quest): claim quest/m2/ts-model/unit-option-fields.md Quest-Claim: 3b0b5822d2d16cc5 From 1e174a4725f31141ed87db225060a5b77924ded8 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 14:55:56 -0800 Subject: [PATCH 2/7] feat(ts): option fields of a generic type at a unit instance A generic type's Option and Poll of a type parameter are rs.OptionOf and rs.PollOf, conditional types that resolve at each instance to that instance's own Option or Poll, so Idle<()> and oneshot::Inner<()> translate, as do trait objects whose Option holds an Item that can be undefined. Co-Authored-By: Claude Opus 5.5 --- quest/m2/ts-model/README.md | 1 - quest/m2/ts-model/unit-option-fields.md | 18 -- ts/README.md | 20 +- .../lang/collects/expected/collects.Tries.ts | 8 +- ts/fixtures/lang/dyns/expected/core/bool.ts | 7 + ts/fixtures/lang/dyns/expected/dyns.Beats.ts | 22 +++ ts/fixtures/lang/dyns/expected/dyns.Over.ts | 2 +- ts/fixtures/lang/dyns/expected/dyns.Peek.ts | 11 ++ ts/fixtures/lang/dyns/expected/dyns.Skips.ts | 27 +++ ts/fixtures/lang/dyns/expected/dyns.Source.ts | 2 +- ts/fixtures/lang/dyns/expected/dyns.ts | 82 +++++++- ts/fixtures/lang/dyns/expected/lib.ts | 58 +++++- ts/fixtures/lang/dyns/src/dyns.rs | 70 +++++++ ts/fixtures/lang/dyns/src/lib.rs | 1 + ts/fixtures/lang/shapes/expected/core/bool.ts | 21 +++ .../lang/shapes/expected/core/task/poll.ts | 61 ++++++ .../lang/shapes/expected/generics.Parked.ts | 176 ++++++++++++++++++ ts/fixtures/lang/shapes/expected/generics.ts | 122 +++++++++++- ts/fixtures/lang/shapes/expected/lib.ts | 4 +- ts/fixtures/lang/shapes/src/generics.rs | 79 +++++++- ts/fixtures/lang/shapes/src/lib.rs | 1 + ts/fixtures/reject/census.txt | 7 +- ts/fixtures/reject/expected.txt | 7 +- ts/fixtures/reject/src/futures.rs | 10 - ts/fixtures/reject/src/objects.rs | 50 +---- ts/fixtures/reject/src/values.rs | 13 +- ts/fixtures/reject/ts.toml | 7 +- ts/runtime/rs.ts | 3 + ts/runtime/rs/task.ts | 3 + ts/src/lower/dynamic.rs | 21 ++- ts/src/lower/mod.rs | 143 ++++++++++---- 31 files changed, 903 insertions(+), 154 deletions(-) delete mode 100644 quest/m2/ts-model/unit-option-fields.md create mode 100644 ts/fixtures/lang/dyns/expected/dyns.Beats.ts create mode 100644 ts/fixtures/lang/dyns/expected/dyns.Peek.ts create mode 100644 ts/fixtures/lang/dyns/expected/dyns.Skips.ts create mode 100644 ts/fixtures/lang/shapes/expected/core/task/poll.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Parked.ts diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index ff6fcbded..f89520ddd 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -65,7 +65,6 @@ kept separate since each lands alone; rejected: one quest for `ArrayString`. - [Bytes from an owner that drops](/quest/m2/ts-model/bytes-owner.md) - `Bytes::from_owner` of a frame's segment - [rangemap](/quest/m2/ts-model/rangemap.md) - B-tree ranges by a borrowed key, and std's iterators in a crate's fields - [Addresses as identity](/quest/m2/ts-model/addresses.md) - `Arc::as_ptr(a) as usize` as a key, which needs a decision -- [Option fields at a unit instance](/quest/m2/ts-model/unit-option-fields.md) - `Idle<()>`'s `Option` field tells `Some(())` from `None` - [Const generic types](/quest/m2/ts-model/const-generics.md) - `ArrayString` and other types with const parameters - [panic! in a 2015 or 2018 crate](/quest/m2/ts-model/begin-panic.md) - the `begin_panic` mapping keyed by its defining module - [A string's bytes through its pointer](/quest/m2/ts-model/str-as-ptr.md) - `str::as_ptr` reads the string's UTF-8 diff --git a/quest/m2/ts-model/unit-option-fields.md b/quest/m2/ts-model/unit-option-fields.md deleted file mode 100644 index 3c411dd22..000000000 --- a/quest/m2/ts-model/unit-option-fields.md +++ /dev/null @@ -1,18 +0,0 @@ -# [S] Option fields of a generic type at a unit instance - -## Goal - -[rust2-ts](/ts/README.md) translates a generic type's `Option` or `Poll` field at an -instance whose `T` is itself `undefined` or pending in JS, such as moq-net's `Idle<()>` -(`util/idle.rs`'s `parked`) and tokio's `oneshot::Inner<()>`, proven by differential -tests against native Rust. At the moq pin it refuses 16 times in the model and 24 in -moq-net whole. - -## Plan - -The type's definition types the field `T | undefined`, which loses `Some(())` against -`None` at `T = ()`; the field needs `rs.None` (or `rs.OuterPending`) where an instance -can already be the bare value. Decide it per field from the instances the crate uses, not -per type, and keep the bare form for every other instance. - -Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index 1ad22f88b..34fd56f66 100644 --- a/ts/README.md +++ b/ts/README.md @@ -101,7 +101,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `struct` | a plain object, typed by an interface; a class if it may drop | | `enum` without data | a union of strings: `"Lite01" \| "Lite02"`, or `never` without variants | | `enum` | a union of objects tagged by variant | -| `Option` | `T \| undefined`; `T \| rs.None` where `T` can already be `undefined`, as `()` and `Option` can, so `Option<()>` and `Option>` keep `None` apart from each `Some` | +| `Option` | `T \| undefined`; `T \| rs.None` where `T` can already be `undefined`, as `()` and `Option` can, so `Option<()>` and `Option>` keep `None` apart from each `Some`; a generic type's `Option` is an `rs.OptionOf`, either as each instance's `T` decides, as a generic `Poll` is an `rs.PollOf` | | `Result` | `rs.Result`: `{ ok: true, value }` or `{ ok: false, error }`; `rs.OK` or `rs.ERR` where the payload is `undefined`, as in `Ok(())` | | `ControlFlow` | `rs.Result`, `Ok` to continue; what `?` branches on is the value it was applied to, but for a `Poll`'s `Pending` and `Ready(None)`, which continue as `Ok` of themselves | | tuple, `[T; N]`, `&[T; N]` | an array, and `rs.Slice` of all of it for `&[T; N]`; for bytes, `u8` or `MaybeUninit`, `Uint8Array` for `[u8; N]`, and `rs.ByteSlice` for `&[u8; N]` | @@ -462,7 +462,11 @@ in a generic type to what only an instance decides the form of, such as `*mut T` which each read of an instance's field casts to what it holds. So is a run of a parameter, whose `u8` instance holds bytes: `[T; N]` is an `rs.ArrayOf`, a `T[]` or a `Uint8Array`, and `[T]`, `Vec`, and `Cow<[T]>` an `rs.SliceOf`, an `rs.VecOf`, -and an `rs.CowOf`. A type's module exports the +and an `rs.CowOf`. An `Option` or a `Poll` of a parameter is an +`rs.OptionOf` or an `rs.PollOf`, a conditional type TypeScript resolves at each +instance to what that instance's own `Option` or `Poll` is, `T | undefined` or +`T | rs.None`, so a read needs no cast, and a value made into a field is cast to it, which +TypeScript infers the instance's arguments from. A type's module exports the type by its own name, which a caller writes `Decoder.Decoder`, and a function whose name and exports it as its own: `function new_` is `Decoder.new`. A module imports a function or value by its name, and a type's module whole by the type's, `import * as Decoder`, @@ -596,9 +600,10 @@ caller can implement. A generic trait's type is generic over the trait's type pa then the associated types its objects bind that no supertrait binds already, so `dyn Source` is a `Source.Source`, and is typed as the trait declares it, as a generic struct is: a call casts what it returns to the form its object's -arguments decide, such as the `rs.u8` of an `rs.Ptr`. One whose method takes or returns -another form at its arguments, such as the `rs.None` of an `Option` whose -`Item` is an `Option` too, is refused. A coercion wraps the value in a class of its type's from `lib.ts`, +arguments decide, such as the `rs.u8` of an `rs.Ptr`. An `Option` is an +`rs.OptionOf`, so an `Item` that can be `undefined` returns `rs.None`. One whose +method takes or returns another form at its arguments, such as the `rs.None` of an +`Option` whose `Item` is a `MaybeUninit`, typed `number`, is refused. A coercion wraps the value in a class of its type's from `lib.ts`, named for the type as a suffix spells it, `new of$dyns_Square(sq)`, which every trait's object of the type shares, whose methods call that type's impls; one from a `&mut` to a value JS copies holds its `rs.Place`, as `ofPlace$dyns_Square`, so `&mut self` writes @@ -1077,13 +1082,14 @@ fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait 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` 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, an instance of a generic type whose field is a `Poll` of a type parameter that can be pending, a closure that owns a value +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 that drops given to a std function that keeps it other than in an iterator or a `poll_fn`, or whose mapping doesn't drop it where std's does, a `Cow` of other than `str` or a slice, a std function without a mapping of its own that makes or reads an `Option` whose `None` is `rs.None`, an `Option` of an `Option` of `()` or another `Option`, an instance of a generic type whose field is an `Option` of a type parameter -that can be `undefined`, a `String`'s `capacity`, looking up a key by +that can be `undefined` though TypeScript's type of it can't, as a `MaybeUninit` +can, or an `Option` or a `Poll` of another form of it whose instance can be `undefined` or pending, a `String`'s `capacity`, looking up a key by another type it borrows as through the crate's own `Borrow`, a `&mut` to a value JS 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 diff --git a/ts/fixtures/lang/collects/expected/collects.Tries.ts b/ts/fixtures/lang/collects/expected/collects.Tries.ts index e08bd553e..0edeac034 100644 --- a/ts/fixtures/lang/collects/expected/collects.Tries.ts +++ b/ts/fixtures/lang/collects/expected/collects.Tries.ts @@ -8,9 +8,9 @@ import type * as Item from "./collects.Item.ts"; * a `None`, as nothing stops it from doing. */ export class Tries { - _0: rs.Vec; + _0: rs.Vec>; - constructor(_0: rs.Vec) { + constructor(_0: rs.Vec>) { this._0 = _0; } @@ -23,14 +23,14 @@ export function fromIter$iter_FromIterator_collects_Item_collects_Item_iter_adap const iter2: rs.Iter = iter; return new Tries(rs.iterToVec(rs.iterMap(new rs.Range(0, 3), (_2: rs.i32): Item.Item | undefined => { return rs.iterToOption(iter2.next()); - }), undefined, 8, true)); + }), undefined, 8, true) as rs.Vec>); } export function fromIter$iter_FromIterator_u8_u8_iter_adapters_GenericShunt_iter_Map_slice_Iter_u8_collects_collects_closure10_Result_never_collects_Item(iter: rs.Iter): Tries { const iter2: rs.Iter = iter; return new Tries(rs.iterToVec(rs.iterMap(new rs.Range(0, 3), (_2: rs.i32): rs.u8 | undefined => { return rs.iterToOption(iter2.next()); - }), undefined, 2, true)); + }), undefined, 2, true) as rs.Vec>); } //# sourceMappingURL=collects.Tries.ts.map diff --git a/ts/fixtures/lang/dyns/expected/core/bool.ts b/ts/fixtures/lang/dyns/expected/core/bool.ts index 05e0dcab5..aafb376d1 100644 --- a/ts/fixtures/lang/dyns/expected/core/bool.ts +++ b/ts/fixtures/lang/dyns/expected/core/bool.ts @@ -9,4 +9,11 @@ export function thenSome$0quywni(self: boolean, t: rs.u8): rs.u8 | undefined { return t; } +export function thenSome$1n1oy1h(self: boolean, _: undefined): undefined | rs.None { + if (!self) { + return rs.None; + } + return undefined; +} + //# sourceMappingURL=bool.ts.map diff --git a/ts/fixtures/lang/dyns/expected/dyns.Beats.ts b/ts/fixtures/lang/dyns/expected/dyns.Beats.ts new file mode 100644 index 000000000..efb98eaad --- /dev/null +++ b/ts/fixtures/lang/dyns/expected/dyns.Beats.ts @@ -0,0 +1,22 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +export interface Beats { + _0: rs.u8; +} + +export function take$dyns_Source(self: Beats): undefined | rs.None { + const _2: rs.u8 | undefined = rs.u8CheckedSub(self._0, 1); + if (_2 !== undefined) { + self._0 = _2; + return undefined; + } + return rs.None; +} + +export function left$dyns_Source(self: Beats): rs.usize { + return self._0 as rs.usize; +} + +//# sourceMappingURL=dyns.Beats.ts.map diff --git a/ts/fixtures/lang/dyns/expected/dyns.Over.ts b/ts/fixtures/lang/dyns/expected/dyns.Over.ts index bea23cc06..66e1c5c9b 100644 --- a/ts/fixtures/lang/dyns/expected/dyns.Over.ts +++ b/ts/fixtures/lang/dyns/expected/dyns.Over.ts @@ -21,7 +21,7 @@ export class Over { } export function run$String(self: Over): string | undefined { - const _3: string | undefined = self.source.take$Source(); + const _3: string | undefined = self.source.take$Source() as string | undefined; if (_3 !== undefined) { const next: string = _3; const other: string = self.scale.scale$Scaled(next); diff --git a/ts/fixtures/lang/dyns/expected/dyns.Peek.ts b/ts/fixtures/lang/dyns/expected/dyns.Peek.ts new file mode 100644 index 000000000..22f910f76 --- /dev/null +++ b/ts/fixtures/lang/dyns/expected/dyns.Peek.ts @@ -0,0 +1,11 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** + * A generic trait returning an `Option` of its parameter, which an object's type has as + * an `rs.OptionOf`, at a parameter that can be `undefined`. + */ +export type Peek = { peek$Peek: () => rs.OptionOf }; + +//# sourceMappingURL=dyns.Peek.ts.map diff --git a/ts/fixtures/lang/dyns/expected/dyns.Skips.ts b/ts/fixtures/lang/dyns/expected/dyns.Skips.ts new file mode 100644 index 000000000..e8ec362fa --- /dev/null +++ b/ts/fixtures/lang/dyns/expected/dyns.Skips.ts @@ -0,0 +1,27 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { thenSome$0quywni } from "./core/bool.ts"; + +/** + * Items that can already be `undefined`, whose `None` is `rs.None`: a countdown's + * remainders, `None` where it skips one, and its ticks. + */ +export interface Skips { + _0: rs.u8; +} + +export function take$dyns_Source(self: Skips): rs.u8 | undefined | rs.None { + const _2: rs.u8 | undefined = rs.u8CheckedSub(self._0, 1); + if (_2 !== undefined) { + self._0 = _2; + return thenSome$0quywni(rs.u8Rem(self._0, 3) !== 0, self._0); + } + return rs.None; +} + +export function left$dyns_Source(self: Skips): rs.usize { + return self._0 as rs.usize; +} + +//# sourceMappingURL=dyns.Skips.ts.map diff --git a/ts/fixtures/lang/dyns/expected/dyns.Source.ts b/ts/fixtures/lang/dyns/expected/dyns.Source.ts index ca84c118a..062ef3359 100644 --- a/ts/fixtures/lang/dyns/expected/dyns.Source.ts +++ b/ts/fixtures/lang/dyns/expected/dyns.Source.ts @@ -3,6 +3,6 @@ import * as rs from "./rs.ts"; /** A trait with an associated type. */ -export type Source = { take$Source: () => Item | undefined; left$Source: () => rs.usize }; +export type Source = { take$Source: () => rs.OptionOf; left$Source: () => rs.usize }; //# sourceMappingURL=dyns.Source.ts.map diff --git a/ts/fixtures/lang/dyns/expected/dyns.ts b/ts/fixtures/lang/dyns/expected/dyns.ts index 114003c7e..209460aec 100644 --- a/ts/fixtures/lang/dyns/expected/dyns.ts +++ b/ts/fixtures/lang/dyns/expected/dyns.ts @@ -1,12 +1,13 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$dyns_Tri, drop$Result_Rc_dyns_Noisy_Rc_dyn_Any, drop$Result_Rc_dyns_Square_Rc_dyn_Any, drop$Result_Rc_dyns_Tri_Rc_dyn_Any, drop$Result_cell_RefMut_dyns_Square_cell_BorrowMutError, drop$Result_cell_RefMut_dyns_Tri_cell_BorrowMutError, drop$Result_cell_Ref_dyn_dyns_Shape_cell_BorrowError, drop$Result_sync_MutexGuard_dyn_dyns_Shape_sync_TryLockError_sync_MutexGuard_dyn_dyns_Shape, drop$Result_sync_RwLockWriteGuard_dyn_dyns_Shape_sync_TryLockError_sync_RwLockWriteGuard_dyn_dyns_Shape, drop$boxed_Box_dyns_Guarded_dyn_dyns_Shape, drop$cell_RefCell_dyns_Guarded_dyn_dyns_Shape, drop$cell_RefCell_dyns_Guarded_dyns_Held, drop$dyns_Guarded_dyns_Held, drop$dyns_Guarded_dyns_Noisy, drop$tuple2_dyns_Tagged_dyns_Tagged, of$Option_u8, of$dyns_By, of$dyns_Drained, of$dyns_Hits, of$dyns_Leaf, of$dyns_Lease, of$dyns_Looped, of$dyns_Many, of$dyns_Noisy, of$dyns_Square, of$dyns_Steps, of$dyns_Tri, of$ref_str, of$tuple2_u8_u8, of$u16, of$u32, of$u8, of$unit, ofPlace$Option_u8, ofPlace$dyns_Held, ofPlace$dyns_Tri, ofPlace$u32, ofPlace$u8, ofPlace$unit } from "./lib.ts"; +import { copy$dyns_Tri, drop$Result_Rc_dyns_Noisy_Rc_dyn_Any, drop$Result_Rc_dyns_Square_Rc_dyn_Any, drop$Result_Rc_dyns_Tri_Rc_dyn_Any, drop$Result_cell_RefMut_dyns_Square_cell_BorrowMutError, drop$Result_cell_RefMut_dyns_Tri_cell_BorrowMutError, drop$Result_cell_Ref_dyn_dyns_Shape_cell_BorrowError, drop$Result_sync_MutexGuard_dyn_dyns_Shape_sync_TryLockError_sync_MutexGuard_dyn_dyns_Shape, drop$Result_sync_RwLockWriteGuard_dyn_dyns_Shape_sync_TryLockError_sync_RwLockWriteGuard_dyn_dyns_Shape, drop$boxed_Box_dyns_Guarded_dyn_dyns_Shape, drop$cell_RefCell_dyns_Guarded_dyn_dyns_Shape, drop$cell_RefCell_dyns_Guarded_dyns_Held, drop$dyns_Guarded_dyns_Held, drop$dyns_Guarded_dyns_Noisy, drop$tuple2_dyns_Tagged_dyns_Tagged, of$Option_u8, of$dyns_Beats, of$dyns_By, of$dyns_Drained, of$dyns_Hits, of$dyns_Leaf, of$dyns_Lease, of$dyns_Looped, of$dyns_Many, of$dyns_Noisy, of$dyns_Skips, of$dyns_Square, of$dyns_Steps, of$dyns_Tri, of$ref_str, of$tuple2_u8_u8, of$u16, of$u32, of$u8, of$unit, ofPlace$Option_u8, ofPlace$dyns_Held, ofPlace$dyns_Tri, ofPlace$u32, ofPlace$u8, ofPlace$unit } from "./lib.ts"; import type * as Any from "./core/any.Any.ts"; -import { thenSome$0quywni } from "./core/bool.ts"; +import { thenSome$0quywni, thenSome$1n1oy1h } from "./core/bool.ts"; import { isSomeAnd$11ghftu, unwrapOr$1iemzn8 as option_unwrapOr$1iemzn8, unwrapOr$1fr498u as option_unwrapOr$1fr498u, map$1rkcfqi as option_map$1rkcfqi, map$1mach0o as option_map$1mach0o, map$1b57zy8 as option_map$1b57zy8, map$11l9b8r as option_map$11l9b8r, map$0vt270f as option_map$0vt270f, map$1yjmt5m as option_map$1yjmt5m, map$00ac22d as option_map$00ac22d, map$13cu53u as option_map$13cu53u, map$1bn5ufk as option_map$1bn5ufk, map$1aeqf1p as option_map$1aeqf1p, mapOr$0vxw5sq as option_mapOr$0vxw5sq, mapOr$0z4nmg2 as option_mapOr$0z4nmg2, mapOr$19a4gco as option_mapOr$19a4gco, andThen$1xlvufy as option_andThen$1xlvufy, andThen$0imujmv as option_andThen$0imujmv } from "./core/option.ts"; import { ok$1oks8md, ok$0y6a1w6, ok$1lmsnde, ok$1wwd922, map$0njbm3o as result_map$0njbm3o, map$0tx2z7o as result_map$0tx2z7o, map$11lf952 as result_map$11lf952, map$0f0ko1g as result_map$0f0ko1g, unwrapOr$0k78oya as result_unwrapOr$0k78oya } from "./core/result.ts"; import { isReady$1olyx0w } from "./core/task/poll.ts"; +import type * as Beats from "./dyns.Beats.ts"; import type * as Chunks from "./dyns.Chunks.ts"; import * as Countdown from "./dyns.Countdown.ts"; import type * as Counted from "./dyns.Counted.ts"; @@ -24,6 +25,7 @@ import type * as Nest from "./dyns.Nest.ts"; import * as Noisy from "./dyns.Noisy.ts"; import * as Over from "./dyns.Over.ts"; import type * as Paired from "./dyns.Paired.ts"; +import type * as Peek from "./dyns.Peek.ts"; import * as Resuming from "./dyns.Resuming.ts"; import type * as Scaled from "./dyns.Scaled.ts"; import type * as Shape from "./dyns.Shape.ts"; @@ -2289,10 +2291,24 @@ export function left$dyns_Source_u8(self: rs.u8): rs.usize { return self as rs.usize; } +export function taken$Option_u8(s: Source.Source): string { + const taken: rs.Vec = new rs.Vec(); + while (true) { + const _3: rs.u8 | undefined | rs.None = s.take$Source() as rs.u8 | undefined | rs.None; + if (_3 !== rs.None) { + const _8: string = rs.fmtFormat([(f: rs.Formatter) => rs.fmtOption(_3, f, rs.u8Debug)]); + taken.items.push(_8); + } else { + break; + } + } + return rs.strsJoin(taken.items, ","); +} + export function taken$String(s: Source.Source): string { const taken: rs.Vec = new rs.Vec(); while (true) { - const _3: string | undefined = s.take$Source(); + const _3: string | undefined = s.take$Source() as string | undefined; if (_3 !== undefined) { const _8: string = rs.fmtFormat([(f: rs.Formatter) => rs.fmtStr(_3, f)]); taken.items.push(_8); @@ -2306,7 +2322,7 @@ export function taken$String(s: Source.Source): string { export function taken$u32(s: Source.Source): string { const taken: rs.Vec = new rs.Vec(); while (true) { - const _3: rs.u32 | undefined = s.take$Source(); + const _3: rs.u32 | undefined = s.take$Source() as rs.u32 | undefined; if (_3 !== undefined) { const _8: string = rs.fmtFormat([(f: rs.Formatter) => rs.u32Debug(_3, f)]); taken.items.push(_8); @@ -2317,6 +2333,15 @@ export function taken$u32(s: Source.Source): string { return rs.strsJoin(taken.items, ","); } +export function taken$unit(s: Source.Source): string { + const taken: rs.Vec = new rs.Vec(); + while ((s.take$Source() as undefined | rs.None) !== rs.None) { + const _8: string = rs.fmtFormat([(f: rs.Formatter) => rs.fmtUnit(undefined, f)]); + taken.items.push(_8); + } + return rs.strsJoin(taken.items, ","); +} + export function generics(data: rs.ByteSlice, out: rs.ByteVec): void { let tri!: Tri.Tri; if (data.length >= 3) { @@ -2397,7 +2422,7 @@ export function generics(data: rs.ByteSlice, out: rs.ByteVec): void { const owned: Source.Source = new of$dyns_Drained(new Drained.Drained(rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(rs.byteSliceRange(data, 3))), (_2: rs.u8): Noisy.Noisy => { return new Noisy.Noisy(_2, rs.arcClone(log)); }), undefined, 8, true))); - const first: Noisy.Noisy | undefined = owned.take$Source(); + const first: Noisy.Noisy | undefined = owned.take$Source() as Noisy.Noisy | undefined; const _268: rs.usize = owned.left$Source(); const _271: rs.RefCell = rs.refCellBorrow(log.value); const args41 = [(f: rs.Formatter) => rs.usizeDisplay(_268, f), (f: rs.Formatter) => rs.fmtOption(first, f, (value: Noisy.Noisy, f: rs.Formatter) => Noisy.fmt$fmt_Debug(value, f)), (f: rs.Formatter) => rs.fmtList(rs.byteVecView(_271.value), f, rs.u8Debug)]; @@ -2410,7 +2435,7 @@ export function generics(data: rs.ByteSlice, out: rs.ByteVec): void { rs.refRelease(_290); put(out, _286); if (rs.u8Rem(a, 2) === 0) { - rs.drop(owned.take$Source()); + rs.drop(owned.take$Source() as Noisy.Noisy | undefined); } rs.drop(owned); const _311: rs.RefCell = rs.refCellBorrow(log.value); @@ -2464,4 +2489,49 @@ export function generics(data: rs.ByteSlice, out: rs.ByteVec): void { } } +export function peek$dyns_Peek_unit_u8(self: rs.u8): undefined | rs.None { + return thenSome$1n1oy1h(rs.u8Rem(self, 2) === 0, undefined); +} + +export function peek$dyns_Peek_Option_u8_u16(self: rs.u16): rs.u8 | undefined | rs.None { + const _2: rs.u16 = rs.u16Rem(self, 3); + if (_2 === 0) { + return rs.None; + } + if (_2 === 1) { + return undefined; + } + return self & 0xff; +} + +/** + * Objects whose methods return an `Option` of what can already be `undefined`: of an + * associated type and of a generic trait's parameter. + */ +export function options(data: rs.ByteSlice, out: rs.ByteVec): void { + if (data.length >= 2) { + const skips: Source.Source = new of$dyns_Skips({ _0: rs.u8Rem(data.data[data.start], 8) }); + const first: rs.u8 | undefined | rs.None = skips.take$Source() as rs.u8 | undefined | rs.None; + const _23: rs.usize = skips.left$Source(); + const _26: string = taken$Option_u8(skips); + const args7 = [(f: rs.Formatter) => rs.fmtOption(first, f, (value: rs.u8 | undefined, f: rs.Formatter) => rs.fmtOption(value, f, rs.u8Debug), rs.None), (f: rs.Formatter) => rs.usizeDisplay(_23, f), (f: rs.Formatter) => rs.fmtPad(f, _26)]; + put(out, rs.fmtFormat([args7[0], " ", args7[1], " ", args7[2], " "])); + const beats: Beats.Beats = { _0: rs.u8Rem(data.data[data.start + 1], 5) }; + const ticked: Source.Source = new of$dyns_Beats(beats); + const _49: undefined | rs.None = ticked.take$Source() as undefined | rs.None; + const _51: string = taken$unit(ticked); + const args8 = [(f: rs.Formatter) => rs.fmtOption(_49, f, rs.fmtUnit, rs.None), (f: rs.Formatter) => rs.fmtPad(f, _51)]; + put(out, rs.fmtFormat([args8[0], " ", args8[1], " "])); + for (const _59 of rs.byteSliceView(data)) { + const unit: Peek.Peek = new of$u8(_59); + const nested: Peek.Peek = new of$u16(_59 as rs.u16); + const _76: undefined | rs.None = unit.peek$Peek() as undefined | rs.None; + const _78: rs.u8 | undefined | rs.None = nested.peek$Peek() as rs.u8 | undefined | rs.None; + const args9 = [(f: rs.Formatter) => rs.fmtOption(_76, f, rs.fmtUnit, rs.None), (f: rs.Formatter) => rs.fmtOption(_78, f, (value: rs.u8 | undefined, f: rs.Formatter) => rs.fmtOption(value, f, rs.u8Debug), rs.None)]; + put(out, rs.fmtFormat([args9[0], " ", args9[1], " "])); + } + rs.drop(skips); + } +} + //# sourceMappingURL=dyns.ts.map diff --git a/ts/fixtures/lang/dyns/expected/lib.ts b/ts/fixtures/lang/dyns/expected/lib.ts index 62a9c5b58..78a2104b2 100644 --- a/ts/fixtures/lang/dyns/expected/lib.ts +++ b/ts/fixtures/lang/dyns/expected/lib.ts @@ -3,7 +3,8 @@ import * as rs from "./rs.ts"; import type * as Any from "./core/any.Any.ts"; import type * as Hasher from "./core/hash.Hasher.ts"; -import { sides$dyns_Shape_u32, intoArea$dyns_Shape_u32, larger$dyns_Shape_u32, tally$dyns_Shape_u32, area$dyns_Shape_u32, grow$dyns_Shape_u32, dyns, shared, asAny$dyns_Erased_u32, asAnyMut$dyns_Erased_u32, key$dyns_Keyed_u32, anys, fns, iters, pointers, boxes, tails, rests, apply$dyns_Scaled_u16_u16, scale$dyns_Scaled_u16_u16, bump$dyns_Scaled_u16_u16, pick$dyns_Scaled_u16_u16, take$dyns_Source_u8 as dyns_take$dyns_Source_u8, left$dyns_Source_u8, generics } from "./dyns.ts"; +import { sides$dyns_Shape_u32, intoArea$dyns_Shape_u32, larger$dyns_Shape_u32, tally$dyns_Shape_u32, area$dyns_Shape_u32, grow$dyns_Shape_u32, dyns, shared, asAny$dyns_Erased_u32, asAnyMut$dyns_Erased_u32, key$dyns_Keyed_u32, anys, fns, iters, pointers, boxes, tails, rests, apply$dyns_Scaled_u16_u16, scale$dyns_Scaled_u16_u16, bump$dyns_Scaled_u16_u16, pick$dyns_Scaled_u16_u16, take$dyns_Source_u8 as dyns_take$dyns_Source_u8, left$dyns_Source_u8, generics, peek$dyns_Peek_unit_u8, peek$dyns_Peek_Option_u8_u16, options } from "./dyns.ts"; +import * as Beats from "./dyns.Beats.ts"; import * as By from "./dyns.By.ts"; import * as Drained from "./dyns.Drained.ts"; import * as Guarded from "./dyns.Guarded.ts"; @@ -16,6 +17,7 @@ import * as Many from "./dyns.Many.ts"; import type * as Nest from "./dyns.Nest.ts"; import * as Noisy from "./dyns.Noisy.ts"; import type * as Shape from "./dyns.Shape.ts"; +import * as Skips from "./dyns.Skips.ts"; import * as Square from "./dyns.Square.ts"; import * as Steps from "./dyns.Steps.ts"; import type * as Tagged from "./dyns.Tagged.ts"; @@ -332,6 +334,22 @@ export class of$Option_u8 { } } +export class of$dyns_Beats { + v: Beats.Beats; + + constructor(v: Beats.Beats) { + this.v = v; + } + + left$Source(): rs.usize { + return Beats.left$dyns_Source(this.v); + } + + take$Source(): rs.OptionOf { + return Beats.take$dyns_Source(this.v) as rs.OptionOf; + } +} + export class of$dyns_By { v: By.By; @@ -371,8 +389,8 @@ export class of$dyns_Drained { return Drained.left$dyns_Source(this.v); } - take$Source(): Noisy.Noisy | undefined { - return Drained.take$dyns_Source(this.v); + take$Source(): rs.OptionOf { + return Drained.take$dyns_Source(this.v) as rs.OptionOf; } } @@ -536,6 +554,22 @@ export class of$dyns_Noisy { } } +export class of$dyns_Skips { + v: Skips.Skips; + + constructor(v: Skips.Skips) { + this.v = v; + } + + left$Source(): rs.usize { + return Skips.left$dyns_Source(this.v); + } + + take$Source(): rs.OptionOf { + return Skips.take$dyns_Source(this.v) as rs.OptionOf; + } +} + export class of$dyns_Square { v: Square.Square; @@ -1037,6 +1071,10 @@ export class of$u16 { return bump$dyns_Scaled_u16_u16(this.v, a as rs.Place); } + peek$Peek(): rs.OptionOf { + return peek$dyns_Peek_Option_u8_u16(this.v) as rs.OptionOf; + } + pick$Scaled(a: rs.Ptr, a2: rs.Ptr): rs.Ptr { return pick$dyns_Scaled_u16_u16(this.v, a as rs.u16, a2 as rs.u16) as rs.Ptr; } @@ -1105,8 +1143,12 @@ export class of$u8 { return left$dyns_Source_u8(this.v); } - take$Source(): string | undefined { - return dyns_take$dyns_Source_u8(this); + peek$Peek(): rs.OptionOf { + return peek$dyns_Peek_unit_u8(this.v) as rs.OptionOf; + } + + take$Source(): rs.OptionOf { + return dyns_take$dyns_Source_u8(this) as rs.OptionOf; } } @@ -1305,8 +1347,8 @@ export class ofPlace$u8 { return left$dyns_Source_u8(this.v.v); } - take$Source(): string | undefined { - return dyns_take$dyns_Source_u8(this.v); + take$Source(): rs.OptionOf { + return dyns_take$dyns_Source_u8(this.v) as rs.OptionOf; } } @@ -1345,6 +1387,8 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By generics(data10, out10); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["stds::stds", (_220: rs.u8, data11: rs.ByteSlice, out11: rs.ByteVec): undefined => { stds(data11, out11); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["dyns::options", (_221: rs.u8, data12: rs.ByteSlice, out12: rs.ByteVec): undefined => { + options(data12, out12); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/dyns/src/dyns.rs b/ts/fixtures/lang/dyns/src/dyns.rs index 2e9e20f46..18df23e81 100644 --- a/ts/fixtures/lang/dyns/src/dyns.rs +++ b/ts/fixtures/lang/dyns/src/dyns.rs @@ -2103,3 +2103,73 @@ pub fn generics(data: &[u8], out: &mut Vec) { } put(out, &format!("{runs:?} {}", over.source.left())); } + +/// Items that can already be `undefined`, whose `None` is `rs.None`: a countdown's +/// remainders, `None` where it skips one, and its ticks. +struct Skips(u8); + +impl Source for Skips { + type Item = Option; + fn take(&mut self) -> Option> { + self.0 = self.0.checked_sub(1)?; + Some((self.0 % 3 != 0).then_some(self.0)) + } + fn left(&self) -> usize { + usize::from(self.0) + } +} + +struct Beats(u8); + +impl Source for Beats { + type Item = (); + fn take(&mut self) -> Option<()> { + self.0 = self.0.checked_sub(1)?; + Some(()) + } + fn left(&self) -> usize { + usize::from(self.0) + } +} + +/// A generic trait returning an `Option` of its parameter, which an object's type has as +/// an `rs.OptionOf`, at a parameter that can be `undefined`. +trait Peek { + fn peek(&self) -> Option; +} + +impl Peek<()> for u8 { + fn peek(&self) -> Option<()> { + (*self % 2 == 0).then_some(()) + } +} + +impl Peek> for u16 { + fn peek(&self) -> Option> { + match *self % 3 { + 0 => None, + 1 => Some(None), + _ => Some(Some(*self as u8)), + } + } +} + +/// Objects whose methods return an `Option` of what can already be `undefined`: of an +/// associated type and of a generic trait's parameter. +pub fn options(data: &[u8], out: &mut Vec) { + let [a, b, ..] = data else { + return; + }; + let mut skips: Box>> = Box::new(Skips(*a % 8)); + let first = skips.take(); + put(out, &format!("{first:?} {} {} ", skips.left(), taken(&mut *skips))); + let mut beats = Beats(*b % 5); + let ticked: &mut dyn Source = &mut beats; + put(out, &format!("{:?} {} ", ticked.take(), taken(ticked))); + for x in data { + let unit: &dyn Peek<()> = x; + let wide = u16::from(*x); + let nested: &dyn Peek> = &wide; + put(out, &format!("{:?} {:?} ", unit.peek(), nested.peek())); + } +} diff --git a/ts/fixtures/lang/dyns/src/lib.rs b/ts/fixtures/lang/dyns/src/lib.rs index 0450248f4..6a427c08e 100644 --- a/ts/fixtures/lang/dyns/src/lib.rs +++ b/ts/fixtures/lang/dyns/src/lib.rs @@ -28,5 +28,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("dyns::rests", |_, data, out| dyns::rests(data, out)), ("dyns::generics", |_, data, out| dyns::generics(data, out)), ("stds::stds", |_, data, out| stds::stds(data, out)), + ("dyns::options", |_, data, out| dyns::options(data, out)), ] } diff --git a/ts/fixtures/lang/shapes/expected/core/bool.ts b/ts/fixtures/lang/shapes/expected/core/bool.ts index 57ba2790b..e30f27734 100644 --- a/ts/fixtures/lang/shapes/expected/core/bool.ts +++ b/ts/fixtures/lang/shapes/expected/core/bool.ts @@ -4,6 +4,13 @@ import * as rs from "../rs.ts"; import type * as Pair from "../fmt.Pair.ts"; import type * as Void from "../shapes.Void.ts"; +export function thenSome$1ctlqen(self: boolean, t: rs.u8 | undefined): rs.u8 | undefined | rs.None { + if (!self) { + return rs.None; + } + return t; +} + export function thenSome$1uv8pdy(self: boolean, t: rs.Result): rs.Result | undefined { if (!self) { return undefined; @@ -25,6 +32,13 @@ export function thenSome$05ku0zn(self: boolean, t: rs.i32): rs.i32 | undefined { return t; } +export function thenSome$19pue01(self: boolean, t: rs.Poll): rs.Poll | undefined { + if (!self) { + return undefined; + } + return t; +} + export function thenSome$0quywni(self: boolean, t: rs.u8): rs.u8 | undefined { if (!self) { return undefined; @@ -32,6 +46,13 @@ export function thenSome$0quywni(self: boolean, t: rs.u8): rs.u8 | undefined { return t; } +export function thenSome$1n1oy1h(self: boolean, _: undefined): undefined | rs.None { + if (!self) { + return rs.None; + } + return undefined; +} + export function then$01xsbgy(self: boolean, _: () => rs.Vec): rs.Vec | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/core/task/poll.ts b/ts/fixtures/lang/shapes/expected/core/task/poll.ts new file mode 100644 index 000000000..5b50f9024 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/core/task/poll.ts @@ -0,0 +1,61 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "../../rs.ts"; + +export function eq$1qrb1lw(self: rs.Poll, other: rs.Poll): boolean { + if (!((self !== rs.Pending ? 0 : 1) === (other !== rs.Pending ? 0 : 1))) { + return false; + } + if (self !== rs.Pending) { + if (other !== rs.Pending) { + return self === other; + } + } + return true; +} + +export function isReady$1bcuu9x(self: rs.Poll): boolean { + if (self !== rs.Pending) { + return true; + } + return false; +} + +export function isReady$099394k(self: rs.Poll | rs.OuterPending): boolean { + if (self !== rs.OuterPending) { + return true; + } + return false; +} + +export function isReady$1olyx0w(self: rs.Poll): boolean { + if (self !== rs.Pending) { + return true; + } + return false; +} + +export function isReady$0mapnag(self: rs.Poll): boolean { + if (self !== rs.Pending) { + return true; + } + return false; +} + +export function isPending$07fnw8g(self: rs.Poll): boolean { + return !isReady$1bcuu9x(self); +} + +export function isPending$1imonrp(self: rs.Poll | rs.OuterPending): boolean { + return !isReady$099394k(self); +} + +export function isPending$189w6zf(self: rs.Poll): boolean { + return !isReady$1olyx0w(self); +} + +export function isPending$1yqrsts(self: rs.Poll): boolean { + return !isReady$0mapnag(self); +} + +//# sourceMappingURL=poll.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Parked.ts b/ts/fixtures/lang/shapes/expected/generics.Parked.ts new file mode 100644 index 000000000..ae6e6d446 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Parked.ts @@ -0,0 +1,176 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { eq$1qrb1lw as poll_eq$1qrb1lw } from "./core/task/poll.ts"; + +/** + * A read's parked state, as moq-net's `Idle` and tokio's `oneshot::Inner` hold theirs: an + * `Option` and a `Poll` of its parameter, directly and in a cell, whose `None` and + * `Pending` each instance decides. Of `()` and `Option` the `None` is `rs.None`, and + * of `Poll` the `Pending` is `rs.OuterPending`. + */ +export class Parked { + parked: rs.OptionOf; + ready: rs.PollOf; + value: rs.Cell>; + + constructor(parked: rs.OptionOf, ready: rs.PollOf, value: rs.Cell>) { + this.parked = parked; + this.ready = ready; + this.value = value; + } +} + +export function new$Option_u8(): Parked { + return new Parked(rs.None as rs.OptionOf, rs.Pending as rs.PollOf, new rs.Cell(rs.None as rs.u8 | undefined | rs.None) as rs.Cell>); +} + +export function new$task_Poll_u8(): Parked> { + return new Parked(undefined as rs.OptionOf>, rs.OuterPending as rs.PollOf>, new rs.Cell(undefined as rs.Poll | undefined) as rs.Cell>>); +} + +export function new$u8(): Parked { + return new Parked(undefined as rs.OptionOf, rs.Pending as rs.PollOf, new rs.Cell(undefined as rs.u8 | undefined) as rs.Cell>); +} + +export function new$unit(): Parked { + return new Parked(rs.None as rs.OptionOf, rs.Pending as rs.PollOf, new rs.Cell(rs.None as undefined | rs.None) as rs.Cell>); +} + +/** Parks `read`, returning whether it parked just so already. */ +export function park$Option_u8(self: Parked, read: rs.u8 | undefined): boolean { + const again: boolean = self.parked === read as rs.u8 | undefined | rs.None; + self.parked = read; + return again; +} + +/** Parks `read`, returning whether it parked just so already. */ +export function park$task_Poll_u8(self: Parked>, read: rs.Poll): boolean { + const again: boolean = rs.eqOption(poll_eq$1qrb1lw)(self.parked, read as rs.Poll | undefined); + self.parked = read; + return again; +} + +/** Parks `read`, returning whether it parked just so already. */ +export function park$u8(self: Parked, read: rs.u8): boolean { + const again: boolean = self.parked === read; + self.parked = read; + return again; +} + +/** Parks `read`, returning whether it parked just so already. */ +export function park$unit(self: Parked, _: undefined): boolean { + const again: boolean = self.parked === undefined; + self.parked = undefined; + return again; +} + +/** Readies `value`, returning what was ready before. */ +export function ready$Option_u8(self: Parked, value: rs.u8 | undefined): rs.Poll { + const old = self.ready; + self.ready = value as rs.Poll; + return old; +} + +/** Readies `value`, returning what was ready before. */ +export function ready$task_Poll_u8(self: Parked>, value: rs.Poll): rs.Poll | rs.OuterPending { + const old = self.ready; + self.ready = value as rs.Poll | rs.OuterPending; + return old; +} + +/** Readies `value`, returning what was ready before. */ +export function ready$u8(self: Parked, value: rs.u8): rs.Poll { + const old = self.ready; + self.ready = value as rs.Poll; + return old; +} + +/** Readies `value`, returning what was ready before. */ +export function ready$unit(self: Parked, _: undefined): rs.Poll { + const old = self.ready; + self.ready = undefined as rs.Poll; + return old; +} + +/** Sends `value` through the cell, returning what was there, as a oneshot's sender does. */ +export function send$Option_u8(self: Parked, value: rs.u8 | undefined | rs.None): rs.u8 | undefined | rs.None { + const _4: rs.Place = self.value; + const old: rs.u8 | undefined | rs.None = _4.v; + _4.v = value; + return old; +} + +/** Sends `value` through the cell, returning what was there, as a oneshot's sender does. */ +export function send$task_Poll_u8(self: Parked>, value: rs.Poll | undefined): rs.Poll | undefined { + const _4: rs.Place | undefined> = self.value; + const old = _4.v; + _4.v = value; + return old; +} + +/** Sends `value` through the cell, returning what was there, as a oneshot's sender does. */ +export function send$u8(self: Parked, value: rs.u8 | undefined): rs.u8 | undefined { + const _4: rs.Place = self.value; + const old = _4.v; + _4.v = value; + return old; +} + +/** Sends `value` through the cell, returning what was there, as a oneshot's sender does. */ +export function send$unit(self: Parked, value: undefined | rs.None): undefined | rs.None { + const _4: rs.Place = self.value; + const old: undefined | rs.None = _4.v; + _4.v = value; + return old; +} + +/** Unparks it and takes what the cell holds, leaving `Pending`. */ +export function disarm$Option_u8(self: Parked): [rs.u8 | undefined | rs.None, rs.u8 | undefined | rs.None] { + self.ready = rs.Pending; + const taken: rs.u8 | undefined | rs.None = self.parked; + self.parked = rs.None; + const _3: rs.u8 | undefined | rs.None = taken; + const _6: rs.Place = self.value; + const taken2: rs.u8 | undefined | rs.None = _6.v; + _6.v = rs.None; + return [_3, taken2] as [rs.u8 | undefined | rs.None, rs.u8 | undefined | rs.None]; +} + +/** Unparks it and takes what the cell holds, leaving `Pending`. */ +export function disarm$task_Poll_u8(self: Parked>): [rs.Poll | undefined, rs.Poll | undefined] { + self.ready = rs.OuterPending; + const taken = self.parked; + self.parked = undefined; + const _3: rs.Poll | undefined = taken; + const _6: rs.Place | undefined> = self.value; + const taken2 = _6.v; + _6.v = undefined; + return [_3, taken2] as [rs.Poll | undefined, rs.Poll | undefined]; +} + +/** Unparks it and takes what the cell holds, leaving `Pending`. */ +export function disarm$u8(self: Parked): [rs.u8 | undefined, rs.u8 | undefined] { + self.ready = rs.Pending; + const taken = self.parked; + self.parked = undefined; + const _3: rs.u8 | undefined = taken; + const _6: rs.Place = self.value; + const taken2 = _6.v; + _6.v = undefined; + return [_3, taken2] as [rs.u8 | undefined, rs.u8 | undefined]; +} + +/** Unparks it and takes what the cell holds, leaving `Pending`. */ +export function disarm$unit(self: Parked): [undefined | rs.None, undefined | rs.None] { + self.ready = rs.Pending; + const taken: undefined | rs.None = self.parked; + self.parked = rs.None; + const _3: undefined | rs.None = taken; + const _6: rs.Place = self.value; + const taken2: undefined | rs.None = _6.v; + _6.v = rs.None; + return [_3, taken2] as [undefined | rs.None, undefined | rs.None]; +} + +//# sourceMappingURL=generics.Parked.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index e106e8026..d35a9e580 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -2,12 +2,14 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Run_generics_Ticked } from "./lib.ts"; -import { then$01xsbgy, then$0y7on52 } from "./core/bool.ts"; +import { thenSome$1ctlqen, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52 } from "./core/bool.ts"; import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$0ggarad as option_map$0ggarad } from "./core/option.ts"; +import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; import * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; import type * as Lends from "./generics.Lends.ts"; +import * as Parked from "./generics.Parked.ts"; import * as Run from "./generics.Run.ts"; import * as Runs from "./generics.Runs.ts"; import * as Tagged from "./generics.Tagged.ts"; @@ -275,4 +277,122 @@ export function runs(data: rs.ByteSlice, out: rs.ByteVec): void { log[Symbol.dispose](); } +/** + * [`Parked`] at `()`, `Option`, `Poll`, and `u8`, whose `None` and `Pending` are + * each a value JS keeps apart from what the instance holds. + */ +export function units(data: rs.ByteSlice, out: rs.ByteVec): void { + const parked: Parked.Parked = Parked.new$unit(); + for (const b of rs.byteSliceView(data)) { + const _12: rs.usize = rs.usizeRem(b >> 4 as rs.usize, rs.sliceFrom([undefined]).length); + if (!(_12 < 1)) { + rs.panic("index out of bounds: the len is " + 1 + " but the index is " + _12); + } + const _22: rs.u8 = rs.u8Rem(b, 4); + if (_22 === 0) { + rs.byteVecPush(out, Parked.park$unit(parked, undefined) ? 1 : 0); + } else if (_22 === 1) { + rs.byteVecPush(out, isPending$1yqrsts(Parked.ready$unit(parked, undefined)) ? 1 : 0); + } else if (_22 === 2) { + const _37: undefined | rs.None = thenSome$1n1oy1h((b & 4) !== 0, undefined); + rs.byteVecPush(out, Parked.send$unit(parked, _37) !== rs.None ? 1 : 0); + } else { + const _42: [undefined | rs.None, undefined | rs.None] = Parked.disarm$unit(parked); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(_42[0] !== rs.None ? 1 : 0, _42[1] !== rs.None ? 1 : 0)), undefined, true); + } + } + const again: boolean = parked.parked === undefined; + const _61: [undefined | rs.None, rs.Poll, undefined | rs.None] = [parked.parked, parked.ready, Parked.send$unit(parked, rs.None)]; + const was2: undefined | rs.None = _61[0]; + const ready: rs.Poll = _61[1]; + const sent2: undefined | rs.None = _61[2]; + const args9 = [(f: rs.Formatter) => rs.fmtOption(was2, f, rs.fmtUnit, rs.None), (f: rs.Formatter) => rs.fmtPoll(ready, f, rs.fmtUnit), (f: rs.Formatter) => rs.fmtOption(sent2, f, rs.fmtUnit, rs.None), (f: rs.Formatter) => rs.fmtBool(again, f)]; + put(out, rs.fmtFormat([args9[0], " ", args9[1], " ", args9[2], " ", args9[3]])); + const items: Array = [undefined, 1, 2]; + const parked2: Parked.Parked = Parked.new$Option_u8(); + for (const b2 of rs.byteSliceView(data)) { + const _98: rs.usize = rs.usizeRem(b2 >> 4 as rs.usize, rs.sliceFrom(items).length); + if (!(_98 < 3)) { + rs.panic("index out of bounds: the len is " + 3 + " but the index is " + _98); + } + const item: rs.u8 | undefined = items[_98]; + const _108: rs.u8 = rs.u8Rem(b2, 4); + if (_108 === 0) { + rs.byteVecPush(out, Parked.park$Option_u8(parked2, item) ? 1 : 0); + } else if (_108 === 1) { + rs.byteVecPush(out, isPending$07fnw8g(Parked.ready$Option_u8(parked2, item)) ? 1 : 0); + } else if (_108 === 2) { + const _123: rs.u8 | undefined | rs.None = thenSome$1ctlqen((b2 & 4) !== 0, item); + rs.byteVecPush(out, Parked.send$Option_u8(parked2, _123) !== rs.None ? 1 : 0); + } else { + const _128: [rs.u8 | undefined | rs.None, rs.u8 | undefined | rs.None] = Parked.disarm$Option_u8(parked2); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(_128[0] !== rs.None ? 1 : 0, _128[1] !== rs.None ? 1 : 0)), undefined, true); + } + } + const again2: boolean = parked2.parked === items[0] as rs.u8 | undefined | rs.None; + const _148: [rs.u8 | undefined | rs.None, rs.Poll, rs.u8 | undefined | rs.None] = [parked2.parked, parked2.ready, Parked.send$Option_u8(parked2, rs.None)]; + const was4: rs.u8 | undefined | rs.None = _148[0]; + const ready2: rs.Poll = _148[1]; + const sent4: rs.u8 | undefined | rs.None = _148[2]; + const args10 = [(f: rs.Formatter) => rs.fmtOption(was4, f, (value: rs.u8 | undefined, f: rs.Formatter) => rs.fmtOption(value, f, rs.u8Debug), rs.None), (f: rs.Formatter) => rs.fmtPoll(ready2, f, (value: rs.u8 | undefined, f: rs.Formatter) => rs.fmtOption(value, f, rs.u8Debug)), (f: rs.Formatter) => rs.fmtOption(sent4, f, (value: rs.u8 | undefined, f: rs.Formatter) => rs.fmtOption(value, f, rs.u8Debug), rs.None), (f: rs.Formatter) => rs.fmtBool(again2, f)]; + put(out, rs.fmtFormat([args10[0], " ", args10[1], " ", args10[2], " ", args10[3]])); + const items2: rs.Poll[] = [rs.Pending, 1]; + const parked3: Parked.Parked> = Parked.new$task_Poll_u8(); + for (const b3 of rs.byteSliceView(data)) { + const _184: rs.usize = rs.usizeRem(b3 >> 4 as rs.usize, rs.sliceFrom(items2).length); + if (!(_184 < 2)) { + rs.panic("index out of bounds: the len is " + 2 + " but the index is " + _184); + } + const item2: rs.Poll = items2[_184]; + const _194: rs.u8 = rs.u8Rem(b3, 4); + if (_194 === 0) { + rs.byteVecPush(out, Parked.park$task_Poll_u8(parked3, item2) ? 1 : 0); + } else if (_194 === 1) { + rs.byteVecPush(out, isPending$1imonrp(Parked.ready$task_Poll_u8(parked3, item2)) ? 1 : 0); + } else if (_194 === 2) { + const _209: rs.Poll | undefined = thenSome$19pue01((b3 & 4) !== 0, item2); + rs.byteVecPush(out, Parked.send$task_Poll_u8(parked3, _209) !== undefined ? 1 : 0); + } else { + const _214: [rs.Poll | undefined, rs.Poll | undefined] = Parked.disarm$task_Poll_u8(parked3); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(_214[0] !== undefined ? 1 : 0, _214[1] !== undefined ? 1 : 0)), undefined, true); + } + } + const _227 = items2[0] as rs.Poll | undefined; + const again3: boolean = rs.eqOption(poll_eq$1qrb1lw)(parked3.parked, _227); + const _234: [rs.Poll | undefined, rs.Poll | rs.OuterPending, rs.Poll | undefined] = [parked3.parked, parked3.ready, Parked.send$task_Poll_u8(parked3, undefined)]; + const was6: rs.Poll | undefined = _234[0]; + const ready3: rs.Poll | rs.OuterPending = _234[1]; + const sent6: rs.Poll | undefined = _234[2]; + const args11 = [(f: rs.Formatter) => rs.fmtOption(was6, f, (value: rs.Poll, f: rs.Formatter) => rs.fmtPoll(value, f, rs.u8Debug)), (f: rs.Formatter) => rs.fmtPoll(ready3, f, (value: rs.Poll, f: rs.Formatter) => rs.fmtPoll(value, f, rs.u8Debug), rs.OuterPending), (f: rs.Formatter) => rs.fmtOption(sent6, f, (value: rs.Poll, f: rs.Formatter) => rs.fmtPoll(value, f, rs.u8Debug)), (f: rs.Formatter) => rs.fmtBool(again3, f)]; + put(out, rs.fmtFormat([args11[0], " ", args11[1], " ", args11[2], " ", args11[3]])); + const items3: Uint8Array = Uint8Array.of(3, 4); + const parked4: Parked.Parked = Parked.new$u8(); + for (const b4 of rs.byteSliceView(data)) { + const _268: rs.usize = rs.usizeRem(b4 >> 4 as rs.usize, rs.byteSliceFrom(items3).length); + if (!(_268 < 2)) { + rs.panic("index out of bounds: the len is " + 2 + " but the index is " + _268); + } + const item3: rs.u8 = items3[_268]; + const _278: rs.u8 = rs.u8Rem(b4, 4); + if (_278 === 0) { + rs.byteVecPush(out, Parked.park$u8(parked4, item3) ? 1 : 0); + } else if (_278 === 1) { + rs.byteVecPush(out, isPending$189w6zf(Parked.ready$u8(parked4, item3)) ? 1 : 0); + } else if (_278 === 2) { + const _293: rs.u8 | undefined = thenSome$0quywni((b4 & 4) !== 0, item3); + rs.byteVecPush(out, Parked.send$u8(parked4, _293) !== undefined ? 1 : 0); + } else { + const _298: [rs.u8 | undefined, rs.u8 | undefined] = Parked.disarm$u8(parked4); + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(_298[0] !== undefined ? 1 : 0, _298[1] !== undefined ? 1 : 0)), undefined, true); + } + } + const again4: boolean = parked4.parked === items3[0]; + const _318: [rs.u8 | undefined, rs.Poll, rs.u8 | undefined] = [parked4.parked, parked4.ready, Parked.send$u8(parked4, undefined)]; + const was8: rs.u8 | undefined = _318[0]; + const ready4: rs.Poll = _318[1]; + const sent8: rs.u8 | undefined = _318[2]; + const args12 = [(f: rs.Formatter) => rs.fmtOption(was8, f, rs.u8Debug), (f: rs.Formatter) => rs.fmtPoll(ready4, f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(sent8, f, rs.u8Debug), (f: rs.Formatter) => rs.fmtBool(again4, f)]; + put(out, rs.fmtFormat([args12[0], " ", args12[1], " ", args12[2], " ", args12[3]])); +} + //# sourceMappingURL=generics.ts.map diff --git a/ts/fixtures/lang/shapes/expected/lib.ts b/ts/fixtures/lang/shapes/expected/lib.ts index 2c990a1aa..4900e09af 100644 --- a/ts/fixtures/lang/shapes/expected/lib.ts +++ b/ts/fixtures/lang/shapes/expected/lib.ts @@ -2,7 +2,7 @@ import * as rs from "./rs.ts"; import { ints, strings, derived, written, counts, rounding, panics, wrapped, collections, enums, iters, adaptors, writes, objects, sources, sets, calls, cells, searches } from "./fmt.ts"; -import { methods, runs } from "./generics.ts"; +import { methods, runs, units } from "./generics.ts"; import type * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; import type * as Run from "./generics.Run.ts"; @@ -218,6 +218,8 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By cells(data26, out27); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["fmt::searches", (_237: rs.u8, data27: rs.ByteSlice, out28: rs.ByteVec): undefined => { searches(data27, out28); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["generics::units", (_238: rs.u8, data28: rs.ByteSlice, out29: rs.ByteVec): undefined => { + units(data28, out29); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 1f060363c..442327807 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -1,8 +1,9 @@ //! Methods on generic structs and enums, each instance its own method. use std::borrow::Cow; -use std::cell::RefCell; +use std::cell::{RefCell, UnsafeCell}; use std::rc::Rc; +use std::task::Poll; use crate::fmt::Pair; use crate::harness::put; @@ -384,3 +385,79 @@ pub fn runs(data: &[u8], out: &mut Vec) { drop(ticks); out.extend(log.borrow().iter()); } + +/// A read's parked state, as moq-net's `Idle` and tokio's `oneshot::Inner` hold theirs: an +/// `Option` and a `Poll` of its parameter, directly and in a cell, whose `None` and +/// `Pending` each instance decides. Of `()` and `Option` the `None` is `rs.None`, and +/// of `Poll` the `Pending` is `rs.OuterPending`. +struct Parked { + parked: Option, + ready: Poll, + value: UnsafeCell>, +} + +impl Parked { + fn new() -> Self { + Self { + parked: None, + ready: Poll::Pending, + value: UnsafeCell::new(None), + } + } + + /// Parks `read`, returning whether it parked just so already. + fn park(&mut self, read: T) -> bool { + let again = self.parked == Some(read); + self.parked = Some(read); + again + } + + /// Readies `value`, returning what was ready before. + fn ready(&mut self, value: T) -> Poll { + std::mem::replace(&mut self.ready, Poll::Ready(value)) + } + + /// Sends `value` through the cell, returning what was there, as a oneshot's sender does. + fn send(&self, value: Option) -> Option { + // SAFETY: nothing else borrows the cell. + unsafe { std::mem::replace(&mut *self.value.get(), value) } + } + + /// Unparks it and takes what the cell holds, leaving `Pending`. + fn disarm(&mut self) -> (Option, Option) { + self.ready = Poll::Pending; + (self.parked.take(), self.value.get_mut().take()) + } +} + +/// [`Parked`] of `$items`, each parked, readied, and sent by the bits of a byte. +macro_rules! parked_at { + ($data:expr, $out:expr, $items:expr) => {{ + let items = $items; + let mut parked = Parked::new(); + for &b in $data { + let item = items[usize::from(b >> 4) % items.len()]; + match b % 4 { + 0 => $out.push(u8::from(parked.park(item))), + 1 => $out.push(u8::from(parked.ready(item).is_pending())), + 2 => $out.push(u8::from(parked.send((b & 4 != 0).then_some(item)).is_some())), + _ => { + let (was, sent) = parked.disarm(); + $out.extend([u8::from(was.is_some()), u8::from(sent.is_some())]); + } + } + } + let again = parked.parked == Some(items[0]); + let (was, ready, sent) = (parked.parked, parked.ready, parked.send(None)); + put($out, &format!("{was:?} {ready:?} {sent:?} {again}")); + }}; +} + +/// [`Parked`] at `()`, `Option`, `Poll`, and `u8`, whose `None` and `Pending` are +/// each a value JS keeps apart from what the instance holds. +pub fn units(data: &[u8], out: &mut Vec) { + parked_at!(data, out, [()]); + parked_at!(data, out, [None, Some(1u8), Some(2)]); + parked_at!(data, out, [Poll::Pending, Poll::Ready(1u8)]); + parked_at!(data, out, [3u8, 4]); +} diff --git a/ts/fixtures/lang/shapes/src/lib.rs b/ts/fixtures/lang/shapes/src/lib.rs index f8800de06..d3eb97926 100644 --- a/ts/fixtures/lang/shapes/src/lib.rs +++ b/ts/fixtures/lang/shapes/src/lib.rs @@ -55,5 +55,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("fmt::calls", |_, data, out| fmt::calls(data, out)), ("fmt::cells", |_, data, out| fmt::cells(data, out)), ("fmt::searches", |_, data, out| fmt::searches(data, out)), + ("generics::units", |_, data, out| generics::units(data, out)), ] } diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 8c74743d0..7096d0965 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -35,7 +35,6 @@ fixtures/reject/src/config.rs:generic+0:51: unsupported type `f16` fixtures/reject/src/config.rs:generic+2:2: a cast from `u64` to `f16` fixtures/reject/src/fmt.rs:chunks_address+1:2: `Debug` of a `ChunksMut`, whose slice std writes as an address, which JS has no counterpart of fixtures/reject/src/fmt.rs:pointer+1:2: `Pointer` formatting, as in `{:p}`, is not supported -fixtures/reject/src/futures.rs:generic_pending+1:2: `futures::Waiting>` is not supported: its field `poll` is `Poll`, typed `rs.Poll`, whose `Pending` would be `rs.OuterPending` as `Poll>` fixtures/reject/src/futures.rs:nested_poll+0:20: `Poll>>>` is not supported: its `Pending` and `Ready` would both be `rs.OuterPending` fixtures/reject/src/futures.rs:pending_output+0:34: a future of `std::task::Poll`, which can be `rs.Pending`, as the future's own `Pending` is fixtures/reject/src/futures.rs:raw_waker_debug+1:2: `Debug` of `std::task::RawWaker`, which writes addresses, which JS has no counterpart of @@ -111,12 +110,10 @@ fixtures/reject/src/objects.rs:dynamic+2:4: unsupported call to `::unwrap_or` fixtures/reject/src/objects.rs:first_of+1:26: a trait object of `[u8; 4]`, which is not supported yet -fixtures/reject/src/objects.rs:nested_item+1:45: `dyn objects::Taken>`, whose `objects::Taken::take` takes or returns a value of another form than its trait's object type gives it, which is not supported yet -fixtures/reject/src/objects.rs:passed+2:11: `dyn objects::Passed>`, whose `objects::Passed::item` takes or returns a value of another form than its trait's object type gives it, which is not supported yet fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, whose object would hold the value there as it is made, not as it is read, which is not supported yet -fixtures/reject/src/objects.rs:received+1:4: `dyn objects::Taken>`, whose `objects::Taken::take` takes or returns a value of another form than its trait's object type gives it, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet fixtures/reject/src/objects.rs:scaled_twice+2:28: `u8` as `dyn objects::Scaled`, whose class has `scale$Scaled` of other arguments of the trait, which is not supported yet +fixtures/reject/src/objects.rs:uninit_item+1:61: `dyn objects::Taken>`, whose `objects::Taken::take` takes or returns a value of another form than its trait's object type gives it, which is not supported yet fixtures/reject/src/panics.rs:call+1:2: `panics::here`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source fixtures/reject/src/panics.rs:call+1:2: a call through std's code of `panics::here`, a `#[track_caller]` function, whose caller is in std's source fixtures/reject/src/panics.rs:caller+1:37: `panics::at`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source @@ -284,7 +281,7 @@ fixtures/reject/src/values.rs:float+0:14: unsupported type `f16` fixtures/reject/src/values.rs:float+0:25: unsupported type `f16` fixtures/reject/src/values.rs:float+1:2: a scalar constant of type `f16` fixtures/reject/src/values.rs:float+1:2: arithmetic on `f16` -fixtures/reject/src/values.rs:generic_unit+1:2: `values::Maybe<()>` is not supported: its field `value` is `Option`, typed `T | undefined`, whose `None` would be `rs.None` as `Option<()>` +fixtures/reject/src/values.rs:generic_uninit+1:2: `values::Maybe>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:lists+1:4: `std::collections::LinkedList::::retain` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/values.rs:manual_impls+2:2: unsupported call to ` as std::fmt::Debug>::fmt` fixtures/reject/src/values.rs:manual_impls+2:25: unsupported call to ` as std::cmp::PartialEq>::eq` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index b3b900074..3a31bd040 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -38,7 +38,6 @@ fixtures/reject/src/config.rs:Meters+0:1: a printable type must implement `Displ fixtures/reject/src/config.rs:generic+0:1: a root must not be generic fixtures/reject/src/fmt.rs:chunks_address+1:11: `Debug` of a `ChunksMut`, whose slice std writes as an address, which JS has no counterpart of fixtures/reject/src/fmt.rs:pointer+1:11: `Pointer` formatting, as in `{:p}`, is not supported -fixtures/reject/src/futures.rs:generic_pending+1:2: `futures::Waiting>` is not supported: its field `poll` is `Poll`, typed `rs.Poll`, whose `Pending` would be `rs.OuterPending` as `Poll>` fixtures/reject/src/futures.rs:nested_poll+0:20: `Poll>>>` is not supported: its `Pending` and `Ready` would both be `rs.OuterPending` fixtures/reject/src/futures.rs:pending_output+0:34: a future of `std::task::Poll`, which can be `rs.Pending`, as the future's own `Pending` is fixtures/reject/src/futures.rs:raw_waker_debug+1:11: `Debug` of `std::task::RawWaker`, which writes addresses, which JS has no counterpart of @@ -113,12 +112,10 @@ fixtures/reject/src/objects.rs:dynamic+2:4: unsupported call to `::unwrap_or` fixtures/reject/src/objects.rs:first_of+1:26: a trait object of `[u8; 4]`, which is not supported yet -fixtures/reject/src/objects.rs:nested_item+1:45: `dyn objects::Taken>`, whose `objects::Taken::take` takes or returns a value of another form than its trait's object type gives it, which is not supported yet -fixtures/reject/src/objects.rs:passed+2:11: `dyn objects::Passed>`, whose `objects::Passed::item` takes or returns a value of another form than its trait's object type gives it, which is not supported yet fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, whose object would hold the value there as it is made, not as it is read, which is not supported yet -fixtures/reject/src/objects.rs:received+1:4: `dyn objects::Taken>`, whose `objects::Taken::take` takes or returns a value of another form than its trait's object type gives it, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet fixtures/reject/src/objects.rs:scaled_twice+2:28: `u8` as `dyn objects::Scaled`, whose class has `scale$Scaled` of other arguments of the trait, which is not supported yet +fixtures/reject/src/objects.rs:uninit_item+1:61: `dyn objects::Taken>`, whose `objects::Taken::take` takes or returns a value of another form than its trait's object type gives it, which is not supported yet fixtures/reject/src/panics.rs:call+1:2: `panics::here`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source fixtures/reject/src/panics.rs:call+1:2: a call through std's code of `panics::here`, a `#[track_caller]` function, whose caller is in std's source fixtures/reject/src/panics.rs:caller+1:37: `panics::at`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source @@ -285,7 +282,7 @@ fixtures/reject/src/values.rs:float+0:14: unsupported type `f16` fixtures/reject/src/values.rs:float+0:25: unsupported type `f16` fixtures/reject/src/values.rs:float+1:2: a scalar constant of type `f16` fixtures/reject/src/values.rs:float+1:2: arithmetic on `f16` -fixtures/reject/src/values.rs:generic_unit+1:2: `values::Maybe<()>` is not supported: its field `value` is `Option`, typed `T | undefined`, whose `None` would be `rs.None` as `Option<()>` +fixtures/reject/src/values.rs:generic_uninit+1:2: `values::Maybe>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:lists+1:4: `std::collections::LinkedList::::retain` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/values.rs:manual_impls+2:11: unsupported call to ` as std::fmt::Debug>::fmt` fixtures/reject/src/values.rs:manual_impls+2:25: unsupported call to ` as std::cmp::PartialEq>::eq` diff --git a/ts/fixtures/reject/src/futures.rs b/ts/fixtures/reject/src/futures.rs index 3207bc1d9..598f1fa00 100644 --- a/ts/fixtures/reject/src/futures.rs +++ b/ts/fixtures/reject/src/futures.rs @@ -10,16 +10,6 @@ pub async fn pending_output() -> std::task::Poll { std::task::Poll::Pending } -/// A generic type's `Poll` is `rs.Poll`, which can't hold `rs.OuterPending`, the -/// `Pending` of a `Poll>`. -pub struct Waiting { - pub poll: std::task::Poll, -} - -pub fn generic_pending(poll: std::task::Poll>) -> bool { - Waiting { poll }.poll.is_pending() -} - /// A census lowers this over `F` and `O`, and `awaited`'s body with it, whose closure names /// `O` as this function's second type parameter: that refuses nothing. pub async fn generic_awaited u8, O: std::future::Future>(f: F, o: O) -> u8 { diff --git a/ts/fixtures/reject/src/objects.rs b/ts/fixtures/reject/src/objects.rs index af0502431..b848f63f5 100644 --- a/ts/fixtures/reject/src/objects.rs +++ b/ts/fixtures/reject/src/objects.rs @@ -45,55 +45,21 @@ pub trait Taken { fn take(&mut self) -> Option; } -impl Taken for u8 { - type Item = Option; - fn take(&mut self) -> Option> { - Some(self.checked_sub(1)) +impl Taken for u32 { + type Item = std::mem::MaybeUninit; + fn take(&mut self) -> Option> { + Some(std::mem::MaybeUninit::new(*self)) } } /// An object whose method returns an `Option` of its associated type, which its type has -/// as `Item | undefined`, bound to an `Option` too, whose `None` that can't tell apart. -pub fn nested_item(x: &mut u8) -> bool { - let s: &mut dyn Taken> = x; +/// as an `rs.OptionOf`, bound to a `MaybeUninit`, whose `None` is `rs.None` +/// though TypeScript's type of it, a `number`, can't be `undefined`. +pub fn uninit_item(x: &mut u32) -> bool { + let s: &mut dyn Taken> = x; s.take().is_some() } -/// An object of nested options a caller made, which a call dispatches through. -pub fn received(x: &mut dyn Taken>) -> bool { - x.take().is_some() -} - -pub trait Passed { - fn pass(&self) -> &dyn Passed>; - fn item(&self) -> Option; -} - -impl Passed for u8 { - fn pass(&self) -> &dyn Passed> { - &() - } - fn item(&self) -> Option { - None - } -} - -impl Passed for () { - fn pass(&self) -> &dyn Passed> { - self - } - fn item(&self) -> Option { - None - } -} - -/// An object of nested options, whose type a method of the trait names before the method -/// returning its `Item` is held. -pub fn passed(x: &u8) -> bool { - let p: &dyn Passed = x; - p.pass().item().is_none() -} - pub trait First { fn first(&self) -> u8; } diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index c6c5dfc0f..a378e6f44 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -11,14 +11,19 @@ pub fn nested(x: Option>>) -> bool { x.is_some() } -/// A generic type's `Option` is `T | undefined`, which can't hold `rs.None`, the `None` -/// of an `Option<()>`. +/// A generic type's `Option` is an `rs.OptionOf`, which decides its `None` by +/// whether TypeScript's type of `T` can be `undefined`, as a `MaybeUninit`'s `number` +/// can't, though its value can, so an `Option>`'s `None` is `rs.None`. pub struct Maybe { pub value: Option, } -pub fn generic_unit(value: Option<()>) -> bool { - Maybe { value }.value.is_some() +pub fn generic_uninit(value: u32) -> bool { + Maybe { + value: Some(std::mem::MaybeUninit::new(value)), + } + .value + .is_some() } /// Parsing an address is not translated yet. diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index c3659bf03..be8aa3beb 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -22,7 +22,6 @@ roots = [ "config::generic", "fmt::chunks_address", "fmt::pointer", - "futures::generic_pending", "futures::nested_poll", "futures::pending_output", "futures::raw_waker_debug", @@ -87,12 +86,10 @@ roots = [ "objects::dyn_size", "objects::dynamic", "objects::first_of", - "objects::nested_item", - "objects::passed", "objects::raw_dyn_iter", - "objects::received", "objects::same_named", "objects::scaled_twice", + "objects::uninit_item", "panics::caller", "panics::iterated", "panics::pointed", @@ -207,7 +204,7 @@ roots = [ "values::block_cloned", "values::dangling_enum", "values::float", - "values::generic_unit", + "values::generic_uninit", "values::lists", "values::manual_impls", "values::nested", diff --git a/ts/runtime/rs.ts b/ts/runtime/rs.ts index f4c095417..a471c17f5 100644 --- a/ts/runtime/rs.ts +++ b/ts/runtime/rs.ts @@ -664,6 +664,9 @@ export const None: unique symbol = /* @__PURE__ */ Symbol("None"); /** The type of `None`. */ export type None = typeof None; +/** A generic type's `Option`, whose `None` each instance decides: `T | undefined`, or `T | None` where `T` can already be `undefined`. */ +export type OptionOf = T | (undefined extends T ? None : undefined); + /** `f` of what `o` holds, or `None`: `Option::map` of a payload that is never `undefined`. */ export function optionMap(o: T | undefined, f: (value: T) => U): U | undefined { return o === undefined ? undefined : f(o); diff --git a/ts/runtime/rs/task.ts b/ts/runtime/rs/task.ts index 26db5ac64..9a14842ed 100644 --- a/ts/runtime/rs/task.ts +++ b/ts/runtime/rs/task.ts @@ -1372,6 +1372,9 @@ export const OuterPending: unique symbol = /* @__PURE__ */ Symbol("OuterPending" /** The type of `OuterPending`. */ export type OuterPending = typeof OuterPending; +/** A generic type's `Poll`, whose `Pending` each instance decides: `Poll`, or `T | OuterPending` where `T` can already be `Pending`. */ +export type PollOf = T | (typeof Pending extends T ? OuterPending : typeof Pending); + /** `Debug` of a `Poll`, through its value's, whose `Pending` is `pending`. */ export function fmtPoll(p: T | typeof Pending | OuterPending, f: Formatter, debug: Debug, pending: typeof Pending | OuterPending = Pending): FmtResult { if (p === pending) return f.out.writeStr("Pending"); diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index bfc60fb65..93484857f 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -283,7 +283,8 @@ impl<'tcx> Translator<'_, 'tcx> { /// Refuse the trait object `ty` of `principal` if a method its object holds takes or /// returns, at its arguments, what its type gives in another form than a cast reaches, - /// as a `T | undefined` of an `Option` whose `T` is an `Option` too. Checked where a + /// as an `rs.OptionOf` whose `T` is a `MaybeUninit`, which can be `undefined` + /// though its type can't. Checked where a /// value becomes one or a call dispatches through one, by which time the object holds each method of a crate's own /// trait, and not where a type names one, as a method may return an object of other /// arguments without end, `Nest>` of `Nest`. @@ -805,6 +806,24 @@ fn fits(defined: &Type, called: &Type) -> bool { _ if defined == called || super::instanced(defined) => true, // The `()` a declared method returns as `void`. (Type::Builtin("undefined"), Type::Builtin("void")) => true, + // What an `rs.OptionOf` or `rs.PollOf` resolves to at the called type's argument. + (Type::Rs("OptionOf", x), Type::Union(y)) => match (&x[..], &y[..]) { + ([x], [some, none]) => { + fits(x, some) + && *none + == match super::holds_undefined(some) { + true => Type::Rs("None", vec![]), + false => Type::Builtin("undefined"), + } + } + _ => false, + }, + (Type::Rs("PollOf", x), called) => match (&x[..], called) { + ([x], Type::Rs("Poll", y)) => matches!(&y[..], [ready] if fits(x, ready) && !super::holds_pending(ready)), + ([x], Type::Union(y)) => matches!(&y[..], [ready, pending] + if fits(x, ready) && super::holds_pending(ready) && *pending == Type::Rs("OuterPending", vec![])), + _ => false, + }, (Type::Rs(a, x), Type::Rs(b, y)) => a == b && all(x, y), (Type::Sym(a, x), Type::Sym(b, y)) => a == b && all(x, y), (Type::Union(x), Type::Union(y)) | (Type::Tuple(x), Type::Tuple(y)) => all(x, y), diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index e110030e1..b0708a460 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -200,6 +200,10 @@ pub struct Translator<'a, 'tcx> { /// holds, with that type where a function type in it takes one, which an instance's /// value is cast to. instanced_fields: rustc_data_structures::fx::FxHashMap>, + /// The fields of generic types whose definition types an `rs.OptionOf` or `rs.PollOf`, + /// by that type, which an instance's value is cast to so TypeScript infers the + /// instance's arguments from it rather than from the `None` or `Pending` it may be. + decided_fields: rustc_data_structures::fx::FxHashMap, /// Source files by name, as indexes into `program.sources`. sources: rustc_data_structures::fx::FxHashMap>, failed: bool, @@ -496,6 +500,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { queue: VecDeque::new(), adts: Default::default(), instanced_fields: Default::default(), + decided_fields: Default::default(), sources: Default::default(), failed: false, env: TypingEnv::fully_monomorphized(), @@ -1903,7 +1908,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { return Type::Builtin("unknown"); } let info = self.adt(adt, span); - if let Some(msg) = self.generic_erasure(adt, args) { + if let Some(msg) = self.generic_erasure(adt, args, span) { self.err(span, msg); } let args = args.types().map(|t| self.ty(t, span)).collect(); @@ -1924,12 +1929,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { self.instanced_fields .insert(field.did, ty.takes(&instanced).then(|| ty.clone())); } + if ty.any(&|t| matches!(t, Type::Rs("OptionOf" | "PollOf", _))) { + self.decided_fields.insert(field.did, ty.clone()); + } ty } /// `value`, for `field` of an instance of `adt` with `args`, cast to the field's type in /// the definition at those arguments where a function type in it takes a form only an - /// instance decides: a function taking what the instance holds takes no other form. + /// instance decides, as a function taking what the instance holds takes no other form, + /// or where it holds an `rs.OptionOf` or `rs.PollOf`. pub fn field_value( &mut self, value: ts::Expr, @@ -1938,8 +1947,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { args: GenericArgsRef<'tcx>, span: Span, ) -> ts::Expr { - let Some(Some(defined)) = self.instanced_fields.get(&field).cloned() else { - return value; + let defined = match self.instanced_fields.get(&field) { + Some(Some(defined)) => defined.clone(), + _ => match self.decided_fields.get(&field) { + Some(defined) => defined.clone(), + None => return value, + }, }; let params: Vec<(String, Type)> = (self.tcx.generics_of(adt).own_params.iter()) .filter(|param| matches!(param.kind, ty::GenericParamDefKind::Type { .. })) @@ -1965,10 +1978,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } /// Why an instance of the crate's generic `adt` with `args` can't hold its fields' values, - /// if it can't: an `Option` or a `Poll` over a type parameter that its definition types - /// `T | undefined` or `rs.Poll`, whose `None` or `Pending` this instance's `T` makes - /// `rs.None` or `rs.OuterPending`. - fn generic_erasure(&self, adt: AdtDef<'tcx>, args: GenericArgsRef<'tcx>) -> Option { + /// if it can't: an `Option` or a `Poll` over a type parameter whose `None` or `Pending` + /// this instance makes `rs.None` or `rs.OuterPending`, where its definition types it + /// `T | undefined` or `rs.Poll`, or where an `rs.OptionOf` or `rs.PollOf` + /// would decide otherwise from TypeScript's type of `T`, or which can't tell its values + /// apart at all. + fn generic_erasure(&mut self, adt: AdtDef<'tcx>, args: GenericArgsRef<'tcx>, span: Span) -> Option { let tcx = self.tcx; if !args.types().any(|t| { let erased = self.erases_to(t); @@ -1976,41 +1991,70 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }) { return None; } - adt.all_fields().find_map(|field| { + let ty = Ty::new_adt(tcx, adt, args); + for field in adt.all_fields() { let generic = tcx.type_of(field.did).instantiate_identity().skip_norm_wip(); - generic.walk().filter_map(|arg| arg.as_type()).find_map(|part| { + for part in generic.walk().filter_map(|arg| arg.as_type()) { let ty::Adt(wrapper, wrapper_args) = part.kind() else { - return None; + continue; + }; + let option = match library::Std::of(tcx, wrapper.did()) { + Some(library::Std::Option) => true, + Some(library::Std::Poll) => false, + _ => continue, }; - let std = library::Std::of(tcx, wrapper.did()); - if !matches!(std, Some(library::Std::Option | library::Std::Poll)) { - return None; - } let inner = wrapper_args.type_at(0); let erased = self.erases_to(inner); + if !erased.param { + continue; + } + let held = ty::EarlyBinder::bind(tcx, part).instantiate(tcx, args).skip_norm_wip(); + if let Some(msg) = self.erasure_conflict(held) { + return Some(format!("`{ty}` is not supported: its field `{}` is `{held}`: {msg}", field.name)); + } let instance = ty::EarlyBinder::bind(tcx, inner).instantiate(tcx, args).skip_norm_wip(); - let ty = Ty::new_adt(tcx, adt, args); - match std { - Some(library::Std::Option) if erased.param && !erased.undefined => { - self.erases_to(instance).undefined.then(|| { + let at = self.erases_to(instance); + let defined = self.ty(inner, span); + let msg = match defined { + // What TypeScript's type of the parameter decides, which must be what the + // instance's erasure does. + Type::Param(_) if if option { !erased.undefined } else { !erased.pending } => { + let Some(param) = inner.walk().filter_map(|arg| arg.as_type()).find(|t| matches!(t.kind(), ty::Param(_))) else { + continue; + }; + let arg = ty::EarlyBinder::bind(tcx, param).instantiate(tcx, args).skip_norm_wip(); + let decided = self.ty(arg, span); + let (holds, alias) = match option { + true => (holds_undefined(&decided) == at.undefined, "OptionOf"), + false => (holds_pending(&decided) == at.pending, "PollOf"), + }; + (!holds).then(|| { format!( - "`{ty}` is not supported: its field `{}` is `Option<{inner}>`, typed `{inner} | undefined`, whose `None` would be `rs.None` as `Option<{instance}>`", - field.name - ) - }) - } - Some(library::Std::Poll) if erased.param && !erased.pending => { - self.erases_to(instance).pending.then(|| { - format!( - "`{ty}` is not supported: its field `{}` is `Poll<{inner}>`, typed `rs.Poll<{inner}>`, whose `Pending` would be `rs.OuterPending` as `Poll<{instance}>`", + "`{ty}` is not supported: its field `{}` is `{part}`, typed `rs.{alias}<{inner}>`, which decides by TypeScript's type of `{arg}` otherwise than `{held}` erases", field.name ) }) } + _ if option && !erased.undefined => at.undefined.then(|| { + format!( + "`{ty}` is not supported: its field `{}` is `Option<{inner}>`, typed `{inner} | undefined`, whose `None` would be `rs.None` as `Option<{instance}>`", + field.name + ) + }), + _ if !option && !erased.pending => at.pending.then(|| { + format!( + "`{ty}` is not supported: its field `{}` is `Poll<{inner}>`, typed `rs.Poll<{inner}>`, whose `Pending` would be `rs.OuterPending` as `Poll<{instance}>`", + field.name + ) + }), _ => None, + }; + if msg.is_some() { + return msg; } - }) - }) + } + } + None } /// The type of an iterator's items, which every iterator has. @@ -2025,13 +2069,19 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } /// The TypeScript type of an `Option`: `T | undefined`, or `T | rs.None` where - /// `T` can already be `undefined`. + /// `T` can already be `undefined`, and in a generic type's definition, of a type + /// parameter, an `rs.OptionOf`, which is either as each instance's `T` decides. fn option_ty(&mut self, inner: Ty<'tcx>, span: Span) -> Type { - let none = match self.erases_to(inner).undefined { + let some = self.ty(inner, span); + let erased = self.erases_to(inner); + if matches!(some, Type::Param(_)) && !erased.undefined { + return Type::Rs("OptionOf", vec![some]); + } + let none = match erased.undefined { true => Type::Rs("None", vec![]), false => Type::Builtin("undefined"), }; - Type::Union(vec![self.ty(inner, span), none]) + Type::Union(vec![some, none]) } /// The TypeScript type of a future of `output`, an `rs.Future`, whose `poll` returns @@ -2047,9 +2097,13 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } /// The TypeScript type of a `Poll`: `rs.Poll`, or `T | rs.OuterPending` where - /// `T` can already be `rs.Pending`. + /// `T` can already be `rs.Pending`, and in a generic type's definition, of a type + /// parameter, an `rs.PollOf`, which is either as each instance's `T` decides. fn poll_ty(&mut self, inner: Ty<'tcx>, span: Span) -> Type { let ready = self.ty(inner, span); + if matches!(ready, Type::Param(_)) && !self.erases_to(inner).pending { + return Type::Rs("PollOf", vec![ready]); + } match self.erases_to(inner).pending { true => Type::Union(vec![ready, Type::Rs("OuterPending", vec![])]), false => Type::Rs("Poll", vec![ready]), @@ -2313,6 +2367,27 @@ pub fn instanced(ty: &Type) -> bool { matches!(ty, Type::Rs("Ptr" | "ArrayOf" | "SliceOf" | "VecOf" | "CowOf", _)) } +/// Whether TypeScript's `undefined extends t` holds of `t`, a type no instance decides, as +/// `rs.OptionOf` asks. +pub fn holds_undefined(t: &Type) -> bool { + match t { + Type::Builtin("undefined") => true, + Type::Union(tys) => tys.iter().any(holds_undefined), + Type::Rs("Poll", tys) => tys.iter().any(holds_undefined), + _ => false, + } +} + +/// Whether TypeScript's `typeof rs.Pending extends t` holds of `t`, a type no instance +/// decides, as `rs.PollOf` asks. +pub fn holds_pending(t: &Type) -> bool { + match t { + Type::Rs("Poll", _) => true, + Type::Union(tys) => tys.iter().any(holds_pending), + _ => false, + } +} + /// `snake_case` to `camelCase`, keeping leading underscores. pub fn camel(name: &str) -> String { let name = name.strip_prefix("r#").unwrap_or(name); From bdb128273608d529216911584d2246cd305b564c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 15:08:26 -0800 Subject: [PATCH 3/7] test(ts): refuse a generic type's Option of an Option of unit Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.txt | 2 ++ ts/fixtures/reject/expected.txt | 2 ++ ts/fixtures/reject/src/values.rs | 8 ++++++++ ts/fixtures/reject/ts.toml | 1 + ts/src/lower/mod.rs | 5 ++++- 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 7096d0965..46db551e5 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -281,6 +281,8 @@ fixtures/reject/src/values.rs:float+0:14: unsupported type `f16` fixtures/reject/src/values.rs:float+0:25: unsupported type `f16` fixtures/reject/src/values.rs:float+1:2: a scalar constant of type `f16` fixtures/reject/src/values.rs:float+1:2: arithmetic on `f16` +fixtures/reject/src/values.rs:generic_nested+1:6: `values::Maybe>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:generic_nested+1:48: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:generic_uninit+1:2: `values::Maybe>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:lists+1:4: `std::collections::LinkedList::::retain` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/values.rs:manual_impls+2:2: unsupported call to ` as std::fmt::Debug>::fmt` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 3a31bd040..f20064674 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -282,6 +282,8 @@ fixtures/reject/src/values.rs:float+0:14: unsupported type `f16` fixtures/reject/src/values.rs:float+0:25: unsupported type `f16` fixtures/reject/src/values.rs:float+1:2: a scalar constant of type `f16` fixtures/reject/src/values.rs:float+1:2: arithmetic on `f16` +fixtures/reject/src/values.rs:generic_nested+1:6: `values::Maybe>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:generic_nested+1:48: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:generic_uninit+1:2: `values::Maybe>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:lists+1:4: `std::collections::LinkedList::::retain` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/values.rs:manual_impls+2:11: unsupported call to ` as std::fmt::Debug>::fmt` diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index a378e6f44..923588bc2 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -26,6 +26,14 @@ pub fn generic_uninit(value: u32) -> bool { .is_some() } +/// A generic type's `Option` of an `Option<()>`, whose `None` and `Some(None)` would +/// both be `rs.None`. +pub fn generic_nested(unit: bool) -> bool { + let maybe: Maybe> = Maybe { value: None }; + drop(maybe); + unit +} + /// Parsing an address is not translated yet. pub fn addr_parsed(s: &str) -> Result { s.parse() diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index be8aa3beb..5d929d01f 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -204,6 +204,7 @@ roots = [ "values::block_cloned", "values::dangling_enum", "values::float", + "values::generic_nested", "values::generic_uninit", "values::lists", "values::manual_impls", diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index b0708a460..163e74556 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -2010,7 +2010,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } let held = ty::EarlyBinder::bind(tcx, part).instantiate(tcx, args).skip_norm_wip(); if let Some(msg) = self.erasure_conflict(held) { - return Some(format!("`{ty}` is not supported: its field `{}` is `{held}`: {msg}", field.name)); + return Some(format!( + "`{ty}` is not supported: its field `{}` is `{held}`: {msg}", + field.name + )); } let instance = ty::EarlyBinder::bind(tcx, inner).instantiate(tcx, args).skip_norm_wip(); let at = self.erases_to(instance); From b205a76353cadd9c285a0cd5584b249998449c4d Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 15:34:33 -0800 Subject: [PATCH 4/7] test(ts): snapshot rangemap's Holder with its Option field an rs.OptionOf Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/iters/expected/iters.Holder.ts | 4 ++-- ts/fixtures/lang/iters/expected/iters.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ts/fixtures/lang/iters/expected/iters.Holder.ts b/ts/fixtures/lang/iters/expected/iters.Holder.ts index 1993ee4ce..6eca0d510 100644 --- a/ts/fixtures/lang/iters/expected/iters.Holder.ts +++ b/ts/fixtures/lang/iters/expected/iters.Holder.ts @@ -12,9 +12,9 @@ export class Holder { tree: rs.Iter<[rs.u8, rs.Ptr]>; owned: rs.Iter; peeked: rs.Peekable; - last: T | undefined; + last: rs.OptionOf; - constructor(slice: rs.Iter>, tree: rs.Iter<[rs.u8, rs.Ptr]>, owned: rs.Iter, peeked: rs.Peekable, last: T | undefined) { + constructor(slice: rs.Iter>, tree: rs.Iter<[rs.u8, rs.Ptr]>, owned: rs.Iter, peeked: rs.Peekable, last: rs.OptionOf) { this.slice = slice; this.tree = tree; this.owned = owned; diff --git a/ts/fixtures/lang/iters/expected/iters.ts b/ts/fixtures/lang/iters/expected/iters.ts index a454fea78..6f8518685 100644 --- a/ts/fixtures/lang/iters/expected/iters.ts +++ b/ts/fixtures/lang/iters/expected/iters.ts @@ -11656,7 +11656,7 @@ export function held$String_iters_held_fields_closure2(data: rs.ByteSlice, make: const _17: [rs.u8, rs.u8] = [option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0), option_unwrapOr$1fr498u(rs.byteSliceLast(data), 0)]; const lo: rs.u8 = _17[0]; const hi: rs.u8 = _17[1]; - const held: Holder.Holder> = new Holder.Holder(rs.iterSlice(items.items) as rs.Iter>, rs.btreeRange(tree, new rs.RangeInclusive(rs.min(lo, hi), rs.max(lo, hi), rs.u8Step)) as rs.Iter<[rs.u8, rs.Ptr]>, rs.vecIntoIter(rs.vecFrom(items.items.slice())), rs.iterPeekable(rs.iterSlice(rs.byteSliceView(data))), undefined); + const held: Holder.Holder> = new Holder.Holder(rs.iterSlice(items.items) as rs.Iter>, rs.btreeRange(tree, new rs.RangeInclusive(rs.min(lo, hi), rs.max(lo, hi), rs.u8Step)) as rs.Iter<[rs.u8, rs.Ptr]>, rs.vecIntoIter(rs.vecFrom(items.items.slice())), rs.iterPeekable(rs.iterSlice(rs.byteSliceView(data))), undefined as rs.OptionOf); for (const _41 of rs.byteSliceView(data)) { Holder.step$String_slice_Iter_u8(held, _41, code, out); } @@ -11676,7 +11676,7 @@ export function held$u16_iters_held_fields_closure0(data: rs.ByteSlice, make: (a const _17: [rs.u8, rs.u8] = [option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0), option_unwrapOr$1fr498u(rs.byteSliceLast(data), 0)]; const lo: rs.u8 = _17[0]; const hi: rs.u8 = _17[1]; - const held: Holder.Holder> = new Holder.Holder(rs.iterSlice(items.items) as rs.Iter>, rs.btreeRange(tree, new rs.RangeInclusive(rs.min(lo, hi), rs.max(lo, hi), rs.u8Step)) as rs.Iter<[rs.u8, rs.Ptr]>, rs.vecIntoIter(rs.vecFrom(items.items.slice())), rs.iterPeekable(rs.iterSlice(rs.byteSliceView(data))), undefined); + const held: Holder.Holder> = new Holder.Holder(rs.iterSlice(items.items) as rs.Iter>, rs.btreeRange(tree, new rs.RangeInclusive(rs.min(lo, hi), rs.max(lo, hi), rs.u8Step)) as rs.Iter<[rs.u8, rs.Ptr]>, rs.vecIntoIter(rs.vecFrom(items.items.slice())), rs.iterPeekable(rs.iterSlice(rs.byteSliceView(data))), undefined as rs.OptionOf); for (const _41 of rs.byteSliceView(data)) { Holder.step$u16_slice_Iter_u8(held, _41, code, out); } From 6f8fd79c93208ee49f20a624ccc2e339941a7c3d Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 16:35:52 -0800 Subject: [PATCH 5/7] wip(ts): unit option fields, checkpoint of the interrupted quest session Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.txt | 1 + ts/fixtures/reject/expected.txt | 1 + ts/fixtures/reject/src/values.rs | 14 ++++++++++++++ ts/fixtures/reject/ts.toml | 1 + ts/src/lower/mod.rs | 26 +++++++++++++++++--------- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 46db551e5..ed8f84f9b 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -284,6 +284,7 @@ fixtures/reject/src/values.rs:float+1:2: arithmetic on `f16` fixtures/reject/src/values.rs:generic_nested+1:6: `values::Maybe>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:generic_nested+1:48: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:generic_uninit+1:2: `values::Maybe>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases +fixtures/reject/src/values.rs:generic_uninit_byte+1:2: `values::MaybeByte` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:lists+1:4: `std::collections::LinkedList::::retain` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/values.rs:manual_impls+2:2: unsupported call to ` as std::fmt::Debug>::fmt` fixtures/reject/src/values.rs:manual_impls+2:25: unsupported call to ` as std::cmp::PartialEq>::eq` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index f20064674..4843c7611 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -285,6 +285,7 @@ fixtures/reject/src/values.rs:float+1:2: arithmetic on `f16` fixtures/reject/src/values.rs:generic_nested+1:6: `values::Maybe>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:generic_nested+1:48: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:generic_uninit+1:2: `values::Maybe>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases +fixtures/reject/src/values.rs:generic_uninit_byte+1:2: `values::MaybeByte` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:lists+1:4: `std::collections::LinkedList::::retain` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/values.rs:manual_impls+2:11: unsupported call to ` as std::fmt::Debug>::fmt` fixtures/reject/src/values.rs:manual_impls+2:25: unsupported call to ` as std::cmp::PartialEq>::eq` diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 923588bc2..9d74ebfbc 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -34,6 +34,20 @@ pub fn generic_nested(unit: bool) -> bool { unit } +/// A generic type's `Option>` is `T | rs.None`, as a `MaybeUninit` can be +/// `undefined`, but for a byte, whose `None` is `undefined`. +pub struct MaybeByte { + pub value: Option>, +} + +pub fn generic_uninit_byte(b: u8) -> bool { + MaybeByte { + value: Some(std::mem::MaybeUninit::new(b)), + } + .value + .is_some() +} + /// Parsing an address is not translated yet. pub fn addr_parsed(s: &str) -> Result { s.parse() diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 5d929d01f..cd2fd7ae8 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -206,6 +206,7 @@ roots = [ "values::float", "values::generic_nested", "values::generic_uninit", + "values::generic_uninit_byte", "values::lists", "values::manual_impls", "values::nested", diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 1f084ebd8..1dae54bf0 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1987,16 +1987,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// Why an instance of the crate's generic `adt` with `args` can't hold its fields' values, /// if it can't: an `Option` or a `Poll` over a type parameter whose `None` or `Pending` - /// this instance makes `rs.None` or `rs.OuterPending`, where its definition types it - /// `T | undefined` or `rs.Poll`, or where an `rs.OptionOf` or `rs.PollOf` - /// would decide otherwise from TypeScript's type of `T`, or which can't tell its values - /// apart at all. + /// this instance makes other than its definition types it, as an `rs.OptionOf` or + /// `rs.PollOf` decides by TypeScript's type of `T`, and otherwise by the definition's + /// own parameter, or which can't tell its values apart at all. fn generic_erasure(&mut self, adt: AdtDef<'tcx>, args: GenericArgsRef<'tcx>, span: Span) -> Option { let tcx = self.tcx; - if !args.types().any(|t| { - let erased = self.erases_to(t); - erased.undefined || erased.pending - }) { + if args.has_param() { return None; } let ty = Ty::new_adt(tcx, adt, args); @@ -2058,7 +2054,19 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { field.name ) }), - _ => None, + // A `MaybeUninit`, which can be `undefined` but for a byte. + _ if option => (!at.undefined).then(|| { + format!( + "`{ty}` is not supported: its field `{}` is `Option<{inner}>`, typed `{inner} | rs.None`, whose `None` would be `undefined` as `Option<{instance}>`", + field.name + ) + }), + _ => (!at.pending).then(|| { + format!( + "`{ty}` is not supported: its field `{}` is `Poll<{inner}>`, typed `{inner} | rs.OuterPending`, whose `Pending` would be `rs.Pending` as `Poll<{instance}>`", + field.name + ) + }), }; if msg.is_some() { return msg; From c47349286b4ba628602a3a18840fc969b575e589 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 17:00:04 -0800 Subject: [PATCH 6/7] fix(ts): check a generic type's byte MaybeUninit option field per field An instance with an open parameter, such as a field's ByteAnd, still decides its other fields' None, so skip only the fields over that parameter. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.txt | 1 + ts/fixtures/reject/expected.txt | 1 + ts/fixtures/reject/src/values.rs | 22 ++++++++++++++++++++++ ts/fixtures/reject/ts.toml | 1 + ts/src/lower/mod.rs | 7 ++++--- 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index b6e1acfe8..b48ffe08e 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -271,6 +271,7 @@ fixtures/reject/src/strs.rs:str_bytes_mut+1:13: unsupported call to `core::str:: fixtures/reject/src/strs.rs:str_bytes_mut+2:13: `core::str::::get_unchecked_mut` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/strs.rs:str_from_bytes_mut+1:20: `std::str::from_utf8_mut` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/values.rs:Bits+0:1: union `values::Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart +fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 257c65d76..f68bb5dca 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -271,6 +271,7 @@ fixtures/reject/src/strs.rs:cow_made+2:53: constructing `std::borrow::Cow<'_, st fixtures/reject/src/strs.rs:str_bytes_mut+1:13: unsupported call to `core::str::::as_bytes_mut` fixtures/reject/src/strs.rs:str_bytes_mut+2:13: `core::str::::get_unchecked_mut` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/strs.rs:str_from_bytes_mut+1:20: `std::str::from_utf8_mut` with a mutable reference to a value JS copies, which its mapping would copy +fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index d0399d5c1..bbd89ecac 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -48,6 +48,28 @@ pub fn generic_uninit_byte(b: u8) -> bool { .is_some() } +/// The same in a generic type's field, whose other parameter is still open. +pub struct ByteAnd { + pub value: Option>, + pub other: U, +} + +pub struct HoldsByte { + pub held: ByteAnd, +} + +pub fn generic_uninit_byte_open(b: u8) -> bool { + HoldsByte { + held: ByteAnd { + value: Some(std::mem::MaybeUninit::new(b)), + other: 1u32, + }, + } + .held + .value + .is_some() +} + /// Parsing an address is not translated yet. pub fn addr_parsed(s: &str) -> Result { s.parse() diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 66bfc60f4..151d9e4ef 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -206,6 +206,7 @@ roots = [ "values::generic_nested", "values::generic_uninit", "values::generic_uninit_byte", + "values::generic_uninit_byte_open", "values::lists", "values::manual_impls", "values::nested", diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index c9c963b8e..e881cc67e 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1986,9 +1986,6 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// own parameter, or which can't tell its values apart at all. fn generic_erasure(&mut self, adt: AdtDef<'tcx>, args: GenericArgsRef<'tcx>, span: Span) -> Option { let tcx = self.tcx; - if args.has_param() { - return None; - } let ty = Ty::new_adt(tcx, adt, args); for field in adt.all_fields() { let generic = tcx.type_of(field.did).instantiate_identity().skip_norm_wip(); @@ -2013,6 +2010,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { field.name )); } + // Over a parameter of the instance's own, which its instances decide. + if held.has_param() { + continue; + } let instance = ty::EarlyBinder::bind(tcx, inner).instantiate(tcx, args).skip_norm_wip(); let at = self.erases_to(instance); let defined = self.ty(inner, span); From 1a1c60cbaa177cb18295a7db059dd87936e5e6b2 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 17:23:54 -0800 Subject: [PATCH 7/7] test(ts): snapshot inlines' Stack with its Option items an rs.OptionOf Co-Authored-By: Claude Opus 5.5 --- .../lang/inlines/expected/inlines.Stack.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ts/fixtures/lang/inlines/expected/inlines.Stack.ts b/ts/fixtures/lang/inlines/expected/inlines.Stack.ts index 6d74fd602..9c789eed1 100644 --- a/ts/fixtures/lang/inlines/expected/inlines.Stack.ts +++ b/ts/fixtures/lang/inlines/expected/inlines.Stack.ts @@ -7,10 +7,10 @@ import * as Text from "./inlines.Text.ts"; /** At most `N` values, held inline, which logs its capacity and length as it drops. */ export class Stack { - items: Array; + items: rs.OptionOf[]; len: rs.usize; - constructor(items: Array, len: rs.usize) { + constructor(items: rs.OptionOf[], len: rs.usize) { this.items = items; this.len = len; } @@ -19,37 +19,37 @@ export class Stack { export function new$String_0(): Stack { return new Stack(rs.arrayFromFn(0, (_2: rs.usize): string | undefined => { return undefined; - }), 0); + }) as rs.OptionOf[], 0); } export function new$String_2(): Stack { return new Stack(rs.arrayFromFn(2, (_2: rs.usize): string | undefined => { return undefined; - }), 0); + }) as rs.OptionOf[], 0); } export function new$inlines_Stack_u8_1_2(): Stack> { return new Stack(rs.arrayFromFn(2, (_2: rs.usize): Stack | undefined => { return undefined; - }), 0); + }) as rs.OptionOf>[], 0); } export function new$inlines_Text_2_4(): Stack { return new Stack(copy$array_Option_inlines_Text_2(rs.arrayFromFn(4, (_2: rs.usize): Text.Text | undefined => { return undefined; - })), 0); + })) as rs.OptionOf[], 0); } export function new$u8_1(): Stack { return new Stack(copy$array_Option_u8(rs.arrayFromFn(1, (_2: rs.usize): rs.u8 | undefined => { return undefined; - })), 0); + })) as rs.OptionOf[], 0); } export function new$u8_3(): Stack { return new Stack(copy$array_Option_u8(rs.arrayFromFn(3, (_2: rs.usize): rs.u8 | undefined => { return undefined; - })), 0); + })) as rs.OptionOf[], 0); } /** `t`, or it back once full. */