diff --git a/quest/m3/README.md b/quest/m3/README.md index ed3e956a9..eed03adf1 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -22,6 +22,7 @@ generic std gap is planned here even when moq never calls it. - [Waiting: drop the lang fixture's mul_add allowance](/quest/m3/ts-fma-allowance.md) - the differential fails on any `mul_add` difference again once wasm32's `fma` rounds exactly - [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library - [Waiting: 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 once a consumer calls them +- [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 - [An adaptor of the crate's own iterator held as a value](/quest/m3/ts-own-iter-value.md) - `map` and the other adaptors of a fieldless enum that implements `Iterator`, taken by value, translate 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 1b64520dc..aa6c7be88 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 6fbe5213f..7ddea666b 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 | @@ -457,7 +457,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 @@ -1005,7 +1005,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..096dffd4f --- /dev/null +++ b/ts/fixtures/lang/futures/expected/core/task/poll.ts @@ -0,0 +1,59 @@ +// Generated by rust2-ts. Do not edit. + +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); + } + 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.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.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..13a042fd3 --- /dev/null +++ b/ts/fixtures/lang/futures/expected/futures.ts @@ -0,0 +1,613 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { drop$task_Poll_futures_Logged, drop$tuple2_task_Waker_Option_task_Waker, drop$tuple3_futures_Held_Option_task_Waker_array2_task_Waker } from "./lib.ts"; +import { isSomeAnd$09tpccf, filter$0v80fk8 } from "./core/option.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"; + +/** 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); + 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: one in a variable, a field, an + * `Option`, which clones over what it holds by the waker's own, and an array. + */ +export function wakers(data: rt.ByteSlice, out: rt.ByteVec): void { + let source!: rt.Waker; + let from!: rt.Waker | undefined; + 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]()); + const held: Held.Held = new Held.Held(rt.NOOP_WAKER.clone()); + let maybe = undefined as rt.Waker | undefined; + const pair: rt.Waker[] = [rt.NOOP_WAKER.clone(), new rt.ArcWaker(rt.arcClone(b), Count.wake$task_Wake as (a0: rt.Arc) => undefined, (arc2: rt.Arc) => arc2[Symbol.dispose]())]; + for (const byte of rt.byteSliceView(data)) { + const _29: rt.u8 = rt.u8Rem(byte, 3); + if (_29 === 0) { + source = new rt.ArcWaker(rt.arcClone(a), Count.wake$task_Wake as (a0: rt.Arc) => undefined, (arc4: rt.Arc) => arc4[Symbol.dispose]()); + } else if (_29 === 1) { + source = new rt.ArcWaker(rt.arcClone(b), Count.wake$task_Wake as (a0: rt.Arc) => undefined, (arc3: rt.Arc) => arc3[Symbol.dispose]()); + } else { + source = rt.NOOP_WAKER.clone(); + } + w = rt.wakerCloneFrom(w, source); + held.waker = rt.wakerCloneFrom(held.waker, w); + if (rt.u8Rem(byte, 4) === 0) { + from = undefined; + } else { + from = source.clone(); + } + maybe = rt.optionCloneFrom(maybe, from, (a0: rt.Waker): rt.Waker => { + return a0.clone(); + }, rt.drop, rt.wakerCloneFromFn()); + const _53: rt.Waker[] = [w.clone(), source.clone()]; + rt.sliceCloneFromSlice(pair, _53, (a02: rt.Waker): rt.Waker => { + return a02.clone(); + }, rt.drop, rt.wakerCloneFromFn()); + rt.dropAll(_53); + 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(); + if (maybe !== undefined) { + maybe.wakeByRef(); + } + rt.iterForEach(rt.iterSlice(pair), (a03: rt.Waker): undefined => { + a03.wakeByRef(); + }); + drop$tuple2_task_Waker_Option_task_Waker([source, from] as [rt.Waker, rt.Waker | undefined]); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(a.strong & 0xff, b.strong & 0xff)), undefined, true); + } + w[Symbol.dispose](); + drop$tuple3_futures_Held_Option_task_Waker_array2_task_Waker([held, maybe, pair] as [Held.Held, rt.Waker | undefined, rt.Waker[]]); + 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..ac7c458b5 --- /dev/null +++ b/ts/fixtures/lang/futures/expected/lib.ts @@ -0,0 +1,33 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { futures } from "./futures.ts"; +import type * as Held from "./futures.Held.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](); + } +} + +export function drop$tuple2_task_Waker_Option_task_Waker(_1: [rt.Waker, rt.Waker | undefined]): void { + _1[0][Symbol.dispose](); + rt.drop(_1[1]); +} + +export function drop$tuple3_futures_Held_Option_task_Waker_array2_task_Waker(_1: [Held.Held, rt.Waker | undefined, rt.Waker[]]): void { + _1[0][Symbol.dispose](); + rt.drop(_1[1]); + rt.dropAll(_1[2]); +} + +/** + * 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..55560a32a --- /dev/null +++ b/ts/fixtures/lang/futures/src/futures.rs @@ -0,0 +1,305 @@ +//! 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::collections::HashSet; +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()); + // 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. +struct Count(AtomicU8); + +impl Wake for Count { + fn wake(self: Arc) { + self.0.fetch_add(1, Ordering::Relaxed); + } +} + +/// 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: one in a variable, a field, an +/// `Option`, which clones over what it holds by the waker's own, and an array. +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(), + }; + let mut maybe: Option = None; + let mut pair = [Waker::noop().clone(), Waker::from(b.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); + held.waker.clone_from(&w); + let from = match byte % 4 { + 0 => None, + _ => Some(source.clone()), + }; + maybe.clone_from(&from); + pair.clone_from(&[w.clone(), source.clone()]); + 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(); + if let Some(m) = &maybe { + m.wake_by_ref(); + } + pair.iter().for_each(Waker::wake_by_ref); + drop((source, from)); + out.extend([Arc::strong_count(&a) as u8, Arc::strong_count(&b) as u8]); + } + drop(w); + drop((held, maybe, pair)); + 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 1f0916d8d..df5dc92f8 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -33,6 +33,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 b09fba426..fc58cb4da 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.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: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 0ab9673d6..f80332df1 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -26,6 +26,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 109dd7bbb..da4c4edec 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 CloneFrom, cloneOver, 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 CloneFrom, cloneOver, 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 @@ -1317,6 +1318,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); @@ -1366,6 +1381,20 @@ export function willWake(a: Waker, b: Waker): boolean { return a.data === b.data && a.vtable === b.vtable; } +/** + * `Waker::clone_from` of `src` over `old`: `old`, where it would wake what `src` would, as + * std's keeps it without a clone or a drop, and else `src`'s clone, made before `old` drops. + */ +export function wakerCloneFrom(old: Waker, src: Waker): Waker { + if (willWake(old, src)) return old; + return cloneOver(old, src, (w) => w.clone(), (w) => w[Symbol.dispose]()); +} + +/** `wakerCloneFrom` as a `CloneFrom`. */ +export function wakerCloneFromFn(): CloneFrom { + return wakerCloneFrom; +} + /** * `Waker::from(arc)`: a waker that holds a count of `arc` and wakes through its `Wake` * impl, `wake` taking that count. Without a `wakeByRef` of its own, one wakes a clone, as @@ -1502,6 +1531,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 9a113a915..aba6f108e 100644 --- a/ts/src/lower/collections.rs +++ b/ts/src/lower/collections.rs @@ -3038,6 +3038,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 15c878685..2c8f16799 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 @@ -162,7 +163,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 @@ -223,6 +224,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 79076d8e3..41a8039bb 100644 --- a/ts/src/lower/fmt.rs +++ b/ts/src/lower/fmt.rs @@ -289,7 +289,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())]) } @@ -467,6 +470,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 8a70fb8d7..8a80ccc4e 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -118,6 +118,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`. @@ -343,6 +345,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. @@ -1498,9 +1501,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); } @@ -3067,6 +3070,7 @@ fn std_clone_from<'tcx>( Ok(("resultCloneFrom", trimmed(fns))) } (Some(Std::RefCell), Some(inner)) => Ok(("refCellCloneFrom", clone_from_fns(b, inner, span))), + (Some(Std::Waker), _) => Ok(("wakerCloneFrom", vec![])), _ => Err(refused(b)), } } diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 4c43b200e..b1a65b1fc 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -785,6 +785,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 11774d539..ff6491443 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1293,6 +1293,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 @@ -1353,7 +1354,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) ) } @@ -1725,6 +1726,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))]), @@ -1979,10 +1986,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 4edbedce7..d7096f972 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); } @@ -250,6 +250,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 @@ -398,6 +405,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) { @@ -408,7 +424,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 c21fd05e0..38adaba8e 100644 --- a/ts/tests/fixtures.rs +++ b/ts/tests/fixtures.rs @@ -415,6 +415,7 @@ mod lang { fields, floats, folds, + futures, hashes, heap, hints,