From 737f0a94104463457b950e8a5642d2580ccc6aa7 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:35:43 -0800 Subject: [PATCH 1/3] chore(quest): plan the rest of std::future and std::task Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 + quest/m3/ts-future-task.md | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 quest/m3/ts-future-task.md diff --git a/quest/m3/README.md b/quest/m3/README.md index e26744448..a4d162942 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -24,6 +24,7 @@ generic std gap is planned here even when moq never calls it. - [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library - [A slice of one byte behind any &mut](/quest/m3/ts-slice-of-one.md) - `slice::from_mut` and `array::from_mut` of a `&mut u8` translate - [Debug of std's iterator adaptors](/quest/m3/ts-iter-adaptor-debug.md) - `Debug` of `Map`, `Zip`, `Peekable`, `DecodeUtf16`, and the other adaptors writes what std's does +- [The rest of std::future and std::task](/quest/m3/ts-future-task.md) - `future::ready`, `Debug` and `Hash` of a `Poll`, and `Waker::clone_from` translate - [Waiting: a slice object for a mutable part of other values](/quest/m3/ts-slice-object.md) - waits on a moq profile showing the `Proxy` view's traps matter, then decides on an `rt.ByteSlice`-like form - [Port the workflow guard to kixelated/quest](/quest/m3/quest-workflow-guard.md) - quest's environment guard parses YAML as this repository's does - [A vector's exact capacity as it shrinks](/quest/m3/ts-vec-exact-capacity.md) - a deque made from a `Vec` splits its slices where std's does diff --git a/quest/m3/ts-future-task.md b/quest/m3/ts-future-task.md new file mode 100644 index 000000000..3d08f3313 --- /dev/null +++ b/quest/m3/ts-future-task.md @@ -0,0 +1,14 @@ +# [M] The rest of std::future and std::task + +## Goal + +[rust2-ts](/ts/README.md) translates the stable public API of `std::future` and `std::task` +that it still refuses: `future::ready` and its `Ready`, `Debug` of a `Poll` and of std's +futures, `Poll::from`, hashing a `Poll`, a `Pending`'s clone, and `Waker::clone_from`. +Differential tests against native Rust cover a `Ready`'s drops and its panics, a `Pending` +that never completes, and `Debug` plain and with `#`. + +## Plan + +Found by [#519](https://github.com/moq-dev/rust2/pull/519). No consumer calls these; the +maintainer plans all of std regardless. From f961810c8c381ca9b80d7da80a2f0b2ad8dfc9e5 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:36:09 -0800 Subject: [PATCH 2/3] feat(ts): translate the rest of std::future and std::task Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 2 +- quest/m3/ts-future-task.md | 14 - quest/m3/ts-raw-waker.md | 25 + ts/Cargo.lock | 4 + ts/README.md | 8 +- ts/fixtures/lang/futures/Cargo.toml | 14 + .../lang/futures/expected/core/option.ts | 22 + .../lang/futures/expected/core/task/poll.ts | 35 ++ .../lang/futures/expected/futures.Count.ts | 15 + .../lang/futures/expected/futures.Later.ts | 14 + .../lang/futures/expected/futures.Logged.ts | 27 + ts/fixtures/lang/futures/expected/futures.ts | 577 ++++++++++++++++++ ts/fixtures/lang/futures/expected/harness.ts | 64 ++ ts/fixtures/lang/futures/expected/lib.ts | 21 + ts/fixtures/lang/futures/src/futures.rs | 272 +++++++++ ts/fixtures/lang/futures/src/lib.rs | 19 + ts/fixtures/lang/futures/ts.toml | 2 + ts/fixtures/reject/census.txt | 2 + ts/fixtures/reject/expected.txt | 2 + ts/fixtures/reject/src/futures.rs | 10 + ts/fixtures/reject/ts.toml | 2 + ts/runtime/rt/task.ts | 75 ++- ts/src/lower/collections.rs | 5 + ts/src/lower/consts.rs | 1 + ts/src/lower/drop.rs | 4 +- ts/src/lower/fmt.rs | 19 +- ts/src/lower/library.rs | 7 +- ts/src/lower/mir/call.rs | 36 +- ts/src/lower/mir/rvalue.rs | 1 + ts/src/lower/mod.rs | 23 +- ts/src/lower/task.rs | 20 +- ts/tests/fixtures.rs | 1 + 32 files changed, 1305 insertions(+), 38 deletions(-) delete mode 100644 quest/m3/ts-future-task.md create mode 100644 quest/m3/ts-raw-waker.md create mode 100644 ts/fixtures/lang/futures/Cargo.toml create mode 100644 ts/fixtures/lang/futures/expected/core/option.ts create mode 100644 ts/fixtures/lang/futures/expected/core/task/poll.ts create mode 100644 ts/fixtures/lang/futures/expected/futures.Count.ts create mode 100644 ts/fixtures/lang/futures/expected/futures.Later.ts create mode 100644 ts/fixtures/lang/futures/expected/futures.Logged.ts create mode 100644 ts/fixtures/lang/futures/expected/futures.ts create mode 100644 ts/fixtures/lang/futures/expected/harness.ts create mode 100644 ts/fixtures/lang/futures/expected/lib.ts create mode 100644 ts/fixtures/lang/futures/src/futures.rs create mode 100644 ts/fixtures/lang/futures/src/lib.rs create mode 100644 ts/fixtures/lang/futures/ts.toml diff --git a/quest/m3/README.md b/quest/m3/README.md index a4d162942..6745df54d 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -24,7 +24,7 @@ generic std gap is planned here even when moq never calls it. - [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library - [A slice of one byte behind any &mut](/quest/m3/ts-slice-of-one.md) - `slice::from_mut` and `array::from_mut` of a `&mut u8` translate - [Debug of std's iterator adaptors](/quest/m3/ts-iter-adaptor-debug.md) - `Debug` of `Map`, `Zip`, `Peekable`, `DecodeUtf16`, and the other adaptors writes what std's does -- [The rest of std::future and std::task](/quest/m3/ts-future-task.md) - `future::ready`, `Debug` and `Hash` of a `Poll`, and `Waker::clone_from` translate +- [Wakers made from a RawWaker](/quest/m3/ts-raw-waker.md) - `Waker::from_raw` and the rest of the raw waker API translate, each vtable call where std makes it - [Waiting: a slice object for a mutable part of other values](/quest/m3/ts-slice-object.md) - waits on a moq profile showing the `Proxy` view's traps matter, then decides on an `rt.ByteSlice`-like form - [Port the workflow guard to kixelated/quest](/quest/m3/quest-workflow-guard.md) - quest's environment guard parses YAML as this repository's does - [A vector's exact capacity as it shrinks](/quest/m3/ts-vec-exact-capacity.md) - a deque made from a `Vec` splits its slices where std's does diff --git a/quest/m3/ts-future-task.md b/quest/m3/ts-future-task.md deleted file mode 100644 index 3d08f3313..000000000 --- a/quest/m3/ts-future-task.md +++ /dev/null @@ -1,14 +0,0 @@ -# [M] The rest of std::future and std::task - -## Goal - -[rust2-ts](/ts/README.md) translates the stable public API of `std::future` and `std::task` -that it still refuses: `future::ready` and its `Ready`, `Debug` of a `Poll` and of std's -futures, `Poll::from`, hashing a `Poll`, a `Pending`'s clone, and `Waker::clone_from`. -Differential tests against native Rust cover a `Ready`'s drops and its panics, a `Pending` -that never completes, and `Debug` plain and with `#`. - -## Plan - -Found by [#519](https://github.com/moq-dev/rust2/pull/519). No consumer calls these; the -maintainer plans all of std regardless. diff --git a/quest/m3/ts-raw-waker.md b/quest/m3/ts-raw-waker.md new file mode 100644 index 000000000..6b0439f3e --- /dev/null +++ b/quest/m3/ts-raw-waker.md @@ -0,0 +1,25 @@ +# [M] Wakers made from a RawWaker + +## Goal + +[rust2-ts](/ts/README.md) translates a waker built by hand: `RawWakerVTable::new`, +`RawWaker::new`, `Waker::from_raw`, `Waker::new`, `RawWaker::from` an `Arc` of a `Wake` +impl, and `==` of two vtables. Each clone, wake, and drop calls the vtable's function as +std's does, proven by differential tests against native Rust that log each call. + +## Plan + +Found while closing the rest of `std::future` and `std::task`. No consumer builds a raw +waker yet; the maintainer plans all of std regardless. + +- `rt.Waker` already keys its identity on `data` and `vtable`. A vtable could be a frozen + object of its four functions, and a raw waker a class that calls them, with `data` the + `*const ()` identity a waker's data already is. +- The vtable's functions are `unsafe fn` pointers in a `static`, so this may lean on + [unsafe code the translator can model](/quest/m3/ts-unsafe.md). +- `Debug` of a `Waker`, a `Context`, a `RawWaker`, and a `RawWakerVTable` writes + addresses, which JS has no counterpart of: those stay refused. + +## Related + +- [Unsafe code the translator can model](/quest/m3/ts-unsafe.md) - the `unsafe fn` pointers a vtable holds diff --git a/ts/Cargo.lock b/ts/Cargo.lock index 55c314d64..2482f81d4 100644 --- a/ts/Cargo.lock +++ b/ts/Cargo.lock @@ -287,6 +287,10 @@ version = "0.0.0" name = "lang-folds-fixture" version = "0.0.0" +[[package]] +name = "lang-futures-fixture" +version = "0.0.0" + [[package]] name = "lang-hashes-fixture" version = "0.0.0" diff --git a/ts/README.md b/ts/README.md index 02414ecef..4e8ddd4ad 100644 --- a/ts/README.md +++ b/ts/README.md @@ -142,8 +142,8 @@ crate translation reaches is refused, and so are two versions of one crate it re | `AtomicU32` and the others of `bool` and integers, `AtomicPtr` | `rt.Atomic`, the `rt.Place` of its value; an `atomic::Ordering` is its discriminant, and one an operation can't take panics as std's does. An `AtomicPtr` compares pointers only to what JS keeps as an object or bytes, as raw pointers do | | `async fn` | a function returning `rt.Future`: a class of its own, its coroutine's state machine | | async closure | a closure returning the future of each call, with what makes the future of a call that consumes it if it owns a value that drops and `Fn` or `FnMut` call it | -| `Poll`, `Waker`, `Context` | `T \| typeof rt.Pending`; `T \| rt.OuterPending` where `T` can already be pending, as `Poll>` can, so `Ready(Pending)` keeps apart from `Pending`; `rt.Waker`, which is also the context, and whose `data` and `vtable` are its identity, which `Waker::data`, `Waker::vtable`, and `will_wake` read | -| `pin!(x)`, `poll_fn(f)`, `future::pending()` | `x`; `rt.PollFn`, which drops `f` with itself; `rt.NEVER` | +| `Poll`, `Waker`, `Context` | `T \| typeof rt.Pending`; `T \| rt.OuterPending` where `T` can already be pending, as `Poll>` can, so `Ready(Pending)` keeps apart from `Pending`; `rt.Waker`, which is also the context, and whose `data` and `vtable` are its identity, which `Waker::data`, `Waker::vtable`, and `will_wake` read, and `clone_from` keeps where the waker it has `will_wake` the source | +| `pin!(x)`, `poll_fn(f)`, `future::pending()`, `future::ready(v)` | `x`; `rt.PollFn`, which drops `f` with itself; `rt.NEVER`, which every `Pending` is; `rt.Ready`, whose first poll or `into_inner` takes `v` and a later one panics, and which drops `v` with itself unless one took it | | `Box::pin(x)`, `dyn Future` | `x`; `rt.Future`: an `async` body or std's future as it is, and another future as an `rt.DynFuture` over its `poll`, which drops it with itself unless borrowed | | `bytes::Bytes` | `rt.ByteSlice`, as `&[u8]` is: its clones and slices share bytes that never change | | `bytes::BytesMut` | `rt.BytesMut`: a window of storage, which what it splits off and freezes share without overlapping | @@ -438,7 +438,7 @@ A collection keeps Rust's key semantics. A hash map or set keeps a key that is a number, string, or boolean in a JS `Map`, which compares those as Rust's `Eq` does, and finds any other key, a 64-bit one past 2^53 included, through its translated `Hash` and `Eq`; a B-tree orders every key by its translated `Ord`. std's impls are the runtime's: an -`Option` orders `None` first, an `Arc` compares, orders, and hashes as what it holds, +`Option` orders `None` first, an `Option` and a `Poll` hash their variant and then what they hold, an `Arc` compares, orders, and hashes as what it holds, so a map of `Arc` finds a `&str`, a `Cell` and a `RefCell` compare and order as what they hold, a `RefCell` borrowing each, a one-time cell compares its `Option`, and arrays, slices, and vectors, of bytes or of other elements, order by their elements and then their length, and compare and hash alike @@ -969,7 +969,7 @@ method taking a closure tells it, or its accumulator or what it finds, apart fro or dropping the closure or passing them to std's iterators outside the crate's own types, or a drop of the accumulator or what it finds, or whose own `find`, `rfind`, or `is_sorted_by` gives its closure a reference to a `[u8; N]`, -a pointer's address (`{:p}`), `write!` with a placeholder into +a pointer's address (`{:p}`) and `Debug` of a `Waker` or a `Context`, which writes addresses, `write!` with a placeholder into the crate's own `fmt::Write`, a `str` pattern other than a `char`, a `&str`, a predicate behind any references and boxes, or chars, a mutable `str` slice, a copy of a closure that changes what it captured, `mem::swap`, `replace`, or `take` of a reference's whole pointee, diff --git a/ts/fixtures/lang/futures/Cargo.toml b/ts/fixtures/lang/futures/Cargo.toml new file mode 100644 index 000000000..c91e92040 --- /dev/null +++ b/ts/fixtures/lang/futures/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lang-futures-fixture" +description = "A part of the language rust2-ts translates, exercised against native Rust" +version = "0.0.0" +edition.workspace = true +license.workspace = true +publish = false + +[lib] +# The cdylib is the native side of the differential test, built for wasm32. +crate-type = ["lib", "cdylib"] + +[lints] +workspace = true diff --git a/ts/fixtures/lang/futures/expected/core/option.ts b/ts/fixtures/lang/futures/expected/core/option.ts new file mode 100644 index 000000000..9929fc6cc --- /dev/null +++ b/ts/fixtures/lang/futures/expected/core/option.ts @@ -0,0 +1,22 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; + +export function isSomeAnd$09tpccf(self: rt.u8 | undefined, f: (a0: rt.u8) => boolean): boolean { + if (self === undefined) { + return false; + } + return f(self); +} + +export function filter$0v80fk8(self: rt.u8 | undefined, predicate: (a0: rt.u8) => boolean): rt.u8 | undefined { + if (self !== undefined) { + const x: rt.u8 = self; + if (predicate(x)) { + return x; + } + } + return undefined; +} + +//# sourceMappingURL=option.ts.map diff --git a/ts/fixtures/lang/futures/expected/core/task/poll.ts b/ts/fixtures/lang/futures/expected/core/task/poll.ts new file mode 100644 index 000000000..029d335a0 --- /dev/null +++ b/ts/fixtures/lang/futures/expected/core/task/poll.ts @@ -0,0 +1,35 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../../rt.ts"; +import type * as Logged from "../../futures.Logged.ts"; + +export function map$0fx224j(self: rt.Poll, f: (a0: rt.u8 | undefined) => rt.u8 | undefined): rt.Poll { + if (self !== rt.Pending) { + return f(self); + } + return rt.Pending; +} + +export function isReady$1tqk3za(self: rt.Poll): boolean { + if (self !== rt.Pending) { + return true; + } + return false; +} + +export function isReady$1ntq2i8(self: rt.Poll): boolean { + if (self !== rt.Pending) { + return true; + } + return false; +} + +export function isPending$1405yze(self: rt.Poll): boolean { + return !isReady$1tqk3za(self); +} + +export function isPending$0vrdvv1(self: rt.Poll): boolean { + return !isReady$1ntq2i8(self); +} + +//# sourceMappingURL=poll.ts.map diff --git a/ts/fixtures/lang/futures/expected/futures.Count.ts b/ts/fixtures/lang/futures/expected/futures.Count.ts new file mode 100644 index 000000000..0551e2902 --- /dev/null +++ b/ts/fixtures/lang/futures/expected/futures.Count.ts @@ -0,0 +1,15 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** Counts the wakes through it. */ +export interface Count { + _0: rt.Atomic; +} + +export function wake$task_Wake(self: rt.Arc): void { + rt.atomicFetch(self.value._0, rt.u8WrappingAdd, 1, 0); + self[Symbol.dispose](); +} + +//# sourceMappingURL=futures.Count.ts.map diff --git a/ts/fixtures/lang/futures/expected/futures.Later.ts b/ts/fixtures/lang/futures/expected/futures.Later.ts new file mode 100644 index 000000000..d4aae532c --- /dev/null +++ b/ts/fixtures/lang/futures/expected/futures.Later.ts @@ -0,0 +1,14 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** The crate's own `IntoFuture`, whose future is std's `Ready`. */ +export interface Later { + _0: rt.u8; +} + +export function intoFuture$future_IntoFuture(self: Later): rt.Ready { + return new rt.Ready(rt.u8WrappingAdd(self._0, 1)); +} + +//# sourceMappingURL=futures.Later.ts.map diff --git a/ts/fixtures/lang/futures/expected/futures.Logged.ts b/ts/fixtures/lang/futures/expected/futures.Logged.ts new file mode 100644 index 000000000..436be604b --- /dev/null +++ b/ts/fixtures/lang/futures/expected/futures.Logged.ts @@ -0,0 +1,27 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** A byte that writes itself into the log as it drops. */ +export class Logged { + _0: rt.u8; + _1: rt.Arc>; + + constructor(_0: rt.u8, _1: rt.Arc>) { + this._0 = _0; + this._1 = _1; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + this._1[Symbol.dispose](); + } +} + +export function drop$ops_Drop(self: Logged): void { + const _5: rt.RefCell = rt.refCellBorrowMut(self._1.value); + rt.byteVecPush(_5.value, self._0); + rt.refMutRelease(_5); +} + +//# sourceMappingURL=futures.Logged.ts.map diff --git a/ts/fixtures/lang/futures/expected/futures.ts b/ts/fixtures/lang/futures/expected/futures.ts new file mode 100644 index 000000000..1d1f50349 --- /dev/null +++ b/ts/fixtures/lang/futures/expected/futures.ts @@ -0,0 +1,577 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { drop$task_Poll_futures_Logged } from "./lib.ts"; +import { isSomeAnd$09tpccf, filter$0v80fk8 } from "./core/option.ts"; +import { map$0fx224j as poll_map$0fx224j, isReady$1ntq2i8, isPending$1405yze, isPending$0vrdvv1 } from "./core/task/poll.ts"; +import * as Count from "./futures.Count.ts"; +import * as Later from "./futures.Later.ts"; +import * as Logged from "./futures.Logged.ts"; +import { put, text } from "./harness.ts"; + +/** The futures and polls `kind` picks. */ +export function futures(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + const _4: rt.u8 = rt.u8Rem(kind, 8); + if (_4 === 0) { + readies(data, out); + } else if (_4 === 1) { + dropped(data, out); + } else if (_4 === 2) { + repolled(data, out); + } else if (_4 === 3) { + reread(data, out); + } else if (_4 === 4) { + pendings(data, out); + } else if (_4 === 5) { + polls(data, out); + } else if (_4 === 6) { + hashes(data, out); + } else { + wakers(data, out); + } +} + +/** What one poll of `f` gives, with a waker that does nothing. */ +export function pollOnce$dyn_Future_usize(f: rt.Future): rt.Poll { + return f.poll(rt.NOOP_WAKER); +} + +/** What one poll of `f` gives, with a waker that does nothing. */ +export function pollOnce$future_Pending_futures_Logged(f: rt.Future): rt.Poll { + return f.poll(rt.NOOP_WAKER); +} + +/** What one poll of `f` gives, with a waker that does nothing. */ +export function pollOnce$future_Pending_u8(f: rt.Future): rt.Poll { + return f.poll(rt.NOOP_WAKER); +} + +/** What one poll of `f` gives, with a waker that does nothing. */ +export function pollOnce$future_Ready_String(f: rt.Ready): rt.Poll { + return f.poll(rt.NOOP_WAKER); +} + +/** What one poll of `f` gives, with a waker that does nothing. */ +export function pollOnce$future_Ready_Vec_u8(f: rt.Ready): rt.Poll { + return f.poll(rt.NOOP_WAKER); +} + +/** What one poll of `f` gives, with a waker that does nothing. */ +export function pollOnce$future_Ready_futures_Logged(f: rt.Ready): rt.Poll { + return f.poll(rt.NOOP_WAKER); +} + +/** What one poll of `f` gives, with a waker that does nothing. */ +export function pollOnce$future_Ready_u8(f: rt.Ready): rt.Poll { + return f.poll(rt.NOOP_WAKER); +} + +/** What one poll of `f` gives, with a waker that does nothing. */ +export function pollOnce$futures_dropped_closure0(f: rt.Future): rt.Poll { + return f.poll(rt.NOOP_WAKER); +} + +/** What one poll of `f` gives, with a waker that does nothing. */ +export function pollOnce$futures_readies_closure0(f: rt.Future): rt.Poll { + return f.poll(rt.NOOP_WAKER); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$future_Pending_futures_Logged(out: rt.ByteVec, _value: rt.Future): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtWriteStr(f, "Pending")]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtWriteStr(f, "Pending")]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$future_PollFn_futures_pendings_closure0(out: rt.ByteVec, _value: rt.Future): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtWriteStr(f, "PollFn")]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtWriteStr(f, "PollFn")]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$future_Ready_Option_Option_u8(out: rt.ByteVec, value: rt.Ready): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, (value: rt.u8 | undefined | rt.None, f: rt.Formatter) => rt.fmtOption(value, f, (value: rt.u8 | undefined, f: rt.Formatter) => rt.fmtOption(value, f, rt.u8Debug), rt.None))]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, (value: rt.u8 | undefined | rt.None, f: rt.Formatter) => rt.fmtOption(value, f, (value: rt.u8 | undefined, f: rt.Formatter) => rt.fmtOption(value, f, rt.u8Debug), rt.None))]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$future_Ready_Option_u8(out: rt.ByteVec, value: rt.Ready): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, (value: rt.u8 | undefined, f: rt.Formatter) => rt.fmtOption(value, f, rt.u8Debug))]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, (value: rt.u8 | undefined, f: rt.Formatter) => rt.fmtOption(value, f, rt.u8Debug))]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$future_Ready_String(out: rt.ByteVec, value: rt.Ready): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, rt.fmtStr)]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, rt.fmtStr)]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$future_Ready_u8(out: rt.ByteVec, value: rt.Ready): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, rt.u8Debug)]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, rt.u8Debug)]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$future_Ready_unit(out: rt.ByteVec, value: rt.Ready): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, rt.fmtUnit)]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => value.debug(f, rt.fmtUnit)]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$task_Poll_Option_u8(out: rt.ByteVec, value: rt.Poll): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, (value: rt.u8 | undefined, f: rt.Formatter) => rt.fmtOption(value, f, rt.u8Debug))]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, (value: rt.u8 | undefined, f: rt.Formatter) => rt.fmtOption(value, f, rt.u8Debug))]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$task_Poll_Result_u8_String(out: rt.ByteVec, value: rt.Poll>): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, (value: rt.Result, f: rt.Formatter) => rt.fmtResult(value, f, rt.u8Debug, rt.fmtStr))]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, (value: rt.Result, f: rt.Formatter) => rt.fmtResult(value, f, rt.u8Debug, rt.fmtStr))]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$task_Poll_String(out: rt.ByteVec, value: rt.Poll): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, rt.fmtStr)]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, rt.fmtStr)]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$task_Poll_array2_u8(out: rt.ByteVec, value: rt.Poll): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, (value: Uint8Array, f: rt.Formatter) => rt.fmtList(value, f, rt.u8Debug))]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, (value: Uint8Array, f: rt.Formatter) => rt.fmtList(value, f, rt.u8Debug))]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$task_Poll_task_Poll_u8(out: rt.ByteVec, value: rt.Poll | rt.OuterPending): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, (value: rt.Poll, f: rt.Formatter) => rt.fmtPoll(value, f, rt.u8Debug), rt.OuterPending)]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, (value: rt.Poll, f: rt.Formatter) => rt.fmtPoll(value, f, rt.u8Debug), rt.OuterPending)]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$task_Poll_u8(out: rt.ByteVec, value: rt.Poll): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, rt.u8Debug)]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, rt.u8Debug)]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. */ +export function debug$task_Poll_unit(out: rt.ByteVec, value: rt.Poll): void { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, rt.fmtUnit)]; + put(out, rt.fmtFormat([args2[0]])); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(value, f, rt.fmtUnit)]; + put(out, rt.fmtFormat([rt.fmtSpec({ alternate: true }, args4[0])])); +} + +/** + * Each byte through a `Ready`, by a poll, `into_inner`, an `.await`, and a clone, with + * each written before and after it gives its value. + */ +export function readies(data: rt.ByteSlice, out: rt.ByteVec): void { + for (const b of rt.byteSliceView(data)) { + const r: rt.Ready = new rt.Ready(b); + debug$future_Ready_u8(out, r); + const copy: rt.Ready = r.clone(); + const _16: rt.Poll = pollOnce$future_Ready_u8(r); + if (_16 !== rt.Pending) { + rt.byteVecPush(out, _16); + } else { + rt.byteVecPush(out, 238); + } + debug$future_Ready_u8(out, r); + debug$future_Ready_u8(out, r.clone()); + rt.byteVecPush(out, copy.intoInner()); + const pinned: rt.Future = new readiesFuture$closure0(b); + const _33: rt.Poll = pollOnce$futures_readies_closure0(pinned); + if (_33 !== rt.Pending) { + rt.byteVecPush(out, _33); + } else { + rt.byteVecPush(out, 238); + } + rt.drop(pinned); + } + debug$future_Ready_unit(out, new rt.Ready(undefined)); + debug$future_Ready_Option_u8(out, new rt.Ready(rt.byteSliceFirst(data))); + debug$future_Ready_Option_Option_u8(out, new rt.Ready(rt.None as rt.u8 | undefined | rt.None)); + const text_: rt.Ready = new rt.Ready(text(data)); + debug$future_Ready_String(out, text_); + const _52: rt.Poll = pollOnce$future_Ready_String(text_); + if (_52 !== rt.Pending) { + rt.iterExtendBytes(out, rt.strBytes(_52), undefined, true); + } + debug$future_Ready_String(out, text_); + const boxed: rt.Future = new rt.Ready(data.length); + const _67: rt.Poll = pollOnce$dyn_Future_usize(boxed); + if (_67 !== rt.Pending) { + rt.byteVecPush(out, _67 & 0xff); + } + rt.drop(boxed); +} + +export class readiesFuture$closure0 { + #state: number = 0; + #b: rt.u8; + #saved!: rt.u8; + #awaitee!: rt.Ready; + + constructor(b: rt.u8) { + this.#b = b; + } + + [Symbol.dispose](): void { + } + + poll(_2: rt.Context): rt.Poll { + b0: { + if (this.#state === 0) { + this.#awaitee = new rt.Ready(this.#b ^ 90); + } else if (this.#state === 1) { + rt.asyncFnResumed(); + } else if (this.#state !== 3) { + break b0; + } + const _7: rt.Poll = this.#awaitee.poll(_2); + if (_7 !== rt.Pending) { + this.#saved = _7; + this.#awaitee = Later.intoFuture$future_IntoFuture({ _0: this.#b }); + } else { + this.#state = 3; + return rt.Pending; + } + } + const _18: rt.Poll = this.#awaitee.poll(_2); + if (_18 !== rt.Pending) { + const _0: rt.Poll = rt.u8WrappingAdd(this.#saved, _18); + this.#state = 1; + return _0; + } + this.#state = 4; + return rt.Pending; + } +} + +/** + * A `Ready`'s value drops with it unless a poll or `into_inner` gave it away, in order with + * the values around it. + */ +export function dropped(data: rt.ByteSlice, out: rt.ByteVec): void { + let _103: boolean = false; + let _102: boolean = false; + const log: rt.Arc> = new rt.Arc(new rt.RefCell(new rt.ByteVec())); + const iter: rt.Iter<[rt.usize, rt.u8]> = rt.iterEnumerate(rt.iterSlice(rt.byteSliceView(data))); + while (true) { + const _10: [rt.usize, rt.u8] | rt.Done = iter.next(); + if (_10 === rt.DONE) { + break; + } + const i: rt.usize = _10[0]; + const b: rt.u8 = _10[1]; + const before: Logged.Logged = new Logged.Logged(b, rt.arcClone(log)); + const r: rt.Ready = new rt.Ready(new Logged.Logged(b ^ 128, rt.arcClone(log)), rt.drop); + _103 = true; + const _25: rt.Arc> = rt.arcClone(log); + _102 = true; + const after: Logged.Logged = new Logged.Logged(b ^ 64, _25); + const _28: rt.usize = rt.usizeRem(i, 6); + if (_28 !== 0) { + if (_28 === 1) { + _103 = false; + r[Symbol.dispose](); + } else if (_28 === 2) { + const _31: rt.Poll = pollOnce$future_Ready_futures_Logged(r); + if (_31 !== rt.Pending) { + const v: Logged.Logged = _31; + const _39: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecPush(_39.value, 240); + rt.refMutRelease(_39); + v[Symbol.dispose](); + } + const _46: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecPush(_46.value, 241); + rt.refMutRelease(_46); + } else if (_28 === 3) { + _103 = false; + const v2: Logged.Logged = r.intoInner(); + _102 = false; + after[Symbol.dispose](); + const _56: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecPush(_56.value, v2._0); + rt.refMutRelease(_56); + v2[Symbol.dispose](); + } else if (_28 === 4) { + const clone: rt.Ready> = new rt.Ready(new rt.Arc(new Logged.Logged(b ^ 32, rt.arcClone(log))), rt.drop); + const copy: rt.Ready> = clone.clone((a0: rt.Arc): rt.Arc => { + return rt.arcClone(a0); + }); + clone[Symbol.dispose](); + const _71: rt.Arc = copy.intoInner(); + const count: rt.usize = _71.strong; + _71[Symbol.dispose](); + const _75: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecPush(_75.value, count & 0xff); + rt.refMutRelease(_75); + } else { + _103 = false; + const pinned: rt.Future = new droppedFuture$closure0(r); + const _83: rt.Poll = pollOnce$futures_dropped_closure0(pinned); + if (_83 !== rt.Pending) { + const v3: rt.u8 = _83; + const _89: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecPush(_89.value, v3); + rt.refMutRelease(_89); + } + rt.drop(pinned); + } + } + before[Symbol.dispose](); + if (_102) { + after[Symbol.dispose](); + } + _102 = false; + if (_103) { + r[Symbol.dispose](); + } + _103 = false; + } + const _98: rt.RefCell = rt.refCellBorrow(log.value); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_98.value))), undefined, true); + rt.refRelease(_98); + log[Symbol.dispose](); +} + +export class droppedFuture$closure0 { + #state: number = 0; + #r: rt.Ready; + #awaitee!: rt.Ready; + + constructor(r: rt.Ready) { + this.#r = r; + } + + [Symbol.dispose](): void { + if (this.#state === 0) { + this.#r[Symbol.dispose](); + } else if (this.#state === 3) { + this.#awaitee[Symbol.dispose](); + } + } + + poll(_2: rt.Context): rt.Poll { + if (this.#state === 0) { + this.#awaitee = this.#r; + } else if (this.#state === 1) { + rt.asyncFnResumed(); + } + const _6: rt.Poll = this.#awaitee.poll(_2); + if (_6 !== rt.Pending) { + const _3: Logged.Logged = _6; + this.#awaitee[Symbol.dispose](); + const _14: rt.u8 = _3._0; + _3[Symbol.dispose](); + this.#state = 1; + return _14; + } + this.#state = 3; + return rt.Pending; + } +} + +/** A `Ready` polled after giving its value panics, as std's does, as does `into_inner`. */ +export function repolled(data: rt.ByteSlice, out: rt.ByteVec): void { + const r: rt.Ready = new rt.Ready(data.length & 0xff); + const _6: rt.Poll = pollOnce$future_Ready_u8(r); + if (_6 !== rt.Pending) { + rt.byteVecPush(out, _6); + } + if (isSomeAnd$09tpccf(rt.byteSliceFirst(data), (b: rt.u8): boolean => { + return rt.u8Rem(b, 2) === 0; + })) { + rt.byteVecPush(out, isReady$1ntq2i8(pollOnce$future_Ready_u8(r)) ? 1 : 0); + } + rt.byteVecPush(out, r.intoInner()); +} + +/** `into_inner` of a `Ready` a poll already took. */ +export function reread(data: rt.ByteSlice, out: rt.ByteVec): void { + const r: rt.Ready = new rt.Ready(rt.byteSliceToByteVec(data)); + if (rt.usizeRem(data.length, 2) === 0) { + pollOnce$future_Ready_Vec_u8(r); + } + rt.iterExtendBytes(out, rt.byteVecIntoIter(r.intoInner()), undefined, true); +} + +/** + * `pending()` is never ready, however often polled, through a box and cloned, and holds + * nothing to drop; `poll_fn`'s future writes only its name. + */ +export function pendings(data: rt.ByteSlice, out: rt.ByteVec): void { + const log: rt.Arc> = new rt.Arc(new rt.RefCell(new rt.ByteVec())); + const _6: rt.Future = rt.NEVER; + rt.NEVER; + const boxed: rt.Future = rt.NEVER; + for (const _11 of rt.byteSliceView(data)) { + const held: Logged.Logged = new Logged.Logged(_11, rt.arcClone(log)); + const _22: rt.Poll = pollOnce$future_Pending_futures_Logged(_6); + rt.byteVecPush(out, isPending$1405yze(_22) ? 1 : 0); + drop$task_Poll_futures_Logged(_22); + rt.byteVecPush(out, isPending$0vrdvv1(pollOnce$future_Pending_u8(boxed)) ? 1 : 0); + const _38: rt.Poll = pollOnce$future_Pending_futures_Logged(rt.NEVER); + rt.byteVecPush(out, isPending$1405yze(_38) ? 1 : 0); + drop$task_Poll_futures_Logged(_38); + held[Symbol.dispose](); + } + debug$future_Pending_futures_Logged(out, _6); + debug$future_Pending_futures_Logged(out, rt.NEVER); + const captured = data.length; + debug$future_PollFn_futures_pendings_closure0(out, new rt.PollFn((_2: rt.Context): rt.Poll => { + return captured; + })); + undefined; + const _59: rt.RefCell = rt.refCellBorrow(log.value); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_59.value))), undefined, true); + rt.refRelease(_59); + log[Symbol.dispose](); +} + +/** + * `Poll`s written, plain, with `#`, and through a width, which reaches what each holds, + * and made `From` what they hold. + */ +export function polls(data: rt.ByteSlice, out: rt.ByteVec): void { + let q!: rt.Poll; + let r!: rt.Poll>; + for (const b of rt.byteSliceView(data)) { + const p: rt.Poll = b; + if (!(rt.u8Rem(b, 3) === 0)) { + q = b; + } else { + q = rt.Pending; + } + debug$task_Poll_u8(out, p); + debug$task_Poll_u8(out, q); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPoll(q, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtPoll(p, f, rt.u8Debug)]; + put(out, rt.fmtFormat([rt.fmtSpec({ align: ">", width: 4 }, args2[0]), "|", rt.fmtSpec({ align: "<", alternate: true, debugHex: "x", width: 5 }, args2[1]), "|", rt.fmtSpec({ zero: true, width: 8 }, args2[1])])); + debug$task_Poll_Option_u8(out, poll_map$0fx224j(b as rt.Poll, (o: rt.u8 | undefined): rt.u8 | undefined => { + return filter$0v80fk8(o, (v: rt.u8): boolean => { + return rt.u8Rem(v, 2) === 0; + }); + })); + const _38: rt.u8 = rt.u8Rem(b, 3); + if (_38 === 0) { + r = rt.Pending; + } else if (_38 === 1) { + r = rt.Ok(b); + } else { + r = rt.Err(rt.fmtToString((f: rt.Formatter) => rt.u8Display(b, f))); + } + debug$task_Poll_Result_u8_String(out, r); + debug$task_Poll_task_Poll_u8(out, nested(b)); + debug$task_Poll_unit(out, undefined); + debug$task_Poll_String(out, text(data)); + debug$task_Poll_array2_u8(out, Uint8Array.of(b, b ^ 1)); + } +} + +/** A `Poll` of a `Poll`, whose own `Pending` is apart from the one it holds. */ +export function nested(b: rt.u8): rt.Poll | rt.OuterPending { + const _2: rt.u8 = rt.u8Rem(b, 3); + if (_2 === 0) { + return rt.OuterPending; + } + if (_2 === 1) { + return rt.Pending; + } + return b as rt.Poll; +} + +/** + * What hashing a `Poll` writes: its variant, then what it holds, as std's derived `Hash` + * writes them. + */ +export function hashes(data: rt.ByteSlice, out: rt.ByteVec): void { + const polls: rt.Poll[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (_2: rt.u8): rt.Poll => { + const b2: rt.u8 = _2; + if (!(rt.u8Rem(b2, 4) === 0)) { + return b2; + } + return rt.Pending; + }), undefined, 2, true); + for (const _9 of polls) { + const h: rt.Hasher = rt.hasher(); + rt.hashPoll(rt.hash8)(_9, h); + rt.iterExtendBytes(out, rt.iterSlice(rt.u64ToBeBytes(rt.hashFinish(h))), undefined, true); + } + const h2: rt.Hasher = rt.hasher(); + rt.hashSlice(rt.hashPoll(rt.hash8))(polls, h2); + rt.hashPoll(rt.hashOption(rt.hashUsize))(data.length as rt.Poll, h2); + rt.hashPoll(rt.hashOption(rt.hashStr))(rt.Pending, h2); + for (const _35 of rt.byteSliceView(data)) { + const _41: rt.Poll | rt.OuterPending = nested(_35); + rt.hashPoll(rt.hashPoll(rt.hash8), rt.OuterPending)(_41, h2); + } + rt.iterExtendBytes(out, rt.iterSlice(rt.u64ToBeBytes(rt.hashFinish(h2))), undefined, true); +} + +/** + * A waker cloned over another by `clone_from` takes the source's place, unless both would + * wake the same task, when std keeps the one it had. + */ +export function wakers(data: rt.ByteSlice, out: rt.ByteVec): void { + let source!: rt.Waker; + const a: rt.Arc = new rt.Arc({ _0: new rt.Atomic(0) }); + const b: rt.Arc = new rt.Arc({ _0: new rt.Atomic(0) }); + let w: rt.Waker = new rt.ArcWaker(rt.arcClone(a), Count.wake$task_Wake as (a0: rt.Arc) => undefined, (arc: rt.Arc) => arc[Symbol.dispose]()); + for (const _14 of rt.byteSliceView(data)) { + const _19: rt.u8 = rt.u8Rem(_14, 3); + if (_19 === 0) { + source = new rt.ArcWaker(rt.arcClone(a), Count.wake$task_Wake as (a0: rt.Arc) => undefined, (arc3: rt.Arc) => arc3[Symbol.dispose]()); + } else if (_19 === 1) { + source = new rt.ArcWaker(rt.arcClone(b), Count.wake$task_Wake as (a0: rt.Arc) => undefined, (arc2: rt.Arc) => arc2[Symbol.dispose]()); + } else { + source = rt.NOOP_WAKER.clone(); + } + const _28: rt.Waker = source; + if (!rt.willWake(w, _28)) { + const old = w; + w = _28.clone(); + old[Symbol.dispose](); + } + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(a.strong & 0xff, b.strong & 0xff)), undefined, true); + rt.byteVecPush(out, rt.willWake(w, source) ? 1 : 0); + w.wakeByRef(); + source[Symbol.dispose](); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(a.strong & 0xff, b.strong & 0xff)), undefined, true); + } + w[Symbol.dispose](); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(rt.atomicLoad(a.value._0, 0), rt.atomicLoad(b.value._0, 0))), undefined, true); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(a.strong & 0xff, b.strong & 0xff)), undefined, true); + b[Symbol.dispose](); + a[Symbol.dispose](); +} + +//# sourceMappingURL=futures.ts.map diff --git a/ts/fixtures/lang/futures/expected/harness.ts b/ts/fixtures/lang/futures/expected/harness.ts new file mode 100644 index 000000000..7c19e53ff --- /dev/null +++ b/ts/fixtures/lang/futures/expected/harness.ts @@ -0,0 +1,64 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { cases } from "./lib.ts"; + +/** Run one case. */ +export function run(input: rt.ByteSlice): rt.ByteVec { + const out: rt.ByteVec = new rt.ByteVec(); + if (!(input.length >= 3)) { + return out; + } + const cases_: [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] = cases(); + cases_[rt.idx(cases_, rt.usizeRem(((input.data[input.start] << 8) | input.data[input.start + 1]) as rt.usize, cases_.length))][1](input.data[input.start + 2], new rt.ByteSlice(input.data, input.start + 3, input.length - 3), out); + return out; +} + +/** + * The first two bytes, as one big-endian number, that pick the case named `name`, which + * `lang.ts` targets by. + */ +export function op(name: string): rt.u16 { + const _11: rt.usize | undefined = rt.iterPosition(rt.iterSlice(cases()), (_2: [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]): boolean => { + return _2[0] === name; + }); + if (_11 !== undefined) { + const _20: rt.Result = rt.u16TryFrom(_11); + if (_20.ok) { + if ((_20.value >> 8) !== 255) { + return _20.value; + } + } + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, name)]; + rt.panicFmt(["case ", args4[0], " is past what two bytes pick"]); + } else { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, name)]; + rt.panicFmt(["no case ", args2[0]]); + } +} + +/** Write `s` and the byte no UTF-8 holds, which ends it. */ +export function put(out: rt.ByteVec, s: string): void { + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(s)); + rt.byteVecPush(out, 255); +} + +/** A char of the alphabet for each byte, by its low bits. */ +export function text(data: rt.ByteSlice): string { + let s: string = ""; + const iterS: Uint8Array = rt.byteSliceView(data); + let iterI: number = 0; + while (true) { + const _5: rt.u8 | rt.Done = iterI < iterS.length ? iterS[iterI++] : rt.DONE; + if (_5 === rt.DONE) { + return s; + } + const _13: rt.usize = rt.u8Rem(_5, 16) as rt.usize; + if (!(_13 < 16)) { + rt.panic("index out of bounds: the len is " + 16 + " but the index is " + _13); + } + s = s + String.fromCodePoint([47, 92, 97, 48, 57, 127, 128, 233, 2047, 2048, 55295, 57344, 65535, 65536, 128512, 1114111][_13]); + } +} + +//# sourceMappingURL=harness.ts.map diff --git a/ts/fixtures/lang/futures/expected/lib.ts b/ts/fixtures/lang/futures/expected/lib.ts new file mode 100644 index 000000000..2bbcf0540 --- /dev/null +++ b/ts/fixtures/lang/futures/expected/lib.ts @@ -0,0 +1,21 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { futures } from "./futures.ts"; +import type * as Logged from "./futures.Logged.ts"; + +export function drop$task_Poll_futures_Logged(_1: rt.Poll): void { + if (_1 !== rt.Pending) { + _1[Symbol.dispose](); + } +} + +/** + * Each case's name and what it runs. The first two bytes, big-endian, pick one modulo + * their count, so a case added at the end moves no other. + */ +export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] { + return [["futures::futures", futures as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; +} + +//# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/lang/futures/src/futures.rs b/ts/fixtures/lang/futures/src/futures.rs new file mode 100644 index 000000000..dcdfa7d37 --- /dev/null +++ b/ts/fixtures/lang/futures/src/futures.rs @@ -0,0 +1,272 @@ +//! std's own futures and what a poll gives: `future::ready`'s, polled, awaited, taken by +//! `into_inner`, cloned, and dropped with its value or after giving it, and polled twice; +//! `future::pending`'s, never ready however often polled; `poll_fn`'s; a `Poll` written, +//! made `From` a value, and hashed; and a waker cloned over another by `clone_from`. + +use std::cell::RefCell; +use std::fmt::Debug; +use std::future::{Future, IntoFuture, Pending, Ready, pending, poll_fn, ready}; +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::pin::{Pin, pin}; +use std::rc::Rc; +use std::sync::Arc; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::task::{Context, Poll, Wake, Waker}; + +/// The futures and polls `kind` picks. +pub fn futures(kind: u8, data: &[u8], out: &mut Vec) { + match kind % 8 { + 0 => readies(data, out), + 1 => dropped(data, out), + 2 => repolled(data, out), + 3 => reread(data, out), + 4 => pendings(data, out), + 5 => polls(data, out), + 6 => hashes(data, out), + _ => wakers(data, out), + } +} + +/// A byte that writes itself into the log as it drops. +struct Logged(u8, Rc>>); + +impl Drop for Logged { + fn drop(&mut self) { + self.1.borrow_mut().push(self.0); + } +} + +/// What one poll of `f` gives, with a waker that does nothing. +fn poll_once(f: Pin<&mut F>) -> Poll { + f.poll(&mut Context::from_waker(Waker::noop())) +} + +/// `value` written by `Debug`, plain and with `#`, apart by a byte no UTF-8 holds. +fn debug(out: &mut Vec, value: &impl Debug) { + crate::harness::put(out, &format!("{value:?}")); + crate::harness::put(out, &format!("{value:#?}")); +} + +/// The crate's own `IntoFuture`, whose future is std's `Ready`. +struct Later(u8); + +impl IntoFuture for Later { + type Output = u8; + type IntoFuture = Ready; + + fn into_future(self) -> Ready { + ready(self.0.wrapping_add(1)) + } +} + +/// Each byte through a `Ready`, by a poll, `into_inner`, an `.await`, and a clone, with +/// each written before and after it gives its value. +fn readies(data: &[u8], out: &mut Vec) { + for &b in data { + let mut r = ready(b); + debug(out, &r); + let copy = r.clone(); + match poll_once(Pin::new(&mut r)) { + Poll::Ready(v) => out.push(v), + Poll::Pending => out.push(0xee), + } + // Taken, which a clone of it keeps. + debug(out, &r); + debug(out, &r.clone()); + out.push(copy.into_inner()); + let awaited = pin!(async move { ready(b ^ 0x5a).await.wrapping_add(Later(b).await) }); + match poll_once(awaited) { + Poll::Ready(v) => out.push(v), + Poll::Pending => out.push(0xee), + } + } + // Of values JS keeps as `undefined`, nests in an `Option`, and holds as text. + debug(out, &ready(())); + debug(out, &ready(data.first().copied())); + debug(out, &ready(None::>)); + let mut text = ready(crate::harness::text(data)); + debug(out, &text); + if let Poll::Ready(s) = poll_once(Pin::new(&mut text)) { + out.extend(s.bytes()); + } + debug(out, &text); + // A `Ready` behind a `dyn Future`, which polls it in place. + let mut boxed: Pin>> = Box::pin(ready(data.len())); + if let Poll::Ready(n) = poll_once(boxed.as_mut()) { + out.push(n as u8); + } +} + +/// A `Ready`'s value drops with it unless a poll or `into_inner` gave it away, in order with +/// the values around it. +fn dropped(data: &[u8], out: &mut Vec) { + let log = Rc::new(RefCell::new(Vec::new())); + for (i, &b) in data.iter().enumerate() { + let before = Logged(b, log.clone()); + let mut r = ready(Logged(b ^ 0x80, log.clone())); + let after = Logged(b ^ 0x40, log.clone()); + match i % 6 { + // Never polled: it drops its value in reverse order of declaration. + 0 => {} + 1 => drop(r), + 2 => { + if let Poll::Ready(v) = poll_once(Pin::new(&mut r)) { + log.borrow_mut().push(0xf0); + drop(v); + } + log.borrow_mut().push(0xf1); + } + 3 => { + let v = r.into_inner(); + drop(after); + log.borrow_mut().push(v.0); + } + 4 => { + let clone = ready(Rc::new(Logged(b ^ 0x20, log.clone()))); + let copy = clone.clone(); + drop(clone); + let count = Rc::strong_count(©.into_inner()); + log.borrow_mut().push(count as u8); + } + // Moved into an `async` block, which awaits it. + _ => { + let awaited = pin!(async move { r.await.0 }); + if let Poll::Ready(v) = poll_once(awaited) { + log.borrow_mut().push(v); + } + } + } + drop(before); + } + out.extend(log.borrow().iter()); +} + +/// A `Ready` polled after giving its value panics, as std's does, as does `into_inner`. +fn repolled(data: &[u8], out: &mut Vec) { + let mut r = ready(data.len() as u8); + if let Poll::Ready(v) = poll_once(Pin::new(&mut r)) { + out.push(v); + } + if data.first().is_some_and(|b| b % 2 == 0) { + out.push(poll_once(Pin::new(&mut r)).is_ready().into()); + } + out.push(r.into_inner()); +} + +/// `into_inner` of a `Ready` a poll already took. +fn reread(data: &[u8], out: &mut Vec) { + let mut r = ready(data.to_vec()); + if data.len() % 2 == 0 { + let _ = poll_once(Pin::new(&mut r)); + } + out.extend(r.into_inner()); +} + +/// `pending()` is never ready, however often polled, through a box and cloned, and holds +/// nothing to drop; `poll_fn`'s future writes only its name. +fn pendings(data: &[u8], out: &mut Vec) { + let log = Rc::new(RefCell::new(Vec::new())); + let mut never: Pending = pending(); + let mut boxed = Box::pin(pending::()); + for &b in data { + let held = Logged(b, log.clone()); + out.push(poll_once(Pin::new(&mut never)).is_pending().into()); + out.push(poll_once(boxed.as_mut()).is_pending().into()); + let mut copy = never.clone(); + out.push(poll_once(Pin::new(&mut copy)).is_pending().into()); + drop(held); + } + debug(out, &never); + debug(out, &never.clone()); + let n = data.len(); + debug(out, &poll_fn(move |_| Poll::Ready(n))); + drop(never); + out.extend(log.borrow().iter()); +} + +/// `Poll`s written, plain, with `#`, and through a width, which reaches what each holds, +/// and made `From` what they hold. +fn polls(data: &[u8], out: &mut Vec) { + for &b in data { + let p = Poll::from(b); + let q: Poll = if b % 3 == 0 { Poll::Pending } else { b.into() }; + debug(out, &p); + debug(out, &q); + crate::harness::put(out, &format!("{q:>4?}|{p:<#5x?}|{p:08?}")); + debug(out, &Poll::Ready(Some(b)).map(|o| o.filter(|v| v % 2 == 0))); + let r: Poll> = match b % 3 { + 0 => Poll::Pending, + 1 => Poll::Ready(Ok(b)), + _ => Poll::Ready(Err(b.to_string())), + }; + debug(out, &r); + debug(out, &nested(b)); + debug(out, &Poll::from(())); + debug(out, &Poll::from(crate::harness::text(data))); + debug(out, &Poll::from([b, b ^ 1])); + } +} + +/// A `Poll` of a `Poll`, whose own `Pending` is apart from the one it holds. +fn nested(b: u8) -> Poll> { + match b % 3 { + 0 => Poll::Pending, + 1 => Poll::Ready(Poll::Pending), + _ => Poll::from(Poll::Ready(b)), + } +} + +/// What hashing a `Poll` writes: its variant, then what it holds, as std's derived `Hash` +/// writes them. +fn hashes(data: &[u8], out: &mut Vec) { + let polls: Vec> = data + .iter() + .map(|&b| if b % 4 == 0 { Poll::Pending } else { Poll::Ready(b) }) + .collect(); + for p in &polls { + let mut h = DefaultHasher::new(); + p.hash(&mut h); + out.extend(h.finish().to_be_bytes()); + } + let mut h = DefaultHasher::new(); + polls.hash(&mut h); + Poll::Ready(Some(data.len())).hash(&mut h); + Poll::>::Pending.hash(&mut h); + for &b in data { + nested(b).hash(&mut h); + } + out.extend(h.finish().to_be_bytes()); +} + +/// Counts the wakes through it. +struct Count(AtomicU8); + +impl Wake for Count { + fn wake(self: Arc) { + self.0.fetch_add(1, Ordering::Relaxed); + } +} + +/// A waker cloned over another by `clone_from` takes the source's place, unless both would +/// wake the same task, when std keeps the one it had. +fn wakers(data: &[u8], out: &mut Vec) { + let a = Arc::new(Count(AtomicU8::new(0))); + let b = Arc::new(Count(AtomicU8::new(0))); + let mut w = Waker::from(a.clone()); + for &byte in data { + let source = match byte % 3 { + 0 => Waker::from(a.clone()), + 1 => Waker::from(b.clone()), + _ => Waker::noop().clone(), + }; + w.clone_from(&source); + out.extend([Arc::strong_count(&a) as u8, Arc::strong_count(&b) as u8]); + out.push(w.will_wake(&source).into()); + w.wake_by_ref(); + drop(source); + out.extend([Arc::strong_count(&a) as u8, Arc::strong_count(&b) as u8]); + } + drop(w); + out.extend([a.0.load(Ordering::Relaxed), b.0.load(Ordering::Relaxed)]); + out.extend([Arc::strong_count(&a) as u8, Arc::strong_count(&b) as u8]); +} diff --git a/ts/fixtures/lang/futures/src/lib.rs b/ts/fixtures/lang/futures/src/lib.rs new file mode 100644 index 000000000..6ff732d8b --- /dev/null +++ b/ts/fixtures/lang/futures/src/lib.rs @@ -0,0 +1,19 @@ +//! A part of the language the driver translates, exercised against native Rust: what +//! each of its modules says. + +mod futures; +#[path = "../../harness.rs"] +pub mod harness; +#[cfg(target_arch = "wasm32")] +#[path = "../../../wasm.rs"] +mod wasm; +#[cfg(target_arch = "wasm32")] +use harness::run as entry; + +use harness::Case; + +/// Each case's name and what it runs. The first two bytes, big-endian, pick one modulo +/// their count, so a case added at the end moves no other. +fn cases() -> Vec<(&'static str, Case)> { + vec![("futures::futures", futures::futures)] +} diff --git a/ts/fixtures/lang/futures/ts.toml b/ts/fixtures/lang/futures/ts.toml new file mode 100644 index 000000000..38a10e75e --- /dev/null +++ b/ts/fixtures/lang/futures/ts.toml @@ -0,0 +1,2 @@ +package = "lang-futures-fixture" +roots = ["harness::run", "harness::op"] diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 461364239..57ecafd66 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -36,6 +36,8 @@ fixtures/reject/src/fmt.rs:written+2:10: `fmt::Arguments` with a placeholder, or fixtures/reject/src/futures.rs:generic_pending+1:2: `futures::Waiting>` is not supported: its field `poll` is `Poll`, typed `rt.Poll`, whose `Pending` would be `rt.OuterPending` as `Poll>` fixtures/reject/src/futures.rs:nested_poll+0:20: `Poll>>>` is not supported: its `Pending` and `Ready` would both be `rt.OuterPending` fixtures/reject/src/futures.rs:pending_output+0:34: a future of `std::task::Poll`, which can be `rt.Pending`, as the future's own `Pending` is +fixtures/reject/src/futures.rs:ready_pending+0:27: a future of `std::task::Poll`, which can be `rt.Pending`, as the future's own `Pending` is +fixtures/reject/src/futures.rs:waker_debug+1:2: `Debug` of `std::task::Waker`, which writes addresses, which JS has no counterpart of fixtures/reject/src/iters.rs:backs+1:8: `DoubleEndedIterator::rfind` of `iters::Backs`, whose closure takes a reference to `[u8; 2]`, which is not supported yet fixtures/reject/src/iters.rs:impl Iterator for Discards+11:3: overriding `Iterator::fold` with a drop of its accumulator, or of the crate's type over it or its closure, which is not supported yet fixtures/reject/src/iters.rs:impl Iterator for Disposes+10:3: overriding `Iterator::fold` with a drop of its accumulator, or of the crate's type over it or its closure, which is not supported yet diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index f261a1624..500c0c328 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -39,6 +39,8 @@ fixtures/reject/src/fmt.rs:written+2:10: `fmt::Arguments` with a placeholder, or fixtures/reject/src/futures.rs:generic_pending+1:2: `futures::Waiting>` is not supported: its field `poll` is `Poll`, typed `rt.Poll`, whose `Pending` would be `rt.OuterPending` as `Poll>` fixtures/reject/src/futures.rs:nested_poll+0:20: `Poll>>>` is not supported: its `Pending` and `Ready` would both be `rt.OuterPending` fixtures/reject/src/futures.rs:pending_output+0:34: a future of `std::task::Poll`, which can be `rt.Pending`, as the future's own `Pending` is +fixtures/reject/src/futures.rs:ready_pending+0:27: a future of `std::task::Poll`, which can be `rt.Pending`, as the future's own `Pending` is +fixtures/reject/src/futures.rs:waker_debug+1:11: `Debug` of `std::task::Waker`, which writes addresses, which JS has no counterpart of fixtures/reject/src/iters.rs:backs+1:8: `DoubleEndedIterator::rfind` of `iters::Backs`, whose closure takes a reference to `[u8; 2]`, which is not supported yet fixtures/reject/src/iters.rs:impl Iterator for Discards+11:3: overriding `Iterator::fold` with a drop of its accumulator, or of the crate's type over it or its closure, which is not supported yet fixtures/reject/src/iters.rs:impl Iterator for Disposes+10:3: overriding `Iterator::fold` with a drop of its accumulator, or of the crate's type over it or its closure, which is not supported yet diff --git a/ts/fixtures/reject/src/futures.rs b/ts/fixtures/reject/src/futures.rs index 269566cf7..848ba0529 100644 --- a/ts/fixtures/reject/src/futures.rs +++ b/ts/fixtures/reject/src/futures.rs @@ -37,3 +37,13 @@ fn poll_pinned( ) -> std::task::Poll { o.poll(cx) } + +/// A `Ready`'s `Ready(Pending)` would be `rt.Pending`, as its own poll's pending is. +pub fn ready_pending() -> std::future::Ready> { + std::future::ready(std::task::Poll::Pending) +} + +/// What a waker writes is its data's and vtable's addresses. +pub fn waker_debug(w: &std::task::Waker) -> String { + format!("{w:?}") +} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index a273e0717..529afb9fe 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -29,6 +29,8 @@ roots = [ "futures::generic_pending", "futures::nested_poll", "futures::pending_output", + "futures::ready_pending", + "futures::waker_debug", "iters::backs", "iters::discards", "iters::disposes", diff --git a/ts/runtime/rt/task.ts b/ts/runtime/rt/task.ts index 751ad63d5..654c9e1e7 100644 --- a/ts/runtime/rt/task.ts +++ b/ts/runtime/rt/task.ts @@ -4,7 +4,8 @@ // A value whose drop JS can observe has `[Symbol.dispose]`, which runs its `Drop` impl // and drops its fields. `None` is `undefined`, which drops as nothing. -import { Abort, type ByteSlice, type Debug, type DebugStruct, debugStructField, debugStructFinish, debugStructFinishNonExhaustive, debugTupleField, debugTupleFinish, Err, type FmtResult, type Formatter, fmtBool, fmtDebugStruct, fmtDebugTuple, fmtPad, fmtStr, type None, objectCopy, OK, Ok, Panic, panic, type Place, place, ptrWrite, type Result, stringAsBytes, vecAssign } from "../rt.ts"; +import { Abort, type ByteSlice, type Debug, type DebugStruct, debugStructField, debugStructFinish, debugStructFinishNonExhaustive, debugTupleField, debugTupleFinish, Err, type FmtResult, type Formatter, fmtBool, fmtDebugStruct, fmtDebugTuple, fmtDebugTupleFields, fmtPad, fmtStr, type None, objectCopy, OK, Ok, Panic, panic, type Place, place, ptrWrite, type Result, stringAsBytes, vecAssign } from "../rt.ts"; +import type { Hasher } from "./hash.ts"; // Drops @@ -1276,6 +1277,20 @@ export const OuterPending: unique symbol = /* @__PURE__ */ Symbol("OuterPending" /** The type of `OuterPending`. */ export type OuterPending = typeof OuterPending; +/** `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"); + return fmtDebugTupleFields(f, "Ready", [(f) => debug(p as T, f)]); +} + +/** `Hash::hash` of a `Poll`, whose `Pending` is `pending`: its discriminant as an `isize`, then its value's. */ +export function hashPoll(hash: (value: T, s: Hasher) => void, pending: typeof Pending | OuterPending = Pending): (value: T | typeof Pending | OuterPending, s: Hasher) => void { + return (value, s) => { + s.isize(value === pending ? 1 : 0); + if (value !== pending) hash(value as T, s); + }; +} + /** `Ok(Pending)` and `Ok(OuterPending)`, shared as `OK` is. */ const OK_PENDING = /* @__PURE__ */ Object.freeze({ ok: true, value: Pending } as const); const OK_OUTER_PENDING = /* @__PURE__ */ Object.freeze({ ok: true, value: OuterPending } as const); @@ -1461,6 +1476,64 @@ export function futurePoll(future: Future, cx: Context): Poll { return future.poll(cx); } +/** + * `future::ready(value)`'s future: its first poll gives `value`, and a later one panics, + * as `into_inner` after it does. A value no poll took drops with it by `drop`, if at all. + */ +export class Ready implements Future { + #value: T | undefined; + #taken: boolean; + readonly #drop: ((value: T) => void) | undefined; + + constructor(value: T, drop?: (value: T) => void, taken = false) { + this.#value = value; + this.#taken = taken; + this.#drop = drop; + } + + /** Moves its value into `to`, or into a new future. */ + $move(to?: this): this { + if (to === undefined) return new Ready(this.#value as T, this.#drop, this.#taken) as this; + to.#value = this.#value; + to.#taken = this.#taken; + return to; + } + + poll(_cx: Context): Poll { + return this.#take("`Ready` polled after completion"); + } + + /** `into_inner`, which consumes it. */ + intoInner(): T { + return this.#take("Called `into_inner()` on `Ready` after completion"); + } + + #take(message: string): T { + if (this.#taken) panic(message); + const value = this.#value as T; + this.#taken = true; + this.#value = undefined; + return value; + } + + /** `clone`, of its value by `clone` unless it copies, or of none once taken. */ + clone(clone?: (value: T) => T): Ready { + if (this.#taken) return new Ready(undefined as T, this.#drop, true); + return new Ready(clone ? clone(this.#value as T) : (this.#value as T), this.#drop); + } + + /** `Debug`, std's derived one of the `Option` it holds. */ + debug(f: Formatter, debug: Debug): FmtResult { + const held = (f: Formatter): FmtResult => + this.#taken ? f.out.writeStr("None") : fmtDebugTupleFields(f, "Some", [(f) => debug(this.#value as T, f)]); + return fmtDebugTupleFields(f, "Ready", [held]); + } + + [Symbol.dispose](): void { + if (!this.#taken) this.#drop?.(this.#value as T); + } +} + /** `future::Pending`: never ready, and drops as nothing. */ class Never implements Future { poll(): Poll { diff --git a/ts/src/lower/collections.rs b/ts/src/lower/collections.rs index 781a5c620..110e1ee59 100644 --- a/ts/src/lower/collections.rs +++ b/ts/src/lower/collections.rs @@ -2960,6 +2960,11 @@ fn hash_fn<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, hasher: Ty<'tcx>, spa let hash = hash_fn(b, inner, hasher, span); Expr::rt("hashOption", [hash].into_iter().chain(b.t.none_arg(inner)).collect()) } + ty::Adt(..) if b.t.std_of(ty) == Some(Std::Poll) => { + let inner = arg(ty, 0); + let hash = hash_fn(b, inner, hasher, span); + Expr::rt("hashPoll", [hash].into_iter().chain(b.t.pending_arg(inner)).collect()) + } ty::Adt(..) if let Some(inner) = arc_of(b, ty) => Expr::rt("hashArc", vec![hash_fn(b, inner, hasher, span)]), ty::Adt(..) if let Some(inner) = reverse_of(b, ty).or_else(|| overflowing_of(b, ty)) => { hash_fn(b, inner, hasher, span) diff --git a/ts/src/lower/consts.rs b/ts/src/lower/consts.rs index 89eb8b7a3..fe5253867 100644 --- a/ts/src/lower/consts.rs +++ b/ts/src/lower/consts.rs @@ -174,6 +174,7 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { Expr::New(Box::new(Expr::Rt("Range".into())), values) } (Std::RangeFull, _) => Expr::undefined(), + (Std::Pending, _) => Expr::Rt("NEVER".into()), (Std::ParseIntError, _) => { Expr::Object(vec![(Key::Name("kind".into()), values.pop().unwrap())]) } diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index b38cea08e..61df517d6 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -53,6 +53,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::Poll | Std::Pin | Std::PollFn + | Std::Ready | Std::HashMap | Std::HashSet | Std::HashTable @@ -161,7 +162,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ty::Adt(adt, _) if self.bound_drops(adt.did()).is_some() => true, ty::Adt(adt, args) => match Std::of(self.tcx, adt.did()) { // Each drops by what it was given, or a `Weak` by its own count. - Some(Std::Waker | Std::PollFn | Std::Weak) => true, + Some(Std::Waker | Std::PollFn | Std::Ready | Std::Weak) => true, Some( Std::Arc | Std::Mutex @@ -215,6 +216,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some( Std::Waker | Std::PollFn + | Std::Ready | Std::Weak | Std::Arc | Std::Mutex diff --git a/ts/src/lower/fmt.rs b/ts/src/lower/fmt.rs index 80ecb9edf..f0e5f370f 100644 --- a/ts/src/lower/fmt.rs +++ b/ts/src/lower/fmt.rs @@ -285,7 +285,10 @@ pub fn std_impl<'tcx>( } // Each its name alone, unpadded. ty::Adt(adt, _) - if tr == Trait::Debug && (library::pinned(tcx, adt.did()) || library::empty_iter(tcx, adt.did())) => + if tr == Trait::Debug + && (library::pinned(tcx, adt.did()) + || library::empty_iter(tcx, adt.did()) + || matches!(Std::of(tcx, adt.did()), Some(Std::Pending | Std::PollFn))) => { rt("writeStr", vec![f, Expr::Str(tcx.item_name(adt.did()).to_string())]) } @@ -459,6 +462,20 @@ pub fn std_impl<'tcx>( let err = b.fmt_fn(tr, args.type_at(1), span); rt("result", vec![value, f, ok, err]) } + (Std::Poll, Trait::Debug) => { + let ready = b.fmt_fn(tr, args.type_at(0), span); + let pending = b.t.pending_arg(args.type_at(0)); + rt("poll", [vec![value, f, ready], pending.into_iter().collect()].concat()) + } + (Std::Waker | Std::Context | Std::WakerVTable, Trait::Debug) => b.err( + span, + format!("`Debug` of `{ty}`, which writes addresses, which JS has no counterpart of"), + ), + // The `Option` it holds until a poll takes its value. + (Std::Ready, Trait::Debug) => { + let held = b.fmt_fn(tr, args.type_at(0), span); + value.member("debug").call(vec![f, held]) + } (Std::Arguments, Trait::Display | Trait::Debug) => rt("arguments", vec![value, f]), (Std::FmtError, Trait::Debug) => rt("errorDebug", vec![value, f]), (Std::PoisonError, Trait::Debug) => rt("poisonErrorDebug", vec![value, f]), diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index d7505aed6..866a4e52d 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -116,6 +116,8 @@ pub enum Std { PollFn, /// `future::Pending`: `rt.NEVER`, a future never ready. Pending, + /// `rt.Ready`: `future::ready`'s future, which owns its value until a poll takes it. + Ready, /// `rt.Range`, an iterator with `start` and `end` fields. Range, /// `rt.RangeInclusive`. @@ -336,6 +338,7 @@ impl Std { ["mem", "manually_drop", "ManuallyDrop"] => Some(Self::ManuallyDrop), ["future", "poll_fn", "PollFn"] => Some(Self::PollFn), ["future", "pending", "Pending"] => Some(Self::Pending), + ["future", "ready", "Ready"] => Some(Self::Ready), ["task", "wake", "RawWakerVTable"] => Some(Self::WakerVTable), ["convert", "Infallible"] => Some(Self::Infallible), // A thread-local on wasm32 is never destroyed, so `try_with` never fails. @@ -1451,9 +1454,9 @@ fn mapping<'tcx>( let mut values = args; let value = values.pop()?; // One between strings, such as into a `Cow` or out of one, keeps the text, and - // `Some` of a value is the value. + // `Some` or `Ready` of a value is the value. let some = matches!(to.kind(), ty::Adt(adt, args) - if Std::of(tcx, adt.did()) == Some(Std::Option) && args.type_at(0) == targ(gargs, 1)); + if matches!(Std::of(tcx, adt.did()), Some(Std::Option | Std::Poll)) && args.type_at(0) == targ(gargs, 1)); if to == from || some { return Some(value); } diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 92497ccfb..a69d14fc1 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -630,7 +630,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { if !ty.is_sized(tcx, self.b.t.typing_env_pub()) { return None; } - if !library::clone_from_clones(self.b.t, ty) { + // std's keeps a waker that would wake the same task, cloning and dropping none. + let waker = self.b.t.std_of(ty) == Some(Std::Waker); + if !waker && !library::clone_from_clones(self.b.t, ty) { let msg = format!( "`{owner}::{name}` of `{ty}`, whose `clone_from` is its own, which is not supported yet" ); @@ -641,11 +643,19 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { _ => (&args[1].node, &args[0].node), }; let place = self.borrowed_place(target, span, out)?; - let source = self.operand(source, span, out); + let mut source = self.operand(source, span, out); + let mut cloned = Vec::new(); + if waker && !super::cfg::pure(&source) { + source = self.b.spill(source, "source", out); + } let clone = crate::lower::collections::trait_fn(tcx, "Clone", "clone"); - let clone = self - .b - .call_def(clone, tcx.mk_args(&[ty.into()]), vec![source], span, out); + let clone = self.b.call_def( + clone, + tcx.mk_args(&[ty.into()]), + vec![source.clone()], + span, + &mut cloned, + ); // std's `Vec` keeps the target's buffer, grown if it holds fewer, as it does // through a box. let own = library::unboxed(self.b.t, ty); @@ -656,9 +666,21 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { _ => clone, }; let drops = self.b.t.drops(ty); - let old = self.swap_out(place, ty, clone, drops.then_some("old"), span, out); + let current = place.0.clone(); + let old = self.swap_out(place, ty, clone, drops.then_some("old"), span, &mut cloned); if drops && let Some(drop) = self.b.t.drop_value(old, ty, span) { - out.push(StmtKind::Expr(drop).at(None)); + cloned.push(StmtKind::Expr(drop).at(None)); + } + match waker { + true => out.push( + StmtKind::If { + cond: Expr::rt("willWake", vec![current, source]).not(), + then: cloned, + els: Vec::new(), + } + .at(None), + ), + false => out.extend(cloned), } done(Expr::undefined(), false) } diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 984761b5a..491b95349 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -778,6 +778,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Expr::New(Box::new(Expr::Rt("RangeFrom".into())), values) } (Std::RangeFull, _) => Expr::undefined(), + (Std::Pending, _) => Expr::Rt("NEVER".into()), (Std::LayoutError, _) => Expr::Rt("LAYOUT_ERROR".into()), (Std::ParseIntError, _) => Expr::Object(vec![(Key::Name("kind".into()), values.pop().unwrap())]), (Std::ErrorKind, _) => Expr::As(Box::new(Expr::Str(def.name.to_string())), self.b.t.ty(ty, span)), diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index ee4ca0eb6..154af6e49 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1294,6 +1294,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | library::Std::Pin | library::Std::PollFn | library::Std::Pending + | library::Std::Ready | library::Std::Range | library::Std::RangeInclusive | library::Std::RangeFrom @@ -1351,7 +1352,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { use library::Std; matches!( self.std_of(self.erased(ty)), - Some(Std::PollFn | Std::LazyCell | Std::LazyLock) + Some(Std::PollFn | Std::Ready | Std::LazyCell | Std::LazyLock) ) } @@ -1718,6 +1719,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let output = self.future_output(Ty::new_adt(tcx, adt, args), span); self.future_ty(output, span) } + // Its own class, whose value `into_inner`, `clone`, and `Debug` read; refused where + // that can be pending, as any future's output is. + library::Std::Ready => { + self.future_ty(args.type_at(0), span); + Type::Rt("Ready", vec![self.ty(args.type_at(0), span)]) + } library::Std::Range => Type::Rt("Range", vec![self.ty(args.type_at(0), span)]), library::Std::RangeInclusive => Type::Rt("RangeInclusive", vec![self.ty(args.type_at(0), span)]), library::Std::RangeTo => Type::Object(vec![("end".into(), self.ty(args.type_at(0), span))]), @@ -1968,10 +1975,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// The JS value of `Pending` in a `Poll`: `rt.Pending`, or `rt.OuterPending` /// where `inner` can already be `rt.Pending`. pub fn pending(&self, inner: Ty<'tcx>) -> ts::Expr { - match self.erases_to(inner).pending { - true => ts::Expr::Rt("OuterPending".into()), - false => ts::Expr::Rt("Pending".into()), - } + self.pending_arg(inner) + .unwrap_or_else(|| ts::Expr::Rt("Pending".into())) + } + + /// `rt.OuterPending` where it is a `Poll`'s `Pending`, for a runtime function + /// whose `pending` is `rt.Pending` unless given. + pub fn pending_arg(&self, inner: Ty<'tcx>) -> Option { + self.erases_to(inner) + .pending + .then(|| ts::Expr::Rt("OuterPending".into())) } /// The JS value of `None` in an `Option`, and of `Break` in a `ControlFlow` diff --git a/ts/src/lower/task.rs b/ts/src/lower/task.rs index e99124378..0f4468694 100644 --- a/ts/src/lower/task.rs +++ b/ts/src/lower/task.rs @@ -81,7 +81,7 @@ impl<'tcx> Body<'_, '_, 'tcx> { return self.future_object(*inner, value, true, dropped, span); } ty::Adt(adt, args) => match Std::of(tcx, adt.did()) { - Some(Std::PollFn | Std::Pending) => true, + Some(Std::PollFn | Std::Pending | Std::Ready) => true, Some(Std::Box | Std::Pin) => { return self.future_object(args.type_at(0), value, borrowed, dropped, span); } @@ -247,6 +247,13 @@ pub fn call<'tcx>( } } } + // A `Ready`'s clones its value, if it still holds it, and every `Pending` is one. + ("Clone", "clone") if owned_std == Some(Std::Ready) => { + let clone = super::collections::clone_fn(b, held(self_ty), call.span); + let clone = (clone != Expr::undefined()).then_some(clone); + first(args).member("clone").call(clone.into_iter().collect()) + } + ("Clone", "clone") if owned_std == Some(Std::Pending) => Expr::Rt("NEVER".into()), // A waker is the executor's, whose `clone` is its own. ("Clone", "clone") if owned_std == Some(Std::Waker) => first(args).member("clone").call(vec![]), // A reference is the object it points at, which its own `Deref` gives: read out of a @@ -395,6 +402,15 @@ pub fn call<'tcx>( ) } ("pending", "pending") => Expr::Rt("NEVER".into()), + // With what drops its value, unless a poll takes it first. + ("ready", "ready") => { + let dropper = b.t.drop_fn(call.args.type_at(0), call.span); + Expr::New( + Box::new(Expr::Rt("Ready".into())), + args.into_iter().chain(dropper).collect(), + ) + } + ("Ready", "into_inner") => first(args).member("intoInner").call(vec![]), // A box's JS value is what it holds. ("Box", "pin") => first(args), ("Future", "poll") => match self_ty.map(Ty::kind) { @@ -405,7 +421,7 @@ pub fn call<'tcx>( b.call_def(call.def_id, tcx.mk_args(&[(*inner).into()]), args, call.span, out) } // std's futures and a `dyn Future` are `rt.Future`s, which poll themselves. - _ if matches!(self_std, Some(Std::PollFn | Std::Pending)) + _ if matches!(self_std, Some(Std::PollFn | Std::Pending | Std::Ready)) || self_ty.is_some_and(|ty| b.t.dyn_future(ty).is_some()) => { let [future, cx] = two(args); diff --git a/ts/tests/fixtures.rs b/ts/tests/fixtures.rs index c7eaf8359..951035446 100644 --- a/ts/tests/fixtures.rs +++ b/ts/tests/fixtures.rs @@ -406,6 +406,7 @@ mod lang { fields, floats, folds, + futures, hashes, heap, hints, From 712300b764feeaecfa5ad92fe21a414ef028d6ce Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:37:07 -0800 Subject: [PATCH 3/3] test(ts): a Poll as a set's key and a waker cloned into a field Co-Authored-By: Claude Opus 5.5 --- .../lang/futures/expected/core/task/poll.ts | 24 +++++++++++ .../lang/futures/expected/futures.Held.ts | 18 +++++++++ ts/fixtures/lang/futures/expected/futures.ts | 40 ++++++++++++++----- ts/fixtures/lang/futures/src/futures.rs | 22 +++++++++- 4 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 ts/fixtures/lang/futures/expected/futures.Held.ts diff --git a/ts/fixtures/lang/futures/expected/core/task/poll.ts b/ts/fixtures/lang/futures/expected/core/task/poll.ts index 029d335a0..096dffd4f 100644 --- a/ts/fixtures/lang/futures/expected/core/task/poll.ts +++ b/ts/fixtures/lang/futures/expected/core/task/poll.ts @@ -3,6 +3,30 @@ import * as rt from "../../rt.ts"; import type * as Logged from "../../futures.Logged.ts"; +export function eq$11qt6c3(self: rt.Poll | rt.OuterPending, other: rt.Poll | rt.OuterPending): boolean { + if (!((self !== rt.OuterPending ? 0 : 1) === (other !== rt.OuterPending ? 0 : 1))) { + return false; + } + if (self !== rt.OuterPending) { + if (other !== rt.OuterPending) { + return eq$1h9nnoh(self, other); + } + } + return true; +} + +export function eq$1h9nnoh(self: rt.Poll, other: rt.Poll): boolean { + if (!((self !== rt.Pending ? 0 : 1) === (other !== rt.Pending ? 0 : 1))) { + return false; + } + if (self !== rt.Pending) { + if (other !== rt.Pending) { + return self === other; + } + } + return true; +} + export function map$0fx224j(self: rt.Poll, f: (a0: rt.u8 | undefined) => rt.u8 | undefined): rt.Poll { if (self !== rt.Pending) { return f(self); diff --git a/ts/fixtures/lang/futures/expected/futures.Held.ts b/ts/fixtures/lang/futures/expected/futures.Held.ts new file mode 100644 index 000000000..1688ee7b7 --- /dev/null +++ b/ts/fixtures/lang/futures/expected/futures.Held.ts @@ -0,0 +1,18 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** A waker a struct holds. */ +export class Held { + waker: rt.Waker; + + constructor(waker: rt.Waker) { + this.waker = waker; + } + + [Symbol.dispose](): void { + this.waker[Symbol.dispose](); + } +} + +//# sourceMappingURL=futures.Held.ts.map diff --git a/ts/fixtures/lang/futures/expected/futures.ts b/ts/fixtures/lang/futures/expected/futures.ts index 1d1f50349..958c52d93 100644 --- a/ts/fixtures/lang/futures/expected/futures.ts +++ b/ts/fixtures/lang/futures/expected/futures.ts @@ -3,8 +3,9 @@ import * as rt from "./rt.ts"; import { drop$task_Poll_futures_Logged } from "./lib.ts"; import { isSomeAnd$09tpccf, filter$0v80fk8 } from "./core/option.ts"; -import { map$0fx224j as poll_map$0fx224j, isReady$1ntq2i8, isPending$1405yze, isPending$0vrdvv1 } from "./core/task/poll.ts"; +import { eq$11qt6c3 as poll_eq$11qt6c3, eq$1h9nnoh as poll_eq$1h9nnoh, map$0fx224j as poll_map$0fx224j, isReady$1ntq2i8, isPending$1405yze, isPending$0vrdvv1 } from "./core/task/poll.ts"; import * as Count from "./futures.Count.ts"; +import * as Held from "./futures.Held.ts"; import * as Later from "./futures.Later.ts"; import * as Logged from "./futures.Logged.ts"; import { put, text } from "./harness.ts"; @@ -535,39 +536,60 @@ export function hashes(data: rt.ByteSlice, out: rt.ByteVec): void { rt.hashPoll(rt.hashPoll(rt.hash8), rt.OuterPending)(_41, h2); } rt.iterExtendBytes(out, rt.iterSlice(rt.u64ToBeBytes(rt.hashFinish(h2))), undefined, true); + const _48: rt.Iter> = rt.iterSlice(polls); + const set: rt.HashSet> = rt.hashExtendKeys(rt.hashNew(rt.hashing(rt.hashPoll(rt.hash8), poll_eq$1h9nnoh)) as rt.HashSet>, _48, undefined, undefined, 469762048); + const _53: rt.Iter | rt.OuterPending> = rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (_22: rt.u8): rt.Poll | rt.OuterPending => { + return nested(_22); + }); + const nests: rt.HashSet | rt.OuterPending> = rt.hashExtendKeys(rt.hashNew(rt.hashing(rt.hashPoll(rt.hashPoll(rt.hash8), rt.OuterPending), poll_eq$11qt6c3)) as rt.HashSet | rt.OuterPending>, _53, undefined, undefined, 469762048); + rt.byteVecPush(out, rt.hashLen(set) & 0xff); + rt.byteVecPush(out, rt.hashLen(nests) & 0xff); + rt.byteVecPush(out, rt.hashContains(set, rt.Pending) ? 1 : 0); + rt.byteVecPush(out, rt.hashContains(nests, rt.Pending) ? 1 : 0); + rt.iterExtendBytes(out, rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (_23: rt.u8): rt.u8 => { + return rt.hashContains(set, _23 as rt.Poll) ? 1 : 0; + }), undefined, true); } /** * A waker cloned over another by `clone_from` takes the source's place, unless both would - * wake the same task, when std keeps the one it had. + * wake the same task, when std keeps the one it had: one in a variable and one in a field. */ export function wakers(data: rt.ByteSlice, out: rt.ByteVec): void { let source!: rt.Waker; const a: rt.Arc = new rt.Arc({ _0: new rt.Atomic(0) }); const b: rt.Arc = new rt.Arc({ _0: new rt.Atomic(0) }); let w: rt.Waker = new rt.ArcWaker(rt.arcClone(a), Count.wake$task_Wake as (a0: rt.Arc) => undefined, (arc: rt.Arc) => arc[Symbol.dispose]()); - for (const _14 of rt.byteSliceView(data)) { - const _19: rt.u8 = rt.u8Rem(_14, 3); - if (_19 === 0) { + const held: Held.Held = new Held.Held(rt.NOOP_WAKER.clone()); + for (const _17 of rt.byteSliceView(data)) { + const _22: rt.u8 = rt.u8Rem(_17, 3); + if (_22 === 0) { source = new rt.ArcWaker(rt.arcClone(a), Count.wake$task_Wake as (a0: rt.Arc) => undefined, (arc3: rt.Arc) => arc3[Symbol.dispose]()); - } else if (_19 === 1) { + } else if (_22 === 1) { source = new rt.ArcWaker(rt.arcClone(b), Count.wake$task_Wake as (a0: rt.Arc) => undefined, (arc2: rt.Arc) => arc2[Symbol.dispose]()); } else { source = rt.NOOP_WAKER.clone(); } - const _28: rt.Waker = source; - if (!rt.willWake(w, _28)) { + const _31: rt.Waker = source; + if (!rt.willWake(w, _31)) { const old = w; - w = _28.clone(); + w = _31.clone(); old[Symbol.dispose](); } + if (!rt.willWake(held.waker, w)) { + const old2 = held.waker; + held.waker = w.clone(); + old2[Symbol.dispose](); + } rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(a.strong & 0xff, b.strong & 0xff)), undefined, true); rt.byteVecPush(out, rt.willWake(w, source) ? 1 : 0); w.wakeByRef(); + held.waker.wakeByRef(); source[Symbol.dispose](); rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(a.strong & 0xff, b.strong & 0xff)), undefined, true); } w[Symbol.dispose](); + held[Symbol.dispose](); rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(rt.atomicLoad(a.value._0, 0), rt.atomicLoad(b.value._0, 0))), undefined, true); rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(a.strong & 0xff, b.strong & 0xff)), undefined, true); b[Symbol.dispose](); diff --git a/ts/fixtures/lang/futures/src/futures.rs b/ts/fixtures/lang/futures/src/futures.rs index dcdfa7d37..a3c8ddd15 100644 --- a/ts/fixtures/lang/futures/src/futures.rs +++ b/ts/fixtures/lang/futures/src/futures.rs @@ -4,6 +4,7 @@ //! made `From` a value, and hashed; and a waker cloned over another by `clone_from`. use std::cell::RefCell; +use std::collections::HashSet; use std::fmt::Debug; use std::future::{Future, IntoFuture, Pending, Ready, pending, poll_fn, ready}; use std::hash::{DefaultHasher, Hash, Hasher}; @@ -236,6 +237,14 @@ fn hashes(data: &[u8], out: &mut Vec) { nested(b).hash(&mut h); } out.extend(h.finish().to_be_bytes()); + // As a set's keys, `Pending` among them. + let set: HashSet> = polls.iter().copied().collect(); + let nests: HashSet>> = data.iter().map(|&b| nested(b)).collect(); + out.push(set.len() as u8); + out.push(nests.len() as u8); + out.push(set.contains(&Poll::Pending).into()); + out.push(nests.contains(&Poll::Ready(Poll::Pending)).into()); + out.extend(data.iter().map(|&b| u8::from(set.contains(&Poll::Ready(b))))); } /// Counts the wakes through it. @@ -247,12 +256,20 @@ impl Wake for Count { } } +/// A waker a struct holds. +struct Held { + waker: Waker, +} + /// A waker cloned over another by `clone_from` takes the source's place, unless both would -/// wake the same task, when std keeps the one it had. +/// wake the same task, when std keeps the one it had: one in a variable and one in a field. fn wakers(data: &[u8], out: &mut Vec) { let a = Arc::new(Count(AtomicU8::new(0))); let b = Arc::new(Count(AtomicU8::new(0))); let mut w = Waker::from(a.clone()); + let mut held = Held { + waker: Waker::noop().clone(), + }; for &byte in data { let source = match byte % 3 { 0 => Waker::from(a.clone()), @@ -260,13 +277,16 @@ fn wakers(data: &[u8], out: &mut Vec) { _ => Waker::noop().clone(), }; w.clone_from(&source); + held.waker.clone_from(&w); out.extend([Arc::strong_count(&a) as u8, Arc::strong_count(&b) as u8]); out.push(w.will_wake(&source).into()); w.wake_by_ref(); + held.waker.wake_by_ref(); drop(source); out.extend([Arc::strong_count(&a) as u8, Arc::strong_count(&b) as u8]); } drop(w); + drop(held); out.extend([a.0.load(Ordering::Relaxed), b.0.load(Ordering::Relaxed)]); out.extend([Arc::strong_count(&a) as u8, Arc::strong_count(&b) as u8]); }