diff --git a/flake.nix b/flake.nix index 3faa5d820..19e077de5 100644 --- a/flake.nix +++ b/flake.nix @@ -297,6 +297,9 @@ typescript bun esbuild + # Its wasm-bindgen fixture's native side runs through the CLI's JS glue, whose + # version must match the fixture's locked wasm-bindgen exactly. + wasm-bindgen-cli_0_2_129 # rust2-ffi's tests compile its generated Kotlin and run it on the JVM, run and # type-check its generated Python, run its Dart, and build its Go through cgo. kotlin diff --git a/quest/m3/ts-wasm-bindgen/README.md b/quest/m3/ts-wasm-bindgen/README.md index dca7c0c0d..e89b8b368 100644 --- a/quest/m3/ts-wasm-bindgen/README.md +++ b/quest/m3/ts-wasm-bindgen/README.md @@ -58,5 +58,4 @@ Decided with the maintainer on 2026-11-06: ## Required -- [Translate wasm-bindgen imports](/quest/m3/ts-wasm-bindgen/imports.md) - web-sys and js-sys imports read from the encoded program, with `JsValue` and imported types as JS values, proven on synchronous calls - [wasm-bindgen closures and promises](/quest/m3/ts-wasm-bindgen/closures.md) - `Closure` and js-sys's futures mapped, proven on web-sys's `VideoEncoder` in headless Chromium diff --git a/quest/m3/ts-wasm-bindgen/closures.md b/quest/m3/ts-wasm-bindgen/closures.md index 755d7e39d..951a41f56 100644 --- a/quest/m3/ts-wasm-bindgen/closures.md +++ b/quest/m3/ts-wasm-bindgen/closures.md @@ -34,7 +34,3 @@ headless Chromium proves it against the same fixture's wasm-bindgen build. Public API: the translator's output for a crate calling wasm-bindgen closures and futures. - -## Required - -- [Translate wasm-bindgen imports](/quest/m3/ts-wasm-bindgen/imports.md) - the shapes, the JS-represented types, and the boundary conversions this builds on diff --git a/quest/m3/ts-wasm-bindgen/imports.md b/quest/m3/ts-wasm-bindgen/imports.md deleted file mode 100644 index a15b70200..000000000 --- a/quest/m3/ts-wasm-bindgen/imports.md +++ /dev/null @@ -1,80 +0,0 @@ -# [XL] Translate wasm-bindgen imports - -## Goal - -[rust2-ts](/ts/README.md) translates every wasm-bindgen import web-sys and js-sys declare, -and wasm-bindgen's own intrinsics, into a direct JS call. It reads each import's JS shape -from the program the `#[wasm_bindgen]` macro encodes. `JsValue` and every imported type -are the JS value itself. Any other wasm-bindgen import or item is refused where -translation reaches it, with its span. A differential test against a native wasm32 build -through the wasm-bindgen CLI proves it on synchronous js-sys and web-sys calls. Closures -and promises are [the next quest's](/quest/m3/ts-wasm-bindgen/closures.md). - -## Plan - -The epic's [Plan](/quest/m3/ts-wasm-bindgen/README.md#plan) holds the settled decisions. -The [spike](https://github.com/moq-dev/rust2/pull/638) picked the mechanism and measured -what follows. - -- Shapes come from the encoded program (rejected: re-reading the `extern` blocks in the - dependency's source). Each `#[wasm_bindgen]` invocation on `wasm32-unknown-unknown` - emits a `#[link_section = "__wasm_bindgen_unstable"]` static, whose initializer every - crate's metadata keeps, as rustc keeps every static's. A wrapper such as - `VideoEncoder::encode_queue_size` calls one `extern "C"` shim in - `wasm_import_module = "__wbindgen_placeholder__"`. Its link name is the `shim` of an - `ImportFunction`, which gives the class, JS name, kind, namespace, `catch`, `variadic`, - and `structural`. Find a crate's statics by their `link_section` through - `CStore::num_def_ids_untracked`, since they sit in anonymous `const _` blocks. Read - them once per crate. Source reading would miss every import a `macro_rules!` makes: - js-sys's `arrays!` makes 308, for its 11 typed arrays, and the encoder path reaches - `Uint8Array`'s. It would also re-implement the macro's ~400 lines of naming rules. -- The decoding is positional and changes with `SCHEMA_VERSION`. That changed 8 times in - the 12 months to 0.2.130, each time adding or removing fields or variants. Decode with - one field list, wasm-bindgen-shared's `shared_api!`, with each field and variant gated - by the schemas that carry it. Support each schema from moq's pin (0.2.129, schema - `0.2.128`) through the newest when this lands, `0.2.130` at the spike. Refuse a program - of any other schema, older or newer, at its crate, naming the supported ones. - wasm-bindgen-shared can be depended on, but only at one version, since cargo unifies - 0.2.x. cli-support's decoder is private, so vendor its 181 lines (MIT or Apache-2.0) - with their source and revision. A new schema is supported once its gates are added. -- JS-represented types: `JsValue`, each `ImportKind::Type`, and their `ErasableGeneric` - instances, such as js-sys's `Promise`, are the JS value. The generated glue between - them is then the identity: the `obj` field, `transmute_copy`, and the - `&*(val as *const JsValue as *const Self)` cast, all refused today. `JsCast` translates - from MIR, over each type's `instanceof` shim, `x instanceof Class`, and its - `is_type_of`. Clone and drop are the identity and nothing, which replaces the heap-table - intrinsics (`__wbindgen_object_clone_ref`, `__wbindgen_object_drop_ref`). -- A wrapper is replaced whole, never translated from its ABI glue. Its arguments and - result convert as the wasm-bindgen CLI's JS glue converts them for each Rust type, and - `wbg_cast::` converts the same way. That covers numbers, 64-bit and 128-bit - integers, `bool`, `char`, strings, `Option`, slices and `Vec`s (a `&mut [u8]` copies - back), string enums by their `variant_values`, and a `Result` under `catch`. A - non-`structural` (`final`) method calls `Class.prototype.name.call(this, ...)`, and - `vendor_prefixes` try each prefixed global. -- Intrinsics: in wasm-bindgen 0.2.130, 54 `__wbindgen_*` intrinsics are ordinary imports - in the program (`raw_module = "__wbindgen_placeholder__"`). Give each one translation - reaches the JS body the CLI's intrinsic table gives it. Treat that module as - wasm-bindgen's own, not custom JS. -- Refusals are per reached import or item, at its span, never per program, since js-sys's - program holds its JSPI bridge (`suspending`, `raw_module`) beside everything else: a - `module`, `raw_module`, or `inline_js` import other than the placeholder, a - `suspending` import, a `generic_per_mono` import, and any export. -- Cargo config: none needed. rust2-ts runs cargo from the package's directory, so a - `.cargo/config.toml`'s `build.rustflags`, such as web-rs's - `--cfg=web_sys_unstable_apis`, apply beside the profile's. The spike's census compiled - web-sys's `VideoEncoder` that way. -- Differential test: a fixture whose harness calls js-sys (`Math`, `JSON`, `Reflect`, - `Array`, typed arrays) and web-sys's non-DOM globals (`TextEncoder`, `console`). Its - native side is the wasm32 build through `wasm-bindgen-cli_0_2_129`, which the flake's - nixpkgs has, with the fixture's lockfile pinned to 0.2.129. Its host is Bun, as - `tests/differential.ts` runs today. It covers each import kind, `catch`, the boundary - conversions' edge values (an `f64` past `u32`, `NaN`, lone surrogates), `JsCast` - failures, and refusals of each other module kind. The translated side runs once per - supported schema, each from its own lockfile, against that one native run, since the - harness's output does not depend on wasm-bindgen's version; that tests every schema's - gates without a CLI per schema. A unit test refuses a program of an unsupported schema. - -Docs: [well-known crates](/ts/README.md#well-known-crates) gains wasm-bindgen's mapping -and the supported schemas. - -Public API: the translator's output for a crate calling wasm-bindgen imports. diff --git a/ts/README.md b/ts/README.md index dfb3a7e0b..a195a06da 100644 --- a/ts/README.md +++ b/ts/README.md @@ -836,6 +836,34 @@ of them: written. Translated code installs no subscriber, and without one an event evaluates none of its fields and does nothing, so leaving it out is exact. Installing one, as `tracing::subscriber::set_global_default` does, is refused as any unmapped call is. +- `wasm-bindgen`'s imports, `js-sys`'s and `web-sys`'s included, are calls of JS itself. + A `JsValue`, and each type an `extern "C"` block declares, is the JS value it holds, + `rs.JsValue`, which is TS's `unknown`; cloning one, dropping one, its `From`, `AsRef`, + and `Deref` into the type it extends, and `JsCast`'s unchecked casts are the value + itself. An import is read from the statics `#[wasm_bindgen]` leaves in the crate, the + ones the CLI reads, by schema 0.2.128 (wasm-bindgen 0.2.129, which js-sys 0.3.106 pins) + or 0.2.130 (0.2.130, which js-sys 0.3.107 pins); a crate of another schema is refused. + Its call is the JS the CLI would write: a function, a method by its class's prototype or + structurally, a getter, a setter, an indexing getter, setter, or deleter, a constructor, + a static method, a symbol-named method, and a variadic call spreading its last argument, + each reached through `globalThis` by its namespace, throwing a `ReferenceError` where no + global has its name, and by its vendor prefix when the unprefixed name is not defined, + which is resolved at each call where the CLI resolves it once. Its arguments and results convert as the CLI's do: numbers as the WebAssembly ABI + coerces them, 64- and 128-bit integers as BigInts, `char`s, strings, slices and vectors + of numbers as typed arrays, of `JsValue`s and of strings as arrays, `Option`s by + `undefined`, an optional `bool` by its truthiness, and string enums by their strings. A + `catch` import is a `Result` of what JS threw, converting its result included where the + CLI's glue converts it, but not where the WebAssembly ABI coerces a number, and an + import that throws otherwise throws through the translation, as it unwinds the WASM. A `thread_local_v2` static is an `rs.LazyCell` read once, an optional + one `None` where it is `undefined`, and a `static_string` its literal. `instanceof`, + `JsCast::has_type` and `dyn_into`, the intrinsics `js-sys` reads, such as `typeof`, + equality, operators, and `debug_string`, and `throw_str` and `throw_val` are JS's own. + Refused, each at its span: imports of custom JS, by `module`, `raw_module`, or + `inline_js`, a `suspending` import, an `experimental_generic_mono` one, which the CLI + makes JS of per instance, a `Closure`, a mutable slice within an `Option`, 0.2.130's + `Nullable`, which no CLI in the dev shell makes glue of to compare against, the + intrinsics only WASM has, such as `memory()`, and a `#[wasm_bindgen]` export, as a + translation exports the crate's `pub` API itself. ## Bindings @@ -1286,6 +1314,15 @@ so adding, removing, or reordering a case changes no other case's inputs. `refused.toml` drops the binding, its `rev` feature makes the dependency's iterator run from either end, and its `shared` feature adds a module of the dependency's name, with the errors in `refused.txt`, `rev.txt`, and `shared.txt`. +- `bindgen` imports JS through `wasm-bindgen`, `js-sys`, and `web-sys`, a workspace of + its own whose lockfile pins wasm-bindgen 0.2.129. Its harness calls imports of each kind + and shape, its own and js-sys's and web-sys's, against JS's globals and what `caller.ts` + defines, catches and throws, casts, and reads statics. Its native side runs through the + glue the `wasm-bindgen` CLI makes for it, which the dev shell provides at 0.2.129, a + fresh instance of the glue per instance of the WASM. A JS error neither side catches is + compared by its message. It is translated again under `wasm-bindgen-0.2.130.lock`, the + next schema, and compared against the same native run. `refused.toml`, with its + `refused` feature, holds what translation refuses, with the errors in `refused.txt`. - `reject` holds constructs outside the subset, a module per kind of construct. Its expected errors are `expected.txt` and its census's spans `census.txt`, each line keyed by the item it points into and sorted, so cases added to different items don't conflict. diff --git a/ts/fixtures/bindgen/Cargo.lock b/ts/fixtures/bindgen/Cargo.lock new file mode 100644 index 000000000..d03235e2f --- /dev/null +++ b/ts/fixtures/bindgen/Cargo.lock @@ -0,0 +1,173 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bindgen-fixture" +version = "0.0.0" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/ts/fixtures/bindgen/Cargo.toml b/ts/fixtures/bindgen/Cargo.toml new file mode 100644 index 000000000..662f1dbb7 --- /dev/null +++ b/ts/fixtures/bindgen/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bindgen-fixture" +description = "A harness over js-sys and web-sys, whose wasm-bindgen imports translate to direct JS calls" +version = "0.0.0" +edition = "2024" +license = "MIT OR Apache-2.0" +publish = false + +# Its own workspace and lockfile, which pins wasm-bindgen to the CLI the dev shell has: +# the native side runs through that CLI's glue, which must match the crate exactly. +# `wasm-bindgen-0.2.130.lock` is the same workspace at the next schema, which only the +# translated side builds. +[workspace] + +[lib] +# The cdylib is the native side of the differential test, built for wasm32. +crate-type = ["lib", "cdylib"] + +[features] +# What translation refuses, each at its span. +refused = [] + +[dependencies] +js-sys = "0.3.106" +wasm-bindgen = "0.2.129" +web-sys = { version = "0.3.106", features = ["TextDecoder", "TextDecoderOptions", "TextEncoder", "console"] } + +# The ts workspace's differential profile, which the tests build the native side with. +[profile.differential] +inherits = "dev" +opt-level = 1 diff --git a/ts/fixtures/bindgen/caller.ts b/ts/fixtures/bindgen/caller.ts new file mode 100644 index 000000000..69172f7f5 --- /dev/null +++ b/ts/fixtures/bindgen/caller.ts @@ -0,0 +1,31 @@ +// The harness, with its cases by name for `differential.ts`. + +import { cases, run } from "./harness.ts"; + +// The class `imports::Vendored` names by its vendor prefix alone, defined before either +// side runs, as a browser defines its prefixed globals. +Object.assign(globalThis, { + webkitRustVendored: class { + name() { + return "webkit"; + } + }, +}); + +// A namespace a getter makes anew each time it is read, whose function says whether it was +// called on the namespace it was read from: by its arguments' count, or -1 where not. +Object.defineProperty(globalThis, "rustFresh", { + get() { + const fresh = { + of(...args: unknown[]) { + return this === fresh ? args.length : -1; + }, + }; + return fresh; + }, +}); + +export { run }; + +/** Each case's name and the byte that picks it, its place in `cases`. */ +export const ops = new Map(cases().items.map(([name], i): [string, Uint8Array] => [name, Uint8Array.of(i)])); diff --git a/ts/fixtures/bindgen/expected/core/bool.ts b/ts/fixtures/bindgen/expected/core/bool.ts new file mode 100644 index 000000000..2573e2e76 --- /dev/null +++ b/ts/fixtures/bindgen/expected/core/bool.ts @@ -0,0 +1,19 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "../rs.ts"; + +export function thenSome$07biun1(self: boolean, t: rs.f64): rs.f64 | undefined { + if (!self) { + return undefined; + } + return t; +} + +export function then$15odrdm(self: boolean, f: () => rs.String): rs.String | undefined { + if (!self) { + return undefined; + } + return f(); +} + +//# sourceMappingURL=bool.ts.map diff --git a/ts/fixtures/bindgen/expected/core/option.ts b/ts/fixtures/bindgen/expected/core/option.ts new file mode 100644 index 000000000..bb2ad68d9 --- /dev/null +++ b/ts/fixtures/bindgen/expected/core/option.ts @@ -0,0 +1,55 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "../rs.ts"; +import { from$convert_From_u8_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_u8_wasm_bindgen_JsValue } from "../wasm_bindgen.ts"; + +export function unwrapOr$04rqm9m(self: rs.char | undefined, default_: rs.char): rs.char { + if (self === undefined) { + return default_; + } + return self; +} + +export function unwrapOr$1b5hww3(self: rs.u32 | undefined, default_: rs.u32): rs.u32 { + if (self === undefined) { + return default_; + } + return self; +} + +export function map$1izrkip(self: rs.f64 | undefined, _: (a0: rs.f64) => rs.u64): rs.u64 | undefined { + if (self === undefined) { + return undefined; + } + return rs.f64ToBits(self); +} + +export function map$0wjyljl(self: rs.JsValue | rs.None, _: (a0: rs.JsValue) => rs.u32): rs.u32 | undefined { + if (self === rs.None) { + return undefined; + } + return rs.jsToU32(rs.jsAny(self).length); +} + +export function map$01bkz05(self: rs.JsValue | rs.None, f: (a0: rs.JsValue) => rs.String): rs.String | undefined { + if (self === rs.None) { + return undefined; + } + return f(self); +} + +export function map$1tgzvu4(self: rs.u32 | undefined, f: (a0: rs.u32) => rs.JsValue): rs.JsValue | rs.None { + if (self === undefined) { + return rs.None; + } + return f(self); +} + +export function mapOr$09yh1c6(self: rs.u8 | undefined, default_: rs.JsValue, _: (a0: rs.u8) => rs.JsValue): rs.JsValue { + if (self === undefined) { + return default_; + } + return wasm_bindgen_from$convert_From_u8_wasm_bindgen_JsValue(self); +} + +//# sourceMappingURL=option.ts.map diff --git a/ts/fixtures/bindgen/expected/core/result.ts b/ts/fixtures/bindgen/expected/core/result.ts new file mode 100644 index 000000000..4ea4a4711 --- /dev/null +++ b/ts/fixtures/bindgen/expected/core/result.ts @@ -0,0 +1,13 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "../rs.ts"; +import { default_$Default_wasm_bindgen_JsValue as wasm_bindgen_default_$Default_wasm_bindgen_JsValue } from "../wasm_bindgen.ts"; + +export function unwrapOrDefault$1g20f1f(self: rs.Result): rs.JsValue { + if (self.ok) { + return self.value; + } + return wasm_bindgen_default_$Default_wasm_bindgen_JsValue(); +} + +//# sourceMappingURL=result.ts.map diff --git a/ts/fixtures/bindgen/expected/harness.Input.ts b/ts/fixtures/bindgen/expected/harness.Input.ts new file mode 100644 index 000000000..c0d9deced --- /dev/null +++ b/ts/fixtures/bindgen/expected/harness.Input.ts @@ -0,0 +1,167 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { copy$array_u8 } from "./lib.ts"; +import { from$convert_From_ref_slice_u8_js_sys_Uint8Array as js_sys_from$convert_From_ref_slice_u8_js_sys_Uint8Array } from "./js_sys.ts"; +import { fromStr$wasm_bindgen_JsValue as wasm_bindgen_fromStr$wasm_bindgen_JsValue, fromF64$wasm_bindgen_JsValue, from$convert_From_u32_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_u32_wasm_bindgen_JsValue, from$convert_From_i64_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_i64_wasm_bindgen_JsValue } from "./wasm_bindgen.ts"; + +/** The rest of a case's input. */ +export interface Input { + _0Data: Uint8Array; + _0Start: number; + _0Len: number; +} + +export function u8(self: Input): rs.u8 { + const _6Data: Uint8Array = self._0Data; + const _6Start: number = self._0Start; + const _6Len: number = self._0Len; + if (_6Len === 0) { + return 0; + } + const b: rs.u8 = _6Data[_6Start]; + self._0Data = _6Data; + self._0Start = _6Start + 1; + self._0Len = _6Len - 1; + return b; +} + +/** Whether the next byte's low bit is clear. */ +export function flag(self: Input): boolean { + return (u8(self) & 1) === 0; +} + +export function bytes$16(self: Input): Uint8Array { + const buf: Uint8Array = rs.bytes(16); + const _4: rs.ByteSlice = rs.byteSliceFrom(buf); + const iterS: Uint8Array = _4.data; + let iterI: number = _4.start; + const iterE: number = _4.start + _4.length; + while (iterI < iterE) { + iterS[iterI++] = u8(self); + } + return copy$array_u8(buf); +} + +export function bytes$4(self: Input): Uint8Array { + const buf: Uint8Array = rs.bytes(4); + const _4: rs.ByteSlice = rs.byteSliceFrom(buf); + const iterS: Uint8Array = _4.data; + let iterI: number = _4.start; + const iterE: number = _4.start + _4.length; + while (iterI < iterE) { + iterS[iterI++] = u8(self); + } + return copy$array_u8(buf); +} + +export function bytes$8(self: Input): Uint8Array { + const buf: Uint8Array = rs.bytes(8); + const _4: rs.ByteSlice = rs.byteSliceFrom(buf); + const iterS: Uint8Array = _4.data; + let iterI: number = _4.start; + const iterE: number = _4.start + _4.length; + while (iterI < iterE) { + iterS[iterI++] = u8(self); + } + return copy$array_u8(buf); +} + +export function u32(self: Input): rs.u32 { + return rs.u32FromBeBytes(bytes$4(self).slice().reverse()); +} + +export function u64(self: Input): rs.u64 { + return rs.u64FromBeBytes(bytes$8(self).slice().reverse()); +} + +export function u128(self: Input): rs.u128 { + return rs.u128FromBeBytes(bytes$16(self).slice().reverse()); +} + +/** A double: an edge, or one of any bits. */ +export function f64(self: Input): rs.f64 { + const _3: rs.f64 | undefined = rs.sliceGet(rs.sliceFrom([0.0, -0.0, 1.5, -2.5, NaN, Infinity, -Infinity, 4294967301.0, 2147483648.0, -2147483649.0, 9007199254740994.0, 1e300, 5e-324, 255.5, -1.0, 65535.5, -128.5, 1.8446744073709552e19]), rs.u8Rem(u8(self), 32) as rs.usize); + if (_3 === undefined) { + return rs.f64FromBits(u64(self)); + } + return _3; +} + +/** Up to 15 bytes. */ +export function slice(self: Input): rs.ByteSlice { + const len: rs.usize = rs.min(rs.u8Rem(u8(self), 16) as rs.usize, self._0Len); + const _11Data: Uint8Array = self._0Data; + const _11Start: number = self._0Start; + const _11Len: number = self._0Len; + if (len > _11Len) { + rs.panic("mid > len"); + } + const taken: rs.ByteSlice = new rs.ByteSlice(_11Data, _11Start, len); + self._0Data = _11Data; + self._0Start = _11Start + len; + self._0Len = _11Len - len; + return taken; +} + +/** Text from up to 15 bytes, each malformed sequence as U+FFFD. */ +export function text(self: Input): rs.String { + return rs.strCowIntoOwned(rs.stringFromUtf8Lossy(slice(self))); +} + +/** A JS value of each kind the cases read. */ +export function value(self: Input): rs.JsValue { + const _2: rs.u8 = rs.u8Rem(u8(self), 12); + if (_2 === 0) { + return undefined; + } + if (_2 === 1) { + return null; + } + if (_2 === 2) { + return true; + } + if (_2 === 3) { + return false; + } + if (_2 === 4) { + return fromF64$wasm_bindgen_JsValue(f64(self)); + } + if (_2 === 5) { + return wasm_bindgen_fromStr$wasm_bindgen_JsValue(text(self).v); + } + if (_2 === 6) { + return wasm_bindgen_from$convert_From_i64_wasm_bindgen_JsValue(rs.i64Cast(u64(self))); + } + if (_2 === 7) { + const _12: rs.ByteSlice = slice(self); + return rs.jsGlobal("Array").from(rs.byteSliceView(_12)) as rs.JsValue; + } + if (_2 === 8) { + return js_sys_from$convert_From_ref_slice_u8_js_sys_Uint8Array(slice(self)); + } + if (_2 === 9) { + return new (rs.jsGlobal("Object"))() as rs.JsValue; + } + if (_2 === 10) { + return wasm_bindgen_from$convert_From_u32_wasm_bindgen_JsValue(rs.u32Rem(u32(self), 1000)); + } + const _20: rs.Slice = rs.sliceFrom(units(self).items); + return rs.jsSpreadMethod(rs.jsGlobal("String"), "fromCharCode", [], rs.jsTyped(rs.global.Uint16Array, _20, false)) as rs.JsValue; +} + +/** UTF-16 code units, lone surrogates among them. */ +export function units(self: Input): rs.Vec { + return rs.iterToVec(rs.iterMap(new rs.Range(0, rs.u8Rem(u8(self), 6)), (_2: rs.u8): rs.u16 => { + const _32: rs.u8 = rs.u8Rem(u8(self), 4); + if (_32 === 0) { + return 55296 | u8(self) as rs.u16; + } + if (_32 === 1) { + return 56320 | u8(self) as rs.u16; + } + return (u8(self) as rs.u16) | 64; + }), undefined, 2, true); +} + +//# sourceMappingURL=harness.Input.ts.map diff --git a/ts/fixtures/bindgen/expected/harness.Owned.ts b/ts/fixtures/bindgen/expected/harness.Owned.ts new file mode 100644 index 000000000..d41b4c38a --- /dev/null +++ b/ts/fixtures/bindgen/expected/harness.Owned.ts @@ -0,0 +1,26 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { DROPS } from "./harness.ts"; + +/** + * A struct of the crate's own over a JS value, laid out as an imported type is, which + * stays a struct: its `Drop` runs. + */ +export class Owned { + obj: rs.JsValue; + + constructor(obj: rs.JsValue) { + this.obj = obj; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function drop$ops_Drop(_self: Owned): void { + rs.atomicFetch(DROPS, rs.u32WrappingAdd, 1, 0); +} + +//# sourceMappingURL=harness.Owned.ts.map diff --git a/ts/fixtures/bindgen/expected/harness.ts b/ts/fixtures/bindgen/expected/harness.ts new file mode 100644 index 000000000..ba5afcb80 --- /dev/null +++ b/ts/fixtures/bindgen/expected/harness.ts @@ -0,0 +1,819 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { thenSome$07biun1, then$15odrdm as bool_then$15odrdm } from "./core/bool.ts"; +import { unwrapOr$04rqm9m as option_unwrapOr$04rqm9m, unwrapOr$1b5hww3 as option_unwrapOr$1b5hww3, map$1izrkip as option_map$1izrkip, map$0wjyljl as option_map$0wjyljl, map$01bkz05 as option_map$01bkz05, mapOr$09yh1c6 as option_mapOr$09yh1c6 } from "./core/option.ts"; +import { unwrapOrDefault$1g20f1f as result_unwrapOrDefault$1g20f1f } from "./core/result.ts"; +import * as Input from "./harness.Input.ts"; +import * as Owned from "./harness.Owned.ts"; +import { GLOBAL as imports_GLOBAL, MISSING, PI as imports_PI, GREETING } from "./imports.ts"; +import * as Kind from "./imports.Kind.ts"; +import { iter$js_sys_Array, from$convert_From_f64_js_sys_Number as js_sys_from$convert_From_f64_js_sys_Number, isValidUtf16$js_sys_JsString, from$convert_From_ref_js_sys_JsString_String as js_sys_from$convert_From_ref_js_sys_JsString_String, from$convert_From_js_sys_JsString_String as js_sys_from$convert_From_js_sys_JsString_String, copyTo$js_sys_Uint8Array, from$convert_From_ref_slice_u8_js_sys_Uint8Array as js_sys_from$convert_From_ref_slice_u8_js_sys_Uint8Array } from "./js_sys.ts"; +import * as ArrayIter from "./js_sys.ArrayIter.ts"; +import { fromStr$wasm_bindgen_JsValue as wasm_bindgen_fromStr$wasm_bindgen_JsValue, fromF64$wasm_bindgen_JsValue, fromBool$wasm_bindgen_JsValue, asF64$wasm_bindgen_JsValue, isString$wasm_bindgen_JsValue, asString$wasm_bindgen_JsValue, asBool$wasm_bindgen_JsValue, isNull$wasm_bindgen_JsValue, isUndefined$wasm_bindgen_JsValue, isNullOrUndefined$wasm_bindgen_JsValue, isSymbol$wasm_bindgen_JsValue, isObject$wasm_bindgen_JsValue, isFunction$wasm_bindgen_JsValue, isBigint$wasm_bindgen_JsValue, jsTypeof$wasm_bindgen_JsValue, isTruthy$wasm_bindgen_JsValue, isFalsy$wasm_bindgen_JsValue, looseEq$wasm_bindgen_JsValue, unsignedShr$wasm_bindgen_JsValue as wasm_bindgen_unsignedShr$wasm_bindgen_JsValue, checkedDiv$wasm_bindgen_JsValue as wasm_bindgen_checkedDiv$wasm_bindgen_JsValue, pow$wasm_bindgen_JsValue as wasm_bindgen_pow$wasm_bindgen_JsValue, lt$wasm_bindgen_JsValue as wasm_bindgen_lt$wasm_bindgen_JsValue, le$wasm_bindgen_JsValue as wasm_bindgen_le$wasm_bindgen_JsValue, ge$wasm_bindgen_JsValue as wasm_bindgen_ge$wasm_bindgen_JsValue, gt$wasm_bindgen_JsValue as wasm_bindgen_gt$wasm_bindgen_JsValue, eq$cmp_PartialEq_wasm_bindgen_JsValue as wasm_bindgen_eq$cmp_PartialEq_wasm_bindgen_JsValue, not$ops_Not_ref_wasm_bindgen_JsValue as wasm_bindgen_not$ops_Not_ref_wasm_bindgen_JsValue, neg$ops_Neg_ref_wasm_bindgen_JsValue as wasm_bindgen_neg$ops_Neg_ref_wasm_bindgen_JsValue, bitand$ops_BitAnd_ref_wasm_bindgen_JsValue as wasm_bindgen_bitand$ops_BitAnd_ref_wasm_bindgen_JsValue, bitor$ops_BitOr_ref_wasm_bindgen_JsValue as wasm_bindgen_bitor$ops_BitOr_ref_wasm_bindgen_JsValue, bitxor$ops_BitXor_ref_wasm_bindgen_JsValue as wasm_bindgen_bitxor$ops_BitXor_ref_wasm_bindgen_JsValue, shl$ops_Shl_ref_wasm_bindgen_JsValue as wasm_bindgen_shl$ops_Shl_ref_wasm_bindgen_JsValue, shr$ops_Shr_ref_wasm_bindgen_JsValue as wasm_bindgen_shr$ops_Shr_ref_wasm_bindgen_JsValue, add$ops_Add_ref_wasm_bindgen_JsValue as wasm_bindgen_add$ops_Add_ref_wasm_bindgen_JsValue, sub$ops_Sub_ref_wasm_bindgen_JsValue as wasm_bindgen_sub$ops_Sub_ref_wasm_bindgen_JsValue, mul$ops_Mul_ref_wasm_bindgen_JsValue as wasm_bindgen_mul$ops_Mul_ref_wasm_bindgen_JsValue, from$convert_From_ref_str_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue, from$convert_From_ref_imports_Vendored_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_ref_imports_Vendored_wasm_bindgen_JsValue, from$convert_From_ref_js_sys_JsString_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_ref_js_sys_JsString_wasm_bindgen_JsValue, from$convert_From_i32_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_i32_wasm_bindgen_JsValue, from$convert_From_u32_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_u32_wasm_bindgen_JsValue, from$convert_From_u8_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_u8_wasm_bindgen_JsValue, from$convert_From_i64_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_i64_wasm_bindgen_JsValue, from$convert_From_i128_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_i128_wasm_bindgen_JsValue, from$convert_From_u128_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_u128_wasm_bindgen_JsValue, fmt$fmt_Debug_wasm_bindgen_JsValue as wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue } from "./wasm_bindgen.ts"; +import { dynInto$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_JsString, dynRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Array, dynRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Error, dynRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Uint8Array, uncheckedInto$wasm_bindgen_JsCast_js_sys_Object_js_sys_Array, uncheckedInto$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Point, uncheckedRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Object, isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Missing, isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Table, isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Vendored, isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_TypeError } from "./wasm_bindgen/cast.ts"; +import * as JsThreadLocal from "./wasm_bindgen.JsThreadLocal.ts"; +import { new_$web_sys_TextDecoderOptions as gen_TextDecoderOptions_new_$web_sys_TextDecoderOptions } from "./web_sys/features/gen_TextDecoderOptions.ts"; + +/** + * Each case's name and what it runs. The first byte picks one modulo their count, so a + * case added at the end moves no other. + */ +export function cases(): rs.Vec<[string, (a0: Input.Input, a1: rs.String) => undefined]> { + return rs.vecFrom([["math", math as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["numbers", numbers as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["bigints", bigints as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["text", text as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["encoding", encoding as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["json", json as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["arrays", arrays as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["typed", typed as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["casts", casts as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["ops", ops as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["classes", classes as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["statics", statics as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["console", logs as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["caught", caught as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["throws", throws as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined], ["edges", edges as (a0: Input.Input, a1: rs.String) => undefined] as [string, (a0: Input.Input, a1: rs.String) => undefined]]); +} + +/** Run one case. */ +export function run(input: rs.ByteSlice): rs.ByteVec { + const out: rs.String = rs.stringNew(); + if (input.length !== 0) { + const op: rs.u8 = input.data[input.start]; + define(); + const cases_: rs.Vec<[string, (a0: Input.Input, a1: rs.String) => undefined]> = cases(); + const case_: (a0: Input.Input, a1: rs.String) => undefined = cases_.items[rs.idx(cases_.items, rs.usizeRem(op as rs.usize, cases_.items.length))][1]; + const _19: Input.Input = { _0Data: input.data, _0Start: input.start + 1, _0Len: input.length - 1 }; + case_(_19, out); + return rs.stringIntoBytes(out); + } + return new rs.ByteVec(); +} + +/** The functions on `globalThis` that the fixture's own imports call. */ +export function define(): void { + const global: rs.JsValue = JsThreadLocal.with$js_sys_Object_Clone_clone_js_sys_Object_js_sys_Object(imports_GLOBAL.force(), (a0: rs.JsValue): rs.JsValue => { + return a0; + }); + const typeof_: rs.JsValue = new (rs.jsGlobal("Function"))("v", "return typeof v") as rs.JsValue; + const identity: rs.JsValue = new (rs.jsGlobal("Function"))("v", "return v") as rs.JsValue; + const check: rs.JsValue = new (rs.jsGlobal("Function"))("v", "if (typeof v !== 'number') throw new TypeError('not a number: ' + String(v))") as rs.JsValue; + const _14: rs.JsValue = global; + const _17: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("rustTypeof"); + const _19: rs.JsValue = typeof_; + rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").set(_14, _17, _19), (item: unknown) => rs.jsToBool(item), false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + const _24: rs.JsValue = global; + const _27: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("rustCheck"); + const _29: rs.JsValue = check; + rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").set(_24, _27, _29), (item2: unknown) => rs.jsToBool(item2), false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + const _34: rs.JsValue = global; + const _37: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("rustIdentity"); + const _39: rs.JsValue = identity; + rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").set(_34, _37, _39), (item3: unknown) => rs.jsToBool(item3), false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); +} + +/** A JS value's text, as `String()` gives it, in a line. */ +export function show(out: rs.String, what: string, v: rs.JsValue): void { + const text: rs.String = js_sys_from$convert_From_js_sys_JsString_String(rs.jsGlobal("String")(v) as rs.JsValue); + const args3 = [(f: rs.Formatter) => rs.fmtPad(f, what), (f: rs.Formatter) => rs.fmtStr(text.v, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, [args3[0], ": ", args3[1], "\n"]), rs.fmtErrorDebug); +} + +export function math(input: Input.Input, out: rs.String): void { + const _5: [rs.f64, rs.f64] = [Input.f64(input), Input.f64(input)]; + const x: rs.f64 = _5[0]; + const y: rs.f64 = _5[1]; + const part = rs.f64ToBits(rs.jsToF64(rs.jsGlobal("Math").floor(x))); + rs.resultUnwrap(rs.fmtWriteString(out, ["floor ", (f: rs.Formatter) => rs.u64Debug(part, f), "\n"]), rs.fmtErrorDebug); + const part2 = rs.f64ToBits(rs.jsToF64(rs.jsGlobal("Math").trunc(x))); + rs.resultUnwrap(rs.fmtWriteString(out, ["trunc ", (f: rs.Formatter) => rs.u64Debug(part2, f), "\n"]), rs.fmtErrorDebug); + const part3 = rs.f64ToBits(rs.jsToF64(rs.jsGlobal("Math").sign(x))); + rs.resultUnwrap(rs.fmtWriteString(out, ["sign ", (f: rs.Formatter) => rs.u64Debug(part3, f), "\n"]), rs.fmtErrorDebug); + const part4 = rs.f32ToBits(rs.jsToF32(rs.jsGlobal("Math").fround(x))); + rs.resultUnwrap(rs.fmtWriteString(out, ["fround ", (f: rs.Formatter) => rs.u32Debug(part4, f), "\n"]), rs.fmtErrorDebug); + const part5 = rs.f64ToBits(rs.jsToF64(rs.jsGlobal("Math").hypot(x, y))); + rs.resultUnwrap(rs.fmtWriteString(out, ["hypot ", (f: rs.Formatter) => rs.u64Debug(part5, f), "\n"]), rs.fmtErrorDebug); + const part6 = rs.f64ToBits(rs.jsToF64(rs.jsGlobal("Math").atan2(x, y))); + rs.resultUnwrap(rs.fmtWriteString(out, ["atan2 ", (f: rs.Formatter) => rs.u64Debug(part6, f), "\n"]), rs.fmtErrorDebug); + const part7 = rs.f64ToBits(rs.jsToF64(rs.jsGlobal("Math").pow(x, y))); + rs.resultUnwrap(rs.fmtWriteString(out, ["pow ", (f: rs.Formatter) => rs.u64Debug(part7, f), "\n"]), rs.fmtErrorDebug); + const part8 = rs.f64ToBits(rs.jsToF64(rs.jsGlobal("Math").max(x, y))); + rs.resultUnwrap(rs.fmtWriteString(out, ["max ", (f: rs.Formatter) => rs.u64Debug(part8, f), "\n"]), rs.fmtErrorDebug); + const _102: rs.i32 = Input.u32(input) | 0; + const part9 = rs.jsToU32(rs.jsGlobal("Math").clz32(_102)); + rs.resultUnwrap(rs.fmtWriteString(out, ["clz32 ", (f: rs.Formatter) => rs.u32Display(part9, f), "\n"]), rs.fmtErrorDebug); + const _118: rs.Slice = rs.sliceRange(rs.sliceFrom([y, Input.f64(input), Input.f64(input)]), 0, rs.u8Rem(Input.u8(input), 4) as rs.usize); + const part11 = rs.f64ToBits(rs.jsToF64(rs.jsSpreadMethod(rs.jsGlobal("Math"), "max", [x], rs.jsTyped(rs.global.Float64Array, _118, false)))); + rs.resultUnwrap(rs.fmtWriteString(out, ["max_of ", (f: rs.Formatter) => rs.u64Debug(part11, f), "\n"]), rs.fmtErrorDebug); +} + +export function numbers(input: Input.Input, out: rs.String): void { + const x: rs.f64 = Input.f64(input); + const part = rs.jsToU8(rs.jsGlobal("Math").round(x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["u8 ", (f: rs.Formatter) => rs.u8Display(part, f), "\n"]), rs.fmtErrorDebug); + const part2 = rs.jsToI8(rs.jsGlobal("Math").round(x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["i8 ", (f: rs.Formatter) => rs.i8Display(part2, f), "\n"]), rs.fmtErrorDebug); + const part3 = rs.jsToU16(rs.jsGlobal("Math").round(x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["u16 ", (f: rs.Formatter) => rs.u16Display(part3, f), "\n"]), rs.fmtErrorDebug); + const part4 = rs.jsToI16(rs.jsGlobal("Math").round(x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["i16 ", (f: rs.Formatter) => rs.i16Display(part4, f), "\n"]), rs.fmtErrorDebug); + const part5 = rs.jsToU32(rs.jsGlobal("Math").round(x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["u32 ", (f: rs.Formatter) => rs.u32Display(part5, f), "\n"]), rs.fmtErrorDebug); + const part6 = rs.jsToI32(rs.jsGlobal("Math").round(x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["i32 ", (f: rs.Formatter) => rs.i32Display(part6, f), "\n"]), rs.fmtErrorDebug); + const part7 = rs.jsToUsize(rs.jsGlobal("Math").round(x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["usize ", (f: rs.Formatter) => rs.usizeDisplay(part7, f), "\n"]), rs.fmtErrorDebug); + const part8 = rs.f32ToBits(rs.jsToF32(rs.jsGlobal("Math").round(x))); + rs.resultUnwrap(rs.fmtWriteString(out, ["f32 ", (f: rs.Formatter) => rs.u32Display(part8, f), "\n"]), rs.fmtErrorDebug); + const part9 = rs.jsToBool(rs.jsGlobal("Math").round(x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["bool ", (f: rs.Formatter) => rs.fmtBool(part9, f), "\n"]), rs.fmtErrorDebug); + const maybe: rs.f64 | undefined = thenSome$07biun1(Input.flag(input), x); + const part10 = ((got: unknown): rs.u32 | undefined => { + return rs.jsIsNone(got) ? undefined : rs.jsToU32(got); + })(rs.jsGlobal("Math").round(((some: rs.f64 | undefined) => some === undefined ? undefined : some)(maybe))); + rs.resultUnwrap(rs.fmtWriteString(out, ["option ", (f: rs.Formatter) => rs.fmtOption(part10, f, rs.u32Debug), "\n"]), rs.fmtErrorDebug); + const n: rs.JsValue = fromF64$wasm_bindgen_JsValue(x); + const _113: boolean = rs.jsToBool(rs.jsGlobal("Number").isInteger(n)); + const _116: boolean = rs.jsToBool(rs.jsGlobal("Number").isSafeInteger(n)); + const args35 = [(f: rs.Formatter) => rs.fmtBool(_113, f), (f: rs.Formatter) => rs.fmtBool(_116, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["integer ", args35[0], " safe ", args35[1], "\n"]), rs.fmtErrorDebug); + const _126: rs.JsValue = js_sys_from$convert_From_f64_js_sys_Number(x); + const _127: rs.u8 = rs.u8Rem(Input.u8(input), 40); + const _124: rs.Result = rs.jsCatch(() => rs.jsAny(_126).toString(_127), (item: unknown) => item as rs.JsValue, false) as rs.Result; + if (_124.ok) { + const part11 = js_sys_from$convert_From_js_sys_JsString_String(_124.value); + rs.resultUnwrap(rs.fmtWriteString(out, ["radix ", (f: rs.Formatter) => rs.fmtStr(part11.v, f), "\n"]), rs.fmtErrorDebug); + } else { + show(out, "radix threw", _124.error); + } + show(out, "string", fromF64$wasm_bindgen_JsValue(x)); +} + +export function bigints(input: Input.Input, out: rs.String): void { + const bits: rs.u32 = rs.u8Rem(Input.u8(input), 140) as rs.u32; + const _9: [rs.u64, rs.u128] = [Input.u64(input), Input.u128(input)]; + const a: rs.u64 = _9[0]; + const b: rs.u128 = _9[1]; + const part = rs.jsToWide(rs.jsGlobal("BigInt").asIntN(bits, rs.jsBigInt(rs.i64Cast(a))), 64, true) as rs.i64; + rs.resultUnwrap(rs.fmtWriteString(out, ["as_int_n ", (f: rs.Formatter) => rs.i64Display(part, f), "\n"]), rs.fmtErrorDebug); + const part2 = rs.jsToWide(rs.jsGlobal("BigInt").asUintN(bits, rs.jsBigInt(a)), 64, false) as rs.u64; + rs.resultUnwrap(rs.fmtWriteString(out, ["as_uint_n ", (f: rs.Formatter) => rs.u64Display(part2, f), "\n"]), rs.fmtErrorDebug); + const part3 = rs.jsToWide(rs.jsGlobal("BigInt").asIntN(bits, rs.jsBigInt(rs.i128Cast(b))), 128, true) as rs.i128; + rs.resultUnwrap(rs.fmtWriteString(out, ["as_int_n128 ", (f: rs.Formatter) => rs.i128Display(part3, f), "\n"]), rs.fmtErrorDebug); + const part4 = rs.jsToWide(rs.jsGlobal("BigInt").asUintN(bits, rs.jsBigInt(b)), 128, false) as rs.u128; + rs.resultUnwrap(rs.fmtWriteString(out, ["as_uint_n128 ", (f: rs.Formatter) => rs.u128Display(part4, f), "\n"]), rs.fmtErrorDebug); + const _56: rs.f64 = Input.f64(input); + const _55: rs.Result = rs.jsCatch(() => rs.jsGlobal("BigInt").asIntN(bits, _56), (item: unknown) => rs.jsToWide(item, 64, true) as rs.i64, false) as rs.Result; + if (_55.ok) { + const arg = _55.value; + rs.resultUnwrap(rs.fmtWriteString(out, ["from number ", (f: rs.Formatter) => rs.i64Display(arg, f), "\n"]), rs.fmtErrorDebug); + } else { + show(out, "from number threw", _55.error); + } + show(out, "i64", wasm_bindgen_from$convert_From_i64_wasm_bindgen_JsValue(rs.i64Cast(a))); + show(out, "u128", wasm_bindgen_from$convert_From_u128_wasm_bindgen_JsValue(b)); + show(out, "i128", wasm_bindgen_from$convert_From_i128_wasm_bindgen_JsValue(rs.i128Cast(b))); +} + +export function text(input: Input.Input, out: rs.String): void { + const c: rs.char = option_unwrapOr$04rqm9m(rs.charFromU32(rs.u32Rem(Input.u32(input), 1114112)), 65533); + const part = rs.jsToString(rs.jsGlobal("String")(rs.jsChar(c))); + rs.resultUnwrap(rs.fmtWriteString(out, ["char_string ", (f: rs.Formatter) => rs.fmtStr(part.v, f), "\n"]), rs.fmtErrorDebug); + const s: rs.String = Input.text(input); + const _25: string = s.v; + const part2 = rs.jsToChar(rs.jsGlobal("String")(_25)); + rs.resultUnwrap(rs.fmtWriteString(out, ["first_char ", (f: rs.Formatter) => rs.fmtCharDebug(part2, f), "\n"]), rs.fmtErrorDebug); + const maybe: rs.String | undefined = bool_then$15odrdm(Input.flag(input), (): rs.String => { + return rs.stringClone(s); + }); + const part3 = ((got: unknown): rs.String | undefined => { + return rs.jsIsNone(got) ? undefined : rs.jsToString(got); + })(rs.jsGlobal("String")(((some: rs.String | undefined) => some === undefined ? undefined : rs.jsString(some))(maybe))); + rs.resultUnwrap(rs.fmtWriteString(out, ["string_option ", (f: rs.Formatter) => rs.fmtOption(part3, f, (value: rs.String, f: rs.Formatter) => rs.fmtStr(value.v, f)), "\n"]), rs.fmtErrorDebug); + const units: rs.Vec = Input.units(input); + const _47: rs.Slice = rs.sliceFrom(units.items); + const js: rs.JsValue = rs.jsSpreadMethod(rs.jsGlobal("String"), "fromCharCode", [], rs.jsTyped(rs.global.Uint16Array, _47, false)) as rs.JsValue; + const _54: rs.u32 = rs.jsToU32(rs.jsAny(js).length); + const args30 = [(f: rs.Formatter) => rs.fmtList(units.items, f, rs.u16Debug), (f: rs.Formatter) => rs.u32Display(_54, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["units ", args30[0], " length ", args30[1], "\n"]), rs.fmtErrorDebug); + const part4 = isValidUtf16$js_sys_JsString(js); + rs.resultUnwrap(rs.fmtWriteString(out, ["well formed ", (f: rs.Formatter) => rs.fmtBool(part4, f), "\n"]), rs.fmtErrorDebug); + const part5 = js_sys_from$convert_From_ref_js_sys_JsString_String(js); + rs.resultUnwrap(rs.fmtWriteString(out, ["lossy ", (f: rs.Formatter) => rs.fmtStr(part5.v, f), "\n"]), rs.fmtErrorDebug); + const part6 = asString$wasm_bindgen_JsValue(wasm_bindgen_from$convert_From_ref_js_sys_JsString_wasm_bindgen_JsValue(js)); + rs.resultUnwrap(rs.fmtWriteString(out, ["as_string ", (f: rs.Formatter) => rs.fmtOption(part6, f, (value: rs.String, f: rs.Formatter) => rs.fmtStr(value.v, f)), "\n"]), rs.fmtErrorDebug); + const part7 = js_sys_from$convert_From_js_sys_JsString_String(rs.jsAny(js).toUpperCase() as rs.JsValue); + rs.resultUnwrap(rs.fmtWriteString(out, ["upper ", (f: rs.Formatter) => rs.fmtStr(part7.v, f), "\n"]), rs.fmtErrorDebug); + const part8 = rs.f64ToBits(rs.jsToF64(rs.jsAny(js).charCodeAt(0))); + rs.resultUnwrap(rs.fmtWriteString(out, ["char_code_at ", (f: rs.Formatter) => rs.u64Debug(part8, f), "\n"]), rs.fmtErrorDebug); + const part9 = asF64$wasm_bindgen_JsValue(rs.jsAny(js).codePointAt(0) as rs.JsValue); + rs.resultUnwrap(rs.fmtWriteString(out, ["code_point_at ", (f: rs.Formatter) => rs.fmtOption(part9, f, rs.f64Debug), "\n"]), rs.fmtErrorDebug); + const _136: rs.u32 = rs.u32Rem(Input.u32(input), 1179648); + const _135: rs.Result = rs.jsCatch(() => rs.jsGlobal("String").fromCodePoint(_136), (item: unknown) => item as rs.JsValue, false) as rs.Result; + if (_135.ok) { + const part10 = js_sys_from$convert_From_js_sys_JsString_String(_135.value); + rs.resultUnwrap(rs.fmtWriteString(out, ["from_code_point ", (f: rs.Formatter) => rs.fmtStr(part10.v, f), "\n"]), rs.fmtErrorDebug); + } else { + show(out, "from_code_point threw", _135.error); + } + const part11 = JsThreadLocal.with$js_sys_JsString_harness_text_closure1_String(GREETING.force(), (s3: rs.JsValue): rs.String => { + return js_sys_from$convert_From_ref_js_sys_JsString_String(s3); + }); + rs.resultUnwrap(rs.fmtWriteString(out, ["greeting ", (f: rs.Formatter) => rs.fmtStr(part11.v, f), "\n"]), rs.fmtErrorDebug); + const _171: string = s.v; + const part12 = rs.jsToBool(rs.jsAny(js).includes(_171, 0)); + rs.resultUnwrap(rs.fmtWriteString(out, ["includes ", (f: rs.Formatter) => rs.fmtBool(part12, f), "\n"]), rs.fmtErrorDebug); +} + +export function encoding(input: Input.Input, out: rs.String): void { + const encoder: rs.JsValue = rs.resultUnwrap(rs.jsCatch(() => new (rs.jsGlobal("TextEncoder"))(), (item: unknown) => item as rs.JsValue, false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + const s: rs.String = Input.text(input); + const part = rs.jsToString(rs.jsAny(encoder).encoding); + rs.resultUnwrap(rs.fmtWriteString(out, ["encoding ", (f: rs.Formatter) => rs.fmtPad(f, part.v), "\n"]), rs.fmtErrorDebug); + const _24: string = s.v; + const part2 = rs.jsToByteVec(rs.jsAny(encoder).encode(_24)); + rs.resultUnwrap(rs.fmtWriteString(out, ["encode ", (f: rs.Formatter) => rs.fmtList(rs.byteVecView(part2), f, rs.u8Debug), "\n"]), rs.fmtErrorDebug); + const labels: string[] = ["utf-8", "UTF8", "latin1", "utf-16le", "no such label"]; + const _36: rs.usize = rs.usizeRem(Input.u8(input) as rs.usize, rs.sliceFrom(labels).length); + if (!(_36 < 5)) { + rs.panic("index out of bounds: the len is " + 5 + " but the index is " + _36); + } + const options: rs.JsValue = gen_TextDecoderOptions_new_$web_sys_TextDecoderOptions(); + const _47: boolean = Input.flag(input); + rs.jsAny(options).fatal = _47; + const part3 = ((got: unknown): boolean | undefined => { + return rs.jsIsNone(got) ? undefined : !!got; + })(rs.jsAny(options).fatal); + rs.resultUnwrap(rs.fmtWriteString(out, ["fatal ", (f: rs.Formatter) => rs.fmtOption(part3, f, rs.fmtBool), "\n"]), rs.fmtErrorDebug); + const _59: rs.Result = rs.jsCatch(() => new (rs.jsGlobal("TextDecoder"))(labels[_36], options), (item2: unknown) => item2 as rs.JsValue, false) as rs.Result; + if (_59.ok) { + const decoder: rs.JsValue = _59.value; + const _68: rs.String = rs.jsToString(rs.jsAny(decoder).encoding); + const _71: boolean = rs.jsToBool(rs.jsAny(decoder).fatal); + const args14 = [(f: rs.Formatter) => rs.fmtPad(f, _68.v), (f: rs.Formatter) => rs.fmtBool(_71, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["decoder ", args14[0], " fatal ", args14[1], "\n"]), rs.fmtErrorDebug); + const _80: rs.ByteSlice = Input.slice(input); + const _78: rs.Result = rs.jsCatch(() => rs.jsAny(decoder).decode(rs.byteSliceView(_80)), (item3: unknown) => rs.jsToString(item3), true) as rs.Result; + if (_78.ok) { + const arg = _78.value; + rs.resultUnwrap(rs.fmtWriteString(out, ["decode ", (f: rs.Formatter) => rs.fmtStr(arg.v, f), "\n"]), rs.fmtErrorDebug); + } else { + show(out, "decode threw", _78.error); + } + } else { + show(out, "decoder threw", _59.error); + } +} + +export function json(input: Input.Input, out: rs.String): void { + let text!: rs.String; + const i: rs.u8 = rs.u8Rem(Input.u8(input), 6); + const texts: string[] = ["{\"a\":1,\"b\":[true,null]}", "[1,2", "\"\\ud800\"", "1e400", "{\"x\":{\"y\":\"z\"}}"]; + if (i === 5) { + text = Input.text(input); + } else { + const _12: rs.usize = i as rs.usize; + if (!(_12 < 5)) { + rs.panic("index out of bounds: the len is " + 5 + " but the index is " + _12); + } + text = rs.stringFrom(texts[_12]); + } + const _16: string = text.v; + const _15: rs.Result = rs.jsCatch(() => rs.jsGlobal("JSON").parse(_16), (item: unknown) => item as rs.JsValue, false) as rs.Result; + if (_15.ok) { + const v: rs.JsValue = _15.value; + const _21: rs.Result = rs.jsCatch(() => rs.jsGlobal("JSON").stringify(v), (item2: unknown) => item2 as rs.JsValue, false) as rs.Result; + if (_21.ok) { + const part = asString$wasm_bindgen_JsValue(_21.value); + rs.resultUnwrap(rs.fmtWriteString(out, ["stringify ", (f: rs.Formatter) => rs.fmtOption(part, f, (value: rs.String, f: rs.Formatter) => rs.fmtStr(value.v, f)), "\n"]), rs.fmtErrorDebug); + } else { + show(out, "stringify threw", _21.error); + } + if (isObject$wasm_bindgen_JsValue(v)) { + const part2 = rs.jsToVec(rs.jsGlobal("Object").keys(v), (item3: unknown) => rs.jsToString(item3)); + rs.resultUnwrap(rs.fmtWriteString(out, ["keys ", (f: rs.Formatter) => rs.fmtList(part2.items, f, (value: rs.String, f: rs.Formatter) => rs.fmtStr(value.v, f)), "\n"]), rs.fmtErrorDebug); + const part3 = rs.jsToU32(rs.jsAny(rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").ownKeys(v), (item4: unknown) => item4 as rs.JsValue, false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f))).length); + rs.resultUnwrap(rs.fmtWriteString(out, ["own_keys ", (f: rs.Formatter) => rs.u32Display(part3, f), "\n"]), rs.fmtErrorDebug); + const _74: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("a"); + show(out, "get a", rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").get(v, _74), (item5: unknown) => item5 as rs.JsValue, false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f))); + const _84: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("x"); + const part4 = rs.jsCatch(() => rs.jsGlobal("Reflect").has(v, _84), (item6: unknown) => rs.jsToBool(item6), false) as rs.Result; + rs.resultUnwrap(rs.fmtWriteString(out, ["has x ", (f: rs.Formatter) => rs.fmtResult(part4, f, rs.fmtBool, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)), "\n"]), rs.fmtErrorDebug); + } + } else { + show(out, "parse threw", _15.error); + } + const _95: rs.JsValue = Input.value(input); + const _93: rs.Result = rs.jsCatch(() => rs.jsGlobal("JSON").stringify(_95), (item7: unknown) => item7 as rs.JsValue, false) as rs.Result; + if (_93.ok) { + const part5 = asString$wasm_bindgen_JsValue(_93.value); + rs.resultUnwrap(rs.fmtWriteString(out, ["value ", (f: rs.Formatter) => rs.fmtOption(part5, f, (value: rs.String, f: rs.Formatter) => rs.fmtStr(value.v, f)), "\n"]), rs.fmtErrorDebug); + } else { + show(out, "value threw", _93.error); + } +} + +export function arrays(input: Input.Input, out: rs.String): void { + const array: rs.JsValue = new (rs.jsGlobal("Array"))() as rs.JsValue; + const _6: rs.u8 = rs.u8Rem(Input.u8(input), 5); + let iterStart: rs.u8 = 0; + const iterEnd: rs.u8 = _6; + while (iterStart++ < iterEnd) { + const _16: rs.JsValue = Input.value(input); + rs.jsToU32(rs.jsAny(array).push(_16)); + } + const part = rs.jsToU32(rs.jsAny(array).length); + rs.resultUnwrap(rs.fmtWriteString(out, ["length ", (f: rs.Formatter) => rs.u32Display(part, f), "\n"]), rs.fmtErrorDebug); + const part2 = js_sys_from$convert_From_js_sys_JsString_String(rs.jsAny(array).join(",") as rs.JsValue); + rs.resultUnwrap(rs.fmtWriteString(out, ["join ", (f: rs.Formatter) => rs.fmtStr(part2.v, f), "\n"]), rs.fmtErrorDebug); + const probe: rs.JsValue = Input.value(input); + const _47: rs.i32 = rs.jsToI32(rs.jsAny(array).indexOf(probe, 0)); + const _51: boolean = rs.jsToBool(rs.jsAny(array).includes(probe, 0)); + const args17 = [(f: rs.Formatter) => rs.i32Display(_47, f), (f: rs.Formatter) => rs.fmtBool(_51, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["index_of ", args17[0], " includes ", args17[1], "\n"]), rs.fmtErrorDebug); + const _65: rs.JsValue = array; + const _64: boolean = rs.jsToBool(rs.jsGlobal("Array").isArray(_65)); + const _69: boolean = rs.jsToBool(rs.jsGlobal("Array").isArray(probe)); + const args18 = [(f: rs.Formatter) => rs.fmtBool(_64, f), (f: rs.Formatter) => rs.fmtBool(_69, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["is_array ", args18[0], " ", args18[1], "\n"]), rs.fmtErrorDebug); + show(out, "pop", rs.jsAny(array).pop() as rs.JsValue); + show(out, "at", rs.jsAny(array).at(-1) as rs.JsValue); + const _87: string = "get"; + const _91: rs.u32 = rs.u32Rem(Input.u32(input), 8); + show(out, _87, rs.jsAny(array)[_91] as rs.JsValue); + const _96: rs.u32 = rs.u32Rem(Input.u32(input), 6); + const _99: rs.JsValue = probe; + rs.jsAny(array)[_96] = _99; + show(out, "reversed", rs.jsAny(array).reverse() as rs.JsValue); + show(out, "sorted", rs.jsAny(array).sort() as rs.JsValue); + const _116: string = "of2"; + const _122: rs.JsValue = array; + show(out, _116, rs.jsGlobal("Array").of(probe, _122) as rs.JsValue); + show(out, "array_of", rs.jsSpreadMethod(rs.jsGlobal("Array"), "of", [], array) as rs.JsValue); + const values: rs.Vec = rs.iterToVec(rs.iterFrom(iter$js_sys_Array(array), ArrayIter.next$iter_Iterator_wasm_bindgen_JsValue, undefined, { hint: ArrayIter.sizeHint$iter_Iterator_wasm_bindgen_JsValue, nth: (it: ArrayIter.ArrayIter, n) => rs.iterFromOption(ArrayIter.nth$iter_Iterator_wasm_bindgen_JsValue(it, n), rs.None), count: ArrayIter.count$iter_Iterator_wasm_bindgen_JsValue, last: (it: ArrayIter.ArrayIter) => rs.iterFromOption(ArrayIter.last$iter_Iterator_wasm_bindgen_JsValue(it), rs.None), back: (it: ArrayIter.ArrayIter) => rs.iterFromOption(ArrayIter.nextBack$iter_DoubleEndedIterator_wasm_bindgen_JsValue(it), rs.None), nthBack: (it: ArrayIter.ArrayIter, n) => rs.iterFromOption(ArrayIter.nthBack$iter_DoubleEndedIterator_wasm_bindgen_JsValue(it, n), rs.None) }), "next", 4); + const part3 = rs.jsToVec(rs.jsGlobal("Array").from(rs.jsArray(values, (item: rs.JsValue) => item)), (item2: unknown) => item2 as rs.JsValue).items.length; + rs.resultUnwrap(rs.fmtWriteString(out, ["values ", (f: rs.Formatter) => rs.usizeDisplay(part3, f), "\n"]), rs.fmtErrorDebug); + const strings: rs.Vec = rs.vecFrom([Input.text(input), Input.text(input)]); + const part4 = rs.jsToVec(rs.jsGlobal("Array").from(rs.jsArray(strings, (item3: rs.String) => rs.jsString(item3))), (item4: unknown) => rs.jsToString(item4)); + rs.resultUnwrap(rs.fmtWriteString(out, ["strings ", (f: rs.Formatter) => rs.fmtList(part4.items, f, (value: rs.String, f: rs.Formatter) => rs.fmtStr(value.v, f)), "\n"]), rs.fmtErrorDebug); + const _170: rs.Slice = rs.sliceFrom([Input.f64(input), Input.f64(input)]); + const part5 = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.jsToVec(rs.jsGlobal("Array").from(rs.jsTyped(rs.global.Float64Array, _170, false)), (item5: unknown) => rs.jsToF64(item5)).items), (f: rs.f64): rs.u64 => { + return rs.f64ToBits(f); + }), undefined, 8, true); + rs.resultUnwrap(rs.fmtWriteString(out, ["floats ", (f: rs.Formatter) => rs.fmtList(part5.items, f, rs.u64Debug), "\n"]), rs.fmtErrorDebug); + const _185: rs.Slice = rs.sliceFrom([Input.u64(input), { hi: 0xffffffff, lo: 0xffffffff } as rs.u64]); + for (const v of rs.jsToVec(rs.jsGlobal("Array").from(rs.jsTyped(rs.global.BigUint64Array, _185, true)), (item6: unknown) => item6 as rs.JsValue).items) { + show(out, "wide", v); + } + const _196: string = "bytes"; + const _200: rs.ByteSlice = Input.slice(input); + show(out, _196, rs.jsGlobal("Array").from(rs.byteSliceView(_200)) as rs.JsValue); +} + +export function typed(input: Input.Input, out: rs.String): void { + const bytes: rs.ByteVec = rs.byteSliceToByteVec(Input.slice(input)); + const array: rs.JsValue = js_sys_from$convert_From_ref_slice_u8_js_sys_Uint8Array(rs.byteSliceRange(rs.byteVecSlice(bytes), 0)); + const part = rs.jsToU32(rs.jsAny(array).length); + rs.resultUnwrap(rs.fmtWriteString(out, ["length ", (f: rs.Formatter) => rs.u32Display(part, f), "\n"]), rs.fmtErrorDebug); + const i: rs.u32 = rs.u32Rem(Input.u32(input), 20); + const _27: rs.u8 = rs.jsToU8(rs.jsAny(array)[i]); + const args20 = [(f: rs.Formatter) => rs.u32Display(i, f), (f: rs.Formatter) => rs.u8Display(_27, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["get_index ", args20[0], " ", args20[1], "\n"]), rs.fmtErrorDebug); + const _37: rs.u32 = rs.u32Rem(Input.u32(input), 20); + const _40: rs.u8 = Input.u8(input); + rs.jsAny(array)[_37] = _40; + const _43: rs.u32 = rs.u32Rem(Input.u32(input), 8); + const _46: rs.u32 = rs.u32Rem(Input.u32(input), 16); + const sub: rs.JsValue = rs.jsAny(array).subarray(_43, _46) as rs.JsValue; + const copy: rs.ByteVec = rs.byteVecOf(0, rs.jsToU32(rs.jsAny(sub).length) as rs.usize); + const _55: rs.ByteSlice = rs.byteVecSlice(copy); + copyTo$js_sys_Uint8Array(sub, _55); + rs.resultUnwrap(rs.fmtWriteString(out, ["subarray ", (f: rs.Formatter) => rs.fmtList(rs.byteVecView(copy), f, rs.u8Debug), "\n"]), rs.fmtErrorDebug); + const _68: rs.u8 = Input.u8(input); + rs.jsAny(array).fill(_68, 1, 3) as rs.JsValue; + const whole: rs.ByteVec = rs.byteVecOf(0, rs.jsToU32(rs.jsAny(array).length) as rs.usize); + const _75: rs.ByteSlice = rs.byteVecSlice(whole); + copyTo$js_sys_Uint8Array(array, _75); + rs.resultUnwrap(rs.fmtWriteString(out, ["filled ", (f: rs.Formatter) => rs.fmtList(rs.byteVecView(whole), f, rs.u8Debug), "\n"]), rs.fmtErrorDebug); + const _92: rs.ByteVec = rs.byteVecFrom(rs.byteVecView(bytes)); + const part2 = rs.jsToByteVec(rs.jsGlobal("Uint8Array").from(rs.jsByteVec(_92))); + rs.resultUnwrap(rs.fmtWriteString(out, ["copy ", (f: rs.Formatter) => rs.fmtList(rs.byteVecView(part2), f, rs.u8Debug), "\n"]), rs.fmtErrorDebug); + const _104: rs.ByteSlice = rs.byteVecSlice(bytes); + const part3 = rs.jsToByteSlice(rs.jsGlobal("Uint8Array").from(rs.byteSliceView(_104))); + rs.resultUnwrap(rs.fmtWriteString(out, ["boxed ", (f: rs.Formatter) => rs.fmtList(rs.byteSliceView(part3), f, rs.u8Debug), "\n"]), rs.fmtErrorDebug); + const _111: rs.Slice = rs.sliceFrom([Input.f64(input), Input.f64(input)]); + const floats: rs.JsValue = new (rs.jsGlobal("Float64Array"))(rs.jsTyped(rs.global.Float64Array, _111, false)) as rs.JsValue; + const _121: rs.u64 = rs.f64ToBits(rs.jsToF64(rs.jsAny(floats)[0])); + const _125: rs.u64 = rs.f64ToBits(rs.jsToF64(rs.jsAny(floats)[5])); + const args25 = [(f: rs.Formatter) => rs.u64Debug(_121, f), (f: rs.Formatter) => rs.u64Debug(_125, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["floats ", args25[0], " ", args25[1], "\n"]), rs.fmtErrorDebug); + const _134: rs.JsValue = floats; + const shorts: rs.JsValue = new (rs.jsGlobal("Int16Array"))(_134) as rs.JsValue; + const _142: rs.i16 = rs.jsToI16(rs.jsAny(shorts)[0]); + const _145: rs.i16 = rs.jsToI16(rs.jsAny(shorts)[1]); + const args26 = [(f: rs.Formatter) => rs.i16Display(_142, f), (f: rs.Formatter) => rs.i16Display(_145, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["shorts ", args26[0], " ", args26[1], "\n"]), rs.fmtErrorDebug); + const _153: rs.Slice = rs.sliceFrom([rs.i64Cast(Input.u64(input)), { hi: -0x80000000, lo: 0 } as rs.i64]); + const wides: rs.JsValue = new (rs.jsGlobal("BigInt64Array"))(rs.jsTyped(rs.global.BigInt64Array, _153, true)) as rs.JsValue; + const _163: rs.i64 = rs.jsToWide(rs.jsAny(wides)[0], 64, true) as rs.i64; + const _166: rs.i64 = rs.jsToWide(rs.jsAny(wides)[1], 64, true) as rs.i64; + const args27 = [(f: rs.Formatter) => rs.i64Display(_163, f), (f: rs.Formatter) => rs.i64Display(_166, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["wides ", args27[0], " ", args27[1], "\n"]), rs.fmtErrorDebug); + show(out, "at", option_mapOr$09yh1c6(((got: unknown): rs.u8 | undefined => { + return got === undefined ? undefined : rs.jsToU8(got); + })(rs.jsToOptionSmall(rs.jsAny(array).at(-1))), null, wasm_bindgen_from$convert_From_u8_wasm_bindgen_JsValue)); +} + +export function casts(input: Input.Input, out: rs.String): void { + const v: rs.JsValue = Input.value(input); + rs.resultUnwrap(rs.fmtWriteString(out, ["debug ", (f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(v, f), "\n"]), rs.fmtErrorDebug); + const _18: boolean = isUndefined$wasm_bindgen_JsValue(v); + const _21: boolean = isNull$wasm_bindgen_JsValue(v); + const _24: boolean = isNullOrUndefined$wasm_bindgen_JsValue(v); + const _27: boolean = isObject$wasm_bindgen_JsValue(v); + const _30: boolean = isFunction$wasm_bindgen_JsValue(v); + const _33: boolean = isString$wasm_bindgen_JsValue(v); + const _36: boolean = isBigint$wasm_bindgen_JsValue(v); + const _39: boolean = isSymbol$wasm_bindgen_JsValue(v); + const _42: boolean = isFalsy$wasm_bindgen_JsValue(v); + const _45: boolean = isTruthy$wasm_bindgen_JsValue(v); + const args28 = [(f: rs.Formatter) => rs.fmtBool(_18, f), (f: rs.Formatter) => rs.fmtBool(_21, f), (f: rs.Formatter) => rs.fmtBool(_24, f), (f: rs.Formatter) => rs.fmtBool(_27, f), (f: rs.Formatter) => rs.fmtBool(_30, f), (f: rs.Formatter) => rs.fmtBool(_33, f), (f: rs.Formatter) => rs.fmtBool(_36, f), (f: rs.Formatter) => rs.fmtBool(_39, f), (f: rs.Formatter) => rs.fmtBool(_42, f), (f: rs.Formatter) => rs.fmtBool(_45, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["undefined ", args28[0], " null ", args28[1], " none ", args28[2], " object ", args28[3], " function ", args28[4], " string ", args28[5], " bigint ", args28[6], " symbol ", args28[7], " falsy ", args28[8], " truthy ", args28[9], "\n"]), rs.fmtErrorDebug); + const _65: rs.u64 | undefined = option_map$1izrkip(asF64$wasm_bindgen_JsValue(v), (a0: rs.f64): rs.u64 => { + return rs.f64ToBits(a0); + }); + const _69: boolean | undefined = asBool$wasm_bindgen_JsValue(v); + const _72: rs.String | undefined = asString$wasm_bindgen_JsValue(v); + const args29 = [(f: rs.Formatter) => rs.fmtOption(_65, f, rs.u64Debug), (f: rs.Formatter) => rs.fmtOption(_69, f, rs.fmtBool), (f: rs.Formatter) => rs.fmtOption(_72, f, (value: rs.String, f: rs.Formatter) => rs.fmtStr(value.v, f))]; + rs.resultUnwrap(rs.fmtWriteString(out, ["as_f64 ", args29[0], " as_bool ", args29[1], " as_string ", args29[2], "\n"]), rs.fmtErrorDebug); + show(out, "typeof", jsTypeof$wasm_bindgen_JsValue(v)); + const kind: Kind.Kind = rs.jsToEnum(["Number", "String", "Object", "Undefined", "__Invalid"], ["number", "string", "object", "undefined"], rs.jsGlobal("rustTypeof")(v)) as Kind.Kind; + const _92: rs.String = rs.jsToString(rs.jsGlobal("String")(rs.jsEnum(["Number", "String", "Object", "Undefined", "__Invalid"], ["number", "string", "object", "undefined"], kind))); + const args30 = [(f: rs.Formatter) => Kind.fmt$fmt_Debug(kind, f), (f: rs.Formatter) => rs.fmtPad(f, _92.v)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["kind ", args30[0], " ", args30[1], "\n"]), rs.fmtErrorDebug); + const part = option_unwrapOr$1b5hww3(option_map$0wjyljl(dynRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Array(v), (a02: rs.JsValue): rs.u32 => { + return rs.jsToU32(rs.jsAny(a02).length); + }), 4294967295); + rs.resultUnwrap(rs.fmtWriteString(out, ["array ", (f: rs.Formatter) => rs.u32Display(part, f), "\n"]), rs.fmtErrorDebug); + const part2 = option_map$0wjyljl(dynRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Uint8Array(v), (a03: rs.JsValue): rs.u32 => { + return rs.jsToU32(rs.jsAny(a03).length); + }); + rs.resultUnwrap(rs.fmtWriteString(out, ["bytes ", (f: rs.Formatter) => rs.fmtOption(part2, f, rs.u32Debug), "\n"]), rs.fmtErrorDebug); + const part3 = isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Table(v); + rs.resultUnwrap(rs.fmtWriteString(out, ["table ", (f: rs.Formatter) => rs.fmtBool(part3, f), "\n"]), rs.fmtErrorDebug); + const part4 = isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Missing(v); + rs.resultUnwrap(rs.fmtWriteString(out, ["missing class ", (f: rs.Formatter) => rs.fmtBool(part4, f), "\n"]), rs.fmtErrorDebug); + const _147: rs.Result = dynInto$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_JsString(v); + if (_147.ok) { + const part5 = rs.jsToU32(rs.jsAny(_147.value).length); + rs.resultUnwrap(rs.fmtWriteString(out, ["js string ", (f: rs.Formatter) => rs.u32Display(part5, f), "\n"]), rs.fmtErrorDebug); + } else { + show(out, "not a js string", _147.error); + } + const part6 = isUndefined$wasm_bindgen_JsValue(uncheckedRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Object(v)); + rs.resultUnwrap(rs.fmtWriteString(out, ["unchecked ", (f: rs.Formatter) => rs.fmtBool(part6, f), "\n"]), rs.fmtErrorDebug); + const other: rs.JsValue = Input.value(input); + const _184: boolean = looseEq$wasm_bindgen_JsValue(v, other); + const _188: boolean = wasm_bindgen_eq$cmp_PartialEq_wasm_bindgen_JsValue(v, other); + const args37 = [(f: rs.Formatter) => rs.fmtBool(_184, f), (f: rs.Formatter) => rs.fmtBool(_188, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["loose_eq ", args37[0], " eq ", args37[1], "\n"]), rs.fmtErrorDebug); + const args38 = [(f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(null, f), (f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(undefined, f), (f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(true, f), (f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(false, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["constants ", args38[0], " ", args38[1], " ", args38[2], " ", args38[3], "\n"]), rs.fmtErrorDebug); + const part7 = fromBool$wasm_bindgen_JsValue(Input.flag(input)); + rs.resultUnwrap(rs.fmtWriteString(out, ["from bool ", (f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(part7, f), "\n"]), rs.fmtErrorDebug); +} + +export function ops(input: Input.Input, out: rs.String): void { + const _3: (a0: Input.Input) => rs.JsValue = (input2: Input.Input): rs.JsValue => { + const _32: rs.u8 = rs.u8Rem(Input.u8(input2), 3); + if (_32 === 0) { + return fromF64$wasm_bindgen_JsValue(Input.f64(input2)); + } + if (_32 === 1) { + return wasm_bindgen_from$convert_From_i64_wasm_bindgen_JsValue(rs.i64Rem(rs.i64Cast(Input.u64(input2)), 1000)); + } + return wasm_bindgen_from$convert_From_u32_wasm_bindgen_JsValue(rs.u32Rem(Input.u32(input2), 50)); + }; + const _6: [rs.JsValue, rs.JsValue] = [_3(input), _3(input)]; + const a: rs.JsValue = _6[0]; + const b: rs.JsValue = _6[1]; + if (!(isBigint$wasm_bindgen_JsValue(a) !== isBigint$wasm_bindgen_JsValue(b))) { + show(out, "add", wasm_bindgen_add$ops_Add_ref_wasm_bindgen_JsValue(a, b)); + show(out, "sub", wasm_bindgen_sub$ops_Sub_ref_wasm_bindgen_JsValue(a, b)); + show(out, "mul", wasm_bindgen_mul$ops_Mul_ref_wasm_bindgen_JsValue(a, b)); + show(out, "checked_div", wasm_bindgen_checkedDiv$wasm_bindgen_JsValue(a, b)); + show(out, "neg", wasm_bindgen_neg$ops_Neg_ref_wasm_bindgen_JsValue(a)); + show(out, "and", wasm_bindgen_bitand$ops_BitAnd_ref_wasm_bindgen_JsValue(a, b)); + show(out, "or", wasm_bindgen_bitor$ops_BitOr_ref_wasm_bindgen_JsValue(a, b)); + show(out, "xor", wasm_bindgen_bitxor$ops_BitXor_ref_wasm_bindgen_JsValue(a, b)); + show(out, "not", fromBool$wasm_bindgen_JsValue(wasm_bindgen_not$ops_Not_ref_wasm_bindgen_JsValue(a))); + show(out, "shl", wasm_bindgen_shl$ops_Shl_ref_wasm_bindgen_JsValue(a, b)); + show(out, "shr", wasm_bindgen_shr$ops_Shr_ref_wasm_bindgen_JsValue(a, b)); + if (!isBigint$wasm_bindgen_JsValue(a)) { + show(out, "unsigned_shr", wasm_bindgen_from$convert_From_u32_wasm_bindgen_JsValue(wasm_bindgen_unsignedShr$wasm_bindgen_JsValue(a, b))); + show(out, "pow", wasm_bindgen_pow$wasm_bindgen_JsValue(a, b)); + } + const _126: boolean = wasm_bindgen_lt$wasm_bindgen_JsValue(a, b); + const _130: boolean = wasm_bindgen_le$wasm_bindgen_JsValue(a, b); + const _134: boolean = wasm_bindgen_gt$wasm_bindgen_JsValue(a, b); + const _138: boolean = wasm_bindgen_ge$wasm_bindgen_JsValue(a, b); + const args6 = [(f: rs.Formatter) => rs.fmtBool(_126, f), (f: rs.Formatter) => rs.fmtBool(_130, f), (f: rs.Formatter) => rs.fmtBool(_134, f), (f: rs.Formatter) => rs.fmtBool(_138, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["lt ", args6[0], " le ", args6[1], " gt ", args6[2], " ge ", args6[3], "\n"]), rs.fmtErrorDebug); + } else { + show(out, "checked_div", wasm_bindgen_checkedDiv$wasm_bindgen_JsValue(a, b)); + const _29: boolean = wasm_bindgen_lt$wasm_bindgen_JsValue(a, b); + const _33: boolean = looseEq$wasm_bindgen_JsValue(a, b); + const args5 = [(f: rs.Formatter) => rs.fmtBool(_29, f), (f: rs.Formatter) => rs.fmtBool(_33, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["mixed lt ", args5[0], " eq ", args5[1], "\n"]), rs.fmtErrorDebug); + } +} + +export function classes(input: Input.Input, out: rs.String): void { + const table: rs.JsValue = new (rs.jsGlobal("Map"))() as rs.JsValue; + const _6: rs.u8 = rs.u8Rem(Input.u8(input), 4); + let iterStart: rs.u8 = 0; + const iterEnd: rs.u8 = _6; + while (iterStart++ < iterEnd) { + const _16: rs.JsValue = Input.value(input); + const _18: rs.JsValue = fromF64$wasm_bindgen_JsValue(Input.f64(input)); + rs.jsAny(table).set(_16, _18) as rs.JsValue; + } + const key: rs.JsValue = Input.value(input); + const _25: rs.JsValue = fromF64$wasm_bindgen_JsValue(Input.f64(input)); + rs.jsAny(table).set(key, _25) as rs.JsValue; + const part = option_map$1izrkip(((got: unknown): rs.f64 | undefined => { + return rs.jsIsNone(got) ? undefined : rs.jsToF64(got); + })(rs.jsAny(table).get(key)), (a0: rs.f64): rs.u64 => { + return rs.f64ToBits(a0); + }); + rs.resultUnwrap(rs.fmtWriteString(out, ["get ", (f: rs.Formatter) => rs.fmtOption(part, f, rs.u64Debug), "\n"]), rs.fmtErrorDebug); + const _49: rs.JsValue = Input.value(input); + const part2 = option_map$1izrkip(((got2: unknown): rs.f64 | undefined => { + return rs.jsIsNone(got2) ? undefined : rs.jsToF64(got2); + })(rs.jsAny(table).get(_49)), (a02: rs.f64): rs.u64 => { + return rs.f64ToBits(a02); + }); + rs.resultUnwrap(rs.fmtWriteString(out, ["get other ", (f: rs.Formatter) => rs.fmtOption(part2, f, rs.u64Debug), "\n"]), rs.fmtErrorDebug); + const part3 = rs.jsToBool(rs.jsGlobal("Map").prototype.has.call(rs.jsAny(table), key)); + rs.resultUnwrap(rs.fmtWriteString(out, ["has ", (f: rs.Formatter) => rs.fmtBool(part3, f), "\n"]), rs.fmtErrorDebug); + const _71: rs.u32 = rs.jsToU32(rs.jsAny(table).size); + const _74: rs.u32 = rs.jsToU32(rs.jsAny(rs.jsDescriptor(rs.jsGlobal("Map").prototype, "size").get).call(table)); + const _77: rs.u32 = rs.jsToU32(rs.jsAny(table).size); + const args22 = [(f: rs.Formatter) => rs.u32Display(_71, f), (f: rs.Formatter) => rs.u32Display(_74, f), (f: rs.Formatter) => rs.u32Display(_77, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["size ", args22[0], " ", args22[1], " ", args22[2], "\n"]), rs.fmtErrorDebug); + const iter2: rs.JsValue = rs.jsAny(table)[rs.global.Symbol.iterator]() as rs.JsValue; + while (true) { + const _87: rs.Result = rs.jsCatch(() => rs.jsAny(iter2).next(), (item: unknown) => item as rs.JsValue, false) as rs.Result; + if (_87.ok) { + const next: rs.JsValue = _87.value; + if (!rs.jsToBool(rs.jsAny(next).done)) { + show(out, "entry", rs.jsAny(next).value as rs.JsValue); + } else { + break; + } + } else { + break; + } + } + const _99: rs.ByteSlice = Input.slice(input); + const items: rs.JsValue = rs.jsGlobal("Array").from(rs.byteSliceView(_99)) as rs.JsValue; + const parity: rs.JsValue = new (rs.jsGlobal("Function"))("x", "return x % 2") as rs.JsValue; + const part4 = rs.jsToU32(rs.jsAny(rs.jsGlobal("Map").groupBy(items, parity) as rs.JsValue).size); + rs.resultUnwrap(rs.fmtWriteString(out, ["groups ", (f: rs.Formatter) => rs.u32Display(part4, f), "\n"]), rs.fmtErrorDebug); + const _120: string = "{\"x\":1.5,\"y\":2}"; + const point: rs.JsValue = uncheckedInto$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Point(rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("JSON").parse(_120), (item2: unknown) => item2 as rs.JsValue, false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f))); + const part5 = rs.f64ToBits(rs.jsToF64(rs.jsAny(point).x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["x ", (f: rs.Formatter) => rs.u64Debug(part5, f), "\n"]), rs.fmtErrorDebug); + const _135: rs.f64 = Input.f64(input); + rs.jsAny(point).x = _135; + const part6 = rs.f64ToBits(rs.jsToF64(rs.jsAny(point).x)); + rs.resultUnwrap(rs.fmtWriteString(out, ["x ", (f: rs.Formatter) => rs.u64Debug(part6, f), "\n"]), rs.fmtErrorDebug); + const key2: rs.String = Input.text(input); + const _151: string = key2.v; + const _154: rs.JsValue = Input.value(input); + rs.jsAny(point)[_151] = _154; + const _156: string = "at"; + const _160: string = key2.v; + show(out, _156, rs.jsAny(point)[_160] as rs.JsValue); + delete rs.jsAny(point)["y"]; + show(out, "y", rs.jsAny(point)["y"] as rs.JsValue); + const part7 = rs.jsToString(rs.jsAny(point).toString()); + rs.resultUnwrap(rs.fmtWriteString(out, ["describe ", (f: rs.Formatter) => rs.fmtStr(part7.v, f), "\n"]), rs.fmtErrorDebug); + const _188: rs.JsValue = point; + const part8 = rs.jsToVec(rs.jsGlobal("Object").keys(_188), (item3: unknown) => rs.jsToString(item3)); + rs.resultUnwrap(rs.fmtWriteString(out, ["keys ", (f: rs.Formatter) => rs.fmtList(part8.items, f, (value: rs.String, f: rs.Formatter) => rs.fmtStr(value.v, f)), "\n"]), rs.fmtErrorDebug); +} + +export function statics(_1: Input.Input, out: rs.String): void { + const part = JsThreadLocal.with$f64_harness_statics_closure0_u64(imports_PI.force(), (pi: rs.f64): rs.u64 => { + return rs.f64ToBits(pi); + }); + rs.resultUnwrap(rs.fmtWriteString(out, ["pi ", (f: rs.Formatter) => rs.u64Debug(part, f), "\n"]), rs.fmtErrorDebug); + const part2 = JsThreadLocal.with$Option_wasm_bindgen_JsValue_Option_T_is_some_wasm_bindgen_JsValue_bool(MISSING.force(), (a0: rs.JsValue | rs.None): boolean => { + return a0 !== rs.None; + }); + rs.resultUnwrap(rs.fmtWriteString(out, ["missing ", (f: rs.Formatter) => rs.fmtBool(part2, f), "\n"]), rs.fmtErrorDebug); + const part3 = JsThreadLocal.with$js_sys_JsString_harness_statics_closure1_u32(GREETING.force(), (s: rs.JsValue): rs.u32 => { + return rs.jsToU32(rs.jsAny(s).length); + }); + rs.resultUnwrap(rs.fmtWriteString(out, ["greeting ", (f: rs.Formatter) => rs.u32Debug(part3, f), "\n"]), rs.fmtErrorDebug); + const _45: rs.JsValue = JsThreadLocal.with$js_sys_Object_Clone_clone_js_sys_Object_js_sys_Object(imports_GLOBAL.force(), (a02: rs.JsValue): rs.JsValue => { + return a02; + }); + const _48: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("Math"); + const part4 = rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").has(_45, _48), (item: unknown) => rs.jsToBool(item), false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + rs.resultUnwrap(rs.fmtWriteString(out, ["global ", (f: rs.Formatter) => rs.fmtBool(part4, f), "\n"]), rs.fmtErrorDebug); + const vendored: rs.JsValue = new (rs.jsVendored("RustVendored", ["webkit"]))() as rs.JsValue; + const _60: rs.String = rs.jsToString(rs.jsAny(vendored).name()); + const _63: boolean = isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Vendored(wasm_bindgen_from$convert_From_ref_imports_Vendored_wasm_bindgen_JsValue(vendored)); + const args15 = [(f: rs.Formatter) => rs.fmtStr(_60.v, f), (f: rs.Formatter) => rs.fmtBool(_63, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["vendored ", args15[0], " ", args15[1], "\n"]), rs.fmtErrorDebug); +} + +export function logs(input: Input.Input, out: rs.String): void { + const _5: rs.JsValue = JsThreadLocal.with$js_sys_Object_Clone_clone_js_sys_Object_js_sys_Object(imports_GLOBAL.force(), (a0: rs.JsValue): rs.JsValue => { + return a0; + }); + const _10: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("console"); + const console: rs.JsValue = rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").get(_5, _10), (item: unknown) => item as rs.JsValue, false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + const _16: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("log"); + const original: rs.JsValue = rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").get(console, _16), (item2: unknown) => item2 as rs.JsValue, false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + const logged: rs.JsValue = new (rs.jsGlobal("Array"))() as rs.JsValue; + const _21: rs.JsValue = new (rs.jsGlobal("Function"))("...args", "this.push(args.map(String).join(' '))") as rs.JsValue; + const _24: rs.JsValue = logged; + const capture: rs.JsValue = rs.jsAny(_21).bind(_24) as rs.JsValue; + const _31: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("log"); + const _33: rs.JsValue = capture; + rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").set(console, _31, _33), (item3: unknown) => rs.jsToBool(item3), false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + rs.jsGlobal("console").log(); + undefined; + const _39: rs.JsValue = Input.value(input); + rs.jsGlobal("console").log(_39); + undefined; + const _42: rs.JsValue = Input.value(input); + const _44: rs.JsValue = wasm_bindgen_fromStr$wasm_bindgen_JsValue(Input.text(input).v); + rs.jsGlobal("console").log(_42, _44); + undefined; + const _50: rs.JsValue = Input.value(input); + const _52: rs.JsValue = Input.value(input); + const _54: rs.JsValue = wasm_bindgen_from$convert_From_u32_wasm_bindgen_JsValue(Input.u32(input)); + const many: rs.JsValue = rs.jsGlobal("Array").of(_50, _52, _54) as rs.JsValue; + rs.jsSpreadMethod(rs.jsGlobal("console"), "log", [], many); + undefined; + const _62: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("log"); + rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").set(console, _62, original), (item4: unknown) => rs.jsToBool(item4), false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + const iter: ArrayIter.ArrayIter = iter$js_sys_Array(logged); + while (true) { + const _69: rs.JsValue | rs.Done = ArrayIter.next$iter_Iterator_wasm_bindgen_JsValue(iter); + if (_69 === rs.DONE) { + break; + } + show(out, "logged", _69); + } +} + +export function caught(input: Input.Input, out: rs.String): void { + const v: rs.JsValue = Input.value(input); + const _5: rs.Result = rs.jsCatch(() => rs.jsGlobal("rustCheck")(v), (_item: unknown) => undefined, false) as rs.Result; + if (_5.ok) { + rs.stringPushStr(out, "a number\n"); + rs.resultUnwrap(rs.OK, rs.fmtErrorDebug); + } else { + const e: rs.JsValue = _5.error; + const error: rs.String | undefined = option_map$01bkz05(dynRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Error(e), (e4: rs.JsValue): rs.String => { + return js_sys_from$convert_From_js_sys_JsString_String(rs.jsAny(e4).message as rs.JsValue); + }); + const _19: boolean = isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_TypeError(e); + const args5 = [(f: rs.Formatter) => rs.fmtOption(error, f, (value: rs.String, f: rs.Formatter) => rs.fmtStr(value.v, f)), (f: rs.Formatter) => rs.fmtBool(_19, f)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["threw ", args5[0], " type error ", args5[1], "\n"]), rs.fmtErrorDebug); + } + const _31: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("length"); + const _28: rs.Result = rs.jsCatch(() => rs.jsGlobal("Reflect").get(v, _31), (item2: unknown) => item2 as rs.JsValue, false) as rs.Result; + if (_28.ok) { + show(out, "length", _28.value); + } else { + show(out, "length threw", _28.error); + } + const _40: rs.Result = rs.jsCatch(() => rs.jsGlobal("Reflect").ownKeys(v), (item3: unknown) => item3 as rs.JsValue, false) as rs.Result; + if (_40.ok) { + const part = rs.jsToU32(rs.jsAny(_40.value).length); + rs.resultUnwrap(rs.fmtWriteString(out, ["own keys ", (f: rs.Formatter) => rs.u32Display(part, f), "\n"]), rs.fmtErrorDebug); + } else { + show(out, "own keys threw", _40.error); + } +} + +/** An import that throws without `catch`: the throw unwinds Rust and leaves the call. */ +export function throws(input: Input.Input, out: rs.String): void { + const bits: rs.i32 = rs.i32Sub(Input.u8(input) as rs.i32, 128); + const part = rs.jsToWide(rs.jsGlobal("BigInt").asIntN(bits, rs.jsBigInt(1)), 64, true) as rs.i64; + rs.resultUnwrap(rs.fmtWriteString(out, ["as_int_n ", (f: rs.Formatter) => rs.i64Display(part, f), "\n"]), rs.fmtErrorDebug); +} + +export const DROPS: rs.Atomic = /* @__PURE__ */ new rs.Atomic(0); + +/** + * Whether `e` is an `Error` of the class `class` names, as each side's engine words its + * message. + */ +export function errorClass(out: rs.String, what: string, e: rs.JsValue): void { + const _7: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("name"); + show(out, what, result_unwrapOrDefault$1g20f1f(rs.jsCatch(() => rs.jsGlobal("Reflect").get(e, _7), (item: unknown) => item as rs.JsValue, false) as rs.Result)); +} + +/** + * Each conversion's edge: namespaces' variadic calls, `catch` around the glue's + * conversions and not the ABI's, optional `bool`s, globals no JS defines, and a struct of + * the crate's own over a JS value. + */ +export function edges(input: Input.Input, out: rs.String): void { + let spread!: rs.JsValue; + let x!: rs.f64 | undefined; + const _9: rs.ByteSlice = Input.slice(input); + const part = rs.jsToByteVec(rs.jsSpreadMethod(rs.jsGlobal("Uint8Array"), "of", [], rs.byteSliceView(_9))); + rs.resultUnwrap(rs.fmtWriteString(out, ["bytes of ", (f: rs.Formatter) => rs.fmtList(rs.byteVecView(part), f, rs.u8Debug), "\n"]), rs.fmtErrorDebug); + if (!Input.flag(input)) { + const like: rs.JsValue = new (rs.jsGlobal("Object"))() as rs.JsValue; + const _20: rs.JsValue = like; + const _23: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("length"); + const _26: rs.JsValue = wasm_bindgen_from$convert_From_i32_wasm_bindgen_JsValue(1); + rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").set(_20, _23, _26), (item: unknown) => rs.jsToBool(item), false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + const _29: rs.JsValue = like; + const _32: rs.JsValue = wasm_bindgen_from$convert_From_ref_str_wasm_bindgen_JsValue("0"); + const _35: rs.JsValue = Input.value(input); + rs.resultUnwrap(rs.jsCatch(() => rs.jsGlobal("Reflect").set(_29, _32, _35), (item2: unknown) => rs.jsToBool(item2), false) as rs.Result, (value: rs.JsValue, f: rs.Formatter) => wasm_bindgen_fmt$fmt_Debug_wasm_bindgen_JsValue(value, f)); + spread = uncheckedInto$wasm_bindgen_JsCast_js_sys_Object_js_sys_Array(like); + } else { + const _16: rs.ByteSlice = Input.slice(input); + spread = rs.jsGlobal("Array").from(rs.byteSliceView(_16)) as rs.JsValue; + } + const _38: rs.JsValue = spread; + const _37: rs.Result = rs.jsCatch(() => rs.jsSpreadMethod(rs.jsGlobal("Array"), "of", [], _38), (item3: unknown) => item3 as rs.JsValue, false) as rs.Result; + if (_37.ok) { + const part2 = rs.jsToU32(rs.jsAny(_37.value).length); + rs.resultUnwrap(rs.fmtWriteString(out, ["spread ", (f: rs.Formatter) => rs.u32Display(part2, f), "\n"]), rs.fmtErrorDebug); + } else { + errorClass(out, "spread threw", _37.error); + } + const _58: rs.Slice = rs.sliceRange(rs.sliceFrom([Input.u32(input), Input.u32(input)]), 0, rs.u8Rem(Input.u8(input), 3) as rs.usize); + const part4 = rs.jsToU32(rs.jsSpreadMethod(rs.jsGlobal("rustFresh"), "of", [], rs.jsTyped(rs.global.Uint32Array, _58, false))); + rs.resultUnwrap(rs.fmtWriteString(out, ["fresh of ", (f: rs.Formatter) => rs.u32Display(part4, f), "\n"]), rs.fmtErrorDebug); + const _76: rs.JsValue = wasm_bindgen_from$convert_From_i32_wasm_bindgen_JsValue(16777215); + const sentinel: rs.u8 | undefined = ((got: unknown): rs.u8 | undefined => { + return got === undefined ? undefined : rs.jsToU8(got); + })(rs.jsToOptionSmall(rs.jsGlobal("rustIdentity")(_76))); + const _79: rs.JsValue = fromF64$wasm_bindgen_JsValue(Input.f64(input)); + const small: rs.u8 | undefined = ((got2: unknown): rs.u8 | undefined => { + return got2 === undefined ? undefined : rs.jsToU8(got2); + })(rs.jsToOptionSmall(rs.jsGlobal("rustIdentity")(_79))); + const args24 = [(f: rs.Formatter) => rs.fmtOption(sentinel, f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(small, f, rs.u8Debug)]; + rs.resultUnwrap(rs.fmtWriteString(out, ["u8 option ", args24[0], " ", args24[1], "\n"]), rs.fmtErrorDebug); + const v: rs.JsValue = Input.value(input); + const _94: rs.Result = rs.jsCatch(() => rs.jsGlobal("String")(v), (item4: unknown) => rs.jsToChar(item4), true) as rs.Result; + if (_94.ok) { + const arg = _94.value; + rs.resultUnwrap(rs.fmtWriteString(out, ["char ", (f: rs.Formatter) => rs.fmtCharDebug(arg, f), "\n"]), rs.fmtErrorDebug); + } else { + errorClass(out, "char threw", _94.error); + } + if (!Input.flag(input)) { + x = undefined; + } else { + x = Input.f64(input); + } + const part5 = ((got3: unknown): boolean | undefined => { + return rs.jsIsNone(got3) ? undefined : !!got3; + })(rs.jsGlobal("Math").abs(((some: rs.f64 | undefined) => some === undefined ? undefined : some)(x))); + rs.resultUnwrap(rs.fmtWriteString(out, ["abs flag ", (f: rs.Formatter) => rs.fmtOption(part5, f, rs.fmtBool), "\n"]), rs.fmtErrorDebug); + const _124: rs.Result = rs.jsCatch(() => rs.jsGlobal("rustNoSuchFunction")(), (_item5: unknown) => undefined, false) as rs.Result; + if (_124.ok) { + rs.stringPushStr(out, "missing function called\n"); + rs.resultUnwrap(rs.OK, rs.fmtErrorDebug); + } else { + errorClass(out, "missing function threw", _124.error); + } + const _132: rs.Result = rs.jsCatch(() => new (rs.jsGlobal("RustNoSuchClass"))(), (item6: unknown) => item6 as rs.JsValue, false) as rs.Result; + if (_132.ok) { + rs.stringPushStr(out, "missing class made\n"); + rs.resultUnwrap(rs.OK, rs.fmtErrorDebug); + } else { + errorClass(out, "missing class threw", _132.error); + } + const before: rs.u32 = rs.atomicLoad(DROPS, 0); + const owned: Owned.Owned = new Owned.Owned(Input.value(input)); + show(out, "owned", owned.obj); + owned[Symbol.dispose](); + const part6 = rs.u32Sub(rs.atomicLoad(DROPS, 0), before); + rs.resultUnwrap(rs.fmtWriteString(out, ["drops ", (f: rs.Formatter) => rs.u32Display(part6, f), "\n"]), rs.fmtErrorDebug); + const v2: rs.JsValue = Input.value(input); + const _163: rs.Result = rs.jsCatch(() => rs.jsGlobal("rustIdentity")(v2), (item7: unknown) => rs.jsToU32(item7), false) as rs.Result; + if (_163.ok) { + const arg2 = _163.value; + rs.resultUnwrap(rs.fmtWriteString(out, ["u32 ", (f: rs.Formatter) => rs.u32Display(arg2, f), "\n"]), rs.fmtErrorDebug); + } else { + errorClass(out, "u32 threw", _163.error); + } + const _181: rs.JsValue = Input.value(input); + const _179: rs.Result = rs.jsCatch(() => rs.jsGlobal("rustIdentity")(_181), (item8: unknown) => ((got4: unknown): rs.i16 | undefined => { + return got4 === undefined ? undefined : rs.jsToI16(got4); + })(rs.jsToOptionSmall(item8)), false) as rs.Result; + if (_179.ok) { + const arg3 = _179.value; + rs.resultUnwrap(rs.fmtWriteString(out, ["i16 option ", (f: rs.Formatter) => rs.fmtOption(arg3, f, rs.i16Debug), "\n"]), rs.fmtErrorDebug); + } else { + errorClass(out, "i16 option threw", _179.error); + } + const _202: rs.JsValue = Input.value(input); + const part7 = rs.jsToU32(rs.jsGlobal("rustIdentity")(_202)); + rs.resultUnwrap(rs.fmtWriteString(out, ["u32 of ", (f: rs.Formatter) => rs.u32Display(part7, f), "\n"]), rs.fmtErrorDebug); +} + +//# sourceMappingURL=harness.ts.map diff --git a/ts/fixtures/bindgen/expected/imports.Kind.ts b/ts/fixtures/bindgen/expected/imports.Kind.ts new file mode 100644 index 000000000..a1ff849ab --- /dev/null +++ b/ts/fixtures/bindgen/expected/imports.Kind.ts @@ -0,0 +1,24 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** What `typeof` says, of the kinds the harness reads. */ +export type Kind = "Number" | "String" | "Object" | "Undefined" | "__Invalid"; + +export function fmt$fmt_Debug(self: Kind, f: rs.Formatter): rs.Result { + let _3!: string; + if (self === "Number") { + _3 = "Number"; + } else if (self === "String") { + _3 = "String"; + } else if (self === "Object") { + _3 = "Object"; + } else if (self === "Undefined") { + _3 = "Undefined"; + } else { + _3 = "__Invalid"; + } + return rs.fmtWriteStr(f, _3); +} + +//# sourceMappingURL=imports.Kind.ts.map diff --git a/ts/fixtures/bindgen/expected/imports.ts b/ts/fixtures/bindgen/expected/imports.ts new file mode 100644 index 000000000..a60a04746 --- /dev/null +++ b/ts/fixtures/bindgen/expected/imports.ts @@ -0,0 +1,40 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as JsThreadLocal from "./wasm_bindgen.JsThreadLocal.ts"; + +export const GLOBAL: rs.LazyCell> = /* @__PURE__ */ new rs.LazyCell((): JsThreadLocal.JsThreadLocal => { + return { __inner: _VAL$GLOBAL }; +}); + +export const MISSING: rs.LazyCell> = /* @__PURE__ */ new rs.LazyCell((): JsThreadLocal.JsThreadLocal => { + return { __inner: _VAL$MISSING }; +}); + +export const PI: rs.LazyCell> = /* @__PURE__ */ new rs.LazyCell((): JsThreadLocal.JsThreadLocal => { + return { __inner: _VAL$PI }; +}); + +export const _VAL$GLOBAL: rs.LazyCell = /* @__PURE__ */ new rs.LazyCell((): rs.JsValue => { + return rs.jsGlobal("globalThis") as rs.JsValue; +}); + +export const _VAL$MISSING: rs.LazyCell = /* @__PURE__ */ new rs.LazyCell((): rs.JsValue | rs.None => { + return ((got: unknown): rs.JsValue | rs.None => { + return rs.jsIsNone(got) ? rs.None : got as rs.JsValue; + })(rs.jsOptionalPath("rustMissing")); +}); + +export const _VAL$PI: rs.LazyCell = /* @__PURE__ */ new rs.LazyCell((): rs.f64 => { + return rs.jsToF64(rs.jsGlobal("Math").PI); +}); + +export const GREETING: rs.LazyCell> = /* @__PURE__ */ new rs.LazyCell((): JsThreadLocal.JsThreadLocal => { + return { __inner: _VAL$GREETING }; +}); + +export const _VAL$GREETING: rs.LazyCell = /* @__PURE__ */ new rs.LazyCell((): rs.JsValue => { + return "hé😀" as rs.JsValue; +}); + +//# sourceMappingURL=imports.ts.map diff --git a/ts/fixtures/bindgen/expected/js_sys.ArrayIter.ts b/ts/fixtures/bindgen/expected/js_sys.ArrayIter.ts new file mode 100644 index 000000000..29618e32c --- /dev/null +++ b/ts/fixtures/bindgen/expected/js_sys.ArrayIter.ts @@ -0,0 +1,56 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { map$1tgzvu4 as option_map$1tgzvu4 } from "./core/option.ts"; + +/** Iterator returned by `Array::iter` */ +export interface ArrayIter<_T> { + range: rs.Range; + array: rs.JsValue; +} + +export function next$iter_Iterator_wasm_bindgen_JsValue(self: ArrayIter): rs.JsValue | rs.Done { + const _2: rs.u32 | rs.Done = self.range.next(); + if (_2 !== rs.DONE) { + return rs.jsAny(self.array)[_2] as rs.JsValue; + } + return rs.DONE; +} + +export function sizeHint$iter_Iterator_wasm_bindgen_JsValue(self: ArrayIter): [rs.usize, rs.usize | undefined] { + return self.range.sizeHint(); +} + +export function count$iter_Iterator_wasm_bindgen_JsValue(self: ArrayIter): rs.usize { + return rs.iterCount(self.range); +} + +export function last$iter_Iterator_wasm_bindgen_JsValue(self: ArrayIter): rs.JsValue | rs.None { + return option_map$1tgzvu4(rs.iterLast(self.range), (index: rs.u32): rs.JsValue => { + return rs.jsAny(self.array)[index] as rs.JsValue; + }); +} + +export function nth$iter_Iterator_wasm_bindgen_JsValue(self: ArrayIter, n: rs.usize): rs.JsValue | rs.None { + const _6: rs.JsValue = self.array; + return option_map$1tgzvu4(rs.iterNth(self.range, n), (index: rs.u32): rs.JsValue => { + return rs.jsAny(_6)[index] as rs.JsValue; + }); +} + +export function nextBack$iter_DoubleEndedIterator_wasm_bindgen_JsValue(self: ArrayIter): rs.JsValue | rs.None { + const _2: rs.u32 | undefined = rs.iterToOption(self.range.nextBack()); + if (_2 !== undefined) { + return rs.jsAny(self.array)[_2] as rs.JsValue; + } + return rs.None; +} + +export function nthBack$iter_DoubleEndedIterator_wasm_bindgen_JsValue(self: ArrayIter, n: rs.usize): rs.JsValue | rs.None { + const _6: rs.JsValue = self.array; + return option_map$1tgzvu4(rs.iterNthBack(self.range, n), (index: rs.u32): rs.JsValue => { + return rs.jsAny(_6)[index] as rs.JsValue; + }); +} + +//# sourceMappingURL=js_sys.ArrayIter.ts.map diff --git a/ts/fixtures/bindgen/expected/js_sys.ts b/ts/fixtures/bindgen/expected/js_sys.ts new file mode 100644 index 000000000..4dffa4be6 --- /dev/null +++ b/ts/fixtures/bindgen/expected/js_sys.ts @@ -0,0 +1,120 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as ArrayIter from "./js_sys.ArrayIter.ts"; +import { isString$wasm_bindgen_JsValue, asString$wasm_bindgen_JsValue, from$convert_From_f64_wasm_bindgen_JsValue as wasm_bindgen_from$convert_From_f64_wasm_bindgen_JsValue, unwrapThrow$wasm_bindgen_UnwrapThrowExt_String_Option_String } from "./wasm_bindgen.ts"; + +/** + * Performs a dynamic check to see whether the `JsValue` provided + * is a value of this type. + * + * Unlike `instanceof`, this can be specialised to use a custom check by + * adding a `#[wasm_bindgen(is_type_of = callback)]` attribute to the + * type import declaration. + * + * Other than that, this is intended to be an internal implementation + * detail of `has_type` and you likely won't need to call this. + */ +export function isTypeOf$__wasm_bindgen_JsCast_js_sys_Array(val: rs.JsValue): boolean { + return ((a0: rs.JsValue): boolean => { + return rs.jsToBool(rs.jsGlobal("Array").isArray(a0)); + })(val); +} + +/** Returns an iterator over the values of the JS array. */ +export function iter$js_sys_Array(self: rs.JsValue): ArrayIter.ArrayIter { + return { range: new rs.Range(0, rs.jsToU32(rs.jsAny(self).length)), array: self }; +} + +export function from$convert_From_f64_js_sys_Number(x: rs.f64): rs.JsValue { + return wasm_bindgen_from$convert_From_f64_wasm_bindgen_JsValue(x); +} + +/** + * Performs a dynamic check to see whether the `JsValue` provided + * is a value of this type. + * + * Unlike `instanceof`, this can be specialised to use a custom check by + * adding a `#[wasm_bindgen(is_type_of = callback)]` attribute to the + * type import declaration. + * + * Other than that, this is intended to be an internal implementation + * detail of `has_type` and you likely won't need to call this. + */ +export function isTypeOf$__wasm_bindgen_JsCast_js_sys_JsString(val: rs.JsValue): boolean { + return isString$wasm_bindgen_JsValue(val); +} + +/** + * Returns whether this string is a valid UTF-16 string. + * + * This is useful for learning whether `String::from(..)` will return a + * lossless representation of the JS string. If this string contains + * unpaired surrogates then `String::from` will succeed but it will be a + * lossy representation of the JS string because unpaired surrogates will + * become replacement characters. + * + * If this function returns `false` then to get a lossless representation + * of the string you'll need to manually use the `iter` method (or the + * `char_code_at` accessor) to view the raw character codes. + * + * For more information, see the documentation on [JS strings vs Rust + * strings][docs] + * + * [docs]: https://wasm-bindgen.github.io/wasm-bindgen/reference/types/str.html + */ +export function isValidUtf16$js_sys_JsString(self: rs.JsValue): boolean { + return rs.iterAll(rs.charDecodeUtf16(iter$js_sys_JsString(self)), (i: rs.Result): boolean => { + return i.ok; + }); +} + +/** + * Returns an iterator over the `u16` character codes that make up this JS + * string. + * + * This method will call `char_code_at` for each code in this JS string, + * returning an iterator of the codes in sequence. + */ +export function iter$js_sys_JsString(self: rs.JsValue): rs.Iter { + return rs.iterMap(new rs.Range(0, rs.jsToU32(rs.jsAny(self).length)), (i: rs.u32): rs.u16 => { + return rs.u16FromF64(rs.jsToF64(rs.jsAny(self).charCodeAt(i))); + }); +} + +export function from$convert_From_ref_js_sys_JsString_String(s: rs.JsValue): rs.String { + return unwrapThrow$wasm_bindgen_UnwrapThrowExt_String_Option_String(asString$wasm_bindgen_JsValue(s), { file: "/cargo/registry/src/index.crates.io-1949cf8c6b5b557f/js-sys-0.3.106/src/lib.rs", line: 10008, col: 27 }); +} + +export function from$convert_From_js_sys_JsString_String(s: rs.JsValue): rs.String { + return from$convert_From_ref_js_sys_JsString_String(s); +} + +/** + * Copy the contents of this JS typed array into the destination + * Rust slice. + * + * This function will efficiently copy the memory from a typed + * array into this Wasm module's own linear memory, initializing + * the memory destination provided. + * + * # Panics + * + * This function will panic if this typed array's length is + * different than the length of the provided `dst` array. + */ +export function copyTo$js_sys_Uint8Array(self: rs.JsValue, dst: rs.ByteSlice): void { + const _5: rs.usize = rs.jsToU32(rs.jsAny(self).length) as rs.usize; + const _8: rs.usize = dst.length; + if (!(_5 === _8)) { + rs.assertFailed("==", (f: rs.Formatter) => rs.usizeDebug(_5, f), (f: rs.Formatter) => rs.usizeDebug(_8, f), undefined as rs.Arguments | undefined); + } else { + rs.jsGlobal("Uint8Array").prototype.set.call(rs.byteSliceView(dst), self); + } +} + +export function from$convert_From_ref_slice_u8_js_sys_Uint8Array(slice: rs.ByteSlice): rs.JsValue { + return new (rs.jsGlobal("Uint8Array"))(rs.byteSliceView(slice)) as rs.JsValue; +} + +//# sourceMappingURL=js_sys.ts.map diff --git a/ts/fixtures/bindgen/expected/lib.ts b/ts/fixtures/bindgen/expected/lib.ts new file mode 100644 index 000000000..e8080a39b --- /dev/null +++ b/ts/fixtures/bindgen/expected/lib.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +export function copy$array_u8(v: Uint8Array): Uint8Array { + return v.slice(); +} + +//# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/bindgen/expected/wasm_bindgen.JsThreadLocal.ts b/ts/fixtures/bindgen/expected/wasm_bindgen.JsThreadLocal.ts new file mode 100644 index 000000000..89015946c --- /dev/null +++ b/ts/fixtures/bindgen/expected/wasm_bindgen.JsThreadLocal.ts @@ -0,0 +1,49 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** + * Wrapper type for imported statics. + * + * This type is used whenever a `static` is imported from a JS module, for + * example this import: + * + * ```ignore + * #[wasm_bindgen] + * extern "C" { + * #[wasm_bindgen(thread_local_v2)] + * static console: JsValue; + * } + * ``` + * + * will generate in Rust a value that looks like: + * + * ```ignore + * static console: JsThreadLocal = ...; + * ``` + */ +export interface JsThreadLocal { + __inner: rs.LazyCell; +} + +export function with$Option_wasm_bindgen_JsValue_Option_T_is_some_wasm_bindgen_JsValue_bool(self: JsThreadLocal, _: (a0: rs.JsValue | rs.None) => boolean): boolean { + return self.__inner.force() !== rs.None; +} + +export function with$f64_harness_statics_closure0_u64(self: JsThreadLocal, f: (a0: rs.f64) => rs.u64): rs.u64 { + return f(self.__inner.force()); +} + +export function with$js_sys_JsString_harness_statics_closure1_u32(self: JsThreadLocal, f: (a0: rs.JsValue) => rs.u32): rs.u32 { + return f(self.__inner.force()); +} + +export function with$js_sys_JsString_harness_text_closure1_String(self: JsThreadLocal, f: (a0: rs.JsValue) => rs.String): rs.String { + return f(self.__inner.force()); +} + +export function with$js_sys_Object_Clone_clone_js_sys_Object_js_sys_Object(self: JsThreadLocal, _: (a0: rs.JsValue) => rs.JsValue): rs.JsValue { + return self.__inner.force(); +} + +//# sourceMappingURL=wasm_bindgen.JsThreadLocal.ts.map diff --git a/ts/fixtures/bindgen/expected/wasm_bindgen.ts b/ts/fixtures/bindgen/expected/wasm_bindgen.ts new file mode 100644 index 000000000..0e5ca50e3 --- /dev/null +++ b/ts/fixtures/bindgen/expected/wasm_bindgen.ts @@ -0,0 +1,403 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** + * Creates a new JS value which is a string. + * + * The utf-8 string provided is copied to the JS heap and the string will + * be owned by the JS garbage collector. + */ +export function fromStr$wasm_bindgen_JsValue(s: string): rs.JsValue { + return s as rs.JsValue; +} + +/** + * Creates a new JS value which is a number. + * + * This function creates a JS value representing a number (a heap + * allocated number) and returns a handle to the JS version of it. + */ +export function fromF64$wasm_bindgen_JsValue(n: rs.f64): rs.JsValue { + return n as rs.JsValue; +} + +/** + * Creates a new JS value which is a boolean. + * + * This function creates a JS object representing a boolean (a heap + * allocated boolean) and returns a handle to the JS version of it. + */ +export function fromBool$wasm_bindgen_JsValue(b: boolean): rs.JsValue { + if (!b) { + return false; + } + return true; +} + +/** + * Returns the `f64` value of this JS value if it's an instance of a + * number. + * + * If this JS value is not an instance of a number then this returns + * `None`. + */ +export function asF64$wasm_bindgen_JsValue(self: rs.JsValue): rs.f64 | undefined { + return ((got: unknown): rs.f64 | undefined => { + return rs.jsIsNone(got) ? undefined : rs.jsToF64(got); + })(((v: unknown) => typeof v === "number" ? v : undefined)(self)); +} + +/** Tests whether this JS value is a JS string. */ +export function isString$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return rs.jsToBool(typeof self === "string"); +} + +/** + * If this JS value is a string value, this function copies the JS string + * value into Wasm linear memory, encoded as UTF-8, and returns it as a + * Rust `String`. + * + * To avoid the copying and re-encoding, consider the + * `JsString::try_from()` function from [js-sys](https://docs.rs/js-sys) + * instead. + * + * If this JS value is not an instance of a string or if it's not valid + * utf-8 then this returns `None`. + * + * # UTF-16 vs UTF-8 + * + * JavaScript strings in general are encoded as UTF-16, but Rust strings + * are encoded as UTF-8. This can cause the Rust string to look a bit + * different than the JS string sometimes. For more details see the + * [documentation about the `str` type][caveats] which contains a few + * caveats about the encodings. + * + * [caveats]: https://wasm-bindgen.github.io/wasm-bindgen/reference/types/str.html + */ +export function asString$wasm_bindgen_JsValue(self: rs.JsValue): rs.String | undefined { + return ((got: unknown): rs.String | undefined => { + return rs.jsIsNone(got) ? undefined : rs.jsToString(got); + })(((v: unknown) => typeof v === "string" ? v : undefined)(self)); +} + +/** + * Returns the `bool` value of this JS value if it's an instance of a + * boolean. + * + * If this JS value is not an instance of a boolean then this returns + * `None`. + */ +export function asBool$wasm_bindgen_JsValue(self: rs.JsValue): boolean | undefined { + return ((got: unknown): boolean | undefined => { + return rs.jsIsNone(got) ? undefined : !!got; + })(((v: unknown) => typeof v === "boolean" ? v : undefined)(self)); +} + +/** Tests whether this JS value is `null` */ +export function isNull$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return rs.jsToBool(self === null); +} + +/** Tests whether this JS value is `undefined` */ +export function isUndefined$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return rs.jsToBool(self === undefined); +} + +/** Tests whether this JS value is `null` or `undefined` */ +export function isNullOrUndefined$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return rs.jsToBool(rs.jsIsNone(self)); +} + +/** Tests whether the type of this JS value is `symbol` */ +export function isSymbol$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return rs.jsToBool(typeof self === "symbol"); +} + +/** Tests whether `typeof self == "object" && self !== null`. */ +export function isObject$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return rs.jsToBool(rs.jsIsObject(self)); +} + +/** Tests whether the type of this JS value is `function`. */ +export function isFunction$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return rs.jsToBool(typeof self === "function"); +} + +/** Tests whether the type of this JS value is `bigint`. */ +export function isBigint$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return rs.jsToBool(typeof self === "bigint"); +} + +/** + * Applies the unary `typeof` JS operator on a `JsValue`. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) + */ +export function jsTypeof$wasm_bindgen_JsValue(self: rs.JsValue): rs.JsValue { + return typeof self as rs.JsValue; +} + +/** + * Tests whether the value is ["truthy"]. + * + * ["truthy"]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy + */ +export function isTruthy$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return !isFalsy$wasm_bindgen_JsValue(self); +} + +/** + * Tests whether the value is ["falsy"]. + * + * ["falsy"]: https://developer.mozilla.org/en-US/docs/Glossary/Falsy + */ +export function isFalsy$wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return rs.jsToBool(!self); +} + +export function asDebugString$wasm_bindgen_JsValue(self: rs.JsValue): rs.String { + return rs.jsToString(rs.jsDebugString(self)); +} + +/** + * Compare two `JsValue`s for equality, using the `==` operator in JS. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality) + */ +export function looseEq$wasm_bindgen_JsValue(self: rs.JsValue, other: rs.JsValue): boolean { + return rs.jsToBool((self as any) == other as any); +} + +/** + * Applies the binary `>>>` JS operator on the two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unsigned_right_shift) + */ +export function unsignedShr$wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.u32 { + return rs.jsToU32((self as any) >>> (rhs as any)); +} + +/** + * Applies the binary `/` JS operator on two `JsValue`s, catching and returning any `RangeError` thrown. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Division) + */ +export function checkedDiv$wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return rs.jsCheckedDiv(self, rhs) as rs.JsValue; +} + +/** + * Applies the binary `**` JS operator on the two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation) + */ +export function pow$wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return (self as any) ** (rhs as any) as rs.JsValue; +} + +/** + * Applies the binary `<` JS operator on the two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than) + */ +export function lt$wasm_bindgen_JsValue(self: rs.JsValue, other: rs.JsValue): boolean { + return rs.jsToBool((self as any) < (other as any)); +} + +/** + * Applies the binary `<=` JS operator on the two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than_or_equal) + */ +export function le$wasm_bindgen_JsValue(self: rs.JsValue, other: rs.JsValue): boolean { + return rs.jsToBool((self as any) <= (other as any)); +} + +/** + * Applies the binary `>=` JS operator on the two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than_or_equal) + */ +export function ge$wasm_bindgen_JsValue(self: rs.JsValue, other: rs.JsValue): boolean { + return rs.jsToBool((self as any) >= (other as any)); +} + +/** + * Applies the binary `>` JS operator on the two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than) + */ +export function gt$wasm_bindgen_JsValue(self: rs.JsValue, other: rs.JsValue): boolean { + return rs.jsToBool((self as any) > (other as any)); +} + +/** + * Compares two `JsValue`s for equality, using the `===` operator in JS. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality) + */ +export function eq$cmp_PartialEq_wasm_bindgen_JsValue(self: rs.JsValue, other: rs.JsValue): boolean { + return rs.jsToBool(self === other); +} + +/** + * Applies the `!` JS operator on a `JsValue`. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_NOT) + */ +export function not$ops_Not_ref_wasm_bindgen_JsValue(self: rs.JsValue): boolean { + return isFalsy$wasm_bindgen_JsValue(self); +} + +/** + * Applies the unary `-` JS operator on a `JsValue`. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unary_negation) + */ +export function neg$ops_Neg_ref_wasm_bindgen_JsValue(self: rs.JsValue): rs.JsValue { + return -(self as any) as rs.JsValue; +} + +/** + * Applies the binary `&` JS operator on two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND) + */ +export function bitand$ops_BitAnd_ref_wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return ((self as any) & rhs as any) as rs.JsValue; +} + +/** + * Applies the binary `|` JS operator on two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR) + */ +export function bitor$ops_BitOr_ref_wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return ((self as any) | rhs as any) as rs.JsValue; +} + +/** + * Applies the binary `^` JS operator on two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR) + */ +export function bitxor$ops_BitXor_ref_wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return ((self as any) ^ rhs as any) as rs.JsValue; +} + +/** + * Applies the binary `<<` JS operator on two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Left_shift) + */ +export function shl$ops_Shl_ref_wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return (self as any) << (rhs as any) as rs.JsValue; +} + +/** + * Applies the binary `>>` JS operator on two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Right_shift) + */ +export function shr$ops_Shr_ref_wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return (self as any) >> (rhs as any) as rs.JsValue; +} + +/** + * Applies the binary `+` JS operator on two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Addition) + */ +export function add$ops_Add_ref_wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return (self as any) + (rhs as any) as rs.JsValue; +} + +/** + * Applies the binary `-` JS operator on two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Subtraction) + */ +export function sub$ops_Sub_ref_wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return (self as any) - (rhs as any) as rs.JsValue; +} + +/** + * Applies the binary `*` JS operator on two `JsValue`s. + * + * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Multiplication) + */ +export function mul$ops_Mul_ref_wasm_bindgen_JsValue(self: rs.JsValue, rhs: rs.JsValue): rs.JsValue { + return (self as any) * (rhs as any) as rs.JsValue; +} + +export function from$convert_From_ref_str_wasm_bindgen_JsValue(s: string): rs.JsValue { + return fromStr$wasm_bindgen_JsValue(s); +} + +export function from$convert_From_ref_imports_Vendored_wasm_bindgen_JsValue(s: rs.JsValue): rs.JsValue { + return s; +} + +export function from$convert_From_ref_js_sys_JsString_wasm_bindgen_JsValue(s: rs.JsValue): rs.JsValue { + return s; +} + +export function asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self: rs.JsValue): rs.JsValue { + return self; +} + +export function from$convert_From_i32_wasm_bindgen_JsValue(n: rs.i32): rs.JsValue { + return fromF64$wasm_bindgen_JsValue(n as rs.f64); +} + +export function from$convert_From_u32_wasm_bindgen_JsValue(n: rs.u32): rs.JsValue { + return fromF64$wasm_bindgen_JsValue(n as rs.f64); +} + +export function from$convert_From_u8_wasm_bindgen_JsValue(n: rs.u8): rs.JsValue { + return fromF64$wasm_bindgen_JsValue(n as rs.f64); +} + +export function from$convert_From_f64_wasm_bindgen_JsValue(n: rs.f64): rs.JsValue { + return fromF64$wasm_bindgen_JsValue(n); +} + +export function from$convert_From_i64_wasm_bindgen_JsValue(arg: rs.i64): rs.JsValue { + return rs.jsBigInt(arg) as rs.JsValue; +} + +export function from$convert_From_i128_wasm_bindgen_JsValue(arg: rs.i128): rs.JsValue { + return rs.jsBigInt(arg) as rs.JsValue; +} + +export function from$convert_From_u128_wasm_bindgen_JsValue(arg: rs.u128): rs.JsValue { + return rs.jsBigInt(arg) as rs.JsValue; +} + +export function fmt$fmt_Debug_wasm_bindgen_JsValue(self: rs.JsValue, f: rs.Formatter): rs.Result { + const part = asDebugString$wasm_bindgen_JsValue(self); + return rs.fmtWriteFmt(f, ["JsValue(", (f: rs.Formatter) => rs.fmtPad(f, part.v), ")"]); +} + +export function default_$Default_wasm_bindgen_JsValue(): rs.JsValue { + return undefined; +} + +/** + * Unwrap this `Option` or `Result`, but instead of panicking on failure, + * throw an exception to JavaScript. + */ +export function unwrapThrow$wasm_bindgen_UnwrapThrowExt_String_Option_String(self: rs.String | undefined, caller: rs.Location): rs.String { + if (self !== undefined) { + return self; + } + const loc: rs.Location = caller; + const _9: string = loc.file; + const _11: rs.u32 = loc.line; + const _13: rs.u32 = loc.col; + const args3 = [(f: rs.Formatter) => rs.fmtPad(f, "called `Option::unwrap_throw()` on a `None` value"), (f: rs.Formatter) => rs.fmtPad(f, _9), (f: rs.Formatter) => rs.u32Display(_11, f), (f: rs.Formatter) => rs.u32Display(_13, f)]; + rs.jsThrow(new rs.global.Error(rs.fmtFormat([args3[0], " (", args3[1], ":", args3[2], ":", args3[3], ")"]).v)); +} + +//# sourceMappingURL=wasm_bindgen.ts.map diff --git a/ts/fixtures/bindgen/expected/wasm_bindgen/cast.ts b/ts/fixtures/bindgen/expected/wasm_bindgen/cast.ts new file mode 100644 index 000000000..be7559ff4 --- /dev/null +++ b/ts/fixtures/bindgen/expected/wasm_bindgen/cast.ts @@ -0,0 +1,345 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "../rs.ts"; +import { isTypeOf$__wasm_bindgen_JsCast_js_sys_Array as js_sys_isTypeOf$__wasm_bindgen_JsCast_js_sys_Array, isTypeOf$__wasm_bindgen_JsCast_js_sys_JsString as js_sys_isTypeOf$__wasm_bindgen_JsCast_js_sys_JsString } from "../js_sys.ts"; +import { asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue as wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue } from "../wasm_bindgen.ts"; + +/** + * Test whether this JS value has a type `T`. + * + * This method will dynamically check to see if this JS object can be + * casted to the JS object of type `T`. Usually this uses the `instanceof` + * operator. This also works with primitive types like + * booleans/strings/numbers as well as cross-realm object like `Array` + * which can originate from other iframes. + * + * In general this is intended to be a more robust version of + * `is_instance_of`, but if you want strictly the `instanceof` operator + * it's recommended to use that instead. + */ +export function hasType$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Array(self: rs.JsValue): boolean { + return js_sys_isTypeOf$__wasm_bindgen_JsCast_js_sys_Array(wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self)); +} + +/** + * Test whether this JS value has a type `T`. + * + * This method will dynamically check to see if this JS object can be + * casted to the JS object of type `T`. Usually this uses the `instanceof` + * operator. This also works with primitive types like + * booleans/strings/numbers as well as cross-realm object like `Array` + * which can originate from other iframes. + * + * In general this is intended to be a more robust version of + * `is_instance_of`, but if you want strictly the `instanceof` operator + * it's recommended to use that instead. + */ +export function hasType$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Error(self: rs.JsValue): boolean { + return isTypeOf$wasm_bindgen_JsCast_js_sys_Error(wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self)); +} + +/** + * Test whether this JS value has a type `T`. + * + * This method will dynamically check to see if this JS object can be + * casted to the JS object of type `T`. Usually this uses the `instanceof` + * operator. This also works with primitive types like + * booleans/strings/numbers as well as cross-realm object like `Array` + * which can originate from other iframes. + * + * In general this is intended to be a more robust version of + * `is_instance_of`, but if you want strictly the `instanceof` operator + * it's recommended to use that instead. + */ +export function hasType$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_JsString(self: rs.JsValue): boolean { + return js_sys_isTypeOf$__wasm_bindgen_JsCast_js_sys_JsString(wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self)); +} + +/** + * Test whether this JS value has a type `T`. + * + * This method will dynamically check to see if this JS object can be + * casted to the JS object of type `T`. Usually this uses the `instanceof` + * operator. This also works with primitive types like + * booleans/strings/numbers as well as cross-realm object like `Array` + * which can originate from other iframes. + * + * In general this is intended to be a more robust version of + * `is_instance_of`, but if you want strictly the `instanceof` operator + * it's recommended to use that instead. + */ +export function hasType$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Uint8Array(self: rs.JsValue): boolean { + return isTypeOf$wasm_bindgen_JsCast_js_sys_Uint8Array(wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self)); +} + +/** + * Performs a dynamic cast (checked at runtime) of this value into the + * target type `T`. + * + * This method will return `Err(self)` if `self.has_type::()` + * returns `false`, and otherwise it will return `Ok(T)` manufactured with + * an unchecked cast (verified correct via the `has_type` operation). + */ +export function dynInto$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_JsString(self: rs.JsValue): rs.Result { + if (!hasType$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_JsString(self)) { + return rs.Err(self); + } + return rs.Ok(uncheckedInto$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_JsString(self)); +} + +/** + * Performs a dynamic cast (checked at runtime) of this value into the + * target type `T`. + * + * This method will return `None` if `self.has_type::()` + * returns `false`, and otherwise it will return `Some(&T)` manufactured + * with an unchecked cast (verified correct via the `has_type` operation). + */ +export function dynRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Array(self: rs.JsValue): rs.JsValue | rs.None { + if (!hasType$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Array(self)) { + return rs.None; + } + return uncheckedRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Array(self); +} + +/** + * Performs a dynamic cast (checked at runtime) of this value into the + * target type `T`. + * + * This method will return `None` if `self.has_type::()` + * returns `false`, and otherwise it will return `Some(&T)` manufactured + * with an unchecked cast (verified correct via the `has_type` operation). + */ +export function dynRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Error(self: rs.JsValue): rs.JsValue | rs.None { + if (!hasType$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Error(self)) { + return rs.None; + } + return uncheckedRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Error(self); +} + +/** + * Performs a dynamic cast (checked at runtime) of this value into the + * target type `T`. + * + * This method will return `None` if `self.has_type::()` + * returns `false`, and otherwise it will return `Some(&T)` manufactured + * with an unchecked cast (verified correct via the `has_type` operation). + */ +export function dynRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Uint8Array(self: rs.JsValue): rs.JsValue | rs.None { + if (!hasType$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Uint8Array(self)) { + return rs.None; + } + return uncheckedRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Uint8Array(self); +} + +/** + * Performs a zero-cost unchecked cast into the specified type. + * + * This method will convert the `self` value to the type `T`, where both + * `self` and `T` are simple wrappers around `JsValue`. This method **does + * not check whether `self` is an instance of `T`**. If used incorrectly + * then this method may cause runtime exceptions in both Rust and JS, this + * should be used with caution. + */ +export function uncheckedInto$wasm_bindgen_JsCast_js_sys_Object_js_sys_Array(self: rs.JsValue): rs.JsValue { + return self; +} + +/** + * Performs a zero-cost unchecked cast into the specified type. + * + * This method will convert the `self` value to the type `T`, where both + * `self` and `T` are simple wrappers around `JsValue`. This method **does + * not check whether `self` is an instance of `T`**. If used incorrectly + * then this method may cause runtime exceptions in both Rust and JS, this + * should be used with caution. + */ +export function uncheckedInto$wasm_bindgen_JsCast_js_sys_Object_web_sys_TextDecoderOptions(self: rs.JsValue): rs.JsValue { + return self; +} + +/** + * Performs a zero-cost unchecked cast into the specified type. + * + * This method will convert the `self` value to the type `T`, where both + * `self` and `T` are simple wrappers around `JsValue`. This method **does + * not check whether `self` is an instance of `T`**. If used incorrectly + * then this method may cause runtime exceptions in both Rust and JS, this + * should be used with caution. + */ +export function uncheckedInto$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Point(self: rs.JsValue): rs.JsValue { + return self; +} + +/** + * Performs a zero-cost unchecked cast into the specified type. + * + * This method will convert the `self` value to the type `T`, where both + * `self` and `T` are simple wrappers around `JsValue`. This method **does + * not check whether `self` is an instance of `T`**. If used incorrectly + * then this method may cause runtime exceptions in both Rust and JS, this + * should be used with caution. + */ +export function uncheckedInto$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_JsString(self: rs.JsValue): rs.JsValue { + return self; +} + +/** + * Performs a zero-cost unchecked cast into a reference to the specified + * type. + * + * This method will convert the `self` value to the type `T`, where both + * `self` and `T` are simple wrappers around `JsValue`. This method **does + * not check whether `self` is an instance of `T`**. If used incorrectly + * then this method may cause runtime exceptions in both Rust and JS, this + * should be used with caution. + * + * This method, unlike `unchecked_into`, does not consume ownership of + * `self` and instead works over a shared reference. + */ +export function uncheckedRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Array(self: rs.JsValue): rs.JsValue { + return wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self); +} + +/** + * Performs a zero-cost unchecked cast into a reference to the specified + * type. + * + * This method will convert the `self` value to the type `T`, where both + * `self` and `T` are simple wrappers around `JsValue`. This method **does + * not check whether `self` is an instance of `T`**. If used incorrectly + * then this method may cause runtime exceptions in both Rust and JS, this + * should be used with caution. + * + * This method, unlike `unchecked_into`, does not consume ownership of + * `self` and instead works over a shared reference. + */ +export function uncheckedRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Error(self: rs.JsValue): rs.JsValue { + return wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self); +} + +/** + * Performs a zero-cost unchecked cast into a reference to the specified + * type. + * + * This method will convert the `self` value to the type `T`, where both + * `self` and `T` are simple wrappers around `JsValue`. This method **does + * not check whether `self` is an instance of `T`**. If used incorrectly + * then this method may cause runtime exceptions in both Rust and JS, this + * should be used with caution. + * + * This method, unlike `unchecked_into`, does not consume ownership of + * `self` and instead works over a shared reference. + */ +export function uncheckedRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Object(self: rs.JsValue): rs.JsValue { + return wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self); +} + +/** + * Performs a zero-cost unchecked cast into a reference to the specified + * type. + * + * This method will convert the `self` value to the type `T`, where both + * `self` and `T` are simple wrappers around `JsValue`. This method **does + * not check whether `self` is an instance of `T`**. If used incorrectly + * then this method may cause runtime exceptions in both Rust and JS, this + * should be used with caution. + * + * This method, unlike `unchecked_into`, does not consume ownership of + * `self` and instead works over a shared reference. + */ +export function uncheckedRef$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_Uint8Array(self: rs.JsValue): rs.JsValue { + return wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self); +} + +/** + * Test whether this JS value is an instance of the type `T`. + * + * This method performs a dynamic check (at runtime) using the JS + * `instanceof` operator. This method returns `self instanceof T`. + * + * Note that `instanceof` does not always work with primitive values or + * across different realms (e.g. iframes). If you're not sure whether you + * specifically need only `instanceof` it's recommended to use `has_type` + * instead. + */ +export function isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Missing(self: rs.JsValue): boolean { + return rs.jsToBool(rs.jsInstanceof(wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self), () => rs.jsGlobal("RustNoSuchClass"))); +} + +/** + * Test whether this JS value is an instance of the type `T`. + * + * This method performs a dynamic check (at runtime) using the JS + * `instanceof` operator. This method returns `self instanceof T`. + * + * Note that `instanceof` does not always work with primitive values or + * across different realms (e.g. iframes). If you're not sure whether you + * specifically need only `instanceof` it's recommended to use `has_type` + * instead. + */ +export function isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Table(self: rs.JsValue): boolean { + return rs.jsToBool(rs.jsInstanceof(wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self), () => rs.jsGlobal("Map"))); +} + +/** + * Test whether this JS value is an instance of the type `T`. + * + * This method performs a dynamic check (at runtime) using the JS + * `instanceof` operator. This method returns `self instanceof T`. + * + * Note that `instanceof` does not always work with primitive values or + * across different realms (e.g. iframes). If you're not sure whether you + * specifically need only `instanceof` it's recommended to use `has_type` + * instead. + */ +export function isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_imports_Vendored(self: rs.JsValue): boolean { + return rs.jsToBool(rs.jsInstanceof(wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self), () => rs.jsVendored("RustVendored", ["webkit"]))); +} + +/** + * Test whether this JS value is an instance of the type `T`. + * + * This method performs a dynamic check (at runtime) using the JS + * `instanceof` operator. This method returns `self instanceof T`. + * + * Note that `instanceof` does not always work with primitive values or + * across different realms (e.g. iframes). If you're not sure whether you + * specifically need only `instanceof` it's recommended to use `has_type` + * instead. + */ +export function isInstanceOf$wasm_bindgen_JsCast_wasm_bindgen_JsValue_js_sys_TypeError(self: rs.JsValue): boolean { + return rs.jsToBool(rs.jsInstanceof(wasm_bindgen_asRef$convert_AsRef_wasm_bindgen_JsValue_wasm_bindgen_JsValue(self), () => rs.jsGlobal("TypeError"))); +} + +/** + * Performs a dynamic check to see whether the `JsValue` provided + * is a value of this type. + * + * Unlike `instanceof`, this can be specialised to use a custom check by + * adding a `#[wasm_bindgen(is_type_of = callback)]` attribute to the + * type import declaration. + * + * Other than that, this is intended to be an internal implementation + * detail of `has_type` and you likely won't need to call this. + */ +export function isTypeOf$wasm_bindgen_JsCast_js_sys_Error(val: rs.JsValue): boolean { + return rs.jsToBool(rs.jsInstanceof(val, () => rs.jsGlobal("Error"))); +} + +/** + * Performs a dynamic check to see whether the `JsValue` provided + * is a value of this type. + * + * Unlike `instanceof`, this can be specialised to use a custom check by + * adding a `#[wasm_bindgen(is_type_of = callback)]` attribute to the + * type import declaration. + * + * Other than that, this is intended to be an internal implementation + * detail of `has_type` and you likely won't need to call this. + */ +export function isTypeOf$wasm_bindgen_JsCast_js_sys_Uint8Array(val: rs.JsValue): boolean { + return rs.jsToBool(rs.jsInstanceof(val, () => rs.jsGlobal("Uint8Array"))); +} + +//# sourceMappingURL=cast.ts.map diff --git a/ts/fixtures/bindgen/expected/web_sys/features/gen_TextDecoderOptions.ts b/ts/fixtures/bindgen/expected/web_sys/features/gen_TextDecoderOptions.ts new file mode 100644 index 000000000..5658e5cfb --- /dev/null +++ b/ts/fixtures/bindgen/expected/web_sys/features/gen_TextDecoderOptions.ts @@ -0,0 +1,15 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "../../rs.ts"; +import { uncheckedInto$wasm_bindgen_JsCast_js_sys_Object_web_sys_TextDecoderOptions } from "../../wasm_bindgen/cast.ts"; + +/** + * Construct a new `TextDecoderOptions`. + * + * *This API requires the following crate features to be activated: `TextDecoderOptions`* + */ +export function new_$web_sys_TextDecoderOptions(): rs.JsValue { + return uncheckedInto$wasm_bindgen_JsCast_js_sys_Object_web_sys_TextDecoderOptions(new (rs.jsGlobal("Object"))() as rs.JsValue); +} + +//# sourceMappingURL=gen_TextDecoderOptions.ts.map diff --git a/ts/fixtures/bindgen/js/custom.js b/ts/fixtures/bindgen/js/custom.js new file mode 100644 index 000000000..2f054e8ab --- /dev/null +++ b/ts/fixtures/bindgen/js/custom.js @@ -0,0 +1,4 @@ +// Custom JS, which translation refuses. +export function custom() { + return 1; +} diff --git a/ts/fixtures/bindgen/refused.toml b/ts/fixtures/bindgen/refused.toml new file mode 100644 index 000000000..f3efc57fc --- /dev/null +++ b/ts/fixtures/bindgen/refused.toml @@ -0,0 +1,2 @@ +package = "bindgen-fixture" +roots = ["harness::run", "refused::run"] diff --git a/ts/fixtures/bindgen/refused.txt b/ts/fixtures/bindgen/refused.txt new file mode 100644 index 000000000..916cd86cb --- /dev/null +++ b/ts/fixtures/bindgen/refused.txt @@ -0,0 +1,10 @@ +src/refused.rs:43:1: `refused::exported`, a `#[wasm_bindgen]` export, which translation refuses: it exports the crate's `pub` API itself +src/refused.rs:49:2: `refused::fill`, of wasm-bindgen: an import's argument of `&mut [u16]`, which wasm-bindgen converts in a way translation does not +src/refused.rs:51:2: `refused::set_timeout`, of wasm-bindgen: an import's argument of `&wasm_bindgen::prelude::ScopedClosure<'_, dyn std::ops::FnMut()>`, which wasm-bindgen converts in a way translation does not +src/refused.rs:53:18: `refused::suspended`, of wasm-bindgen: `suspending`, which suspends the WASM stack on a promise, as no JS call can +src/refused.rs:54:16: `refused::generic`, of wasm-bindgen: `experimental_generic_mono`, which the CLI makes JS of per instance, as translation does not +src/refused.rs:55:2: `refused::custom`, of wasm-bindgen: its JS file `js/custom.js`, custom JS, which translation refuses +src/refused.rs:55:13: `refused::raw`, of wasm-bindgen: its JS module `./raw.js`, custom JS, which translation refuses +src/refused.rs:55:21: `refused::inline`, of wasm-bindgen: its `inline_js`, custom JS, which translation refuses +wasm-bindgen-0.2.129/src/closure.rs:359:17: `wasm_bindgen::__rt::wbg_cast`, of wasm-bindgen: an import's argument of `wasm_bindgen::closure::OwnedClosure`, which wasm-bindgen converts in a way translation does not +wasm-bindgen-0.2.129/src/lib.rs:1752:5: `wasm_bindgen::__wbindgen_memory`, of wasm-bindgen: wasm-bindgen's intrinsic `__wbindgen_memory`, which translation refuses diff --git a/ts/fixtures/bindgen/src/harness.rs b/ts/fixtures/bindgen/src/harness.rs new file mode 100644 index 000000000..553f71910 --- /dev/null +++ b/ts/fixtures/bindgen/src/harness.rs @@ -0,0 +1,685 @@ +//! The one entry point both sides of the differential test run: native Rust built for +//! wasm32 and run through wasm-bindgen's CLI, and its TypeScript translation. The first +//! input byte picks what to exercise and the rest is its input; the output records each +//! result as a line of text, so the two sides agree only if each step does. + +use std::fmt::Write; +use std::sync::atomic::{AtomicU32, Ordering}; + +use js_sys::{ + Array, BigInt64Array, Float64Array, Function, Int16Array, JSON, JsString, Math, Object, Reflect, Uint8Array, +}; +use wasm_bindgen::{JsCast, JsValue}; +use web_sys::{TextDecoder, TextDecoderOptions, TextEncoder, console}; + +use crate::imports::{self, Point, Table}; + +/// What a case runs on the rest of the input, writing each result to `out`. +type Case = fn(&mut Input, &mut String); + +/// Each case's name and what it runs. The first byte picks one modulo their count, so a +/// case added at the end moves no other. +pub fn cases() -> Vec<(&'static str, Case)> { + vec![ + ("math", math), + ("numbers", numbers), + ("bigints", bigints), + ("text", text), + ("encoding", encoding), + ("json", json), + ("arrays", arrays), + ("typed", typed), + ("casts", casts), + ("ops", ops), + ("classes", classes), + ("statics", statics), + ("console", logs), + ("caught", caught), + ("throws", throws), + ("edges", edges), + ] +} + +/// Run one case. +pub fn run(input: &[u8]) -> Vec { + let mut out = String::new(); + let Some((&op, data)) = input.split_first() else { + return Vec::new(); + }; + define(); + let cases = cases(); + let (_, case) = cases[usize::from(op) % cases.len()]; + case(&mut Input(data), &mut out); + out.into_bytes() +} + +/// The functions on `globalThis` that the fixture's own imports call. +fn define() { + let global = imports::GLOBAL.with(Object::clone); + let typeof_ = Function::new_with_args("v", "return typeof v"); + let identity = Function::new_with_args("v", "return v"); + let check = Function::new_with_args( + "v", + "if (typeof v !== 'number') throw new TypeError('not a number: ' + String(v))", + ); + Reflect::set(&global, &"rustTypeof".into(), &typeof_).unwrap(); + Reflect::set(&global, &"rustCheck".into(), &check).unwrap(); + Reflect::set(&global, &"rustIdentity".into(), &identity).unwrap(); +} + +/// The rest of a case's input. +pub struct Input<'a>(&'a [u8]); + +/// Doubles at each edge an import's coercion has. +const EDGES: [f64; 18] = [ + 0.0, + -0.0, + 1.5, + -2.5, + f64::NAN, + f64::INFINITY, + f64::NEG_INFINITY, + 4_294_967_301.0, + 2_147_483_648.0, + -2_147_483_649.0, + 9_007_199_254_740_994.0, + 1e300, + 5e-324, + 255.5, + -1.0, + 65_535.5, + -128.5, + 18_446_744_073_709_551_616.0, +]; + +impl Input<'_> { + fn u8(&mut self) -> u8 { + match self.0.split_first() { + Some((&b, rest)) => { + self.0 = rest; + b + } + None => 0, + } + } + + /// Whether the next byte's low bit is clear. + fn flag(&mut self) -> bool { + self.u8() & 1 == 0 + } + + fn bytes(&mut self) -> [u8; N] { + let mut buf = [0u8; N]; + for b in &mut buf { + *b = self.u8(); + } + buf + } + + fn u32(&mut self) -> u32 { + u32::from_le_bytes(self.bytes()) + } + + fn u64(&mut self) -> u64 { + u64::from_le_bytes(self.bytes()) + } + + fn u128(&mut self) -> u128 { + u128::from_le_bytes(self.bytes()) + } + + /// A double: an edge, or one of any bits. + fn f64(&mut self) -> f64 { + let pick = self.u8(); + match EDGES.get(usize::from(pick % 32)) { + Some(&edge) => edge, + None => f64::from_bits(self.u64()), + } + } + + /// Up to 15 bytes. + fn slice(&mut self) -> &[u8] { + let len = usize::from(self.u8() % 16).min(self.0.len()); + let (taken, rest) = self.0.split_at(len); + self.0 = rest; + taken + } + + /// Text from up to 15 bytes, each malformed sequence as U+FFFD. + fn text(&mut self) -> String { + String::from_utf8_lossy(self.slice()).into_owned() + } + + /// A JS value of each kind the cases read. + fn value(&mut self) -> JsValue { + match self.u8() % 12 { + 0 => JsValue::UNDEFINED, + 1 => JsValue::NULL, + 2 => JsValue::TRUE, + 3 => JsValue::FALSE, + 4 => JsValue::from_f64(self.f64()), + 5 => JsValue::from_str(&self.text()), + 6 => JsValue::from(self.u64() as i64), + 7 => imports::bytes_array(self.slice()).into(), + 8 => Uint8Array::from(self.slice()).into(), + 9 => Object::new().into(), + 10 => JsValue::from(self.u32() % 1000), + _ => JsString::from_char_code(&self.units()).into(), + } + } + + /// UTF-16 code units, lone surrogates among them. + fn units(&mut self) -> Vec { + let len = self.u8() % 6; + (0..len) + .map(|_| match self.u8() % 4 { + 0 => 0xd800 | u16::from(self.u8()), + 1 => 0xdc00 | u16::from(self.u8()), + _ => u16::from(self.u8()) | 0x40, + }) + .collect() + } +} + +/// A JS value's text, as `String()` gives it, in a line. +fn show(out: &mut String, what: &str, v: &JsValue) { + let text = String::from(imports::js_string(v)); + writeln!(out, "{what}: {text:?}").unwrap(); +} + +fn math(input: &mut Input, out: &mut String) { + let (x, y) = (input.f64(), input.f64()); + writeln!(out, "floor {:?}", Math::floor(x).to_bits()).unwrap(); + writeln!(out, "trunc {:?}", Math::trunc(x).to_bits()).unwrap(); + writeln!(out, "sign {:?}", Math::sign(x).to_bits()).unwrap(); + writeln!(out, "fround {:?}", Math::fround(x).to_bits()).unwrap(); + writeln!(out, "hypot {:?}", Math::hypot(x, y).to_bits()).unwrap(); + writeln!(out, "atan2 {:?}", Math::atan2(x, y).to_bits()).unwrap(); + writeln!(out, "pow {:?}", Math::pow(x, y).to_bits()).unwrap(); + writeln!(out, "max {:?}", Math::max(x, y).to_bits()).unwrap(); + writeln!(out, "clz32 {}", Math::clz32(input.u32() as i32)).unwrap(); + let rest = [y, input.f64(), input.f64()]; + writeln!( + out, + "max_of {:?}", + imports::max_of(x, &rest[..usize::from(input.u8() % 4)]).to_bits() + ) + .unwrap(); +} + +fn numbers(input: &mut Input, out: &mut String) { + let x = input.f64(); + writeln!(out, "u8 {}", imports::round_u8(x)).unwrap(); + writeln!(out, "i8 {}", imports::round_i8(x)).unwrap(); + writeln!(out, "u16 {}", imports::round_u16(x)).unwrap(); + writeln!(out, "i16 {}", imports::round_i16(x)).unwrap(); + writeln!(out, "u32 {}", imports::round_u32(x)).unwrap(); + writeln!(out, "i32 {}", imports::round_i32(x)).unwrap(); + writeln!(out, "usize {}", imports::round_usize(x)).unwrap(); + writeln!(out, "f32 {}", imports::round_f32(x).to_bits()).unwrap(); + writeln!(out, "bool {}", imports::round_bool(x)).unwrap(); + let maybe = (input.flag()).then_some(x); + writeln!(out, "option {:?}", imports::round_option(maybe)).unwrap(); + let n = JsValue::from_f64(x); + writeln!( + out, + "integer {} safe {}", + js_sys::Number::is_integer(&n), + js_sys::Number::is_safe_integer(&n) + ) + .unwrap(); + match js_sys::Number::from(x).to_string_with_radix(input.u8() % 40) { + Ok(s) => writeln!(out, "radix {:?}", String::from(s)).unwrap(), + Err(e) => show(out, "radix threw", &e), + } + show(out, "string", &JsValue::from_f64(x)); +} + +fn bigints(input: &mut Input, out: &mut String) { + let bits = u32::from(input.u8() % 140); + let (a, b) = (input.u64(), input.u128()); + writeln!(out, "as_int_n {}", imports::as_int_n(bits, a as i64)).unwrap(); + writeln!(out, "as_uint_n {}", imports::as_uint_n(bits, a)).unwrap(); + writeln!(out, "as_int_n128 {}", imports::as_int_n128(bits, b as i128)).unwrap(); + writeln!(out, "as_uint_n128 {}", imports::as_uint_n128(bits, b)).unwrap(); + match imports::as_int_n_number(bits, input.f64()) { + Ok(v) => writeln!(out, "from number {v}").unwrap(), + Err(e) => show(out, "from number threw", &e), + } + show(out, "i64", &JsValue::from(a as i64)); + show(out, "u128", &JsValue::from(b)); + show(out, "i128", &JsValue::from(b as i128)); +} + +fn text(input: &mut Input, out: &mut String) { + let c = char::from_u32(input.u32() % 0x11_0000).unwrap_or('\u{fffd}'); + writeln!(out, "char_string {:?}", imports::char_string(c)).unwrap(); + let s = input.text(); + writeln!(out, "first_char {:?}", imports::first_char(&s)).unwrap(); + let maybe = (input.flag()).then(|| s.clone()); + writeln!(out, "string_option {:?}", imports::string_option(maybe)).unwrap(); + let units = input.units(); + let js = JsString::from_char_code(&units); + writeln!(out, "units {units:?} length {}", js.length()).unwrap(); + writeln!(out, "well formed {}", js.is_valid_utf16()).unwrap(); + writeln!(out, "lossy {:?}", String::from(&js)).unwrap(); + writeln!(out, "as_string {:?}", JsValue::from(&js).as_string()).unwrap(); + writeln!(out, "upper {:?}", String::from(js.to_upper_case())).unwrap(); + writeln!(out, "char_code_at {:?}", js.char_code_at(0).to_bits()).unwrap(); + writeln!(out, "code_point_at {:?}", js.code_point_at(0).as_f64()).unwrap(); + match JsString::from_code_point1(input.u32() % 0x12_0000) { + Ok(s) => writeln!(out, "from_code_point {:?}", String::from(s)).unwrap(), + Err(e) => show(out, "from_code_point threw", &e), + } + writeln!(out, "greeting {:?}", imports::GREETING.with(|s| String::from(s))).unwrap(); + writeln!(out, "includes {}", js.includes(&s, 0)).unwrap(); +} + +fn encoding(input: &mut Input, out: &mut String) { + let encoder = TextEncoder::new().unwrap(); + let s = input.text(); + writeln!(out, "encoding {}", encoder.encoding()).unwrap(); + writeln!(out, "encode {:?}", encoder.encode_with_input(&s)).unwrap(); + let labels = ["utf-8", "UTF8", "latin1", "utf-16le", "no such label"]; + let label = labels[usize::from(input.u8()) % labels.len()]; + let options = TextDecoderOptions::new(); + options.set_fatal(input.flag()); + writeln!(out, "fatal {:?}", options.get_fatal()).unwrap(); + match TextDecoder::new_with_label_and_options(label, &options) { + Ok(decoder) => { + writeln!(out, "decoder {} fatal {}", decoder.encoding(), decoder.fatal()).unwrap(); + match decoder.decode_with_u8_array(input.slice()) { + Ok(s) => writeln!(out, "decode {s:?}").unwrap(), + Err(e) => show(out, "decode threw", &e), + } + } + Err(e) => show(out, "decoder threw", &e), + } +} + +fn json(input: &mut Input, out: &mut String) { + let texts = [ + "{\"a\":1,\"b\":[true,null]}", + "[1,2", + "\"\\ud800\"", + "1e400", + "{\"x\":{\"y\":\"z\"}}", + ]; + let text = match input.u8() % 6 { + 5 => input.text(), + i => texts[usize::from(i)].to_string(), + }; + match imports::parse(&text) { + Ok(v) => { + match JSON::stringify(&v) { + // `undefined` where JSON has no text for it, which no `String` is. + Ok(s) => writeln!(out, "stringify {:?}", s.as_string()).unwrap(), + Err(e) => show(out, "stringify threw", &e), + } + if v.is_object() { + writeln!(out, "keys {:?}", imports::keys(&v)).unwrap(); + let keys = Reflect::own_keys(&v).unwrap(); + writeln!(out, "own_keys {}", keys.length()).unwrap(); + show(out, "get a", &Reflect::get(&v, &"a".into()).unwrap()); + writeln!(out, "has x {:?}", Reflect::has(&v, &"x".into())).unwrap(); + } + } + Err(e) => show(out, "parse threw", &e), + } + match JSON::stringify(&input.value()) { + Ok(s) => writeln!(out, "value {:?}", s.as_string()).unwrap(), + Err(e) => show(out, "value threw", &e), + } +} + +fn arrays(input: &mut Input, out: &mut String) { + let array = Array::new(); + for _ in 0..input.u8() % 5 { + array.push(&input.value()); + } + writeln!(out, "length {}", array.length()).unwrap(); + writeln!(out, "join {:?}", String::from(array.join(","))).unwrap(); + let probe = input.value(); + writeln!( + out, + "index_of {} includes {}", + array.index_of(&probe, 0), + array.includes(&probe, 0) + ) + .unwrap(); + writeln!(out, "is_array {} {}", Array::is_array(&array), Array::is_array(&probe)).unwrap(); + show(out, "pop", &array.pop()); + show(out, "at", &array.at(-1)); + show(out, "get", &array.get(input.u32() % 8)); + array.set(input.u32() % 6, probe.clone()); + show(out, "reversed", &array.reverse()); + show(out, "sorted", &array.sort()); + show(out, "of2", &Array::of2(&probe, &array)); + show(out, "array_of", &imports::array_of(&array)); + let values: Vec = array.iter().collect(); + writeln!(out, "values {}", imports::values_array(values).len()).unwrap(); + let strings = vec![input.text(), input.text()]; + writeln!(out, "strings {:?}", imports::strings_array(strings)).unwrap(); + let floats = [input.f64(), input.f64()]; + let back: Vec = imports::floats_array(&floats).iter().map(|f| f.to_bits()).collect(); + writeln!(out, "floats {back:?}").unwrap(); + let wides = [input.u64(), u64::MAX]; + for v in imports::wides_array(&wides) { + show(out, "wide", &v); + } + show(out, "bytes", &imports::bytes_array(input.slice()).into()); +} + +fn typed(input: &mut Input, out: &mut String) { + let bytes = input.slice().to_vec(); + let array = Uint8Array::from(&bytes[..]); + writeln!(out, "length {}", array.length()).unwrap(); + let i = input.u32() % 20; + writeln!(out, "get_index {i} {}", array.get_index(i)).unwrap(); + array.set_index(input.u32() % 20, input.u8()); + let sub = array.subarray(input.u32() % 8, input.u32() % 16); + let mut copy = vec![0; sub.length() as usize]; + sub.copy_to(&mut copy); + writeln!(out, "subarray {copy:?}").unwrap(); + array.fill(input.u8(), 1, 3); + let mut whole = vec![0; array.length() as usize]; + array.copy_to(&mut whole); + writeln!(out, "filled {whole:?}").unwrap(); + writeln!(out, "copy {:?}", imports::bytes_copy(bytes.clone())).unwrap(); + writeln!(out, "boxed {:?}", imports::bytes_boxed(&bytes)).unwrap(); + let floats = Float64Array::new_from_slice(&[input.f64(), input.f64()]); + writeln!( + out, + "floats {:?} {:?}", + floats.get_index(0).to_bits(), + floats.get_index(5).to_bits() + ) + .unwrap(); + let shorts = Int16Array::new(&floats); + writeln!(out, "shorts {} {}", shorts.get_index(0), shorts.get_index(1)).unwrap(); + let wides = BigInt64Array::new_from_slice(&[input.u64() as i64, i64::MIN]); + writeln!(out, "wides {} {}", wides.get_index(0), wides.get_index(1)).unwrap(); + show(out, "at", &array.at(-1).map_or(JsValue::NULL, JsValue::from)); +} + +fn casts(input: &mut Input, out: &mut String) { + let v = input.value(); + writeln!(out, "debug {v:?}").unwrap(); + writeln!( + out, + "undefined {} null {} none {} object {} function {} string {} bigint {} symbol {} falsy {} truthy {}", + v.is_undefined(), + v.is_null(), + v.is_null_or_undefined(), + v.is_object(), + v.is_function(), + v.is_string(), + v.is_bigint(), + v.is_symbol(), + v.is_falsy(), + v.is_truthy() + ) + .unwrap(); + writeln!( + out, + "as_f64 {:?} as_bool {:?} as_string {:?}", + v.as_f64().map(f64::to_bits), + v.as_bool(), + v.as_string() + ) + .unwrap(); + show(out, "typeof", &v.js_typeof()); + let kind = imports::type_of(&v); + writeln!(out, "kind {kind:?} {}", imports::kind_string(kind)).unwrap(); + writeln!( + out, + "array {}", + v.dyn_ref::().map(Array::length).unwrap_or(u32::MAX) + ) + .unwrap(); + writeln!(out, "bytes {:?}", v.dyn_ref::().map(Uint8Array::length)).unwrap(); + writeln!(out, "table {}", v.is_instance_of::()).unwrap(); + writeln!(out, "missing class {}", v.is_instance_of::()).unwrap(); + match v.clone().dyn_into::() { + Ok(s) => writeln!(out, "js string {}", s.length()).unwrap(), + Err(back) => show(out, "not a js string", &back), + } + let obj: &Object = v.unchecked_ref(); + writeln!(out, "unchecked {}", obj.is_undefined()).unwrap(); + let other = input.value(); + writeln!(out, "loose_eq {} eq {}", v.loose_eq(&other), v == other).unwrap(); + writeln!( + out, + "constants {:?} {:?} {:?} {:?}", + JsValue::NULL, + JsValue::UNDEFINED, + JsValue::TRUE, + JsValue::FALSE + ) + .unwrap(); + writeln!(out, "from bool {:?}", JsValue::from_bool(input.flag())).unwrap(); +} + +fn ops(input: &mut Input, out: &mut String) { + let pick = |input: &mut Input| match input.u8() % 3 { + 0 => JsValue::from_f64(input.f64()), + 1 => JsValue::from(input.u64() as i64 % 1000), + _ => JsValue::from(input.u32() % 50), + }; + let (a, b) = (pick(input), pick(input)); + // Each operator throws mixing a BigInt with a number, which `checked_div` alone catches. + if a.is_bigint() != b.is_bigint() { + show(out, "checked_div", &a.checked_div(&b)); + writeln!(out, "mixed lt {} eq {}", a.lt(&b), a.loose_eq(&b)).unwrap(); + return; + } + show(out, "add", &(&a + &b)); + show(out, "sub", &(&a - &b)); + show(out, "mul", &(&a * &b)); + show(out, "checked_div", &a.checked_div(&b)); + show(out, "neg", &(-&a)); + show(out, "and", &(&a & &b)); + show(out, "or", &(&a | &b)); + show(out, "xor", &(&a ^ &b)); + show(out, "not", &JsValue::from_bool(!&a)); + show(out, "shl", &(&a << &b)); + show(out, "shr", &(&a >> &b)); + if !a.is_bigint() { + show(out, "unsigned_shr", &JsValue::from(a.unsigned_shr(&b))); + show(out, "pow", &a.pow(&b)); + } + writeln!(out, "lt {} le {} gt {} ge {}", a.lt(&b), a.le(&b), a.gt(&b), a.ge(&b)).unwrap(); +} + +fn classes(input: &mut Input, out: &mut String) { + let table = Table::new(); + for _ in 0..input.u8() % 4 { + table.set(&input.value(), &JsValue::from_f64(input.f64())); + } + let key = input.value(); + table.set(&key, &JsValue::from_f64(input.f64())); + writeln!(out, "get {:?}", table.get(&key).map(f64::to_bits)).unwrap(); + writeln!(out, "get other {:?}", table.get(&input.value()).map(f64::to_bits)).unwrap(); + writeln!(out, "has {}", table.has_final(&key)).unwrap(); + writeln!( + out, + "size {} {} {}", + table.size(), + table.size_final(), + table.size_structural() + ) + .unwrap(); + let iter = table.entries(); + while let Ok(next) = iter.next() + && !next.done() + { + show(out, "entry", &next.value()); + } + let items = imports::bytes_array(input.slice()); + let parity = Function::new_with_args("x", "return x % 2"); + writeln!(out, "groups {}", Table::group_by(&items, &parity).size()).unwrap(); + let point: Point = imports::parse("{\"x\":1.5,\"y\":2}").unwrap().unchecked_into(); + writeln!(out, "x {:?}", point.x().to_bits()).unwrap(); + point.set_x(input.f64()); + writeln!(out, "x {:?}", point.x().to_bits()).unwrap(); + let key = input.text(); + point.put(&key, &input.value()); + show(out, "at", &point.at(&key)); + point.remove("y"); + show(out, "y", &point.at("y")); + writeln!(out, "describe {:?}", point.describe()).unwrap(); + writeln!(out, "keys {:?}", imports::keys(&point)).unwrap(); +} + +fn statics(_: &mut Input, out: &mut String) { + writeln!(out, "pi {:?}", imports::PI.with(|pi| pi.to_bits())).unwrap(); + writeln!(out, "missing {:?}", imports::MISSING.with(Option::is_some)).unwrap(); + writeln!(out, "greeting {:?}", imports::GREETING.with(|s| s.length())).unwrap(); + let global = imports::GLOBAL.with(Object::clone); + writeln!(out, "global {}", Reflect::has(&global, &"Math".into()).unwrap()).unwrap(); + // A class only its vendor-prefixed name defines, which the caller defines. + let vendored = imports::Vendored::new(); + writeln!( + out, + "vendored {:?} {}", + vendored.name(), + JsValue::from(&vendored).is_instance_of::() + ) + .unwrap(); +} + +fn logs(input: &mut Input, out: &mut String) { + // What `console.log` logs, captured into an array in its place. + let console = Reflect::get(&imports::GLOBAL.with(Object::clone), &"console".into()).unwrap(); + let original = Reflect::get(&console, &"log".into()).unwrap(); + let logged = Array::new(); + let capture = Function::new_with_args("...args", "this.push(args.map(String).join(' '))").bind0(&logged); + Reflect::set(&console, &"log".into(), &capture).unwrap(); + console::log_0(); + console::log_1(&input.value()); + console::log_2(&input.value(), &JsValue::from_str(&input.text())); + let many = Array::of3(&input.value(), &input.value(), &JsValue::from(input.u32())); + console::log(&many); + Reflect::set(&console, &"log".into(), &original).unwrap(); + for line in logged.iter() { + show(out, "logged", &line); + } +} + +fn caught(input: &mut Input, out: &mut String) { + let v = input.value(); + match imports::check(&v) { + Ok(()) => writeln!(out, "a number").unwrap(), + Err(e) => { + let error = e.dyn_ref::().map(|e| String::from(e.message())); + writeln!( + out, + "threw {error:?} type error {}", + e.is_instance_of::() + ) + .unwrap(); + } + } + match Reflect::get(&v, &"length".into()) { + Ok(len) => show(out, "length", &len), + Err(e) => show(out, "length threw", &e), + } + match Reflect::own_keys(&v) { + Ok(keys) => writeln!(out, "own keys {}", keys.length()).unwrap(), + Err(e) => show(out, "own keys threw", &e), + } +} + +/// An import that throws without `catch`: the throw unwinds Rust and leaves the call. +fn throws(input: &mut Input, out: &mut String) { + let bits = i32::from(input.u8()) - 128; + let v = imports::as_int_n_signed(bits, 1); + writeln!(out, "as_int_n {v}").unwrap(); +} + +/// A struct of the crate's own over a JS value, laid out as an imported type is, which +/// stays a struct: its `Drop` runs. +#[repr(transparent)] +struct Owned { + obj: JsValue, +} + +/// How many `Owned`s have dropped. +static DROPS: AtomicU32 = AtomicU32::new(0); + +impl Drop for Owned { + fn drop(&mut self) { + DROPS.fetch_add(1, Ordering::Relaxed); + } +} + +/// Whether `e` is an `Error` of the class `class` names, as each side's engine words its +/// message. +fn error_class(out: &mut String, what: &str, e: &JsValue) { + let name = Reflect::get(e, &"name".into()).unwrap_or_default(); + show(out, what, &name); +} + +/// Each conversion's edge: namespaces' variadic calls, `catch` around the glue's +/// conversions and not the ABI's, optional `bool`s, globals no JS defines, and a struct of +/// the crate's own over a JS value. +fn edges(input: &mut Input, out: &mut String) { + writeln!(out, "bytes of {:?}", imports::bytes_of(input.slice())).unwrap(); + let spread: Array = match input.flag() { + true => imports::bytes_array(input.slice()), + false => { + // An array-like without an iterator. + let like = Object::new(); + Reflect::set(&like, &"length".into(), &JsValue::from(1)).unwrap(); + Reflect::set(&like, &"0".into(), &input.value()).unwrap(); + like.unchecked_into() + } + }; + match imports::array_of_caught(&spread) { + Ok(array) => writeln!(out, "spread {}", array.length()).unwrap(), + Err(e) => error_class(out, "spread threw", &e), + } + let counts = [input.u32(), input.u32()]; + let fresh = imports::fresh_of(&counts[..usize::from(input.u8() % 3)]); + writeln!(out, "fresh of {fresh}").unwrap(); + // The `None` an `Option` takes `0xFFFFFF` for, beside an input's value. + let sentinel = imports::u8_option(&JsValue::from(0x00ff_ffff)); + let small = imports::u8_option(&JsValue::from_f64(input.f64())); + writeln!(out, "u8 option {sentinel:?} {small:?}").unwrap(); + let v = input.value(); + match imports::char_caught(&v) { + Ok(c) => writeln!(out, "char {c:?}").unwrap(), + Err(e) => error_class(out, "char threw", &e), + } + let x = match input.flag() { + true => Some(input.f64()), + false => None, + }; + writeln!(out, "abs flag {:?}", imports::abs_flag(x)).unwrap(); + match imports::missing_function() { + Ok(()) => writeln!(out, "missing function called").unwrap(), + Err(e) => error_class(out, "missing function threw", &e), + } + match imports::Missing::new() { + Ok(_) => writeln!(out, "missing class made").unwrap(), + Err(e) => error_class(out, "missing class threw", &e), + } + let before = DROPS.load(Ordering::Relaxed); + { + let owned = Owned { obj: input.value() }; + show(out, "owned", &owned.obj); + } + writeln!(out, "drops {}", DROPS.load(Ordering::Relaxed) - before).unwrap(); + // Last, as each may throw past the case: `catch` imports whose numbers the ABI coerces, + // and a plain one. + let v = input.value(); + match imports::u32_caught(&v) { + Ok(n) => writeln!(out, "u32 {n}").unwrap(), + Err(e) => error_class(out, "u32 threw", &e), + } + match imports::i16_option_caught(&input.value()) { + Ok(n) => writeln!(out, "i16 option {n:?}").unwrap(), + Err(e) => error_class(out, "i16 option threw", &e), + } + writeln!(out, "u32 of {}", imports::u32_of(&input.value())).unwrap(); +} diff --git a/ts/fixtures/bindgen/src/imports.rs b/ts/fixtures/bindgen/src/imports.rs new file mode 100644 index 000000000..05586412f --- /dev/null +++ b/ts/fixtures/bindgen/src/imports.rs @@ -0,0 +1,209 @@ +//! Imports of the fixture's own, of each kind and shape wasm-bindgen declares, against +//! JS's globals and functions the harness defines on `globalThis` first. + +use js_sys::{Array, JsString, Object}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + // A JS number into each Rust number type, which the WebAssembly ABI coerces: an `f64` + // past `u32` wraps, and NaN is zero. + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_u8(x: f64) -> u8; + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_i8(x: f64) -> i8; + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_u16(x: f64) -> u16; + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_i16(x: f64) -> i16; + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_u32(x: f64) -> u32; + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_i32(x: f64) -> i32; + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_usize(x: f64) -> usize; + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_f32(x: f64) -> f32; + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_bool(x: f64) -> bool; + #[wasm_bindgen(js_namespace = Math, js_name = round)] + pub fn round_option(x: Option) -> Option; + + // 64- and 128-bit integers, as BigInts both ways. + #[wasm_bindgen(js_namespace = BigInt, js_name = asIntN)] + pub fn as_int_n(bits: u32, v: i64) -> i64; + #[wasm_bindgen(js_namespace = BigInt, js_name = asIntN)] + pub fn as_int_n_signed(bits: i32, v: i64) -> i64; + #[wasm_bindgen(js_namespace = BigInt, js_name = asUintN)] + pub fn as_uint_n(bits: u32, v: u64) -> u64; + #[wasm_bindgen(js_namespace = BigInt, js_name = asIntN)] + pub fn as_int_n128(bits: u32, v: i128) -> i128; + #[wasm_bindgen(js_namespace = BigInt, js_name = asUintN)] + pub fn as_uint_n128(bits: u32, v: u128) -> u128; + + // Text: a `char`, a `&str`, and a `String` to JS and back. + #[wasm_bindgen(js_name = String)] + pub fn char_string(c: char) -> String; + #[wasm_bindgen(js_name = String)] + pub fn first_char(s: &str) -> char; + #[wasm_bindgen(js_name = String)] + pub fn string_option(s: Option) -> Option; + #[wasm_bindgen(js_name = String)] + pub fn js_string(v: &JsValue) -> JsString; + + // Slices and vectors: bytes, numbers, and JS values. + #[wasm_bindgen(js_namespace = Array, js_name = from)] + pub fn bytes_array(bytes: &[u8]) -> Array; + #[wasm_bindgen(js_namespace = Array, js_name = from)] + pub fn floats_array(floats: &[f64]) -> Vec; + #[wasm_bindgen(js_namespace = Array, js_name = from)] + pub fn wides_array(wides: &[u64]) -> Vec; + #[wasm_bindgen(js_namespace = Array, js_name = from)] + pub fn values_array(values: Vec) -> Vec; + #[wasm_bindgen(js_namespace = Array, js_name = from)] + pub fn strings_array(values: Vec) -> Vec; + #[wasm_bindgen(js_namespace = Uint8Array, js_name = from)] + pub fn bytes_copy(bytes: Vec) -> Vec; + #[wasm_bindgen(js_namespace = Uint8Array, js_name = from)] + pub fn bytes_boxed(bytes: &[u8]) -> Box<[u8]>; + #[wasm_bindgen(js_namespace = Object, js_name = keys)] + pub fn keys(o: &JsValue) -> Vec; + + // A variadic call, whose last argument JS spreads. + #[wasm_bindgen(variadic, js_namespace = Math, js_name = max)] + pub fn max_of(first: f64, rest: &[f64]) -> f64; + #[wasm_bindgen(variadic, js_namespace = Array, js_name = of)] + pub fn array_of(values: &Array) -> Array; + + // `catch`, into a `Result` of what JS threw. + #[wasm_bindgen(catch, js_namespace = JSON, js_name = parse)] + pub fn parse(text: &str) -> Result; + #[wasm_bindgen(catch, js_namespace = BigInt, js_name = asIntN)] + pub fn as_int_n_number(bits: u32, v: f64) -> Result; + #[wasm_bindgen(catch, js_name = rustCheck)] + pub fn check(v: &JsValue) -> Result<(), JsValue>; + + // Statics, of a namespace's value, an optional one, and a string literal. + #[wasm_bindgen(thread_local_v2, js_namespace = Math, js_name = PI)] + pub static PI: f64; + #[wasm_bindgen(thread_local_v2, js_name = rustMissing)] + pub static MISSING: Option; + #[wasm_bindgen(thread_local_v2, js_name = globalThis)] + pub static GLOBAL: Object; + + // A class of its own: a constructor, methods, getters and setters, structural and on + // its prototype, indexing, and `instanceof`. + #[wasm_bindgen(extends = Object, js_name = Map)] + #[derive(Clone, Debug)] + pub type Table; + #[wasm_bindgen(constructor, js_class = Map)] + pub fn new() -> Table; + #[wasm_bindgen(method, js_class = Map)] + pub fn set(this: &Table, key: &JsValue, value: &JsValue) -> Table; + #[wasm_bindgen(method, js_class = Map, js_name = get)] + pub fn get(this: &Table, key: &JsValue) -> Option; + #[wasm_bindgen(method, final, js_class = Map, js_name = has)] + pub fn has_final(this: &Table, key: &JsValue) -> bool; + #[wasm_bindgen(method, getter, js_class = Map)] + pub fn size(this: &Table) -> u32; + #[wasm_bindgen(method, getter, final, js_class = Map, js_name = size)] + pub fn size_final(this: &Table) -> u32; + #[wasm_bindgen(method, structural, getter = size)] + pub fn size_structural(this: &Table) -> u32; + + #[wasm_bindgen(extends = Object)] + #[derive(Clone, Debug)] + pub type Point; + #[wasm_bindgen(method, structural, getter)] + pub fn x(this: &Point) -> f64; + #[wasm_bindgen(method, structural, setter)] + pub fn set_x(this: &Point, x: f64); + #[wasm_bindgen(method, structural, indexing_getter)] + pub fn at(this: &Point, key: &str) -> JsValue; + #[wasm_bindgen(method, structural, indexing_setter)] + pub fn put(this: &Point, key: &str, value: &JsValue); + #[wasm_bindgen(method, structural, indexing_deleter)] + pub fn remove(this: &Point, key: &str); + #[wasm_bindgen(method, structural, js_name = toString)] + pub fn describe(this: &Point) -> String; + + // A class no global names, which no value is an instance of. + #[wasm_bindgen(extends = Object, js_name = RustNoSuchClass)] + pub type Missing; + + // A class by its vendor-prefixed name, where the unprefixed one is not defined. + #[wasm_bindgen(extends = Object, vendor_prefix = webkit, js_name = RustVendored)] + pub type Vendored; + #[wasm_bindgen(constructor, js_class = RustVendored)] + pub fn new() -> Vendored; + #[wasm_bindgen(method, js_class = RustVendored)] + pub fn name(this: &Vendored) -> String; + + // A static method of a class, and one named by a symbol. + #[wasm_bindgen(static_method_of = Table, js_class = Map, js_name = groupBy)] + pub fn group_by(items: &Array, key: &js_sys::Function) -> Table; + #[wasm_bindgen(method, js_class = Map, js_name = "[Symbol.iterator]")] + pub fn entries(this: &Table) -> js_sys::Iterator; + + // A string enum, both ways. + #[wasm_bindgen(js_name = rustTypeof)] + pub fn type_of(v: &JsValue) -> Kind; + #[wasm_bindgen(js_name = String)] + pub fn kind_string(k: Kind) -> String; +} + +/// What `typeof` says, of the kinds the harness reads. +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Kind { + Number = "number", + String = "string", + Object = "object", + Undefined = "undefined", +} + +// A string literal as a JS string, which rustfmt would drop, as plain Rust gives no +// `extern` static a value. +#[rustfmt::skip] +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(thread_local_v2, static_string)] + pub static GREETING: JsString = "hé\u{1f600}"; +} + +#[wasm_bindgen] +extern "C" { + // A namespace's variadic function, called on its namespace, and one spreading what has + // no iterator. + #[wasm_bindgen(variadic, js_namespace = Uint8Array, js_name = of)] + pub fn bytes_of(bytes: &[u8]) -> Vec; + #[wasm_bindgen(catch, variadic, js_namespace = Array, js_name = of)] + pub fn array_of_caught(values: &Array) -> Result; + // A namespace a getter makes anew each time it is read, read once a call. + #[wasm_bindgen(variadic, js_namespace = rustFresh, js_name = of)] + pub fn fresh_of(values: &[u32]) -> u32; + + // What converting a `catch` import's result throws: caught where the JS glue converts, + // as for a `char`, and thrown where the WebAssembly ABI coerces, as for a `u32`. + #[wasm_bindgen(catch, js_name = String)] + pub fn char_caught(v: &JsValue) -> Result; + #[wasm_bindgen(catch, js_name = rustIdentity)] + pub fn u32_caught(v: &JsValue) -> Result; + #[wasm_bindgen(js_name = rustIdentity)] + pub fn u32_of(v: &JsValue) -> u32; + // An optional small integer, which crosses the ABI's `i32` as it is, as a `u32` does. + #[wasm_bindgen(js_name = rustIdentity)] + pub fn u8_option(v: &JsValue) -> Option; + #[wasm_bindgen(catch, js_name = rustIdentity)] + pub fn i16_option_caught(v: &JsValue) -> Result, JsValue>; + + // An optional `bool`, which the JS glue reads by its truthiness. + #[wasm_bindgen(js_namespace = Math, js_name = abs)] + pub fn abs_flag(x: Option) -> Option; + + // Globals no JS defines: each throws a `ReferenceError`, as naming it does. + #[wasm_bindgen(catch, js_name = rustNoSuchFunction)] + pub fn missing_function() -> Result<(), JsValue>; + #[wasm_bindgen(catch, constructor, js_class = RustNoSuchClass)] + pub fn new() -> Result; +} diff --git a/ts/fixtures/bindgen/src/lib.rs b/ts/fixtures/bindgen/src/lib.rs new file mode 100644 index 000000000..df6f22e5d --- /dev/null +++ b/ts/fixtures/bindgen/src/lib.rs @@ -0,0 +1,15 @@ +//! A harness over js-sys and web-sys, and imports of its own of each kind wasm-bindgen +//! declares, which translation makes direct JS calls: the JS value is the value itself. +//! +//! `harness::run` is the entry point both sides of the differential test run: natively +//! through wasm-bindgen's CLI, and translated. + +pub mod harness; +mod imports; +#[cfg(feature = "refused")] +pub mod refused; +#[cfg(target_arch = "wasm32")] +#[path = "../../wasm.rs"] +mod wasm; +#[cfg(target_arch = "wasm32")] +use harness::run as entry; diff --git a/ts/fixtures/bindgen/src/refused.rs b/ts/fixtures/bindgen/src/refused.rs new file mode 100644 index 000000000..8a7ba3c4a --- /dev/null +++ b/ts/fixtures/bindgen/src/refused.rs @@ -0,0 +1,56 @@ +//! What translation refuses of wasm-bindgen, each at its span: imports of custom JS by +//! each kind of module, one that suspends on a promise, one generic per monomorphization, +//! a closure, an optional mutable slice, the intrinsics only WASM has, and an export. + +// `suspending` warns that it is experimental. +#![expect(deprecated, reason = "a refusal of each kind of import, experimental ones too")] + +use wasm_bindgen::JsValue; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(module = "/js/custom.js")] +extern "C" { + fn custom() -> u32; +} + +#[wasm_bindgen(raw_module = "./raw.js")] +extern "C" { + fn raw() -> u32; +} + +#[wasm_bindgen(inline_js = "export function inline() { return 1; }")] +extern "C" { + fn inline() -> u32; +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(suspending, js_namespace = Promise, js_name = resolve)] + fn suspended(v: &JsValue) -> JsValue; + + #[wasm_bindgen(experimental_generic_mono, js_name = String)] + fn generic(v: &T) -> JsValue; + + #[wasm_bindgen(js_name = rustFill)] + fn fill(v: Option<&mut [u16]>); + + #[wasm_bindgen(js_name = setTimeout)] + fn set_timeout(f: &Closure, ms: u32); +} + +/// An export, which JS calls into WASM by. +#[wasm_bindgen] +pub fn exported(x: u32) -> u32 { + x + 1 +} + +pub fn run() -> u32 { + let mut units = [0u16; 2]; + fill(Some(&mut units)); + let closure = Closure::new(|| {}); + set_timeout(&closure, 0); + let memory = wasm_bindgen::memory(); + let suspended = suspended(&memory); + let generic = generic(&suspended); + custom() + raw() + inline() + exported(u32::from(generic.is_null())) + u32::from(units[0]) +} diff --git a/ts/fixtures/bindgen/ts.toml b/ts/fixtures/bindgen/ts.toml new file mode 100644 index 000000000..4c5dc4544 --- /dev/null +++ b/ts/fixtures/bindgen/ts.toml @@ -0,0 +1,2 @@ +package = "bindgen-fixture" +roots = ["harness::run"] diff --git a/ts/fixtures/bindgen/wasm-bindgen-0.2.130.lock b/ts/fixtures/bindgen/wasm-bindgen-0.2.130.lock new file mode 100644 index 000000000..05e663b16 --- /dev/null +++ b/ts/fixtures/bindgen/wasm-bindgen-0.2.130.lock @@ -0,0 +1,166 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bindgen-fixture" +version = "0.0.0" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "js-sys" +version = "0.3.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "wasm-bindgen" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c" +dependencies = [ + "cfg-if", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d179596ff5ec0f6de4db64d54011bde6289cc49603d3a6e4ce3f329207ab88" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/ts/fixtures/stock/expected/hashlink/linked_hash_map.DropFilteredValues.ts b/ts/fixtures/stock/expected/hashlink/linked_hash_map.DropFilteredValues.ts index 6945f9a8a..2d35e4186 100644 --- a/ts/fixtures/stock/expected/hashlink/linked_hash_map.DropFilteredValues.ts +++ b/ts/fixtures/stock/expected/hashlink/linked_hash_map.DropFilteredValues.ts @@ -20,12 +20,8 @@ export function dropLater$u16_u32(self: DropFilteredValues, node } export function drop$ops_Drop_u16_u32(self: DropFilteredValues): void { - let endFree!: Node.Node | undefined; - while (true) { - endFree = self.curFree; - if (!(self.curFree !== self.free.v)) { - break; - } + const endFree: Node.Node | undefined = self.curFree; + while (self.curFree !== self.free.v) { const curFree: Node.Node = asPtr$hashlink_linked_hash_map_OptNonNullExt_hashlink_linked_hash_map_Node_u16_u32_Option_ptr_NonNull_hashlink_linked_hash_map_Node_u16_u32(self.curFree); Node.takeEntry$u16_u32(curFree); self.curFree = curFree.links.at0; diff --git a/ts/fixtures/stock/expected/rangemap/inclusive_map.RangeInclusiveMap.ts b/ts/fixtures/stock/expected/rangemap/inclusive_map.RangeInclusiveMap.ts index aec532c45..ed1e3164e 100644 --- a/ts/fixtures/stock/expected/rangemap/inclusive_map.RangeInclusiveMap.ts +++ b/ts/fixtures/stock/expected/rangemap/inclusive_map.RangeInclusiveMap.ts @@ -310,10 +310,9 @@ export function remove$u64_u32_u64(self: RangeInclusiveMap = RangeInclusiveStartWrapper.new$u64(new rs.RangeInclusive(rs.rangeInclusiveEnd(range2), rs.rangeInclusiveEnd(range2), rs.u64Step)); while (true) { - const newRangeEndAsStart: RangeInclusiveStartWrapper.RangeInclusiveStartWrapper = RangeInclusiveStartWrapper.new$u64(new rs.RangeInclusive(rs.rangeInclusiveEnd(range2), rs.rangeInclusiveEnd(range2), rs.u64Step)); - const _44: [rs.RangeBound>, rs.RangeBound>] = [{ tag: "Excluded", _0: rangeStartWrapper } as rs.RangeBound>, { tag: "Included", _0: newRangeEndAsStart } as rs.RangeBound>]; - const _39: [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, rs.u32] | undefined = option_map$0uvwlsi(rs.iterToOption(rs.btreeRange(self.btm, _44, RangeInclusiveStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeInclusiveStartWrapper.partialCmp$cmp_PartialOrd_u64)).next()), (_26: [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, rs.u32]): [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, rs.u32] => { + const _39: [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, rs.u32] | undefined = option_map$0uvwlsi(rs.iterToOption(rs.btreeRange(self.btm, [{ tag: "Excluded", _0: rangeStartWrapper } as rs.RangeBound>, { tag: "Included", _0: newRangeEndAsStart } as rs.RangeBound>] as [rs.RangeBound>, rs.RangeBound>], RangeInclusiveStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeInclusiveStartWrapper.partialCmp$cmp_PartialOrd_u64)).next()), (_26: [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, rs.u32]): [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, rs.u32] => { return [RangeInclusiveStartWrapper.clone$Clone_u64(_26[0]), _26[1]] as [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, rs.u32]; }); if (_39 !== undefined) { @@ -353,10 +352,9 @@ export function remove$u64_unit_u64(self: RangeInclusiveMap = RangeInclusiveStartWrapper.new$u64(new rs.RangeInclusive(rs.rangeInclusiveEnd(range2), rs.rangeInclusiveEnd(range2), rs.u64Step)); while (true) { - const newRangeEndAsStart: RangeInclusiveStartWrapper.RangeInclusiveStartWrapper = RangeInclusiveStartWrapper.new$u64(new rs.RangeInclusive(rs.rangeInclusiveEnd(range2), rs.rangeInclusiveEnd(range2), rs.u64Step)); - const _44: [rs.RangeBound>, rs.RangeBound>] = [{ tag: "Excluded", _0: rangeStartWrapper } as rs.RangeBound>, { tag: "Included", _0: newRangeEndAsStart } as rs.RangeBound>]; - const _39: [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, undefined] | undefined = option_map$05vmvpg(rs.iterToOption(rs.btreeRange(self.btm, _44, RangeInclusiveStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeInclusiveStartWrapper.partialCmp$cmp_PartialOrd_u64)).next()), (_26: [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, undefined]): [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, undefined] => { + const _39: [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, undefined] | undefined = option_map$05vmvpg(rs.iterToOption(rs.btreeRange(self.btm, [{ tag: "Excluded", _0: rangeStartWrapper } as rs.RangeBound>, { tag: "Included", _0: newRangeEndAsStart } as rs.RangeBound>] as [rs.RangeBound>, rs.RangeBound>], RangeInclusiveStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeInclusiveStartWrapper.partialCmp$cmp_PartialOrd_u64)).next()), (_26: [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, undefined]): [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, undefined] => { const _54: RangeInclusiveStartWrapper.RangeInclusiveStartWrapper = RangeInclusiveStartWrapper.clone$Clone_u64(_26[0]); _26[1]; return [_54, undefined] as [RangeInclusiveStartWrapper.RangeInclusiveStartWrapper, undefined]; diff --git a/ts/fixtures/stock/expected/rangemap/map.RangeMap.ts b/ts/fixtures/stock/expected/rangemap/map.RangeMap.ts index a6b74292c..68372cacd 100644 --- a/ts/fixtures/stock/expected/rangemap/map.RangeMap.ts +++ b/ts/fixtures/stock/expected/rangemap/map.RangeMap.ts @@ -242,10 +242,9 @@ export function insert$u64_u64(self: RangeMap, range: rs.Range, rs.u64] = [RangeStartWrapper.clone$Clone_u64(candidate[0]), candidate[1]]; adjustTouchingRangesForInsert$u64_u64(self, _32[0], _32[1], newStartWrapper.endWrapper.range, newValue); } + const newRangeEndAsStart: RangeStartWrapper.RangeStartWrapper = RangeStartWrapper.new$u64(new rs.Range(newStartWrapper.endWrapper.range.end, newStartWrapper.endWrapper.range.end, rs.u64Step)); while (true) { - const newRangeEndAsStart: RangeStartWrapper.RangeStartWrapper = RangeStartWrapper.new$u64(new rs.Range(newStartWrapper.endWrapper.range.end, newStartWrapper.endWrapper.range.end, rs.u64Step)); - const _48: [rs.RangeBound>, rs.RangeBound>] = [{ tag: "Included", _0: newStartWrapper } as rs.RangeBound>, { tag: "Included", _0: newRangeEndAsStart } as rs.RangeBound>]; - const _44: [RangeStartWrapper.RangeStartWrapper, rs.u64] | rs.Done = rs.btreeRange(self.btm, _48, RangeStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeStartWrapper.partialCmp$cmp_PartialOrd_u64)).next(); + const _44: [RangeStartWrapper.RangeStartWrapper, rs.u64] | rs.Done = rs.btreeRange(self.btm, [{ tag: "Included", _0: newStartWrapper } as rs.RangeBound>, { tag: "Included", _0: newRangeEndAsStart } as rs.RangeBound>] as [rs.RangeBound>, rs.RangeBound>], RangeStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeStartWrapper.partialCmp$cmp_PartialOrd_u64)).next(); if (_44 !== rs.DONE) { if (rs.u64Eq(_44[0].endWrapper.range.start, newStartWrapper.endWrapper.range.end)) { if (rs.u64Ne(_44[1], newValue)) { @@ -295,10 +294,9 @@ export function insert$u64_unit(self: RangeMap, range: rs.Ran candidate[1]; adjustTouchingRangesForInsert$u64_unit(self, _33, undefined, newStartWrapper.endWrapper.range, undefined); } + const newRangeEndAsStart: RangeStartWrapper.RangeStartWrapper = RangeStartWrapper.new$u64(new rs.Range(newStartWrapper.endWrapper.range.end, newStartWrapper.endWrapper.range.end, rs.u64Step)); while (true) { - const newRangeEndAsStart: RangeStartWrapper.RangeStartWrapper = RangeStartWrapper.new$u64(new rs.Range(newStartWrapper.endWrapper.range.end, newStartWrapper.endWrapper.range.end, rs.u64Step)); - const _48: [rs.RangeBound>, rs.RangeBound>] = [{ tag: "Included", _0: newStartWrapper } as rs.RangeBound>, { tag: "Included", _0: newRangeEndAsStart } as rs.RangeBound>]; - const _44: [RangeStartWrapper.RangeStartWrapper, undefined] | rs.Done = rs.btreeRange(self.btm, _48, RangeStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeStartWrapper.partialCmp$cmp_PartialOrd_u64)).next(); + const _44: [RangeStartWrapper.RangeStartWrapper, undefined] | rs.Done = rs.btreeRange(self.btm, [{ tag: "Included", _0: newStartWrapper } as rs.RangeBound>, { tag: "Included", _0: newRangeEndAsStart } as rs.RangeBound>] as [rs.RangeBound>, rs.RangeBound>], RangeStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeStartWrapper.partialCmp$cmp_PartialOrd_u64)).next(); if (_44 !== rs.DONE) { if (rs.u64Eq(_44[0].endWrapper.range.start, newStartWrapper.endWrapper.range.end)) { if (false) { @@ -342,10 +340,9 @@ export function remove$u64_u64(self: RangeMap, range: rs.Range = RangeStartWrapper.new$u64(new rs.Range(range2.end, range2.end, rs.u64Step)); while (true) { - const newRangeEndAsStart: RangeStartWrapper.RangeStartWrapper = RangeStartWrapper.new$u64(new rs.Range(range2.end, range2.end, rs.u64Step)); - const _37: [rs.RangeBound>, rs.RangeBound>] = [{ tag: "Excluded", _0: startWrapper } as rs.RangeBound>, { tag: "Excluded", _0: newRangeEndAsStart } as rs.RangeBound>]; - const _32: [RangeStartWrapper.RangeStartWrapper, rs.u64] | undefined = option_map$02ffuo4(rs.iterToOption(rs.btreeRange(self.btm, _37, RangeStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeStartWrapper.partialCmp$cmp_PartialOrd_u64)).next()), (_23: [RangeStartWrapper.RangeStartWrapper, rs.u64]): [RangeStartWrapper.RangeStartWrapper, rs.u64] => { + const _32: [RangeStartWrapper.RangeStartWrapper, rs.u64] | undefined = option_map$02ffuo4(rs.iterToOption(rs.btreeRange(self.btm, [{ tag: "Excluded", _0: startWrapper } as rs.RangeBound>, { tag: "Excluded", _0: newRangeEndAsStart } as rs.RangeBound>] as [rs.RangeBound>, rs.RangeBound>], RangeStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeStartWrapper.partialCmp$cmp_PartialOrd_u64)).next()), (_23: [RangeStartWrapper.RangeStartWrapper, rs.u64]): [RangeStartWrapper.RangeStartWrapper, rs.u64] => { return [RangeStartWrapper.clone$Clone_u64(_23[0]), _23[1]] as [RangeStartWrapper.RangeStartWrapper, rs.u64]; }); if (_32 !== undefined) { @@ -385,10 +382,9 @@ export function remove$u64_unit(self: RangeMap, range: rs.Ran if (_10 !== undefined) { adjustOverlappingRangesForRemove$u64_unit(self, _10[0], undefined, range2); } + const newRangeEndAsStart: RangeStartWrapper.RangeStartWrapper = RangeStartWrapper.new$u64(new rs.Range(range2.end, range2.end, rs.u64Step)); while (true) { - const newRangeEndAsStart: RangeStartWrapper.RangeStartWrapper = RangeStartWrapper.new$u64(new rs.Range(range2.end, range2.end, rs.u64Step)); - const _37: [rs.RangeBound>, rs.RangeBound>] = [{ tag: "Excluded", _0: startWrapper } as rs.RangeBound>, { tag: "Excluded", _0: newRangeEndAsStart } as rs.RangeBound>]; - const _32: [RangeStartWrapper.RangeStartWrapper, undefined] | undefined = option_map$105h877(rs.iterToOption(rs.btreeRange(self.btm, _37, RangeStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeStartWrapper.partialCmp$cmp_PartialOrd_u64)).next()), (_23: [RangeStartWrapper.RangeStartWrapper, undefined]): [RangeStartWrapper.RangeStartWrapper, undefined] => { + const _32: [RangeStartWrapper.RangeStartWrapper, undefined] | undefined = option_map$105h877(rs.iterToOption(rs.btreeRange(self.btm, [{ tag: "Excluded", _0: startWrapper } as rs.RangeBound>, { tag: "Excluded", _0: newRangeEndAsStart } as rs.RangeBound>] as [rs.RangeBound>, rs.RangeBound>], RangeStartWrapper.eq$cmp_PartialEq_u64, rs.cmpGt(RangeStartWrapper.partialCmp$cmp_PartialOrd_u64)).next()), (_23: [RangeStartWrapper.RangeStartWrapper, undefined]): [RangeStartWrapper.RangeStartWrapper, undefined] => { const _52: RangeStartWrapper.RangeStartWrapper = RangeStartWrapper.clone$Clone_u64(_23[0]); _23[1]; return [_52, undefined] as [RangeStartWrapper.RangeStartWrapper, undefined]; diff --git a/ts/justfile b/ts/justfile index 3a4ccd4be..9c99d4015 100644 --- a/ts/justfile +++ b/ts/justfile @@ -11,6 +11,9 @@ export PATH := if env("RUST2_TS_TOOLCHAIN", "") == "" { env("PATH") } else { env # The model fixture, its own workspace, which builds moq-net at the moq pin. model := "fixtures/model/Cargo.toml" +# The wasm-bindgen fixture, its own workspace, which pins wasm-bindgen to the dev shell's CLI. +bindgen := "fixtures/bindgen/Cargo.toml" + # The moq revision `moq` and `census` translate and `just ffi` exports by default: the one # the model fixture builds. Read without taplo, which the nightly's runners outside Nix lack. moq_rev_match := replace_regex(read(model), '(?s).*\nmoq-net = \{[^\n]*\brev = "([0-9a-f]{40})".*', '$1') @@ -23,12 +26,16 @@ default: check: cargo clippy --locked --all-targets -- -D warnings cargo clippy --locked --all-targets --manifest-path {{ model }} -- -D warnings + cargo clippy --locked --all-targets --manifest-path {{ bindgen }} -- -D warnings cargo fmt --all --check cargo fmt --check --manifest-path {{ model }} + cargo fmt --check --manifest-path {{ bindgen }} cargo shear cargo shear fixtures/model + cargo shear fixtures/bindgen cargo sort --workspace --check --no-format cargo sort --check --no-format fixtures/model + cargo sort --check --no-format fixtures/bindgen tsc -p runtime # The bench against moq's TypeScript at the pin, so a pin that drops what it uses fails here. just moq-pin @@ -43,12 +50,16 @@ test *args: fix: cargo clippy --locked --fix --allow-staged --allow-dirty --all-targets cargo clippy --locked --fix --allow-staged --allow-dirty --all-targets --manifest-path {{ model }} + cargo clippy --locked --fix --allow-staged --allow-dirty --all-targets --manifest-path {{ bindgen }} cargo fmt --all cargo fmt --manifest-path {{ model }} + cargo fmt --manifest-path {{ bindgen }} cargo shear --fix cargo shear --fix fixtures/model + cargo shear --fix fixtures/bindgen cargo sort --workspace --no-format cargo sort --no-format fixtures/model + cargo sort --no-format fixtures/bindgen # Run the driver: `just ts run --config --out `. run *args: diff --git a/ts/runtime/rs.ts b/ts/runtime/rs.ts index 6f937c40c..b02805331 100644 --- a/ts/runtime/rs.ts +++ b/ts/runtime/rs.ts @@ -28,6 +28,7 @@ export * from "./rs/hash.ts"; export * from "./rs/hook.ts"; export * from "./rs/int.ts"; export * from "./rs/iter.ts"; +export * from "./rs/js.ts"; export * from "./rs/location.ts"; export * from "./rs/rand.ts"; export * from "./rs/sort.ts"; diff --git a/ts/runtime/rs/js.ts b/ts/runtime/rs/js.ts new file mode 100644 index 000000000..8f984c5e6 --- /dev/null +++ b/ts/runtime/rs/js.ts @@ -0,0 +1,328 @@ +// wasm-bindgen, for rust2-ts output: a `JsValue`, or any type an import declares, is the +// JS value itself, and an import is a direct JS call. These are the conversions at the +// boundary, which take a Rust value to the JS one wasm-bindgen's JS glue passes and back +// as it does, coercing as the WebAssembly ABI coerces, and the JS bodies of wasm-bindgen's +// intrinsics, as its CLI gives them. `rs.ts` re-exports all of it. + +import { ByteSlice, ByteVec, byteSliceFrom, Err, Ok, type Result, Slice, Vec } from "../rs.ts"; +import { type char, type f32, type i8, type i16, type i32, intFromBig, intToBig, type isize, type Limbs, type u8, type u16, type u32, type usize } from "./int.ts"; +import { String as RsString, stringFrom } from "./str.ts"; + +/** A JS value, as a `JsValue` and every imported type are. */ +export type JsValue = unknown; + +/** The global object, whose properties an import names. */ +// biome-ignore lint/suspicious/noExplicitAny: an import may name any global, with any shape. +export const global: any = globalThis; + +/** A JS value as the JS glue reads it, of any shape. */ +// biome-ignore lint/suspicious/noExplicitAny: an import's value has whatever shape JS gives it. +export function jsAny(v: unknown): any { + return v; +} + +/** `u64`, `i64`, `u128`, or `i128` to JS: a BigInt of its value. */ +export function jsBigInt(v: number | Limbs): bigint { + return intToBig(v); +} + +/** `char` to JS: a string of it. */ +export function jsChar(c: char): string { + return String.fromCodePoint(c); +} + +/** `String` to JS. */ +export function jsString(s: RsString): string { + return s.v; +} + +/** A `Vec` or a `Box<[u8]>` to JS: a copy, as the JS glue copies it out of memory. */ +export function jsByteVec(v: ByteVec): Uint8Array { + return v.data.slice(0, v.length); +} + +/** A typed array constructor, of a slice's elements. */ +type Typed = { new (length: number): ArrayLike & { [n: number]: unknown; set(a: ArrayLike): void } }; + +/** A slice of numbers to JS: a typed array of their values, as JS views them in memory. */ +export function jsTyped(of: Typed, s: Slice, big: boolean): ArrayLike { + const out = new of(s.length); + for (let i = 0; i < s.length; i++) out[i] = big ? intToBig(s.data[s.start + i] as number | Limbs) : s.data[s.start + i]; + return out; +} + +/** What JS wrote into a typed array `jsTyped` made of `s`, back into `s`, as JS writes a `&mut` slice in memory. */ +export function jsTypedBack(s: Slice, from: ArrayLike, back: (v: unknown) => T): void { + for (let i = 0; i < s.length; i++) s.data[s.start + i] = back(from[i]); +} + +/** A slice or `Vec` of JS values, strings, or other items to JS: an array of each, converted. */ +export function jsArray(items: Slice | Vec, to: (v: T) => unknown): unknown[] { + const [data, start, length] = items instanceof Slice ? [items.data, items.start, items.length] : [items.items, 0, items.items.length]; + const out = new Array(length); + for (let i = 0; i < length; i++) out[i] = to(data[start + i]); + return out; +} + +/** Whether a JS value is `None` to an `Option`: `undefined` or `null`. */ +export function jsIsNone(v: unknown): boolean { + return v === undefined || v === null; +} + +// From JS, as an import's result: each integer through the WebAssembly ABI's `i32`, which +// is `ToInt32`, then read as the Rust type, as wasm-bindgen's `from_abi` reads it. `+` is +// its `ToNumber`, which throws on a BigInt as the ABI does, where `|` would mix them. + +export function jsToU8(v: unknown): u8 { + return +(v as number) & 0xff; +} + +export function jsToI8(v: unknown): i8 { + return (+(v as number) << 24) >> 24; +} + +export function jsToU16(v: unknown): u16 { + return +(v as number) & 0xffff; +} + +export function jsToI16(v: unknown): i16 { + return (+(v as number) << 16) >> 16; +} + +export function jsToU32(v: unknown): u32 { + return +(v as number) >>> 0; +} + +export function jsToI32(v: unknown): i32 { + return +(v as number) | 0; +} + +export function jsToUsize(v: unknown): usize { + return +(v as number) >>> 0; +} + +export function jsToIsize(v: unknown): isize { + return +(v as number) | 0; +} + +/** `f64`, through the ABI's `ToNumber`, which throws on a BigInt or a Symbol. */ +export function jsToF64(v: unknown): number { + return +(v as number); +} + +export function jsToF32(v: unknown): f32 { + return Math.fround(+(v as number)); +} + +export function jsToBool(v: unknown): boolean { + return (+(v as number) | 0) !== 0; +} + +/** A 64- or 128-bit integer: `BigInt.asIntN` and `asUintN` take a BigInt only, as the ABI's `ToBigInt64` does. */ +export function jsToWide(v: unknown, bits: number, signed: boolean): number | Limbs { + // The JS glue splits a 128-bit value by a shift, which throws on anything but a BigInt. + if (bits === 128) (v as bigint) >> 64n; + const big = signed ? BigInt.asIntN(bits, v as bigint) : BigInt.asUintN(bits, v as bigint); + return intFromBig(big, bits, signed); +} + +/** `char`: a string's first code point, or 0 for an empty one. No `char` is a surrogate, so a lone one throws, as the JS glue throws for an `Option`. */ +export function jsToChar(v: unknown): char { + const c = ((v as string).codePointAt(0) as number) | 0; + if (c >= 0x110000 || (c >= 0xd800 && c < 0xe000)) throw new Error(`expected a valid Unicode scalar value, found ${c}`); + return c as char; +} + +/** A string as UTF-8 holds it: each lone surrogate as U+FFFD, as `TextEncoder` writes it. */ +function wellFormed(s: string): string { + return s.replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?` or `Box<[u8]>`: a copy of an array-like's bytes, each wrapped as `Uint8Array.set` wraps it. */ +export function jsToByteVec(v: unknown): ByteVec { + const items = v as ArrayLike; + const out = new ByteVec(items.length); + out.data.set(items); + out.length = items.length; + return out; +} + +/** A `Box<[u8]>`: a copy of an array-like's bytes, as `jsToByteVec` makes. */ +export function jsToByteSlice(v: unknown): ByteSlice { + return byteSliceFrom(Uint8Array.from(jsToByteVec(v).data)); +} + +/** A `Vec` of numbers, JS values, or strings: each of an array-like's items, converted. */ +export function jsToVec(v: unknown, from: (v: unknown) => T): Vec { + const items = v as ArrayLike; + const out = new Array(items.length); + for (let i = 0; i < items.length; i++) out[i] = from(items[i]); + return new Vec(out, out.length); +} + +/** A string enum's variant, by the name translation holds it as, to its JS string: `undefined` for the variant standing for any other. */ +export function jsEnum(names: readonly string[], values: readonly string[], v: string): string | undefined { + return values[names.indexOf(v)]; +} + +/** A JS value to the string enum's variant whose string it is, or else the last, which stands for any other. */ +export function jsToEnum(names: readonly T[], values: readonly string[], v: unknown): T { + const i = values.indexOf(v as string); + return names[i < 0 ? names.length - 1 : i]; +} + +/** A `catch` import's call: `Ok` of its result, converted, or `Err` of what it threw. What converting throws is caught where the JS glue converts it (`glue`), and thrown where the WebAssembly ABI coerces it, after the glue returns. */ +export function jsCatch(call: () => unknown, from: (v: unknown) => T, glue: boolean): Result { + let v: unknown; + try { + v = call(); + if (glue) return Ok(from(v)); + } catch (e) { + return Err(e); + } + return Ok(from(v)); +} + +/** A variadic import's last argument, spread as a spread reads it: by its iterator, throwing where it has none. */ +// biome-ignore lint/suspicious/noExplicitAny: arguments of any type, after those before them. +export function jsSpread(v: unknown): any[] { + return [...(v as Iterable)]; +} + +/** A variadic import's call of `f` on `self`: `rest`, then `last` spread by its iterator. */ +// biome-ignore lint/suspicious/noExplicitAny: an import's result has whatever shape JS gives it. +export function jsSpreadCall(f: unknown, self: unknown, rest: unknown[], last: unknown): any { + return Reflect.apply(f as (...args: unknown[]) => unknown, self, [...rest, ...(last as Iterable)]); +} + +/** `base[key](...rest, ...last)`, as a spread call reads it: `base` once, its function, then the arguments spread. */ +// biome-ignore lint/suspicious/noExplicitAny: an import's result has whatever shape JS gives it. +export function jsSpreadMethod(base: unknown, key: PropertyKey, rest: unknown[], last: unknown): any { + return jsSpreadCall(jsAny(base)[key], base, rest, last); +} + +/** `instanceof` the class `class_` names, false where naming it or the check throws, as where the JS glue finds no such class. */ +export function jsInstanceof(v: unknown, class_: () => unknown): boolean { + try { + return v instanceof (class_() as new () => unknown); + } catch (_) { + return false; + } +} + +/** `__wbindgen_is_object`: an object, not `null`, or a function's not. */ +export function jsIsObject(v: unknown): boolean { + return typeof v === "object" && v !== null; +} + +/** An `optional` static's value: `null` where the global it starts from is not defined, as the JS glue's `typeof` check gives, else each property of it in turn. `root` names the global, or is the one a vendor prefix found. */ +export function jsOptionalPath(root: unknown, ...props: string[]): unknown { + let v = typeof root === "string" ? global[root] : root; + if (v === undefined) return null; + for (const prop of props) v = jsAny(v)?.[prop]; + return v; +} + +/** A global the JS glue names as an identifier: a `ReferenceError` where none is defined, as reading an undeclared identifier throws. */ +// biome-ignore lint/suspicious/noExplicitAny: a global, of whatever shape JS gives it. +export function jsGlobal(name: string): any { + if (!(name in global)) throw new ReferenceError(`${name} is not defined`); + return global[name]; +} + +/** A global by name, or the first of its vendor-prefixed names that is defined. */ +// biome-ignore lint/suspicious/noExplicitAny: a global, of whatever shape JS gives it. +export function jsVendored(name: string, prefixes: readonly string[]): any { + for (const prefix of ["", ...prefixes]) { + if (typeof global[prefix + name] !== "undefined") return global[prefix + name]; + } + return undefined; +} + +/** A property's descriptor on `obj` or the nearest prototype that has one, as the JS glue finds a non-structural getter or setter. */ +export function jsDescriptor(obj: unknown, id: string): PropertyDescriptor { + let o = obj as object | null; + while (o) { + const desc = Object.getOwnPropertyDescriptor(o, id); + if (desc) return desc; + o = Object.getPrototypeOf(o); + } + return {}; +} + +/** `throw`, as an expression. */ +export function jsThrow(v: unknown): never { + throw v; +} + +/** `__wbindgen_debug_string`, the text of `JsValue`'s `Debug`, as the CLI's `debugString` gives it. */ +export function jsDebugString(val: unknown): string { + const type = typeof val; + if (type === "number" || type === "boolean" || val == null) return `${val}`; + if (type === "string") return `"${val}"`; + if (type === "symbol") { + const description = (val as symbol).description; + return description == null ? "Symbol" : `Symbol(${description})`; + } + if (type === "function") { + const name = (val as { name: unknown }).name; + return typeof name === "string" && name.length > 0 ? `Function(${name})` : "Function"; + } + if (Array.isArray(val)) return `[${val.map(jsDebugString).join(", ")}]`; + // A BigInt, as any other object, by its class. + const builtIn = /\[object ([^\]]+)\]/.exec(Object.prototype.toString.call(val)); + if (!builtIn || builtIn.length <= 1) return Object.prototype.toString.call(val); + const className = builtIn[1]; + if (className === "Object") { + try { + return `Object(${JSON.stringify(val)})`; + } catch (_) { + return "Object"; + } + } + if (val instanceof Error) return `${val.name}: ${val.message}\n${val.stack}`; + return className; +} + +/** `__wbindgen_try_into_number`: the number, or what converting threw. */ +export function jsTryIntoNumber(v: unknown): unknown { + try { + return +(v as number); + } catch (e) { + return e; + } +} + +/** `__wbindgen_checked_div`: the quotient, or the `RangeError` dividing a BigInt by zero threw. */ +export function jsCheckedDiv(a: unknown, b: unknown): unknown { + try { + return jsAny(a) / jsAny(b); + } catch (e) { + if (e instanceof RangeError) return e; + throw e; + } +} + +/** `__wbindgen_copy_to_typed_array`: `data` into `js`'s bytes. */ +export function jsCopyToTypedArray(data: Uint8Array, js: unknown): void { + const dst = js as ArrayBufferView; + new Uint8Array(dst.buffer, dst.byteOffset, dst.byteLength).set(data); +} + +/** `__wbindgen_string_from_utf8_lossy`: the bytes as UTF-8, each malformed sequence as U+FFFD. */ +export function jsUtf8Lossy(data: Uint8Array): string { + return new TextDecoder("utf-8", { ignoreBOM: true }).decode(data); +} diff --git a/ts/runtime/tsconfig.json b/ts/runtime/tsconfig.json index dfc30f28a..887d2c98e 100644 --- a/ts/runtime/tsconfig.json +++ b/ts/runtime/tsconfig.json @@ -12,5 +12,5 @@ "verbatimModuleSyntax": true, "erasableSyntaxOnly": true }, - "files": ["rs.ts", "rs/bytes.ts", "rs/collections.ts", "rs/int.ts", "rs/iter.ts", "rs/rand.ts", "rs/sort.ts", "rs/str.ts", "rs/task.ts", "rs/time.ts"] + "files": ["rs.ts", "rs/bytes.ts", "rs/collections.ts", "rs/int.ts", "rs/iter.ts", "rs/js.ts", "rs/rand.ts", "rs/sort.ts", "rs/str.ts", "rs/task.ts", "rs/time.ts"] } diff --git a/ts/src/census.rs b/ts/src/census.rs index af87c448c..958b34085 100644 --- a/ts/src/census.rs +++ b/ts/src/census.rs @@ -136,6 +136,7 @@ fn ranked<'a, K: Ord>(groups: &'a Groups) -> Vec<(&'a K, &'a Vec<&'a Refusal> /// Code that refusal messages quote as written, rather than a type or path they name. const VERBATIM: &[&str] = &[ "#[track_caller]", + "#[wasm_bindgen]", "()", "+", "-", @@ -180,15 +181,18 @@ const VERBATIM: &[&str] = &[ "Vec::splice", "alloc", "array::from_ref", + "async", "async fn", "become", "bool", "break", "by_ref", + "catch", "clone_from", "collect", "copy_nonoverlapping", "dyn Future", + "experimental_generic_mono", "f64", "flatten", "fmt::Arguments", @@ -198,11 +202,14 @@ const VERBATIM: &[&str] = &[ "format_args!", "if", "if let", + "inline_js", + "instanceof", "let", "let ... else", "match", "next", "offset_from", + "pub", "ref mut", "rs.None", "rs.OuterPending", @@ -212,6 +219,8 @@ const VERBATIM: &[&str] = &[ "skip", "str", "str::as_ptr", + "suspending", + "this", "undefined", "write_bytes", "write_str", diff --git a/ts/src/lower/bindgen/convert.rs b/ts/src/lower/bindgen/convert.rs new file mode 100644 index 000000000..da82eab48 --- /dev/null +++ b/ts/src/lower/bindgen/convert.rs @@ -0,0 +1,333 @@ +//! What wasm-bindgen's JS glue passes for each Rust type at an import, and what it reads +//! back, from the value translation holds: numbers as JS numbers, 64- and 128-bit integers +//! as BigInts, strings as JS strings, `Option`'s `None` as `undefined`, byte slices as +//! views of their bytes, other slices and vectors as typed arrays or arrays, string enums as +//! their strings, and JS values as themselves. What comes back is coerced as the +//! WebAssembly ABI coerces it, then read as wasm-bindgen's `from_abi` reads it. + +use rustc_middle::ty::{self, Ty}; +use rustc_span::Span; + +use super::super::body::Body; +use super::super::library::Std; +use crate::ts::{Expr, Param, Stmt, StmtKind, Type}; + +/// A converted argument, with what writes back what JS wrote into its copy after the call. +pub struct Arg { + pub value: Expr, + pub after: Vec, +} + +/// The typed array JS views a slice of `elem` as, and whether its items are BigInts. +fn typed(elem: Ty<'_>) -> Option<(&'static str, bool)> { + Some(match elem.kind() { + ty::Int(ty::IntTy::I8) => ("Int8Array", false), + ty::Uint(ty::UintTy::U8) => ("Uint8Array", false), + ty::Int(ty::IntTy::I16) => ("Int16Array", false), + ty::Uint(ty::UintTy::U16) => ("Uint16Array", false), + ty::Int(ty::IntTy::I32 | ty::IntTy::Isize) => ("Int32Array", false), + ty::Uint(ty::UintTy::U32 | ty::UintTy::Usize) => ("Uint32Array", false), + ty::Int(ty::IntTy::I64) => ("BigInt64Array", true), + ty::Uint(ty::UintTy::U64) => ("BigUint64Array", true), + ty::Float(ty::FloatTy::F32) => ("Float32Array", false), + ty::Float(ty::FloatTy::F64) => ("Float64Array", false), + _ => return None, + }) +} + +/// Why translation refuses an import's `ty`, which its JS glue converts in a way translation +/// doesn't. +fn refuse(ty: Ty<'_>, at: &str) -> String { + format!("an import's {at} of `{ty}`, which wasm-bindgen converts in a way translation does not") +} + +/// The JS value the glue passes for an argument `value` of `ty`. +pub fn arg<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + ty: Ty<'tcx>, + value: Expr, + span: Span, + out: &mut Vec, +) -> Result { + if let ty::Ref(_, inner, ty::Mutability::Mut) = ty.kind() + && let ty::Slice(elem) = inner.kind() + && let Some((class, big)) = typed(*elem) + && !super::super::byte(*elem) + { + // A copy JS writes into, written back after, as it writes the slice in memory. + let copy = b.fresh("view"); + let made = Expr::rs( + "jsTyped", + vec![global(class), value.clone(), Expr::lit(big.to_string())], + ); + let back = from_lambda(b, *elem, span)?; + let after = + vec![StmtKind::Expr(Expr::rs("jsTypedBack", vec![value, Expr::ident(copy.clone()), back])).at(None)]; + out.push(StmtKind::decl(false, copy.clone(), None, Some(made)).at(None)); + return Ok(Arg { + value: Expr::ident(copy), + after, + }); + } + Ok(Arg { + value: to_js(b, ty, value, span)?, + after: Vec::new(), + }) +} + +/// `global.`, a property of the global object. +pub fn global(name: &str) -> Expr { + Expr::Rs("global".into()).member(name) +} + +/// The JS value of `value`, a `ty`. +pub fn to_js<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr, span: Span) -> Result { + Ok(match ty.kind() { + ty::Bool | ty::Float(_) => value, + ty::Int(i) if !matches!(i, ty::IntTy::I64 | ty::IntTy::I128) => value, + ty::Uint(u) if !matches!(u, ty::UintTy::U64 | ty::UintTy::U128) => value, + ty::Int(_) | ty::Uint(_) => Expr::rs("jsBigInt", vec![value]), + ty::Char => Expr::rs("jsChar", vec![value]), + ty::Tuple(tys) if tys.is_empty() => value, + ty::Ref(_, inner, _) => match inner.kind() { + ty::Str => value, + ty::Slice(elem) if super::super::byte(*elem) => Expr::rs("byteSliceView", vec![value]), + // What JS writes into one is written back only where it is the argument itself. + ty::Slice(_) if matches!(ty.kind(), ty::Ref(_, _, ty::Mutability::Mut)) => { + return Err(refuse(ty, "argument")); + } + ty::Slice(elem) if let Some((class, big)) = typed(*elem) => { + Expr::rs("jsTyped", vec![global(class), value, Expr::lit(big.to_string())]) + } + ty::Slice(elem) => { + let to = to_lambda(b, *elem, span)?; + Expr::rs("jsArray", vec![value, to]) + } + _ if represented(b, *inner) => value, + // A reference to a string enum or a number, which the glue passes as the value. + _ if matches!(ty.kind(), ty::Ref(_, _, ty::Mutability::Not)) && b.t.is_copy(*inner) => { + to_js(b, *inner, value, span)? + } + _ => return Err(refuse(ty, "argument")), + }, + ty::Adt(adt, args) => match b.t.std_of(ty) { + Some(Std::Js) => value, + Some(Std::String) => Expr::rs("jsString", vec![value]), + Some(Std::Option) => { + let inner = args.type_at(0); + let some = b.fresh("some"); + let none = b.t.none(inner); + let converted = to_js(b, inner, Expr::ident(some.clone()), span)?; + // Its value once, then `undefined` for `None`. + Expr::Call( + Box::new(Expr::Lambda( + vec![Param { + name: some.clone(), + ty: Some(b.t.ty(ty, span)), + }], + Box::new(Expr::Cond( + Box::new(Expr::ident(some).binary("===", none)), + Box::new(Expr::undefined()), + Box::new(converted), + )), + )), + vec![value], + ) + } + Some(Std::Vec | Std::Box) => match boxed_slice(b, ty) { + Some(elem) if super::super::byte(elem) => match b.t.std_of(ty) { + Some(Std::Vec) => Expr::rs("jsByteVec", vec![value]), + _ => Expr::rs("byteSliceView", vec![value]).member("slice").call(vec![]), + }, + Some(elem) if let Some((class, big)) = typed(elem) => Expr::rs( + "jsTyped", + vec![global(class), slice_of(b, ty, value), Expr::lit(big.to_string())], + ), + Some(elem) => { + let to = to_lambda(b, elem, span)?; + Expr::rs("jsArray", vec![value, to]) + } + None => return Err(refuse(ty, "argument")), + }, + None if let Some(values) = super::lower::string_enum(b, adt.did()) => { + let names = enum_names(b, adt.did()); + Expr::rs("jsEnum", vec![names, values, value]) + } + _ => return Err(refuse(ty, "argument")), + }, + _ => return Err(refuse(ty, "argument")), + }) +} + +/// The Rust value of `value`, what an import returned, as a `ty`. +pub fn from_js<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr, span: Span) -> Result { + let int = |name: &str| Expr::rs(name, vec![value.clone()]); + Ok(match ty.kind() { + ty::Bool => int("jsToBool"), + ty::Uint(ty::UintTy::U8) => int("jsToU8"), + ty::Int(ty::IntTy::I8) => int("jsToI8"), + ty::Uint(ty::UintTy::U16) => int("jsToU16"), + ty::Int(ty::IntTy::I16) => int("jsToI16"), + ty::Uint(ty::UintTy::U32) => int("jsToU32"), + ty::Int(ty::IntTy::I32) => int("jsToI32"), + ty::Uint(ty::UintTy::Usize) => int("jsToUsize"), + ty::Int(ty::IntTy::Isize) => int("jsToIsize"), + ty::Float(ty::FloatTy::F64) => int("jsToF64"), + ty::Float(ty::FloatTy::F32) => int("jsToF32"), + ty::Int(i) => { + let bits = if *i == ty::IntTy::I64 { 64 } else { 128 }; + let wide = Expr::rs("jsToWide", vec![value, Expr::lit(bits.to_string()), Expr::lit("true")]); + Expr::As(Box::new(wide), b.t.ty(ty, span)) + } + ty::Uint(u) => { + let bits = if *u == ty::UintTy::U64 { 64 } else { 128 }; + let wide = Expr::rs("jsToWide", vec![value, Expr::lit(bits.to_string()), Expr::lit("false")]); + Expr::As(Box::new(wide), b.t.ty(ty, span)) + } + ty::Char => int("jsToChar"), + ty::Tuple(tys) if tys.is_empty() => Expr::undefined(), + ty::Adt(adt, args) => match b.t.std_of(ty) { + Some(Std::Js) => Expr::As(Box::new(value), b.t.ty(ty, span)), + Some(Std::String) => int("jsToString"), + Some(Std::Option) => { + let inner = args.type_at(0); + let some = b.fresh("got"); + let none = b.t.none(inner); + // The JS glue reads an optional `bool` by its truthiness, not through the ABI's `i32`. + let converted = match inner.kind() { + ty::Bool => Expr::Unary("!!", Box::new(Expr::ident(some.clone()))), + _ => from_js(b, inner, Expr::ident(some.clone()), span)?, + }; + // An optional small integer crosses the ABI's `i32` as it is, its `0xFFFFFF` `None`. + let (value, is_none) = match small(inner) { + true => ( + Expr::rs("jsToOptionSmall", vec![value]), + Expr::ident(some.clone()).binary("===", Expr::undefined()), + ), + false => (value, Expr::rs("jsIsNone", vec![Expr::ident(some.clone())])), + }; + let ret = b.t.ty(ty, span); + Expr::Call( + Box::new(Expr::Arrow( + vec![Param { + name: some.clone(), + ty: Some(Type::Builtin("unknown")), + }], + ret, + vec![ + StmtKind::Return(Some(Expr::Cond(Box::new(is_none), Box::new(none), Box::new(converted)))) + .at(None), + ], + )), + vec![value], + ) + } + Some(Std::Vec | Std::Box) => match boxed_slice(b, ty) { + Some(elem) if super::super::byte(elem) => match b.t.std_of(ty) { + Some(Std::Vec) => int("jsToByteVec"), + _ => int("jsToByteSlice"), + }, + Some(elem) => { + let from = from_lambda(b, elem, span)?; + let vec = Expr::rs("jsToVec", vec![value, from]); + match b.t.std_of(ty) { + Some(Std::Vec) => vec, + _ => Expr::rs("vecSlice", vec![vec]), + } + } + None => return Err(refuse(ty, "result")), + }, + None if let Some(values) = super::lower::string_enum(b, adt.did()) => { + let names = enum_names(b, adt.did()); + let ts = b.t.ty(ty, span); + Expr::As(Box::new(Expr::rs("jsToEnum", vec![names, values, value])), ts) + } + _ => return Err(refuse(ty, "result")), + }, + _ => return Err(refuse(ty, "result")), + }) +} + +/// Whether the JS glue converts an import's result of `ty`, so that a `catch` import catches +/// what converting it throws, rather than the WebAssembly ABI, after the glue has returned. +pub fn converted_in_glue<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind() { + // A 128-bit integer, which the glue splits into halves, and no other number. + ty::Bool | ty::Float(_) | ty::Int(_) | ty::Uint(_) => { + matches!(ty.kind(), ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128)) + } + ty::Tuple(tys) => !tys.is_empty(), + ty::Adt(_, args) if b.t.std_of(ty) == Some(Std::Option) => !small(args.type_at(0)), + _ => b.t.std_of(ty) != Some(Std::Js), + } +} + +/// Whether `ty` is an integer the ABI's `i32` carries as an `Option`'s payload: the JS glue +/// passes its value as is, and its `0xFFFFFF` is `None`. +fn small(ty: Ty<'_>) -> bool { + matches!( + ty.kind(), + ty::Int(ty::IntTy::I8 | ty::IntTy::I16) | ty::Uint(ty::UintTy::U8 | ty::UintTy::U16) + ) +} + +/// `(v) => `, for each item of a slice or vector of `elem`. +fn to_lambda<'tcx>(b: &mut Body<'_, '_, 'tcx>, elem: Ty<'tcx>, span: Span) -> Result { + let item = b.fresh("item"); + let ty = b.t.ty(elem, span); + let body = to_js(b, elem, Expr::ident(item.clone()), span)?; + Ok(Expr::Lambda( + vec![Param { + name: item, + ty: Some(ty), + }], + Box::new(body), + )) +} + +/// `(v: unknown) => `, for each item an import returns of `elem`. +pub fn from_lambda<'tcx>(b: &mut Body<'_, '_, 'tcx>, elem: Ty<'tcx>, span: Span) -> Result { + let item = b.fresh("item"); + let body = from_js(b, elem, Expr::ident(item.clone()), span)?; + Ok(Expr::Lambda( + vec![Param { + name: item, + ty: Some(Type::Builtin("unknown")), + }], + Box::new(body), + )) +} + +/// The slice element type of `ty`, a `Vec` or a `Box<[T]>`. +fn boxed_slice<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>) -> Option> { + let ty::Adt(_, args) = ty.kind() else { return None }; + match b.t.std_of(ty)? { + Std::Vec => Some(args.type_at(0)), + Std::Box => match args.type_at(0).kind() { + ty::Slice(elem) => Some(*elem), + _ => None, + }, + _ => None, + } +} + +/// The `rs.Slice` of `value`, a `Vec` or a `Box<[T]>` of `ty`. +fn slice_of<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr) -> Expr { + match b.t.std_of(ty) { + Some(Std::Vec) => Expr::rs("vecSlice", vec![value]), + _ => value, + } +} + +/// Whether `ty` is JS-represented, seen through references. +pub fn represented<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>) -> bool { + b.t.std_of(ty.peel_refs()) == Some(Std::Js) +} + +/// The names translation holds a string enum's variants by, in order. +fn enum_names(b: &Body<'_, '_, '_>, did: rustc_hir::def_id::DefId) -> Expr { + let names = (b.t.tcx.adt_def(did).variants().iter()) + .map(|v| Expr::Str(v.name.to_string())) + .collect(); + Expr::Array(names) +} diff --git a/ts/src/lower/bindgen/decode.rs b/ts/src/lower/bindgen/decode.rs new file mode 100644 index 000000000..03bae46f3 --- /dev/null +++ b/ts/src/lower/bindgen/decode.rs @@ -0,0 +1,482 @@ +//! The programs `#[wasm_bindgen]` encodes, decoded as the wasm-bindgen CLI decodes them. +//! +//! Vendored from wasm-bindgen's `crates/cli-support/src/decode.rs` at 0.2.130 +//! (c93b8ae848d4d9e6beaeaf9516e18a7cadd818a2), MIT OR Apache-2.0, whose decoder is +//! private. Its field list is `shared_api!`'s from `crates/shared/src/lib.rs` at that +//! revision, with each field another supported schema lacks gated by the schema that +//! added it. Decoding is positional, so a schema is supported only once its gates are +//! here. A malformed program is an error, not a panic. + +use std::str; + +/// A schema whose programs decode here, by its `SCHEMA_VERSION`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Schema { + /// wasm-bindgen 0.2.128 and 0.2.129. + V0_2_128, + /// wasm-bindgen 0.2.130. + V0_2_130, +} + +impl Schema { + /// Every supported schema, oldest first. + pub const ALL: [Schema; 2] = [Schema::V0_2_128, Schema::V0_2_130]; + + pub fn of(version: &str) -> Option { + Self::ALL.into_iter().find(|s| s.version() == version) + } + + pub fn version(self) -> &'static str { + match self { + Schema::V0_2_128 => "0.2.128", + Schema::V0_2_130 => "0.2.130", + } + } +} + +/// Why a program didn't decode. +pub type Error = String; + +/// The bytes of a program left to decode, in its schema. +pub struct Data<'src> { + bytes: &'src [u8], + schema: Schema, +} + +impl<'src> Data<'src> { + fn byte(&mut self) -> Result { + let (&b, rest) = self.bytes.split_first().ok_or("a program ends early")?; + self.bytes = rest; + Ok(b) + } +} + +pub trait Decode<'src>: Sized { + fn decode(data: &mut Data<'src>) -> Result; +} + +/// Decode `bytes`, a whole program of `schema`. +pub fn program(bytes: &[u8], schema: Schema) -> Result, Error> { + let mut data = Data { bytes, schema }; + let program = Program::decode(&mut data)?; + match data.bytes.is_empty() { + true => Ok(program), + false => Err("a program has bytes past its end".into()), + } +} + +/// A string the macro wrote, or that an expression it wrote evaluated to. +pub struct LitOrExpr<'src>(pub &'src str); + +impl<'src> Decode<'src> for LitOrExpr<'src> { + fn decode(data: &mut Data<'src>) -> Result { + Ok(Self(<&'src str>::decode(data)?)) + } +} + +impl<'src> Decode<'src> for bool { + fn decode(data: &mut Data<'src>) -> Result { + Ok(data.byte()? != 0) + } +} + +impl<'src> Decode<'src> for u32 { + fn decode(data: &mut Data<'src>) -> Result { + let mut cur = 0u32; + let mut offset = 0; + loop { + let byte = data.byte()?; + let bits = u32::from(byte & 0x7f); + cur |= bits + .checked_shl(offset) + .filter(|_| offset < 32) + .ok_or("a number past 32 bits")?; + if byte & 0x80 == 0 { + break Ok(cur); + } + offset += 7; + } + } +} + +impl<'src> Decode<'src> for &'src str { + fn decode(data: &mut Data<'src>) -> Result { + let n = u32::decode(data)? as usize; + if n > data.bytes.len() { + return Err("a string past the program's end".into()); + } + let (a, b) = data.bytes.split_at(n); + data.bytes = b; + str::from_utf8(a).map_err(|_| "a string that is not UTF-8".into()) + } +} + +impl<'src> Decode<'src> for String { + fn decode(data: &mut Data<'src>) -> Result { + Ok(<&'src str>::decode(data)?.to_string()) + } +} + +impl<'src, T: Decode<'src>> Decode<'src> for Vec { + fn decode(data: &mut Data<'src>) -> Result { + let n = u32::decode(data)?; + // Each item takes at least a byte, which bounds what a length can make us hold. + let mut v = Vec::with_capacity((n as usize).min(data.bytes.len())); + for _ in 0..n { + v.push(T::decode(data)?); + } + Ok(v) + } +} + +impl<'src, T: Decode<'src>> Decode<'src> for Option { + fn decode(data: &mut Data<'src>) -> Result { + match data.byte()? { + 0 => Ok(None), + 1 => Ok(Some(T::decode(data)?)), + _ => Err("an option tag past 1".into()), + } + } +} + +/// A struct whose fields decode in order. A field marked `since` is in programs of that +/// schema and later, and `None` in earlier ones. +macro_rules! decode_struct { + ($name:ident ($($lt:tt)*) { $($(#[since($since:ident)])? $field:ident: $ty:ty,)* }) => { + #[allow(dead_code, reason = "decoded to read what follows, whether or not translation reads it")] + pub struct $name <$($lt)*> { + $(pub $field: decode_struct!(@ty $($since)? $ty),)* + } + + impl<'a> Decode<'a> for $name <$($lt)*> { + fn decode(data: &mut Data<'a>) -> Result { + Ok($name { + $($field: decode_struct!(@field data $($since)? $ty),)* + }) + } + } + }; + (@ty $since:ident $ty:ty) => (Option<$ty>); + (@ty $ty:ty) => ($ty); + (@field $data:ident $since:ident $ty:ty) => ( + match $data.schema >= Schema::$since { + true => Some(<$ty>::decode($data)?), + false => None, + } + ); + (@field $data:ident $ty:ty) => (<$ty>::decode($data)?); +} + +/// An enum whose variants are numbered in order. +macro_rules! decode_enum { + ($name:ident ($($lt:tt)*) { $($variant:ident $(($ty:ty))?,)* }) => { + #[allow(dead_code, reason = "decoded to read what follows, whether or not translation reads it")] + pub enum $name <$($lt)*> { + $($variant $(($ty))?,)* + } + + impl<'a> Decode<'a> for $name <$($lt)*> { + fn decode(data: &mut Data<'a>) -> Result { + let tag = data.byte()?; + let mut n = 0u8; + $( + if tag == n { + return Ok($name::$variant $((<$ty>::decode(data)?))?); + } + n += 1; + )* + let _ = n; + Err(format!("variant {tag} of `{}`, which has fewer", stringify!($name))) + } + } + }; +} + +decode_struct!(Program('a) { + exports: Vec>, + enums: Vec>, + imports: Vec>, + structs: Vec>, + typescript_custom_sections: Vec>, + local_modules: Vec>, + inline_js: Vec<&'a str>, + unique_crate_identifier: &'a str, + #[since(V0_2_130)] + module_path: LitOrExpr<'a>, + package_json: Option<&'a str>, + linked_modules: Vec>, +}); + +decode_struct!(Import('a) { + module: Option>, + js_namespace: Option>, + reexport: Option, + generate_typescript: bool, + kind: ImportKind<'a>, +}); + +decode_struct!(LinkedModule('a) { + module: ImportModule<'a>, + link_function_name: &'a str, +}); + +decode_enum!(ImportModule('a) { + Named(&'a str), + RawNamed(&'a str), + Inline(u32), +}); + +decode_enum!(ImportKind('a) { + Function(ImportFunction<'a>), + Static(ImportStatic<'a>), + String(ImportString<'a>), + Type(ImportType<'a>), + Enum(StringEnum<'a>), + DynamicUnion(DynamicUnion<'a>), +}); + +decode_struct!(ImportFunction('a) { + shim: &'a str, + catch: bool, + variadic: bool, + assert_no_shim: bool, + suspending: bool, + method: Option>, + structural: bool, + function: Function<'a>, + generic_per_mono: bool, +}); + +decode_struct!(MethodData('a) { + class: &'a str, + kind: MethodKind<'a>, +}); + +decode_enum!(MethodKind('a) { + Constructor, + Operation(Operation<'a>), +}); + +decode_struct!(Operation('a) { + is_static: bool, + kind: OperationKind<'a>, +}); + +decode_enum!(OperationKind('a) { + Regular, + RegularThis, + Getter(&'a str), + Setter(&'a str), + IndexingGetter, + IndexingSetter, + IndexingDeleter, +}); + +decode_struct!(ImportStatic('a) { + name: &'a str, + shim: &'a str, +}); + +decode_struct!(ImportString('a) { + shim: &'a str, + string: &'a str, +}); + +decode_struct!(ImportType('a) { + name: &'a str, + instanceof_shim: &'a str, + vendor_prefixes: Vec<&'a str>, +}); + +decode_struct!(StringEnum('a) { + name: &'a str, + variant_values: Vec<&'a str>, + comments: Vec<&'a str>, + generate_typescript: bool, + private: bool, + js_namespace: Option>, +}); + +decode_enum!(StartKind() { + None, + Public, + Private, +}); + +decode_struct!(DynamicUnion('a) { + name: &'a str, + variant_strings: Vec<&'a str>, + variant_type_cnt: u32, + comments: Vec<&'a str>, + generate_typescript: bool, + private: bool, + fallback: bool, +}); + +decode_struct!(Export('a) { + class: Option<&'a str>, + comments: Vec<&'a str>, + consumed: bool, + function: Function<'a>, + js_namespace: Option>, + method_kind: MethodKind<'a>, + start: StartKind, +}); + +decode_struct!(Enum('a) { + name: &'a str, + signed: bool, + variants: Vec>, + comments: Vec<&'a str>, + generate_typescript: bool, + js_namespace: Option>, + private: bool, +}); + +decode_struct!(EnumVariant('a) { + name: &'a str, + value: u32, + comments: Vec<&'a str>, +}); + +decode_struct!(Function('a) { + args: Vec>, + asyncness: bool, + jspi: bool, + name: &'a str, + generate_typescript: bool, + generate_jsdoc: bool, + variadic: bool, + ret_ty_override: Option<&'a str>, + ret_desc: Option<&'a str>, +}); + +decode_struct!(FunctionArgumentData('a) { + name: String, + ty_override: Option<&'a str>, + optional: bool, + desc: Option<&'a str>, +}); + +decode_struct!(Struct('a) { + name: &'a str, + fields: Vec>, + comments: Vec<&'a str>, + is_inspectable: bool, + generate_typescript: bool, + js_namespace: Option>, + private: bool, + extends: Option<&'a str>, + extends_js_class: Option<&'a str>, + extends_js_namespace: Option>, +}); + +decode_struct!(StructField('a) { + name: &'a str, + readonly: bool, + comments: Vec<&'a str>, + generate_typescript: bool, + generate_jsdoc: bool, +}); + +decode_struct!(LocalModule('a) { + identifier: &'a str, + contents: &'a str, + linked_module: bool, +}); + +/// Each program in a `__wasm_bindgen_unstable` static's bytes, with its schema: a +/// length-prefixed JSON prefix naming the schema, then a length-prefixed program, as many +/// times as the static holds. A schema other than the supported ones is an error naming +/// them, since its programs would decode wrongly. +pub fn section(mut bytes: &[u8]) -> Result, Error> { + fn chunk<'a>(bytes: &mut &'a [u8]) -> Result<&'a [u8], Error> { + let (len, rest) = bytes.split_first_chunk::<4>().ok_or("a section ends inside a length")?; + let len = u32::from_le_bytes(*len) as usize; + if len > rest.len() { + return Err("a section's length past its end".into()); + } + let (chunk, rest) = rest.split_at(len); + *bytes = rest; + Ok(chunk) + } + let mut programs = Vec::new(); + while !bytes.is_empty() { + let prefix = str::from_utf8(chunk(&mut bytes)?).map_err(|_| "a schema prefix that is not UTF-8")?; + let version = prefix + .split_once("\"schema_version\":\"") + .and_then(|(_, rest)| rest.split_once('"')) + .map(|(version, _)| version) + .ok_or_else(|| format!("a schema prefix without its version: {prefix}"))?; + let schema = Schema::of(version).ok_or_else(|| { + let supported: Vec<_> = Schema::ALL.iter().map(|s| s.version()).collect(); + format!( + "wasm-bindgen schema {version}, while translation reads {}", + supported.join(" and ") + ) + })?; + programs.push((schema, chunk(&mut bytes)?)); + } + Ok(programs) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A section of one program of `schema_version`, holding `program`. + fn section_of(schema_version: &str, program: &[u8]) -> Vec { + let prefix = format!("{{\"schema_version\":\"{schema_version}\",\"version\":\"{schema_version}\"}}"); + let mut bytes = Vec::new(); + bytes.extend((prefix.len() as u32).to_le_bytes()); + bytes.extend(prefix.as_bytes()); + bytes.extend((program.len() as u32).to_le_bytes()); + bytes.extend(program); + bytes + } + + /// An empty program, as each schema encodes it. + fn empty(schema: Schema) -> Vec { + // No exports, enums, imports, structs, sections, modules, or snippets, then the + // crate's identifier. + let mut program = vec![0, 0, 0, 0, 0, 0, 0, 3, b'a', b'-', b'b']; + if schema >= Schema::V0_2_130 { + program.extend([4, b'a', b':', b':', b'b']); + } + // No `package.json` and no linked modules. + program.extend([0, 0]); + program + } + + #[test] + fn each_schema() { + for schema in Schema::ALL { + let bytes = section_of(schema.version(), &empty(schema)); + let programs = section(&bytes).unwrap(); + let [(got, data)] = programs[..] else { + panic!("one program") + }; + assert_eq!(got, schema); + let program = super::program(data, schema).unwrap(); + assert_eq!(program.unique_crate_identifier, "a-b"); + assert_eq!( + program.module_path.map(|p| p.0), + (schema >= Schema::V0_2_130).then_some("a::b") + ); + } + // One schema's program is malformed in the other's. + assert!(super::program(&empty(Schema::V0_2_130), Schema::V0_2_128).is_err()); + assert!(super::program(&empty(Schema::V0_2_128), Schema::V0_2_130).is_err()); + } + + #[test] + fn unsupported_schema() { + for version in ["0.2.122", "0.2.129", "0.2.131"] { + let err = section(§ion_of(version, &[])).err().unwrap(); + assert_eq!( + err, + format!("wasm-bindgen schema {version}, while translation reads 0.2.128 and 0.2.130") + ); + } + } +} diff --git a/ts/src/lower/bindgen/lower.rs b/ts/src/lower/bindgen/lower.rs new file mode 100644 index 000000000..8b4f4ccdd --- /dev/null +++ b/ts/src/lower/bindgen/lower.rs @@ -0,0 +1,526 @@ +//! A call of a function `#[wasm_bindgen]` made around an import, replaced whole by the JS +//! call the import stands for, as the wasm-bindgen CLI's JS glue makes it, and the other +//! glue of wasm-bindgen that is the identity once its types are JS values. + +use std::rc::Rc; + +use rustc_hir::def_id::DefId; +use rustc_middle::ty::{self, Instance, Ty}; +use rustc_span::Span; + +use super::convert::{self, global}; +use super::{Glue, Import, Kind, MethodKind, Module, Op, PLACEHOLDER}; +use crate::lower::body::Body; +use crate::lower::library::Std; +use crate::ts::{Expr, Key, Param, Stmt, StmtKind, Type}; + +/// A call of `instance`, which is wasm-bindgen's `glue`, given `args`. +pub fn call<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + instance: Instance<'tcx>, + glue: Glue<'tcx>, + mut args: Vec, + span: Span, + out: &mut Vec, +) -> Expr { + let tcx = b.t.tcx; + let path = tcx.def_path_str(instance.def_id()); + let lowered = match glue { + Glue::Identity => Ok(args.swap_remove(0)), + Glue::Nothing => Ok(Expr::undefined()), + Glue::Force => Ok(args.swap_remove(0).member("force").call(Vec::new())), + Glue::Throw { error } => { + let value = args.swap_remove(0); + let thrown = match error { + true => Expr::New(Box::new(global("Error")), vec![value]), + false => value, + }; + Ok(Expr::rs("jsThrow", vec![thrown])) + } + Glue::Cast { from, to } => { + convert::to_js(b, from, args.swap_remove(0), span).and_then(|js| convert::from_js(b, to, js, span)) + } + Glue::Import(Err(why)) => Err(why), + Glue::Import(Ok(import)) => lower(b, instance, &import, args, span, out), + }; + match lowered { + Ok(value) => value, + Err(why) => b.err(span, format!("`{path}`, of wasm-bindgen: {why}")), + } +} + +/// A constant `JsValue` of a slot wasm-bindgen reserves, `idx`: `undefined`, `null`, +/// `true`, or `false`. +pub fn constant(idx: u32) -> Option { + // `JSIDX_OFFSET` and the reserved slots after it, as wasm-bindgen's `__rt` numbers them. + Some(Expr::lit(match idx.checked_sub(1024)? { + 0 => "undefined", + 1 => "null", + 2 => "true", + 3 => "false", + _ => return None, + })) +} + +fn lower<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + instance: Instance<'tcx>, + import: &Rc, + args: Vec, + span: Span, + out: &mut Vec, +) -> Result { + let is_intrinsic = match &import.module { + None => false, + Some(Module::Raw(module)) if module == PLACEHOLDER => true, + Some(Module::Raw(module)) => { + return Err(format!( + "its JS module `{module}`, custom JS, which translation refuses" + )); + } + // The program names it by the crate's name and a hash of where it is, then its path. + Some(Module::Named(module)) => { + let file = module.split_once('/').map_or(module.as_str(), |(_, file)| file); + return Err(format!("its JS file `{file}`, custom JS, which translation refuses")); + } + Some(Module::Inline) => return Err("its `inline_js`, custom JS, which translation refuses".into()), + }; + if let Kind::Function(f) = &import.kind { + if f.suspending { + return Err("`suspending`, which suspends the WASM stack on a promise, as no JS call can".into()); + } + if f.generic_per_mono { + return Err( + "`experimental_generic_mono`, which the CLI makes JS of per instance, as translation does not".into(), + ); + } + if f.asyncness || f.jspi { + return Err("an `async` import, whose promise translation does not yet await".into()); + } + } + let sig = b.t.sig(instance.def_id(), instance.args); + let inputs = sig.inputs().to_vec(); + let output = sig.output(); + let (catch, variadic) = match &import.kind { + Kind::Function(f) => (f.catch, f.variadic), + _ => (false, false), + }; + // What the call returns before `catch` wraps it in a `Result`. + let ret = match catch { + true => result_ok(b, output).ok_or("`catch` on an import that returns no `Result`")?, + false => output, + }; + let mut values = Vec::with_capacity(args.len()); + let mut after = Vec::new(); + for (ty, value) in inputs.iter().zip(args) { + let arg = convert::arg(b, *ty, value, span, out)?; + values.push(arg.value); + after.extend(arg.after); + } + let call = match is_intrinsic { + true => { + let Kind::Function(f) = &import.kind else { + return Err("an intrinsic that is not a function".into()); + }; + intrinsic(&f.name, values) + .ok_or_else(|| format!("wasm-bindgen's intrinsic `{}`, which translation refuses", f.name))? + } + false => shape(b, import, &inputs, values, variadic, output)?, + }; + let value = match catch { + true => { + let ty::Adt(_, result_args) = output.kind() else { + unreachable!("a `Result`") + }; + let err = result_args.type_at(1); + if !convert::represented(b, err) { + return Err(format!("`catch` into `{err}`, which is not a JS value")); + } + let from = convert::from_lambda(b, ret, span)?; + let glue = Expr::lit(convert::converted_in_glue(b, ret).to_string()); + let caught = Expr::rs("jsCatch", vec![Expr::Lambda(Vec::new(), Box::new(call)), from, glue]); + let ts = b.t.ty(output, span); + Expr::As(Box::new(caught), ts) + } + false if ret.is_unit() || ret.is_never() => { + out.push(StmtKind::Expr(call).at(None)); + out.extend(after); + return Ok(Expr::undefined()); + } + false if after.is_empty() => convert::from_js(b, ret, call, span)?, + false => { + let got = b.fresh("got"); + out.push(StmtKind::decl(false, got.clone(), None, Some(call)).at(None)); + out.extend(after); + return convert::from_js(b, ret, Expr::ident(got), span); + } + }; + if !after.is_empty() { + let got = b.fresh("got"); + out.push(StmtKind::decl(false, got.clone(), None, Some(value)).at(None)); + out.extend(after); + return Ok(Expr::ident(got)); + } + Ok(value) +} + +/// The `Ok` type of `ty`, a `Result`. +fn result_ok<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>) -> Option> { + match (b.t.std_of(ty), ty.kind()) { + (Some(Std::Result), ty::Adt(_, args)) => Some(args.type_at(0)), + _ => None, + } +} + +/// The JS call of `import` with `values`, as the CLI's glue makes it. +fn shape<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + import: &Import, + inputs: &[Ty<'tcx>], + mut values: Vec, + variadic: bool, + output: Ty<'tcx>, +) -> Result { + let _ = inputs; + Ok(match &import.kind { + Kind::String(string) => Expr::Str(string.clone()), + Kind::Static { name } => { + let optional = b.t.std_of(output) == Some(Std::Option); + let path = resolve(import, name)?; + match optional { + true => Expr::rs("jsOptionalPath", path_parts(&path)), + false => path.expr(), + } + } + Kind::Instanceof { class } => { + let class = resolve(import, class)?.expr(); + let value = values.pop().ok_or("an `instanceof` check of nothing")?; + Expr::rs("jsInstanceof", vec![value, Expr::Lambda(Vec::new(), Box::new(class))]) + } + Kind::Function(f) => { + let Some(method) = &f.method else { + let callee = resolve(import, &f.name)?.expr(); + return Ok(apply(callee, None, values, variadic)); + }; + let class = || resolve(import, &method.class).map(|c| c.expr()); + match &method.kind { + MethodKind::Constructor => match variadic { + true => { + let (last, rest) = split_last(values)?; + global("Reflect") + .member("construct") + .call(vec![class()?, spread(rest, last)]) + } + false => Expr::New(Box::new(class()?), values), + }, + MethodKind::Operation { is_static, op } => { + let is_static = *is_static; + let receiver = |values: &mut Vec| match values.is_empty() { + true => Err("a method without its receiver".to_string()), + false => Ok(js_any(values.remove(0))), + }; + match op { + Op::RegularThis => return Err("`this` of an export, on an import".into()), + Op::Regular if is_static => apply(class()?, Some(&f.name), values, variadic), + Op::Regular if f.structural => { + let this = receiver(&mut values)?; + apply(this, Some(&f.name), values, variadic) + } + Op::Regular => { + let method = js_any(path(class()?.member("prototype"), &f.name)); + let this = receiver(&mut values)?; + match variadic { + true => { + let (last, rest) = split_last(values)?; + method.member("apply").call(vec![this, spread(rest, last)]) + } + false => method.member("call").call([vec![this], values].concat()), + } + } + Op::Getter(field) if f.structural => match is_static { + true => path(class()?, field), + false => path(receiver(&mut values)?, field), + }, + Op::Setter(field) if f.structural => { + let value = values.pop().ok_or("a setter without its value")?; + let target = match is_static { + true => path(class()?, field), + false => path(receiver(&mut values)?, field), + }; + Expr::Assign(Box::new(target), Box::new(value)) + } + Op::Getter(field) | Op::Setter(field) => { + let anchor = match is_static { + true => class()?, + false => class()?.member("prototype"), + }; + let which = if matches!(op, Op::Getter(_)) { "get" } else { "set" }; + let descriptor = Expr::rs("jsDescriptor", vec![anchor, Expr::Str(field.clone())]); + js_any(descriptor.member(which)).member("call").call(values) + } + Op::IndexingGetter | Op::IndexingSetter | Op::IndexingDeleter if !f.structural => { + return Err("a non-structural indexing operation, which the CLI refuses".into()); + } + Op::IndexingGetter => { + let target = match is_static { + true => class()?, + false => receiver(&mut values)?, + }; + let index = values.pop().ok_or("an index of nothing")?; + target.index(index) + } + Op::IndexingSetter => { + let target = match is_static { + true => class()?, + false => receiver(&mut values)?, + }; + let value = values.pop().ok_or("an indexing setter without its value")?; + let index = values.pop().ok_or("an index of nothing")?; + Expr::Assign(Box::new(target.index(index)), Box::new(value)) + } + Op::IndexingDeleter => { + let target = match is_static { + true => class()?, + false => receiver(&mut values)?, + }; + let index = values.pop().ok_or("an index of nothing")?; + Expr::Unary("delete ", Box::new(target.index(index))) + } + } + } + } + } + }) +} + +/// `f(...)` or `obj.name(...)`, its last value spread if `variadic`. +fn apply(target: Expr, name: Option<&str>, values: Vec, variadic: bool) -> Expr { + let target = js_any(target); + let callee = match name { + Some(name) => path(target, name), + None => target, + }; + if !variadic { + return callee.call(values); + } + let Ok((last, rest)) = split_last(values) else { + return callee.call(Vec::new()); + }; + // As a spread call: what it is a method of read once, the function read from it, and + // then the arguments spread, a namespace's function called on its namespace. + match callee { + Expr::Member(base, Key::Name(key)) => { + Expr::rs("jsSpreadMethod", vec![*base, Expr::Str(key), Expr::Array(rest), last]) + } + Expr::Index(base, key) => Expr::rs("jsSpreadMethod", vec![*base, *key, Expr::Array(rest), last]), + callee => Expr::rs("jsSpreadCall", vec![callee, Expr::undefined(), Expr::Array(rest), last]), + } +} + +fn split_last(mut values: Vec) -> Result<(Expr, Vec), String> { + let last = values.pop().ok_or("a variadic import without arguments")?; + Ok((last, values)) +} + +/// `[...rest, ...last]`, the arguments of a variadic call: `last` by its iterator, as a +/// spread reads it, which throws where it has none. +fn spread(rest: Vec, last: Expr) -> Expr { + let last = Expr::rs("jsSpread", vec![last]); + match rest.is_empty() { + true => last, + false => Expr::Array(rest).member("concat").call(vec![last]), + } +} + +/// `value` as TypeScript's `any`, which a JS value's property is read from: as it is where +/// it is already a global's, of `rs.global`, or `rs.jsAny`'s. +fn js_any(value: Expr) -> Expr { + fn typed_any(e: &Expr) -> bool { + match e { + Expr::Rs(name) => name == "global", + Expr::Member(base, _) | Expr::Index(base, _) => typed_any(base), + Expr::Call(f, _) => { + matches!(&**f, Expr::Rs(name) if ["jsAny", "jsGlobal", "jsVendored"].contains(&name.as_str())) + } + _ => false, + } + } + match typed_any(&value) { + true => value, + false => Expr::rs("jsAny", vec![value]), + } +} + +/// `base` then each of `name`'s properties, as the CLI reads a dotted JS name. +fn path(base: Expr, name: &str) -> Expr { + if let Some(inner) = name.strip_prefix('[').and_then(|n| n.strip_suffix(']')) { + let mut key = inner.split('.'); + let mut index = global(key.next().unwrap_or_default()); + for part in key { + index = index.member(part); + } + return base.index(index); + } + name.split('.') + .filter(|part| !part.is_empty()) + .fold(base, |base, part| match is_ident(part) { + true => base.member(part), + false => base.index(Expr::Str(part.into())), + }) +} + +fn is_ident(name: &str) -> bool { + let mut chars = name.chars(); + chars + .next() + .is_some_and(|c| c.is_ascii_alphabetic() || c == '_' || c == '$') + && chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$') +} + +/// Where a global an import names is: under the global object by its namespace, or the +/// first of its vendor-prefixed names that is defined. +enum Path { + Vendored(String, Vec), + Global(Vec), +} + +impl Path { + fn expr(&self) -> Expr { + match self { + Path::Vendored(name, prefixes) => Expr::rs( + "jsVendored", + vec![ + Expr::Str(name.clone()), + Expr::Array(prefixes.iter().map(|p| Expr::Str(p.clone())).collect()), + ], + ), + Path::Global(parts) => { + // A global the JS glue names as an identifier, which throws where none is defined. + let (first, rest) = parts.split_first().expect("a global's name"); + path(Expr::rs("jsGlobal", vec![Expr::Str(first.clone())]), &rest.join(".")) + } + } + } +} + +/// `rs.jsOptionalPath`'s arguments for `path`: the global's root, then each property. +fn path_parts(path: &Path) -> Vec { + match path { + Path::Vendored(..) => vec![path.expr()], + Path::Global(parts) => parts.iter().map(|p| Expr::Str(p.clone())).collect(), + } +} + +/// Where `item` is, as the CLI's `determine_import` resolves it. +fn resolve(import: &Import, item: &str) -> Result { + if !import.vendor_prefixes.is_empty() { + if import.namespace.is_some() { + return Err("vendor prefixes through a namespace, which the CLI refuses".into()); + } + return Ok(Path::Vendored(item.into(), import.vendor_prefixes.clone())); + } + let mut parts = import.namespace.clone().unwrap_or_default(); + parts.extend(item.split('.').map(str::to_string)); + match parts.is_empty() || parts.iter().any(String::is_empty) { + true => Err(format!("the JS name `{item}`, which names no global")), + false => Ok(Path::Global(parts)), + } +} + +/// The JS body of wasm-bindgen's intrinsic `name` of `args`, as the CLI gives it, if +/// translation has one: what reads the WASM instance, such as its memory, has none. +fn intrinsic(name: &str, mut args: Vec) -> Option { + let any = |e: Expr| Expr::As(Box::new(e), Type::Builtin("any")); + let one = |args: &mut Vec| args.pop(); + let typeof_is = |v: Expr, t: &str| Expr::Unary("typeof ", Box::new(v)).binary("===", Expr::Str(t.into())); + let binary = |args: &mut Vec, op: &'static str| { + let b = args.pop()?; + let a = args.pop()?; + Some(any(a).binary(op, any(b))) + }; + let got = |v: Expr, t: &str| { + // The value where it is of type `t`, else `undefined`, for an `Option` to read. + let checked = Expr::Lambda( + vec![Param { + name: "v".into(), + ty: Some(Type::Builtin("unknown")), + }], + Box::new(Expr::Cond( + Box::new(typeof_is(Expr::ident("v"), t)), + Box::new(Expr::ident("v")), + Box::new(Expr::undefined()), + )), + ); + checked.call(vec![v]) + }; + Some(match name { + "__wbindgen_is_null" => one(&mut args)?.binary("===", Expr::lit("null")), + "__wbindgen_is_undefined" => one(&mut args)?.binary("===", Expr::undefined()), + "__wbindgen_is_null_or_undefined" => Expr::rs("jsIsNone", vec![one(&mut args)?]), + "__wbindgen_is_symbol" => typeof_is(one(&mut args)?, "symbol"), + "__wbindgen_is_object" => Expr::rs("jsIsObject", vec![one(&mut args)?]), + "__wbindgen_is_function" => typeof_is(one(&mut args)?, "function"), + "__wbindgen_is_string" => typeof_is(one(&mut args)?, "string"), + "__wbindgen_is_bigint" => typeof_is(one(&mut args)?, "bigint"), + "__wbindgen_typeof" => Expr::Unary("typeof ", Box::new(one(&mut args)?)), + "__wbindgen_in" => binary(&mut args, "in")?, + "__wbindgen_is_falsy" => Expr::Unary("!", Box::new(one(&mut args)?)), + "__wbindgen_try_into_number" => Expr::rs("jsTryIntoNumber", vec![one(&mut args)?]), + "__wbindgen_neg" => Expr::Unary("-", Box::new(any(one(&mut args)?))), + "__wbindgen_bit_and" => binary(&mut args, "&")?, + "__wbindgen_bit_or" => binary(&mut args, "|")?, + "__wbindgen_bit_xor" => binary(&mut args, "^")?, + "__wbindgen_bit_not" => Expr::Unary("~", Box::new(any(one(&mut args)?))), + "__wbindgen_shl" => binary(&mut args, "<<")?, + "__wbindgen_shr" => binary(&mut args, ">>")?, + "__wbindgen_unsigned_shr" => binary(&mut args, ">>>")?, + "__wbindgen_add" => binary(&mut args, "+")?, + "__wbindgen_sub" => binary(&mut args, "-")?, + "__wbindgen_div" => binary(&mut args, "/")?, + "__wbindgen_checked_div" => { + let b = args.pop()?; + Expr::rs("jsCheckedDiv", vec![args.pop()?, b]) + } + "__wbindgen_mul" => binary(&mut args, "*")?, + "__wbindgen_rem" => binary(&mut args, "%")?, + "__wbindgen_pow" => binary(&mut args, "**")?, + "__wbindgen_lt" => binary(&mut args, "<")?, + "__wbindgen_le" => binary(&mut args, "<=")?, + "__wbindgen_ge" => binary(&mut args, ">=")?, + "__wbindgen_gt" => binary(&mut args, ">")?, + "__wbindgen_number_get" => got(one(&mut args)?, "number"), + "__wbindgen_boolean_get" => got(one(&mut args)?, "boolean"), + "__wbindgen_string_get" => got(one(&mut args)?, "string"), + "__wbindgen_bigint_get_as_i64" => got(one(&mut args)?, "bigint"), + "__wbindgen_string_from_utf8_lossy" => Expr::rs("jsUtf8Lossy", vec![one(&mut args)?]), + "__wbindgen_debug_string" => Expr::rs("jsDebugString", vec![one(&mut args)?]), + "__wbindgen_throw" => { + let error = Expr::New(Box::new(global("Error")), vec![one(&mut args)?]); + Expr::rs("jsThrow", vec![error]) + } + "__wbindgen_rethrow" => Expr::rs("jsThrow", vec![one(&mut args)?]), + "__wbindgen_jsval_eq" => { + let b = args.pop()?; + args.pop()?.binary("===", b) + } + "__wbindgen_jsval_loose_eq" => binary(&mut args, "==")?, + "__wbindgen_copy_to_typed_array" => { + let js = args.pop()?; + Expr::rs("jsCopyToTypedArray", vec![args.pop()?, js]) + } + _ => return None, + }) +} + +/// The string enum `did` is, as the JS strings of its variants, if a program declares it. +pub fn string_enum(b: &Body<'_, '_, '_>, did: DefId) -> Option { + let tcx = b.t.tcx; + let adt = tcx.adt_def(did); + let last = adt.variants().iter().last()?; + if !adt.is_enum() || last.name.as_str() != "__Invalid" { + return None; + } + let found = b.t.programs().string_enum(tcx, did)?; + (found.values.len() + 1 == adt.variants().len()) + .then(|| Expr::Array(found.values.iter().map(|v| Expr::Str(v.clone())).collect())) +} diff --git a/ts/src/lower/bindgen/mod.rs b/ts/src/lower/bindgen/mod.rs new file mode 100644 index 000000000..b0a38430c --- /dev/null +++ b/ts/src/lower/bindgen/mod.rs @@ -0,0 +1,569 @@ +//! wasm-bindgen, which translation maps itself, as it maps std: `JsValue` and every type +//! an import declares are the JS value itself, and each function `#[wasm_bindgen]` made +//! around an import is replaced whole by the JS call it stands for. +//! +//! Each invocation of the macro on wasm32 encodes what it declares in a +//! `#[link_section = "__wasm_bindgen_unstable"]` static, the program the wasm-bindgen CLI +//! reads, which every crate's metadata keeps. A function it made around an import declares +//! the import's `extern "C"` shim inside its own body, so the shim's parent's parent is +//! that function, and the shim's name keys the import's shape in the program: its class, +//! JS name, kind, namespace, and `catch`. What else the program declares, such as custom +//! JS or an export, is refused where translation reaches it. + +mod convert; +pub mod decode; +mod lower; + +use std::rc::Rc; + +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{CrateNum, DefId, DefIndex, LOCAL_CRATE}; +use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt}; +use rustc_span::hygiene::{ExpnKind, MacroKind}; + +use super::Translator; + +pub use lower::{call, constant}; + +/// The module wasm-bindgen's imports name in place of where a global is: the JS that runs +/// the module provides them, and wasm-bindgen's own intrinsics are its imports too. +const PLACEHOLDER: &str = "__wbindgen_placeholder__"; + +/// Whether `did` is wasm-bindgen's `__rt::LazyCell`, where a `thread_local_v2` import keeps +/// its value from first use: std's `LazyCell`, but for being `Sync` where WASM has no +/// threads, which translation makes an `rs.LazyCell` alike. +pub fn is_lazy(tcx: TyCtxt<'_>, did: DefId) -> bool { + tcx.crate_name(did.krate).as_str() == "wasm_bindgen" + && matches!(tcx.def_kind(did), DefKind::Struct) + && tcx + .def_path(did) + .data + .iter() + .filter_map(|d| d.data.get_opt_name()) + .map(|n| n.to_string()) + .eq(["__rt", "LazyCell"]) +} + +/// Whether `did` is a type whose value is a JS value: wasm-bindgen's `JsValue`, or a type +/// an import declares, which the macro makes a `#[repr(transparent)]` struct whose `obj` +/// is the JS-represented type it extends, beside the `PhantomData` of its type +/// parameters, such as js-sys's `Promise`, whose instances are then one JS value. Its +/// `obj` must be the macro's own, so that a struct a crate writes the same way keeps its +/// fields and its `Drop`. +pub fn represents(tcx: TyCtxt<'_>, did: DefId) -> bool { + if !matches!(tcx.def_kind(did), DefKind::Struct) { + return false; + } + if tcx.crate_name(did.krate).as_str() == "wasm_bindgen" && tcx.item_name(did).as_str() == "JsValue" { + return true; + } + let adt = tcx.adt_def(did); + if !adt.repr().transparent() { + return false; + } + let mut obj = None; + for field in &adt.non_enum_variant().fields { + let ty = tcx.type_of(field.did).instantiate_identity().skip_norm_wip(); + match ty.kind() { + ty::Adt(inner, _) if field.name.as_str() == "obj" && from_macro(tcx, field.did) => obj = Some(inner.did()), + ty::Adt(inner, _) if inner.is_phantom_data() => {} + _ => return false, + } + } + obj.is_some_and(|inner| represents(tcx, inner)) +} + +/// Whether `#[wasm_bindgen]`, the attribute, wrote `did`, by whatever path it is named. +fn from_macro(tcx: TyCtxt<'_>, did: DefId) -> bool { + let expn = tcx.def_span(did).ctxt().outer_expn_data(); + matches!(expn.kind, ExpnKind::Macro(MacroKind::Attr, _)) + && expn + .macro_def_id + .is_some_and(|m| tcx.crate_name(m.krate).as_str() == "wasm_bindgen_macro") +} + +/// What of wasm-bindgen a call reaches in place of its MIR. +pub enum Glue<'tcx> { + /// A function the macro made around an import. + Import(Wrapper), + /// What is its argument, once JS values are themselves: `JsValue`'s clone, `JsCast`'s + /// unchecked casts, and `transmute_copy` between JS-represented types. + Identity, + /// The value of wasm-bindgen's `LazyCell`, an `rs.LazyCell`, made on first use. + Force, + /// `JsValue`'s `Drop`, which frees its slot of wasm-bindgen's heap, where JS collects + /// the value itself. + Nothing, + /// `throw_str`, which throws an `Error` of its message, or `throw_val`, which throws + /// its value. + Throw { error: bool }, + /// `wbg_cast::`, which converts as an import's argument and result do. + Cast { from: Ty<'tcx>, to: Ty<'tcx> }, +} + +/// What of wasm-bindgen `instance` is, if a call of it maps rather than lowers from MIR. +pub fn glue<'tcx>(t: &Translator<'_, 'tcx>, instance: Instance<'tcx>) -> Option> { + let InstanceKind::Item(did) = instance.def else { + return None; + }; + let tcx = t.tcx; + let krate = tcx.crate_name(did.krate); + let name = tcx.opt_item_name(did)?; + let represented = |ty: Ty<'tcx>| matches!(ty.peel_refs().kind(), ty::Adt(adt, _) if represents(tcx, adt.did())); + match (krate.as_str(), name.as_str()) { + ("core", "transmute_copy") => { + // What wasm-bindgen's glue passes: a reference to a `ManuallyDrop` of the value. + let src = instance.args.type_at(0); + let src = match src.kind() { + ty::Adt(adt, args) if tcx.is_lang_item(adt.did(), rustc_attr_ir::LangItem::ManuallyDrop) => { + args.type_at(0) + } + _ => src, + }; + return (represented(src) && represented(instance.args.type_at(1))).then_some(Glue::Identity); + } + _ if crate::lower::of_std(tcx, did.krate) => return None, + ("wasm_bindgen", "throw_str") => return Some(Glue::Throw { error: true }), + ("wasm_bindgen", "throw_val") => return Some(Glue::Throw { error: false }), + ("wasm_bindgen", "wbg_cast") => { + return Some(Glue::Cast { + from: instance.args.type_at(0), + to: instance.args.type_at(1), + }); + } + _ => {} + } + // A trait's method, as the trait declares it or an impl gives it, and the type it is of. + let of_trait = match tcx.trait_of_assoc(did) { + Some(trait_id) => Some((trait_id, instance.args.type_at(0))), + None => tcx.trait_impl_of_assoc(did).map(|imp| { + let trait_ref = tcx.impl_trait_ref(imp).instantiate(tcx, instance.args).skip_norm_wip(); + (trait_ref.def_id, trait_ref.self_ty()) + }), + }; + let lazy = |ty: Ty<'tcx>| matches!(ty.peel_refs().kind(), ty::Adt(adt, _) if is_lazy(tcx, adt.did())); + if let Some((trait_id, self_ty)) = of_trait + && lazy(self_ty) + && tcx.item_name(trait_id).as_str() == "Deref" + { + return Some(Glue::Force); + } + if name.as_str() == "force" + && let Some(imp) = tcx.inherent_impl_of_assoc(did) + && lazy(tcx.type_of(imp).instantiate_identity().skip_norm_wip()) + { + return Some(Glue::Force); + } + // What the macro implements between the types an import declares and `JsValue`, which + // are one JS value: `From`, `AsRef`, `Deref`, and the like, of one JS value to another. + if of_trait.is_some() + && tcx + .trait_impl_of_assoc(did) + .is_some_and(|imp| tcx.is_automatically_derived(imp)) + && let sig = t.sig(did, instance.args) + && let [input] = sig.inputs() + && represented(*input) + && represented(sig.output()) + { + return Some(Glue::Identity); + } + if let Some((trait_id, self_ty)) = of_trait + && represented(self_ty) + { + let trait_name = tcx.item_name(trait_id); + let trait_krate = tcx.crate_name(trait_id.krate); + match (trait_krate.as_str(), trait_name.as_str(), name.as_str()) { + // `JsValue`'s, and what an import's type derives, which clones its `obj`. + ("core", "Clone", "clone") + if is_js_value(tcx, self_ty.peel_refs()) + || tcx + .trait_impl_of_assoc(did) + .is_some_and(|imp| tcx.is_automatically_derived(imp)) => + { + return Some(Glue::Identity); + } + ("core", "Drop", "drop") => return Some(Glue::Nothing), + ("wasm_bindgen", "JsCast", "unchecked_from_js" | "unchecked_from_js_ref") => return Some(Glue::Identity), + _ => {} + } + } + t.programs().wrapper(did).cloned().map(Glue::Import) +} + +/// Whether `ty` is wasm-bindgen's `JsValue`. +fn is_js_value(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool { + matches!(ty.kind(), ty::Adt(adt, _) + if tcx.crate_name(adt.did().krate).as_str() == "wasm_bindgen" && tcx.item_name(adt.did()).as_str() == "JsValue") +} + +/// What an import is, from the program that declares it. +#[derive(Debug)] +pub struct Import { + pub shim: String, + /// Where it is from, if it is not a global: custom JS, or wasm-bindgen's placeholder. + pub module: Option, + pub namespace: Option>, + pub kind: Kind, + /// The prefixes the program lists for the class or global it names, tried in turn + /// where the unprefixed one is not defined. + pub vendor_prefixes: Vec, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Module { + /// `module = "..."`: a local JS file. + Named(String), + /// `raw_module = "..."`. + Raw(String), + /// `inline_js = "..."`. + Inline, +} + +#[derive(Debug)] +pub enum Kind { + Function(Function), + /// A static's value, read once. + Static { + name: String, + }, + /// A string constant. + String(String), + /// A type's `instanceof` check. + Instanceof { + class: String, + }, +} + +#[derive(Debug)] +pub struct Function { + /// Its JS name, a path of properties. + pub name: String, + pub catch: bool, + pub variadic: bool, + pub structural: bool, + pub suspending: bool, + pub generic_per_mono: bool, + pub asyncness: bool, + pub jspi: bool, + pub method: Option, +} + +#[derive(Debug)] +pub struct Method { + pub class: String, + pub kind: MethodKind, +} + +#[derive(Debug)] +pub enum MethodKind { + Constructor, + Operation { is_static: bool, op: Op }, +} + +#[derive(Debug)] +pub enum Op { + Regular, + RegularThis, + Getter(String), + Setter(String), + IndexingGetter, + IndexingSetter, + IndexingDeleter, +} + +/// A string enum: its JS strings, in the order of its Rust variants, the last of which +/// stands for any other. +#[derive(Debug)] +pub struct StringEnum { + pub values: Vec, + /// The module path of its invocation, where the schema records one. + pub module_path: Option, +} + +/// What a wrapper stands for: its import, or why translation refuses it. +pub type Wrapper = Result, String>; + +/// The programs of every crate that translates, read once. +#[derive(Default)] +pub struct Programs { + /// Each function the macro made around an import, by the function. + wrappers: FxHashMap, + /// Each crate's string enums, by name. + enums: FxHashMap<(CrateNum, String), Vec>>, + /// The functions each exported function's shim calls, with the export's JS name. + exports: rustc_data_structures::fx::FxHashSet, +} + +impl Programs { + /// Read the programs of `crates` and the package's. + pub fn read(tcx: TyCtxt<'_>, crates: impl IntoIterator) -> Self { + let mut programs = Self::default(); + for krate in crates.into_iter().chain([LOCAL_CRATE]) { + programs.read_crate(tcx, krate); + } + programs + } + + /// The import the macro made `did` around, if it did. + pub fn wrapper(&self, did: DefId) -> Option<&Wrapper> { + self.wrappers.get(&did) + } + + /// Whether `did` is a `#[wasm_bindgen]` export: the function an export's shim calls. + pub fn is_export(&self, did: DefId) -> bool { + self.exports.contains(&did) + } + + /// The string enum `did` is, by its crate, name, and module. + pub fn string_enum(&self, tcx: TyCtxt<'_>, did: DefId) -> Option> { + let found = self.enums.get(&(did.krate, tcx.item_name(did).to_string()))?; + let module = tcx.def_path_str(tcx.parent(did)); + let module = match did.is_local() { + true => format!("{}::{module}", tcx.crate_name(LOCAL_CRATE)), + false => module, + }; + let mut matching = found + .iter() + .filter(|e| e.module_path.as_ref().is_none_or(|path| *path == module)); + let first = matching.next()?; + // Two of one name, which a schema without module paths can't tell apart. + matching.next().is_none().then(|| first.clone()) + } + + fn read_crate(&mut self, tcx: TyCtxt<'_>, krate: CrateNum) { + let ids: Vec = match krate { + LOCAL_CRATE => tcx.iter_local_def_id().map(|id| id.to_def_id()).collect(), + _ => { + let count = rustc_metadata::creader::CStore::from_tcx(tcx).num_def_ids_untracked(krate); + (0..count) + .map(|index| DefId { + krate, + index: DefIndex::from_usize(index), + }) + .collect() + } + }; + let mut statics = Vec::new(); + // The functions declaring each `extern "C"` shim in wasm-bindgen's placeholder module. + let mut shims: FxHashMap = FxHashMap::default(); + let mut generated = Vec::new(); + let mut generic = Vec::new(); + for did in ids { + match tcx.def_kind(did) { + DefKind::Static { .. } + if tcx + .codegen_fn_attrs(did) + .link_section + .is_some_and(|s| s.as_str() == "__wasm_bindgen_unstable") => + { + statics.push(did) + } + DefKind::Fn if tcx.is_foreign_item(did) && in_placeholder(tcx, did) => { + if let Some(wrapper) = enclosing_fn(tcx, did) { + shims.insert(tcx.item_name(did).to_string(), wrapper); + } + } + DefKind::Fn if tcx.item_name(did).as_str().starts_with("__wasm_bindgen_generated_") => { + generated.push(did) + } + // An import generic per monomorphization declares no shim, but a function the + // CLI rewrites per instance, which describes the instance's signature. + DefKind::Fn if tcx.item_name(did).as_str() == "breaks_if_inlined" => { + if let parent = tcx.parent(did) + && matches!(tcx.def_kind(parent), DefKind::Fn | DefKind::AssocFn) + { + generic.push(parent); + } + } + _ => {} + } + } + if statics.is_empty() { + return; + } + for wrapper in generic { + let why = "`experimental_generic_mono`, which the CLI makes JS of per instance, as translation does not"; + self.wrappers.insert(wrapper, Err(why.into())); + } + let mut imports = Vec::new(); + let mut exported = false; + for did in statics { + if let Err(why) = self.read_static(tcx, did, &mut imports, &mut exported) { + // Every import of the crate is refused, as none of its shapes can be trusted. + let name = tcx.crate_name(krate); + for wrapper in shims.into_values() { + self.wrappers.insert(wrapper, Err(format!("`{name}`'s program: {why}"))); + } + return; + } + } + for import in imports { + if let Some(wrapper) = shims.remove(&import.shim) { + self.wrappers.insert(wrapper, Ok(Rc::new(import))); + } + } + // A shim no program names, which no supported schema declares. + for (shim, wrapper) in shims { + self.wrappers.insert( + wrapper, + Err(format!("`{shim}`, which no program of its crate declares")), + ); + } + if exported { + for shim in generated { + self.exports.extend(exported_callees(tcx, shim)); + } + } + } + + /// Read the programs of the static `did` into `imports` and the crate's string enums, + /// noting whether any exports. + fn read_static( + &mut self, + tcx: TyCtxt<'_>, + did: DefId, + imports: &mut Vec, + exported: &mut bool, + ) -> Result<(), String> { + let alloc = tcx + .eval_static_initializer(did) + .map_err(|_| "a program static that does not evaluate".to_string())?; + let alloc = alloc.inner(); + let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len()); + for (schema, bytes) in decode::section(bytes)? { + let program = decode::program(bytes, schema)?; + *exported |= !program.exports.is_empty() || !program.structs.is_empty(); + // A type's vendor prefixes apply to its constructor and static methods. + let prefixes: FxHashMap<&str, Vec> = (program.imports.iter()) + .filter_map(|import| match &import.kind { + decode::ImportKind::Type(t) if !t.vendor_prefixes.is_empty() => { + Some((t.name, t.vendor_prefixes.iter().map(|p| p.to_string()).collect())) + } + _ => None, + }) + .collect(); + for import in program.imports { + let module = import.module.map(|module| match module { + decode::ImportModule::Named(m) => Module::Named(m.into()), + decode::ImportModule::RawNamed(m) => Module::Raw(m.into()), + decode::ImportModule::Inline(_) => Module::Inline, + }); + let (shim, kind, item) = match import.kind { + decode::ImportKind::Function(f) => { + let method = f.method.map(|m| Method { + class: m.class.into(), + kind: match m.kind { + decode::MethodKind::Constructor => MethodKind::Constructor, + decode::MethodKind::Operation(op) => MethodKind::Operation { + is_static: op.is_static, + op: match op.kind { + decode::OperationKind::Regular => Op::Regular, + decode::OperationKind::RegularThis => Op::RegularThis, + decode::OperationKind::Getter(g) => Op::Getter(g.into()), + decode::OperationKind::Setter(s) => Op::Setter(s.into()), + decode::OperationKind::IndexingGetter => Op::IndexingGetter, + decode::OperationKind::IndexingSetter => Op::IndexingSetter, + decode::OperationKind::IndexingDeleter => Op::IndexingDeleter, + }, + }, + }, + }); + // What the CLI resolves the vendor prefixes of: the class, or the function. + let item = method + .as_ref() + .map_or(f.function.name, |m| m.class.as_str()) + .to_string(); + let function = Function { + name: f.function.name.into(), + catch: f.catch, + variadic: f.variadic, + structural: f.structural, + suspending: f.suspending, + generic_per_mono: f.generic_per_mono, + asyncness: f.function.asyncness, + jspi: f.function.jspi, + method, + }; + (f.shim, Kind::Function(function), item) + } + decode::ImportKind::Static(s) => (s.shim, Kind::Static { name: s.name.into() }, s.name.into()), + decode::ImportKind::String(s) => (s.shim, Kind::String(s.string.into()), String::new()), + decode::ImportKind::Type(t) => ( + t.instanceof_shim, + Kind::Instanceof { class: t.name.into() }, + t.name.into(), + ), + decode::ImportKind::Enum(e) => { + let string_enum = StringEnum { + values: e.variant_values.iter().map(|v| v.to_string()).collect(), + module_path: program.module_path.as_ref().map(|p| p.0.to_string()), + }; + (self.enums.entry((did.krate, e.name.to_string())).or_default()).push(Rc::new(string_enum)); + continue; + } + // A union has no shim: what converts it is refused where it converts. + decode::ImportKind::DynamicUnion(_) => continue, + }; + imports.push(Import { + shim: shim.into(), + module, + namespace: import.js_namespace, + kind, + vendor_prefixes: prefixes.get(item.as_str()).cloned().unwrap_or_default(), + }); + } + } + Ok(()) + } +} + +/// The function the shim `did` is declared in: its `extern` block's, or for an import +/// generic per monomorphization, that of the block it is nested in. +fn enclosing_fn(tcx: TyCtxt<'_>, did: DefId) -> Option { + let mut at = tcx.opt_parent(did)?; + loop { + at = tcx.opt_parent(at)?; + match tcx.def_kind(at) { + DefKind::Fn | DefKind::AssocFn => return Some(at), + DefKind::Mod | DefKind::Impl { .. } | DefKind::Trait => return None, + _ => {} + } + } +} + +/// Whether the foreign function `did` is imported from wasm-bindgen's placeholder module. +fn in_placeholder(tcx: TyCtxt<'_>, did: DefId) -> bool { + tcx.wasm_import_module_map(did.krate) + .get(&did) + .is_some_and(|module| module == PLACEHOLDER) +} + +/// The functions the export shim `shim` calls, of which the export is one: in its body, +/// or in a closure it passes on, as it does to catch an unwind. +fn exported_callees(tcx: TyCtxt<'_>, shim: DefId) -> Vec { + let mut callees = Vec::new(); + let mut bodies = vec![shim]; + while let Some(body) = bodies.pop() { + if !tcx.is_mir_available(body) { + continue; + } + let mir = tcx.optimized_mir(body); + for block in mir.basic_blocks.iter() { + let rustc_middle::mir::TerminatorKind::Call { func, .. } = &block.terminator().kind else { + continue; + }; + let ty::FnDef(callee, args) = *func.ty(&mir.local_decls, tcx).kind() else { + continue; + }; + bodies.extend(args.skip_binder().types().filter_map(|t| match t.kind() { + ty::Closure(closure, _) => Some(*closure), + _ => None, + })); + if tcx.crate_name(callee.krate).as_str() != "wasm_bindgen" && !crate::lower::of_std(tcx, callee.krate) { + callees.push(callee); + } + } + } + callees +} diff --git a/ts/src/lower/body.rs b/ts/src/lower/body.rs index 9a8c2d556..69bbadec4 100644 --- a/ts/src/lower/body.rs +++ b/ts/src/lower/body.rs @@ -118,6 +118,9 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { if let Some(sym) = self.t.bound_fn(instance, span) { return Expr::Sym(sym).call(args.into_iter().map(Expr::typed).collect()); } + if let Some(glue) = super::bindgen::glue(self.t, instance) { + return super::bindgen::call(self, instance, glue, args, span, out); + } if let ty::InstanceKind::Virtual(method, _) = instance.def && self.t.dispatches(instance) { diff --git a/ts/src/lower/consts.rs b/ts/src/lower/consts.rs index 17d590ccf..c846f4f31 100644 --- a/ts/src/lower/consts.rs +++ b/ts/src/lower/consts.rs @@ -103,6 +103,20 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { [(field_ty, field)] => self.value(*field_ty, *field, span), _ => self.err(span, format!("a constant of type `{ty}` without one field")), }, + // A reserved slot of wasm-bindgen's heap, by the index the `JsValue` within holds. + ty::Adt(adt, _) if Std::of(tcx, adt.did()) == Some(Std::Js) => { + let mut node = (ty, valtree); + while let [(inner_ty, inner), ..] = children(node.1)[..] { + node = (inner_ty, inner); + } + match leaf(node.1).and_then(|idx| super::bindgen::constant(idx.to_u32())) { + Some(value) => value, + None => self.err( + span, + format!("a constant `{ty}` of wasm-bindgen's heap, which only WASM has"), + ), + } + } ty::Adt(adt, args) => { let fields = children(valtree); // Its alignment is an enum, whose variant `i` is `1 << i`. diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index ceb6a2325..fd49bacc6 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -211,6 +211,8 @@ pub enum Std { BytesMut, /// `rand::rngs::ThreadRng`: `rs.ThreadRng`, which holds no state. ThreadRng, + /// wasm-bindgen's `JsValue`, or a type an import declares: the JS value itself. + Js, /// A handle of one of tokio's sync channels, or what one lends: a class of /// `rs/tokio.ts`'s, which [`super::tokio::Handle`] names. Sync, @@ -252,6 +254,12 @@ impl Std { pub fn of(tcx: TyCtxt<'_>, did: DefId) -> Option { if !is_std(tcx, did) { + if super::bindgen::represents(tcx, did) { + return Some(Self::Js); + } + if super::bindgen::is_lazy(tcx, did) { + return Some(Self::LazyCell); + } return super::stock::of(tcx, did); } use rustc_span::sym; diff --git a/ts/src/lower/mir/memory.rs b/ts/src/lower/mir/memory.rs index adf0c8860..672b59df6 100644 --- a/ts/src/lower/mir/memory.rs +++ b/ts/src/lower/mir/memory.rs @@ -59,6 +59,16 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { Some(Std::Once) if self.incomplete(val, ty) => crate::lower::task::once_unrun(), Some(Std::Once) => self.unreadable(ty, span), // Its function, which the first `force` runs. + // wasm-bindgen's, around a lazy of its own: the function, wherever it keeps it. + Some(Std::LazyCell) + if let ty::Adt(adt, _) = ty.kind() + && crate::lower::bindgen::is_lazy(tcx, adt.did()) => + { + match self.lazy_init(val, ty) { + Some(init) => self.lazy("LazyCell", init, span), + None => self.unreadable(ty, span), + } + } Some(Std::LazyCell) => match self.variant_fields(val, ty) { Some((variant, fields)) if variant == "Uninit" => self.lazy("LazyCell", fields[0], span), _ => self.unreadable(ty, span), @@ -92,6 +102,19 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { Expr::New(Box::new(Expr::Rs(class.into())), vec![f]) } + /// The function a lazy `val`, a `ty`, has yet to run, in whichever of its fields holds it. + fn lazy_init(&self, val: ConstValue, ty: Ty<'tcx>) -> Option<(ConstValue, Ty<'tcx>)> { + match ty.kind() { + ty::FnPtr(..) | ty::FnDef(..) => Some((val, ty)), + ty::Adt(..) => self + .destructure(val, ty)? + .fields + .iter() + .find_map(|&(v, t)| self.lazy_init(v, t)), + _ => None, + } + } + /// The refusal of a `ty` in a state translation can't read, as a `OnceLock` const /// evaluation can't have filled. fn unreadable(&mut self, ty: Ty<'tcx>, span: Span) -> Expr { diff --git a/ts/src/lower/mir/mod.rs b/ts/src/lower/mir/mod.rs index 1a83f7561..b474845ab 100644 --- a/ts/src/lower/mir/mod.rs +++ b/ts/src/lower/mir/mod.rs @@ -3408,6 +3408,16 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } } + /// Whether `bb` starts a loop: a block it dominates jumps back to it. + fn loop_header(&self, bb: BasicBlock) -> bool { + let blocks = &self.mir.basic_blocks; + let dominators = blocks.dominators(); + dominators.is_reachable(bb) + && blocks.predecessors()[bb] + .iter() + .any(|&p| dominators.is_reachable(p) && dominators.dominates(bb, p)) + } + /// Emit every pending temporary, in order. fn flush_pending(&mut self, out: &mut Vec) { for p in std::mem::take(&mut self.pending) { @@ -3420,14 +3430,34 @@ impl cfg::Lower for Fx<'_, '_, '_, '_> { fn node(&mut self, node: usize, out: &mut Vec) -> Exit { let exit = self.block(BasicBlock::from_usize(node), out); let forward = |to: usize| self.forward(BasicBlock::from_usize(to)).as_usize(); - match exit { + let exit = match exit { Exit::Goto(to) => Exit::Goto(forward(to)), Exit::Branch(arms, default) => Exit::Branch( arms.into_iter().map(|(cond, to)| (cond, forward(to))).collect(), forward(default), ), Exit::End => Exit::End, + }; + // A loop runs its body anew each time: what is held back to fold into its one read + // runs where it was made, before the loop, if running it again would differ. + let targets: Vec = match &exit { + Exit::Goto(to) => vec![*to], + Exit::Branch(arms, default) => arms.iter().map(|(_, to)| *to).chain([*default]).collect(), + Exit::End => Vec::new(), + }; + if targets + .into_iter() + .any(|to| self.loop_header(BasicBlock::from_usize(to))) + { + let (held, kept): (Vec<_>, Vec<_>) = std::mem::take(&mut self.pending) + .into_iter() + .partition(|p| p.effect || p.memory); + self.pending = kept; + for p in held { + self.flush_one(p, out); + } } + exit } fn flush(&mut self, out: &mut Vec) { diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index abed0033e..b19a8853e 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -718,6 +718,26 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } } Some(Std::ControlFlow) => super::call::control_flow_field(self, base, ty, variant), + // An imported type's `obj` is the JS value itself, and its marker nothing. + Some(Std::Js) => { + let ty::Adt(_, args) = ty.kind() else { unreachable!() }; + let def = &adt.non_enum_variant().fields[field]; + match def.name.as_str() { + "obj" => base, + _ if def + .ty(tcx, args) + .skip_norm_wip() + .ty_adt_def() + .is_some_and(|f| f.is_phantom_data()) => + { + Expr::undefined() + } + name => self.err( + span, + format!("`{ty}`'s `{name}`, a slot of wasm-bindgen's heap, which only WASM has"), + ), + } + } _ if transparent(self.b.t, ty) || crate::lower::library::wrapped(tcx, ty).is_some() => base, None if self.b.t.own(adt.did()) => { let info = self.b.t.adt(*adt, span); diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 3446ee3c4..91887cf7d 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -837,6 +837,14 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { crate::lower::library::cow_made(borrowed, variant, values.pop().unwrap()) } (Std::Result, i) => Expr::result(i == 0, values.pop().unwrap()), + // An imported type is its `obj`, the JS value itself. + (Std::Js, _) => match def.fields.iter().position(|f| f.name.as_str() == "obj") { + Some(obj) => values.swap_remove(obj), + None => self.err( + span, + format!("a `{ty}` made of a slot of wasm-bindgen's heap, which only WASM has"), + ), + }, (Std::FmtError, _) => Expr::New(Box::new(Expr::Rs("FmtError".into())), Vec::new()), ( std @ (Std::ParseBoolError diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index b9a0254e8..b4e617248 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -7,6 +7,7 @@ mod alloc; mod bind; +mod bindgen; pub use bind::check_module; pub mod array_slice; mod body; @@ -298,6 +299,8 @@ pub struct Translator<'a, 'tcx> { pub hashers: rustc_data_structures::fx::FxHashMap, SymId>, /// The field of each struct's byte slice field held as its parts that holds its bytes. pub slices: rustc_data_structures::fx::FxHashSet, + /// The wasm-bindgen programs of the crates that translate, read on first use. + programs: std::cell::OnceCell, } /// The crates that translate as one program: the package, and every crate it depends on @@ -547,6 +550,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { slots: Default::default(), hashers: Default::default(), slices: Default::default(), + programs: Default::default(), }) } @@ -942,7 +946,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let ty::Adt(adt, _) = self_ty.kind() else { return None; }; - if !self.crates.translates(adt.did()) { + // A JS value has no class: its methods are functions of it. + if !self.crates.translates(adt.did()) || bindgen::represents(self.tcx, adt.did()) { return None; } let span = self.tcx.def_span(instance.def_id()); @@ -1059,6 +1064,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { match instance.def { InstanceKind::Item(did) => { self.bound_module(instance).is_none() + && bindgen::glue(self, instance).is_none() && ((self.crates.translates(did) && !stock::maps(self.tcx, instance)) || library::from_mir(self, env, instance)) } @@ -1074,6 +1080,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { !self.lowers(self.typing_env(), instance) && !self.dispatches(instance) && self.bound_module(instance).is_none() + && bindgen::glue(self, instance).is_none() } _ => true, } @@ -1087,6 +1094,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } } + /// The wasm-bindgen programs of the crates that translate. + pub fn programs(&self) -> &bindgen::Programs { + self.programs + .get_or_init(|| bindgen::Programs::read(self.tcx, self.crates.crates.iter().copied())) + } + /// A translated struct or enum, translating its definition the first time. pub fn adt(&mut self, adt: AdtDef<'tcx>, span: Span) -> Adt { if let Some(info) = self.adts.get(&adt.did()) { @@ -1885,6 +1898,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { library::Std::Bytes | library::Std::IoSlice => Type::Rs("ByteSlice", vec![]), library::Std::BytesMut => Type::Rs("BytesMut", vec![]), library::Std::ThreadRng => Type::Rs("ThreadRng", vec![]), + library::Std::Js => Type::Rs("JsValue", vec![]), // A handle over the type of the values its channel holds. library::Std::Sync => { let handle = tokio::Handle::of(tcx, adt.did()).expect("a tokio handle"); @@ -2209,6 +2223,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }, Some(library::Std::Option) => self.erases_to(args.type_at(0)).option(), Some(library::Std::Poll) => self.erases_to(args.type_at(0)).poll(), + // Any JS value, `undefined` included. + Some(library::Std::Js) => Erased { + undefined: true, + ..Erased::default() + }, Some(library::Std::ControlFlow) if library::Flow::of(self.tcx, args) == library::Flow::Option => { self.erases_to(args.type_at(1)).option() } @@ -2231,6 +2250,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { self.err(span, format!("`{}` is from another crate", tcx.def_path_str(def_id))); return; } + if self.programs().is_export(def_id) { + let path = tcx.def_path_str(def_id); + self.err( + span, + format!( + "`{path}`, a `#[wasm_bindgen]` export, which translation refuses: it exports the crate's `pub` API itself" + ), + ); + return; + } let std = !self.crates.translates(def_id); let site = std.then(|| (self.sites.get(&instance).copied().unwrap_or(span), def_id)); let outer = std::mem::replace(&mut self.site, site); diff --git a/ts/src/lower/names.rs b/ts/src/lower/names.rs index 6e4972a87..a5c88d8cd 100644 --- a/ts/src/lower/names.rs +++ b/ts/src/lower/names.rs @@ -145,7 +145,8 @@ impl<'tcx> Translator<'_, 'tcx> { let named = match tcx.def_kind(parent) { DefKind::Impl { .. } => { let ty = tcx.type_of(parent).instantiate_identity().skip_norm_wip().peel_refs(); - !matches!(ty.kind(), ty::Adt(adt, _) if self.own(adt.did())) + // A JS value's, which has no class, are its Rust module's. + !matches!(ty.kind(), ty::Adt(adt, _) if self.own(adt.did()) && !super::bindgen::represents(tcx, adt.did())) } _ => tcx.defaultness(def_id).has_value(), }; diff --git a/ts/src/print.rs b/ts/src/print.rs index c66a73e33..62ac83ff9 100644 --- a/ts/src/print.rs +++ b/ts/src/print.rs @@ -981,7 +981,22 @@ impl Printer<'_> { } Expr::New(f, args) => { self.write("new "); - self.base(f); + // A call in the callee would take `new`'s arguments as its own. + fn calls(e: &Expr) -> bool { + match e.unlocated() { + Expr::Call(..) => true, + Expr::Member(base, _) | Expr::Index(base, _) => calls(base), + _ => false, + } + } + match calls(f) { + true => { + self.write("("); + self.expr(f, 0); + self.write(")"); + } + false => self.base(f), + } self.write("("); self.list(args); self.write(")"); diff --git a/ts/src/program.rs b/ts/src/program.rs index b4c66d5d4..b1120988b 100644 --- a/ts/src/program.rs +++ b/ts/src/program.rs @@ -26,6 +26,7 @@ pub const RUNTIME: &[(&str, &str)] = &[ ("rs/hook.ts", include_str!("../runtime/rs/hook.ts")), ("rs/int.ts", include_str!("../runtime/rs/int.ts")), ("rs/iter.ts", include_str!("../runtime/rs/iter.ts")), + ("rs/js.ts", include_str!("../runtime/rs/js.ts")), ("rs/location.ts", include_str!("../runtime/rs/location.ts")), ("rs/rand.ts", include_str!("../runtime/rs/rand.ts")), ("rs/sort.ts", include_str!("../runtime/rs/sort.ts")), diff --git a/ts/tests/differential.ts b/ts/tests/differential.ts index f6a49d326..e1e3d9378 100644 --- a/ts/tests/differential.ts +++ b/ts/tests/differential.ts @@ -1,7 +1,7 @@ // Runs a fixture's harness both ways on the same inputs and fails on any difference: // natively, as the fixture's wasm32 build, and as its TypeScript translation. // -// usage: node differential.ts [cases] +// usage: node differential.ts [cases] [glue.js] // // A harness, or a hand-written caller wrapping one, exports `run(input: rs.ByteSlice): // Vec`, what `inputs.ts` reads to pick its inputs, and may export `within(input, @@ -14,29 +14,35 @@ // TypeScript, and the two agree only if their messages do. An abort traps natively // without a panic message, as wasm32 has nowhere to print std's, and throws `Abort` in // TypeScript, so the two agree that it aborted but not on why. Either leaves the native -// side a fresh instance, so the translation's hook is reset after one. +// side a fresh instance, so the translation's hook is reset after one. A module that +// wasm-bindgen's CLI processed runs with [glue.js], the `_bg.js` of its glue, and a JS +// exception thrown through either side agrees only if its text does. import { readFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { pathToFileURL } from "node:url"; import { inputs } from "./inputs.ts"; -import { type Outcome, Wasm } from "./wasm.ts"; +import { type Glue, type Outcome, Wasm } from "./wasm.ts"; -const [wasmPath, harnessPath, countArg] = process.argv.slice(2); -if (!wasmPath || !harnessPath) throw new Error("usage: differential.ts [cases]"); +const [wasmPath, harnessPath, countArg, gluePath] = process.argv.slice(2); +if (!wasmPath || !harnessPath) throw new Error("usage: differential.ts [cases] [glue.js]"); const count = Number(countArg ?? "20000"); const harness = await import(pathToFileURL(harnessPath).href); // The translation's runtime, beside the harness, which makes the input its slice type. const rs = await import(pathToFileURL(join(dirname(harnessPath), "rs.ts")).href); -const wasm = new Wasm(await WebAssembly.compile(await readFile(wasmPath))); +// The glue's module, loaded anew for each instance, whose memory it caches views of. +let loads = 0; +const glue = async (): Promise => (gluePath ? import(`${pathToFileURL(gluePath).href}?instance=${loads++}`) : undefined); +const wasm = new Wasm(await WebAssembly.compile(await readFile(wasmPath)), await glue()); function translated(input: Uint8Array): Outcome { try { const out = harness.run(rs.byteSliceFrom(input)); return out.data.slice(0, out.length); } catch (err) { + if (gluePath && !(err instanceof Error && (err.name === "Abort" || err.name === "Panic"))) return { thrown: `${err}` }; if (!(err instanceof Error) || (err.name !== "Abort" && err.name !== "Panic")) throw err; rs.takeHook(); return err.name === "Abort" ? "abort" : { panic: err.message }; @@ -45,6 +51,7 @@ function translated(input: Uint8Array): Outcome { function show(outcome: Outcome): string { if (outcome === "abort") return outcome; + if ("thrown" in outcome) return `threw ${outcome.thrown}`; return outcome instanceof Uint8Array ? Buffer.from(outcome).toString("hex") : `panic ${JSON.stringify(outcome.panic)}`; } @@ -62,6 +69,7 @@ let failures = 0; let panics = 0; let aborts = 0; for (const input of cases) { + if (wasm.stale) wasm.renew((await glue()) as Glue); const want = wasm.call(input); const wantNote = wasm.note; let got: Outcome; @@ -75,7 +83,7 @@ for (const input of cases) { continue; } if (want === "abort") aborts++; - else if (!(want instanceof Uint8Array)) panics++; + else if (!(want instanceof Uint8Array) && "panic" in want) panics++; const close = want instanceof Uint8Array && got instanceof Uint8Array && harness.within?.(input, want, got) === true; if ((show(want) !== show(got) && !close) || wantNote !== gotNote) { failures++; diff --git a/ts/tests/fixtures.rs b/ts/tests/fixtures.rs index 97ac0c1ae..fde8f2fb5 100644 --- a/ts/tests/fixtures.rs +++ b/ts/tests/fixtures.rs @@ -42,12 +42,17 @@ fn driver(name: &str, config: &Path, args: &[&std::ffi::OsStr], build: &str) -> /// The command [`driver`] runs. fn driver_command(name: &str, config: &Path, args: &[&std::ffi::OsStr], build: &str) -> Command { + driver_at(&fixture(name).join("Cargo.toml"), config, args, build) +} + +/// The command that runs the driver on the package of `manifest`. +fn driver_at(manifest: &Path, config: &Path, args: &[&std::ffi::OsStr], build: &str) -> Command { let mut command = Command::new(env!("CARGO_BIN_EXE_rust2-ts")); command .arg("--config") .arg(config) .arg("--manifest-path") - .arg(fixture(name).join("Cargo.toml")) + .arg(manifest) .args(args) .env("CARGO_TARGET_DIR", scratch().join(build)) .env("RUSTFLAGS", remap()); @@ -211,6 +216,12 @@ fn typecheck(out: &Path) { /// translated side through `entry`, a module in `out`. `build` are the native build's /// arguments, such as `--features`, in the workspace whose manifest is `workspace`. fn differential(workspace: &Path, package: &str, build: &[&str], out: &Path, entry: &str, cases: u32) { + let wasm = native(workspace, package, build); + compare(&wasm, out, entry, cases, None); +} + +/// Build the harness natively, as wasm32, returning its module. +fn native(workspace: &Path, package: &str, build: &[&str]) -> PathBuf { let target_dir = scratch().join("wasm"); let status = cargo() .args([ @@ -233,26 +244,34 @@ fn differential(workspace: &Path, package: &str, build: &[&str], out: &Path, ent .status() .expect("running cargo"); assert!(status.success(), "building {package} for {TARGET} failed"); - let wasm = target_dir + target_dir .join(TARGET) .join(PROFILE) - .join(format!("{}.wasm", package.replace('-', "_"))); + .join(format!("{}.wasm", package.replace('-', "_"))) +} + +/// Run the native module `wasm` and the translation in `out`, through its module `entry`, +/// on the same inputs: `wasm` with wasm-bindgen's JS `glue`, the `_bg.js` its CLI made, if +/// it imports any. +fn compare(wasm: &Path, out: &Path, entry: &str, cases: u32, glue: Option<&Path>) { std::fs::write(out.join("package.json"), "{ \"type\": \"module\" }\n").unwrap(); // One thread: the cases run one at a time, and V8's background compilers and // collectors would spend more CPU than they save. let output = Command::new("node") .arg("--single-threaded") .arg(manifest_dir().join("tests").join("differential.ts")) - .arg(&wasm) + .arg(wasm) .arg(out.join(entry)) .arg(cases.to_string()) + .args(glue) .output() .expect("running node, which the dev shell provides"); let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); assert!( output.status.success(), - "{package} differs from native Rust:\n{stdout}{stderr}" + "{} differs from native Rust:\n{stdout}{stderr}", + wasm.display() ); println!("{stdout}"); } @@ -668,6 +687,87 @@ export const run = (input: rs.ByteSlice) => script(rs.byteSliceView(input)); differential(&workspace(), "task-fixture", &[], out, "caller.ts", 800); } +/// js-sys's and web-sys's imports, and the fixture's own of each kind, as direct JS calls. +/// The native side runs through wasm-bindgen's CLI, whose JS glue must match the +/// fixture's locked wasm-bindgen exactly; the translated side, once per schema translation +/// reads, against that one native run: the locked version's, then the next one's, from +/// the fixture's other lockfile. What translation refuses fails at its span. +#[test] +fn bindgen() { + let wasm = native(&fixture("bindgen").join("Cargo.toml"), "bindgen-fixture", &[]); + let glue = scratch().join("bindgen").join("glue"); + let status = Command::new("wasm-bindgen") + .args(["--target", "bundler", "--out-dir"]) + .arg(&glue) + .arg(&wasm) + .status() + .expect("running wasm-bindgen, which the dev shell provides"); + assert!(status.success(), "wasm-bindgen failed on {}", wasm.display()); + let (glued, js) = (glue.join("bindgen_fixture_bg.wasm"), glue.join("bindgen_fixture_bg.js")); + // Each case as many times as `model`'s, of about as many steps. + let cases = 300; + + let out = translated("bindgen"); + snapshot("bindgen", &out); + std::fs::copy(fixture("bindgen").join("caller.ts"), out.join("caller.ts")).unwrap(); + typecheck(&out); + compare(&glued, &out, "caller.ts", cases, Some(&js)); + + // The next schema: a copy of the fixture with its other lockfile, beside the shared + // `wasm.rs` its `lib.rs` reaches. + let next = scratch().join("bindgen").join("next"); + let _ = std::fs::remove_dir_all(&next); + for file in [ + "Cargo.toml", + "ts.toml", + "src/lib.rs", + "src/harness.rs", + "src/imports.rs", + ] { + let to = next.join(file); + std::fs::create_dir_all(to.parent().unwrap()).unwrap(); + std::fs::copy(fixture("bindgen").join(file), to).unwrap(); + } + std::fs::copy( + fixture("bindgen").join("wasm-bindgen-0.2.130.lock"), + next.join("Cargo.lock"), + ) + .unwrap(); + std::fs::copy( + manifest_dir().join("fixtures").join("wasm.rs"), + scratch().join("bindgen").join("wasm.rs"), + ) + .unwrap(); + let out = scratch().join("bindgen").join("next-out"); + let output = driver_at( + &next.join("Cargo.toml"), + &next.join("ts.toml"), + &["--out".as_ref(), out.as_ref()], + "build", + ) + .output() + .expect("running rust2-ts"); + assert!( + output.status.success(), + "translating bindgen at the next schema failed:\n{}", + String::from_utf8_lossy(&output.stderr) + ); + std::fs::copy(fixture("bindgen").join("caller.ts"), out.join("caller.ts")).unwrap(); + typecheck(&out); + compare(&glued, &out, "caller.ts", cases, Some(&js)); + + let out = scratch().join("bindgen").join("refused"); + let args: [&std::ffi::OsStr; 5] = [ + "--out".as_ref(), + out.as_ref(), + "--".as_ref(), + "--features".as_ref(), + "refused".as_ref(), + ]; + let output = driver("bindgen", &fixture("bindgen").join("refused.toml"), &args, "build"); + rejected(&out, &output, &fixture("bindgen").join("refused.txt")); +} + /// Each construct outside the subset fails with its span, and nothing is written. #[test] fn reject() { diff --git a/ts/tests/wasm.ts b/ts/tests/wasm.ts index f1e876fca..0a02f884d 100644 --- a/ts/tests/wasm.ts +++ b/ts/tests/wasm.ts @@ -11,23 +11,61 @@ interface Exports { message_len(): number; note?(): number; note_len?(): number; + /** What a module wasm-bindgen's CLI processed runs before any call. */ + __wbindgen_start?(): void; } +/** + * The JS glue wasm-bindgen's CLI makes for a module, `--target bundler`'s `_bg.js`: the + * imports it gives each instance, and where it keeps the instance it calls into. It + * caches views of that instance's memory, so each instance needs the module loaded anew. + */ +export type Glue = Record & { __wbg_set_wasm(exports: unknown): void }; + /** * How a call ended: its output, a panic with the message the module saved before it - * trapped, or an abort, which trapped without running the panic hook. + * trapped, an abort, which trapped without running the panic hook, or what JS threw + * through it, as an import without `catch` throws. */ -export type Outcome = Uint8Array | { panic: string } | "abort"; +export type Outcome = Uint8Array | { panic: string } | { thrown: string } | "abort"; export class Wasm { #module: WebAssembly.Module; - #exports: Exports; + #exports!: Exports; + #glued: boolean; /** What a fixture's panic hooks wrote before the last call trapped, if it exports `note`. */ note = ""; + /** Whether the last call left the instance mid-call, which only `renew` replaces for a module with glue. */ + stale = false; - constructor(module: WebAssembly.Module) { + constructor(module: WebAssembly.Module, glue?: Glue) { this.#module = module; - this.#exports = new WebAssembly.Instance(module, {}).exports as unknown as Exports; + this.#glued = glue !== undefined; + this.#instantiate(glue); + } + + #instantiate(glue?: Glue) { + const imports: WebAssembly.Imports = {}; + for (const { module } of WebAssembly.Module.imports(this.#module)) { + if (glue) imports[module] = glue as WebAssembly.ModuleImports; + } + this.#exports = new WebAssembly.Instance(this.#module, imports).exports as unknown as Exports; + if (glue) { + glue.__wbg_set_wasm(this.#exports); + this.#exports.__wbindgen_start?.(); + } + } + + /** A fresh instance, with a fresh load of the glue's module, after a call left this one stale. */ + renew(glue: Glue) { + this.#instantiate(glue); + this.stale = false; + } + + /** Leave the instance after a call that left it mid-call: a fresh one now, or with glue, once `renew` loads its module anew. */ + #leave() { + if (this.#glued) this.stale = true; + else this.#instantiate(); } /** Copy `input` into the instance, where the next `run` reads it. */ @@ -53,13 +91,17 @@ export class Wasm { this.input(input); return this.output(this.run()); } catch (err) { - if (!(err instanceof WebAssembly.RuntimeError)) throw err; + if (!(err instanceof WebAssembly.RuntimeError)) { + if (!this.#glued) throw err; + this.#leave(); + return { thrown: `${err}` }; + } const ptr = this.#exports.message(); const text = (ptr: number, len: number) => new TextDecoder().decode(new Uint8Array(this.#exports.memory.buffer, ptr, len)); const outcome: Outcome = ptr === 0 ? "abort" : { panic: text(ptr, this.#exports.message_len()) }; if (this.#exports.note && this.#exports.note_len) this.note = text(this.#exports.note(), this.#exports.note_len()); // A trap leaves the instance mid-call: start a fresh one. - this.#exports = new WebAssembly.Instance(this.#module, {}).exports as unknown as Exports; + this.#leave(); return outcome; } }