From 7ae6f50e11513e839cc3365af3a83162d4228723 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 07:21:04 -0800 Subject: [PATCH 1/4] chore(quest): claim std::panic Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 + quest/m3/ts-std-panic.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 quest/m3/ts-std-panic.md diff --git a/quest/m3/README.md b/quest/m3/README.md index bd7ae0e67..6cd382eaa 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -41,3 +41,4 @@ generic std gap is planned here even when moq never calls it. - [A map built with BuildHasherDefault](/quest/m3/ts-build-hasher-default.md) - `FxHashMap`-style maps, of `BuildHasherDefault` over a crate's own `Hasher`, translate - [Equality of hash maps and sets](/quest/m3/ts-hash-map-eq.md) - `==` of two hash maps or sets translates as std's - [A name or an operator argument at its own Rust span](/quest/m3/ts-argument-spans.md) - a `tsc` error on a plain-name or operator argument names that argument's Rust, not the method it is passed to +- [std::panic](/quest/m3/ts-std-panic.md) - `AssertUnwindSafe`, `catch_unwind`, `resume_unwind`, and `panic_any` translate as native's under `panic=abort` diff --git a/quest/m3/ts-std-panic.md b/quest/m3/ts-std-panic.md new file mode 100644 index 000000000..9fe3e517d --- /dev/null +++ b/quest/m3/ts-std-panic.md @@ -0,0 +1,16 @@ +# [M] std::panic + +## Goal + +[rust2-ts](/ts/README.md) translates the stable API of `std::panic` that can match native +Rust under wasm32's `panic=abort`: `AssertUnwindSafe` made, read, dereferenced, called, +awaited, printed, and defaulted, `catch_unwind`, `resume_unwind`, and `panic_any`, proven by +a differential test against native Rust. What can't match stays refused with its reason. + +## Plan + +Found by [#528](https://github.com/moq-dev/rust2/pull/528): `AssertUnwindSafe` can't be +made, so it can't be a future or a function either. A panic aborts, so `catch_unwind` only +ever returns `Ok`, and `resume_unwind` aborts without running the hook. + +Public API: the translator's output. From 79ac3692e44e3b576b56cf19429f5780bfbe0373 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 10:49:20 -0800 Subject: [PATCH 2/4] feat(ts): translate std::panic where it matches panic=abort Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 3 +- quest/m3/ts-panic-hook.md | 22 + quest/m3/ts-panic-location.md | 29 + quest/m3/ts-std-panic.md | 16 - ts/Cargo.lock | 4 + ts/README.md | 12 +- ts/fixtures/lang/panics/Cargo.toml | 14 + .../lang/panics/expected/core/any.Any.ts | 5 + .../lang/panics/expected/core/option.ts | 12 + .../lang/panics/expected/core/result.ts | 20 + .../lang/panics/expected/core/task/poll.ts | 16 + ts/fixtures/lang/panics/expected/harness.ts | 58 ++ ts/fixtures/lang/panics/expected/lib.ts | 50 ++ .../lang/panics/expected/panics.Countdown.ts | 20 + .../lang/panics/expected/panics.Logged.ts | 26 + .../lang/panics/expected/panics.Point.ts | 24 + ts/fixtures/lang/panics/expected/panics.ts | 513 ++++++++++++++++++ ts/fixtures/lang/panics/src/lib.rs | 24 + ts/fixtures/lang/panics/src/panics.rs | 295 ++++++++++ ts/fixtures/lang/panics/ts.toml | 2 + ts/fixtures/reject/census.txt | 4 + ts/fixtures/reject/expected.txt | 4 + ts/fixtures/reject/src/lib.rs | 1 + ts/fixtures/reject/src/panics.rs | 14 + ts/fixtures/reject/ts.toml | 2 + ts/runtime/rt.ts | 5 + ts/src/lower/body.rs | 8 +- ts/src/lower/copy.rs | 5 +- ts/src/lower/drop.rs | 34 +- ts/src/lower/dynamic.rs | 4 +- ts/src/lower/fmt.rs | 12 +- ts/src/lower/library.rs | 85 ++- ts/src/lower/mir/call.rs | 16 +- ts/src/lower/mir/place.rs | 1 + ts/src/lower/mir/rvalue.rs | 2 +- ts/src/lower/mod.rs | 8 + ts/src/lower/task.rs | 10 +- ts/tests/fixtures.rs | 1 + 38 files changed, 1325 insertions(+), 56 deletions(-) create mode 100644 quest/m3/ts-panic-hook.md create mode 100644 quest/m3/ts-panic-location.md delete mode 100644 quest/m3/ts-std-panic.md create mode 100644 ts/fixtures/lang/panics/Cargo.toml create mode 100644 ts/fixtures/lang/panics/expected/core/any.Any.ts create mode 100644 ts/fixtures/lang/panics/expected/core/option.ts create mode 100644 ts/fixtures/lang/panics/expected/core/result.ts create mode 100644 ts/fixtures/lang/panics/expected/core/task/poll.ts create mode 100644 ts/fixtures/lang/panics/expected/harness.ts create mode 100644 ts/fixtures/lang/panics/expected/lib.ts create mode 100644 ts/fixtures/lang/panics/expected/panics.Countdown.ts create mode 100644 ts/fixtures/lang/panics/expected/panics.Logged.ts create mode 100644 ts/fixtures/lang/panics/expected/panics.Point.ts create mode 100644 ts/fixtures/lang/panics/expected/panics.ts create mode 100644 ts/fixtures/lang/panics/src/lib.rs create mode 100644 ts/fixtures/lang/panics/src/panics.rs create mode 100644 ts/fixtures/lang/panics/ts.toml create mode 100644 ts/fixtures/reject/src/panics.rs diff --git a/quest/m3/README.md b/quest/m3/README.md index 6cd382eaa..df7bc92a7 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -41,4 +41,5 @@ generic std gap is planned here even when moq never calls it. - [A map built with BuildHasherDefault](/quest/m3/ts-build-hasher-default.md) - `FxHashMap`-style maps, of `BuildHasherDefault` over a crate's own `Hasher`, translate - [Equality of hash maps and sets](/quest/m3/ts-hash-map-eq.md) - `==` of two hash maps or sets translates as std's - [A name or an operator argument at its own Rust span](/quest/m3/ts-argument-spans.md) - a `tsc` error on a plain-name or operator argument names that argument's Rust, not the method it is passed to -- [std::panic](/quest/m3/ts-std-panic.md) - `AssertUnwindSafe`, `catch_unwind`, `resume_unwind`, and `panic_any` translate as native's under `panic=abort` +- [A panic's Location](/quest/m3/ts-panic-location.md) - `Location::caller` and `#[track_caller]` translate with native Rust's locations +- [A panic hook](/quest/m3/ts-panic-hook.md) - `set_hook`, `take_hook`, and `PanicHookInfo` translate, every panic running the hook diff --git a/quest/m3/ts-panic-hook.md b/quest/m3/ts-panic-hook.md new file mode 100644 index 000000000..7a01d1585 --- /dev/null +++ b/quest/m3/ts-panic-hook.md @@ -0,0 +1,22 @@ +# [L] A panic hook + +## Goal + +[rust2-ts](/ts/README.md) translates `panic::set_hook`, `take_hook`, and the +`PanicHookInfo` a hook is given: its payload, `payload_as_str`, `location`, and `Display`, +with every panic the translation makes running the hook as native Rust's does under +`panic=abort`, proven by a differential test. + +## Plan + +Split from the PR that translated the rest of `std::panic`, which refuses both functions +with a span. A hook reads each panic's `Location`, so every panic needs one, the runtime's +own too, such as an index out of bounds or an overflow, at the location std's +`#[track_caller]` reports. The differential harness sets its own hook natively, so a case +that sets one must leave the harness able to read the message. + +Public API: the translator's output, and the runtime's `Panic`. + +## Required + +- [A panic's Location](/quest/m3/ts-panic-location.md) - `Location::caller` and `#[track_caller]` translate diff --git a/quest/m3/ts-panic-location.md b/quest/m3/ts-panic-location.md new file mode 100644 index 000000000..54ae8e753 --- /dev/null +++ b/quest/m3/ts-panic-location.md @@ -0,0 +1,29 @@ +# [L] A panic's Location + +## Goal + +[rust2-ts](/ts/README.md) translates `panic::Location`: `Location::caller` in the crate's +functions, `#[track_caller]` ones included, and `file`, `line`, `column`, `Display`, +`Debug`, and comparisons of what it gives, matching native Rust's values, proven by a +differential test. + +## Plan + +Split from the PR that translated the rest of `std::panic`, which refuses `Location` with a +span. Natively, `Location::caller` in a function without `#[track_caller]` is a constant of +its call's span, and in one with it, the location its caller passed, so a translation has to +thread that location through each call of such a function, as rustc's codegen does. + +Things to settle while building it: + +- The file path rustc reports, which must match the native build's. +- Calls that reach a `#[track_caller]` function indirectly: through a function pointer, a + trait object, or std's generic code calling the crate's trait impl. Refuse what can't + match, with a span. +- What MIR inlining does to the spans the translator reads. + +Public API: the translator's output. + +## Related + +- [A panic hook](/quest/m3/ts-panic-hook.md) - a hook reads each panic's `Location` diff --git a/quest/m3/ts-std-panic.md b/quest/m3/ts-std-panic.md deleted file mode 100644 index 9fe3e517d..000000000 --- a/quest/m3/ts-std-panic.md +++ /dev/null @@ -1,16 +0,0 @@ -# [M] std::panic - -## Goal - -[rust2-ts](/ts/README.md) translates the stable API of `std::panic` that can match native -Rust under wasm32's `panic=abort`: `AssertUnwindSafe` made, read, dereferenced, called, -awaited, printed, and defaulted, `catch_unwind`, `resume_unwind`, and `panic_any`, proven by -a differential test against native Rust. What can't match stays refused with its reason. - -## Plan - -Found by [#528](https://github.com/moq-dev/rust2/pull/528): `AssertUnwindSafe` can't be -made, so it can't be a future or a function either. A panic aborts, so `catch_unwind` only -ever returns `Ok`, and `resume_unwind` aborts without running the hook. - -Public API: the translator's output. diff --git a/ts/Cargo.lock b/ts/Cargo.lock index 55c314d64..1b64520dc 100644 --- a/ts/Cargo.lock +++ b/ts/Cargo.lock @@ -359,6 +359,10 @@ version = "0.0.0" name = "lang-orders-fixture" version = "0.0.0" +[[package]] +name = "lang-panics-fixture" +version = "0.0.0" + [[package]] name = "lang-pointers-fixture" version = "0.0.0" diff --git a/ts/README.md b/ts/README.md index b86c6cae3..ee69c7e3f 100644 --- a/ts/README.md +++ b/ts/README.md @@ -106,7 +106,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `ControlFlow` | `rt.Result`, `Ok` to continue; what `?` branches on is the value it was applied to, but for a `Poll`'s `Pending` and `Ready(None)`, which continue as `Ok` of themselves | | tuple, `[T; N]`, `&[T]`, `Box<[T]>`, `Vec` | an array; for bytes, `Uint8Array` for `[u8; N]`, `rt.ByteSlice` for `&[u8]`, `&[u8; N]`, and `Box<[u8]>`, and `rt.ByteVec` for `Vec` | | `Cow<[T]>` | `rt.Cow`: the `Vec` it owns, or an `rt.Borrowed` holding the slice it borrows; `rt.ByteCow` for bytes, an `rt.ByteVec` or an `rt.ByteSlice` | -| `&T`, `&mut T`, `Box`, `Reverse`, `NonZero` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, a `String`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rt.Place`, which for what a box holds is the place holding the box | +| `&T`, `&mut T`, `Box`, `Reverse`, `NonZero`, `AssertUnwindSafe` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, a `String`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rt.Place`, which for what a box holds is the place holding the box | | `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rt.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, or copy the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to an array of other values than bytes, which a slice lends as a copy of its own; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rt` class such as `rt.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | | `MaybeUninit` | `T`: `undefined` until written, or an object whose parts kept as objects are there, holding `undefined` | | `ManuallyDrop` | `T`, which nothing drops but its own `drop`; its `Debug`, comparisons, and constants are refused | @@ -171,7 +171,11 @@ wraps silently. A panic throws `rt.Panic`, with the message native Rust panics w aborts as wasm32's does: it runs no cleanup, so what the translation held is undefined after one, as a wasm trap leaves it, and a caller that catches `rt.Panic` must not use it again. An abort without a panic, as std's for an `RwLock` locked again while held, -throws `rt.Abort`, a `Panic` with std's abort message. +throws `rt.Abort`, a `Panic` with std's abort message. So `catch_unwind` only ever returns +`Ok`, `resume_unwind` throws `rt.Abort` without running the hook, and `panic_any` panics +with its payload if that is text, or `Box` as std's hook writes any other. A +`Location`, and with it `set_hook` and `take_hook`, is refused: translated panics carry +none. Each integer type is its own type to `tsc`, named as Rust's: `rt.u8` is a `number` with a type-only brand, so a `u16` where a `u8` goes fails the type check, while a plain @@ -281,7 +285,7 @@ its length, as std's do. `Debug` of std's iterator adaptors, `Map` through `Flat `DecodeUtf16`, and a `str`'s escapes, writes their fields as std's does, each iterator it wraps by its own `Debug`, no closure, and the state each keeps, such as what a `Peekable` peeked, what a `Skip` has left to skip, and the countdown std's `StepBy` of a small unsigned range keeps -in its end. `Debug` of `Bound`, `Ordering`, `Reverse`, and `ControlFlow` is +in its end. `Debug` of `Bound`, `Ordering`, `Reverse`, `AssertUnwindSafe`, and `ControlFlow` is their derived one, whichever way each is held, and a guard of a cell or a lock, mapped or not, writes what it lends by its own `Debug` and `Display`. `Debug` of a `Cell`, a `RefCell`, a `Mutex`, and an `RwLock` writes what each holds, or `` and @@ -544,7 +548,7 @@ whose `IoSlice` has no JS value, so a call of it fails. A `dyn Any` is such an object with no methods, whose class `is`, `downcast_ref`, `downcast_mut`, and `Box`'s, `Arc`'s, and `Rc`'s `downcast` check for: `a instanceof of$dyns_Square ? -a.v : undefined`. A `&mut` one of a value JS copies is the class holding its `rt.Place`, +a.v : undefined`. Its `Debug` writes `Any { .. }`, as std's does. A `&mut` one of a value JS copies is the class holding its `rt.Place`, or the class itself behind a `Box`, as a `&mut dyn Trait`'s is, and `Arc::downcast` gives back the `Arc` the object was made from, or, of one made from a box, an `rt.ArcDyn` of the value sharing its count. diff --git a/ts/fixtures/lang/panics/Cargo.toml b/ts/fixtures/lang/panics/Cargo.toml new file mode 100644 index 000000000..b2e127c71 --- /dev/null +++ b/ts/fixtures/lang/panics/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lang-panics-fixture" +description = "A part of the language rust2-ts translates, exercised against native Rust" +version = "0.0.0" +edition.workspace = true +license.workspace = true +publish = false + +[lib] +# The cdylib is the native side of the differential test, built for wasm32. +crate-type = ["lib", "cdylib"] + +[lints] +workspace = true diff --git a/ts/fixtures/lang/panics/expected/core/any.Any.ts b/ts/fixtures/lang/panics/expected/core/any.Any.ts new file mode 100644 index 000000000..ba72bb625 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/core/any.Any.ts @@ -0,0 +1,5 @@ +// Generated by rust2-ts. Do not edit. + +export type Any = object; + +//# sourceMappingURL=any.Any.ts.map diff --git a/ts/fixtures/lang/panics/expected/core/option.ts b/ts/fixtures/lang/panics/expected/core/option.ts new file mode 100644 index 000000000..0fb1b6278 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/core/option.ts @@ -0,0 +1,12 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; + +export function unwrapOr$0r2bmpr(self: rt.u8 | undefined, default_: rt.u8): rt.u8 { + if (self === undefined) { + return default_; + } + return self; +} + +//# sourceMappingURL=option.ts.map diff --git a/ts/fixtures/lang/panics/expected/core/result.ts b/ts/fixtures/lang/panics/expected/core/result.ts new file mode 100644 index 000000000..4955b47a9 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/core/result.ts @@ -0,0 +1,20 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import type * as Any from "../core/any.Any.ts"; + +export function asRef$16amweg(self: rt.Result): rt.Result { + if (self.ok) { + return rt.Ok(self.value); + } + return rt.Err(self.error); +} + +export function mapOr$059pexg(self: rt.Result, default_: rt.u8, f: (a0: rt.u8) => rt.u8): rt.u8 { + if (self.ok) { + return f(self.value); + } + return default_; +} + +//# sourceMappingURL=result.ts.map diff --git a/ts/fixtures/lang/panics/expected/core/task/poll.ts b/ts/fixtures/lang/panics/expected/core/task/poll.ts new file mode 100644 index 000000000..d2153f840 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/core/task/poll.ts @@ -0,0 +1,16 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../../rt.ts"; + +export function isReady$1ntq2i8(self: rt.Poll): boolean { + if (self !== rt.Pending) { + return true; + } + return false; +} + +export function isPending$0vrdvv1(self: rt.Poll): boolean { + return !isReady$1ntq2i8(self); +} + +//# sourceMappingURL=poll.ts.map diff --git a/ts/fixtures/lang/panics/expected/harness.ts b/ts/fixtures/lang/panics/expected/harness.ts new file mode 100644 index 000000000..a2460a9db --- /dev/null +++ b/ts/fixtures/lang/panics/expected/harness.ts @@ -0,0 +1,58 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { cases } from "./lib.ts"; + +/** Run one case. */ +export function run(input: rt.ByteSlice): rt.ByteVec { + const out: rt.ByteVec = new rt.ByteVec(); + if (!(input.length >= 3)) { + return out; + } + const cases_: [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] = cases(); + cases_[rt.idx(cases_, rt.usizeRem(((input.data[input.start] << 8) | input.data[input.start + 1]) as rt.usize, cases_.length))][1](input.data[input.start + 2], new rt.ByteSlice(input.data, input.start + 3, input.length - 3), out); + return out; +} + +/** + * The first two bytes, as one big-endian number, that pick the case named `name`, which + * `lang.ts` targets by. + */ +export function op(name: string): rt.u16 { + const _11: rt.usize | undefined = rt.iterPosition(rt.iterSlice(cases()), (_2: [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]): boolean => { + return _2[0] === name; + }); + if (_11 !== undefined) { + const _20: rt.Result = rt.u16TryFrom(_11); + if (_20.ok) { + if ((_20.value >> 8) !== 255) { + return _20.value; + } + } + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, name)]; + rt.panicFmt(["case ", args4[0], " is past what two bytes pick"]); + } else { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, name)]; + rt.panicFmt(["no case ", args2[0]]); + } +} + +/** A char of the alphabet for each byte, by its low bits. */ +export function text(data: rt.ByteSlice): string { + let s: string = ""; + const iterS: Uint8Array = rt.byteSliceView(data); + let iterI: number = 0; + while (true) { + const _5: rt.u8 | rt.Done = iterI < iterS.length ? iterS[iterI++] : rt.DONE; + if (_5 === rt.DONE) { + return s; + } + const _13: rt.usize = rt.u8Rem(_5, 16) as rt.usize; + if (!(_13 < 16)) { + rt.panic("index out of bounds: the len is " + 16 + " but the index is " + _13); + } + s = s + String.fromCodePoint([47, 92, 97, 48, 57, 127, 128, 233, 2047, 2048, 55295, 57344, 65535, 65536, 128512, 1114111][_13]); + } +} + +//# sourceMappingURL=harness.ts.map diff --git a/ts/fixtures/lang/panics/expected/lib.ts b/ts/fixtures/lang/panics/expected/lib.ts new file mode 100644 index 000000000..55b1b49fb --- /dev/null +++ b/ts/fixtures/lang/panics/expected/lib.ts @@ -0,0 +1,50 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import type * as Any from "./core/any.Any.ts"; +import { wrapped, called, awaited, raised } from "./panics.ts"; +import type * as Point from "./panics.Point.ts"; + +export function copy$panics_Point(v: Point.Point): Point.Point { + return { x: v.x, y: v.y }; +} + +export function drop$Result_u8_boxed_Box_dyn_Any_marker_Send(_1: rt.Result): void { + if (!_1.ok) { + rt.drop(_1.error); + } +} + +export class of$String { + v: string; + + constructor(v: string) { + this.v = v; + } +} + +export class of$ref_str { + v: string; + + constructor(v: string) { + this.v = v; + } +} + +export class of$u8 { + v: rt.u8; + + constructor(v: rt.u8) { + this.v = v; + } +} + +/** + * Each case's name and what it runs. The first two bytes, big-endian, pick one modulo + * their count, so a case added at the end moves no other. + */ +export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] { + return [["panics::wrapped", wrapped as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["panics::called", called as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["panics::awaited", awaited as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["panics::raised", raised as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; +} + +//# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/lang/panics/expected/panics.Countdown.ts b/ts/fixtures/lang/panics/expected/panics.Countdown.ts new file mode 100644 index 000000000..506b89705 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/panics.Countdown.ts @@ -0,0 +1,20 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** A future to poll: ready after `n` polls, waking itself on each that is pending. */ +export interface Countdown { + _0: rt.u8; +} + +export function poll$Future(self: Countdown, cx: rt.Context): rt.Poll { + if (!(self._0 === 0)) { + const _7: Countdown = self; + _7._0 = rt.u8Sub(_7._0, 1); + cx.wakeByRef(); + return rt.Pending; + } + return 7; +} + +//# sourceMappingURL=panics.Countdown.ts.map diff --git a/ts/fixtures/lang/panics/expected/panics.Logged.ts b/ts/fixtures/lang/panics/expected/panics.Logged.ts new file mode 100644 index 000000000..a098971bd --- /dev/null +++ b/ts/fixtures/lang/panics/expected/panics.Logged.ts @@ -0,0 +1,26 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** Logs its byte as it drops. */ +export class Logged { + _0: rt.u8; + _1: rt.RefCell; + + constructor(_0: rt.u8, _1: rt.RefCell) { + this._0 = _0; + this._1 = _1; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function drop$ops_Drop(self: Logged): void { + const _5: rt.RefCell = rt.refCellBorrowMut(self._1); + rt.byteVecPush(_5.value, self._0); + rt.refMutRelease(_5); +} + +//# sourceMappingURL=panics.Logged.ts.map diff --git a/ts/fixtures/lang/panics/expected/panics.Point.ts b/ts/fixtures/lang/panics/expected/panics.Point.ts new file mode 100644 index 000000000..affb31107 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/panics.Point.ts @@ -0,0 +1,24 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export function fmt$fmt_Debug(self: Point, f: rt.Formatter): rt.Result { + const _3: string = "Point"; + const _4: string = "x"; + const _5: rt.Fmt = (f: rt.Formatter) => rt.u8Debug(self.x, f); + const _7: string = "y"; + const _8: rt.Fmt = (f: rt.Formatter) => rt.u8Debug(self.y, f); + return rt.debugStructFinish(rt.debugStructField(rt.debugStructField(rt.fmtDebugStruct(f, _3), _4, _5), _7, _8)); +} + +export function default$Default(): Point { + return { x: 0, y: 0 }; +} + +/** A point, which JS copies. */ +export interface Point { + x: rt.u8; + y: rt.u8; +} + +//# sourceMappingURL=panics.Point.ts.map diff --git a/ts/fixtures/lang/panics/expected/panics.ts b/ts/fixtures/lang/panics/expected/panics.ts new file mode 100644 index 000000000..a2c45aab0 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/panics.ts @@ -0,0 +1,513 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { copy$panics_Point, drop$Result_u8_boxed_Box_dyn_Any_marker_Send, of$String, of$ref_str, of$u8 } from "./lib.ts"; +import type * as Any from "./core/any.Any.ts"; +import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr } from "./core/option.ts"; +import { asRef$16amweg, mapOr$059pexg as result_mapOr$059pexg } from "./core/result.ts"; +import { isPending$0vrdvv1 } from "./core/task/poll.ts"; +import { text } from "./harness.ts"; +import * as Countdown from "./panics.Countdown.ts"; +import * as Logged from "./panics.Logged.ts"; +import * as Point from "./panics.Point.ts"; + +export function seven(): rt.u8 { + return 7; +} + +/** What `f` returns, through `catch_unwind`. */ +export function caught$String_panics_called_closure6(f: () => string): string { + const _2: rt.Result = rt.Ok(f()); + if (_2.ok) { + return _2.value; + } + rt.panicFmt(["internal error: entered unreachable code: a panic aborts"]); +} + +/** What `f` returns, through `catch_unwind`. */ +export function caught$Vec_u8_panics_called_closure5(f: () => rt.ByteVec): rt.ByteVec { + const _2: rt.Result = rt.Ok(f()); + if (_2.ok) { + return _2.value; + } + rt.panicFmt(["internal error: entered unreachable code: a panic aborts"]); +} + +/** What `f` returns, through `catch_unwind`. */ +export function caught$u8_boxed_Box_dyn_ops_FnOnce_unit_u8_panic_UnwindSafe(f: () => rt.u8): rt.u8 { + const _2: rt.Result = rt.Ok(f()); + if (_2.ok) { + return _2.value; + } + rt.panicFmt(["internal error: entered unreachable code: a panic aborts"]); +} + +/** What `f` returns, through `catch_unwind`. */ +export function caught$u8_panics_called_closure4(f: () => rt.u8): rt.u8 { + const _2: rt.Result = rt.Ok(f()); + if (_2.ok) { + return _2.value; + } + rt.panicFmt(["internal error: entered unreachable code: a panic aborts"]); +} + +/** What `f` returns, through `catch_unwind`. */ +export function caught$u8_panics_seven(_: () => rt.u8): rt.u8 { + const _2: rt.Result = rt.Ok(seven()); + if (_2.ok) { + return _2.value; + } + rt.panicFmt(["internal error: entered unreachable code: a panic aborts"]); +} + +/** What `f` returns, through `catch_unwind`. */ +export function caught$u8_ref_panics_called_closure7(f: () => rt.u8): rt.u8 { + const _2: rt.Result = rt.Ok(f()); + if (_2.ok) { + return _2.value; + } + rt.panicFmt(["internal error: entered unreachable code: a panic aborts"]); +} + +/** `AssertUnwindSafe` of values, read and written as what it holds. */ +export function wrapped(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + let f!: rt.ByteVec; + let _199: boolean = false; + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + const _7: rt.u8 = rt.u8Rem(kind, 6); + if (_7 === 0) { + let w: rt.usize = data.length; + w = rt.usizeAdd(w, 1); + rt.byteVecPush(out, w & 0xff); + w = rt.usizeWrappingMul(w, 3); + rt.byteVecPush(out, w & 0xff); + w = rt.usizeAdd(w, first as rt.usize); + rt.byteVecPush(out, w & 0xff); + } else if (_7 === 1) { + const v: rt.ByteVec = rt.byteSliceToByteVec(data); + rt.byteVecPush(v, first); + rt.byteVecPush(v, 9); + rt.byteVecRetain(v, (b2: rt.u8): boolean => { + return rt.u8Rem(b2, 2) === 0; + }); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(v)); + rt.byteVecPush(out, v.length & 0xff); + } else if (_7 === 2) { + const p: Point.Point = { x: first, y: 2 }; + const _54: rt.u8 = rt.u8WrappingAdd(p.x, 1); + p.x = _54; + const _60: rt.u8 = rt.u8WrappingMul(p.y, first); + p.y = _60; + const copy: Point.Point = copy$panics_Point(p); + p.x = 0; + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(copy.x, copy.y, p.x, p.y)), undefined, true); + let nested: rt.u8 = first; + nested = rt.u8WrappingAdd(nested, 5); + rt.byteVecPush(out, nested); + } else if (_7 === 3) { + const _97: rt.u8 = first; + const _99: rt.u8 = first; + const _101: string = text(data); + const _103: rt.i8 = -1; + const _105: Point.Point = { x: first, y: 0 }; + const _109: [rt.u8, string] = [first, "a"]; + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.u8Debug(_97, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.u8Debug(_99, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtStr(_101, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.i8Debug(_103, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => Point.fmt$fmt_Debug(_105, f)])]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.u8Debug(_109[0], f), (f: rt.Formatter) => rt.fmtStr(_109[1], f)], f)])]; + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(rt.fmtFormat([args2[0], "|", rt.fmtSpec({ alternate: true }, args2[1]), "|", args2[2], "|", rt.fmtSpec({ width: 5 }, args2[3]), "|", args2[4], "|", rt.fmtSpec({ alternate: true }, args2[5])]))); + } else if (_7 === 4) { + const d: rt.u8 = 0; + const s2: string = ""; + const p2: Point.Point = Point.default$Default(); + const v2: rt.ByteVec = new rt.ByteVec(); + const e: rt.u8 = first; + f = rt.byteSliceToByteVec(data); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(d, rt.stringLen(s2) & 0xff, p2.x, p2.y, v2.length & 0xff, e, f.length & 0xff)), undefined, true); + } else { + const log: rt.RefCell = new rt.RefCell(new rt.ByteVec()); + const a: Logged.Logged = new Logged.Logged(1, log); + let b: Logged.Logged = new Logged.Logged(2, log); + const _163: Logged.Logged = new Logged.Logged(3, log); + _199 = true; + const c: Logged.Logged = _163; + a[Symbol.dispose](); + const _167: Logged.Logged = new Logged.Logged(4, log); + const _169: Logged.Logged = b; + _169[Symbol.dispose](); + Object.assign(_169, _167); + const _171: Logged.Logged = new Logged.Logged(5, log); + b[Symbol.dispose](); + b = _171; + if (rt.u8Rem(first, 2) === 0) { + _199 = false; + c[Symbol.dispose](); + } + const _181: rt.RefCell = rt.refCellBorrowMut(log); + rt.byteVecPush(_181.value, b._0); + rt.refMutRelease(_181); + b[Symbol.dispose](); + const _191: rt.RefCell = rt.refCellBorrow(log); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_191.value))), undefined, true); + rt.refRelease(_191); + if (_199) { + c[Symbol.dispose](); + } + _199 = false; + } +} + +/** `AssertUnwindSafe` of functions called, and `catch_unwind` of each kind of function. */ +export function called(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + const _8: rt.RefCell = new rt.RefCell(new rt.ByteVec()); + const _10: rt.u8 = rt.u8Rem(kind, 7); + if (_10 === 0) { + const n: rt.usize = ((): rt.usize => { + return data.length; + })(); + const s: rt.u8 = seven(); + let count: rt.u8 = first; + ((): undefined => { + count = rt.u8WrappingAdd(count, 3); + })(); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(n & 0xff, s, count)), undefined, true); + } else if (_10 === 1) { + const owned: Logged.Logged = new Logged.Logged(1, _8); + const kept: Logged.Logged = new Logged.Logged(2, _8); + const captured3 = owned._0; + const captured4 = first; + const r: rt.u8 = ((): rt.u8 => { + const _510: rt.RefCell = rt.refCellBorrowMut(_8); + rt.byteVecPush(_510.value, rt.u8Add(10, captured3)); + rt.refMutRelease(_510); + return captured4; + })(); + const _33: rt.RefCell = rt.refCellBorrowMut(_8); + rt.byteVecPush(_33.value, r); + rt.refMutRelease(_33); + const captured5 = kept; + const back: Logged.Logged = rt.closure((): Logged.Logged => { + return captured5; + }, (): void => { + captured5[Symbol.dispose](); + })(); + const _40: rt.RefCell = rt.refCellBorrowMut(_8); + rt.byteVecPush(_40.value, rt.u8Add(20, back._0)); + rt.refMutRelease(_40); + back[Symbol.dispose](); + const _50: rt.RefCell = rt.refCellBorrow(_8); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_50.value))), undefined, true); + rt.refRelease(_50); + owned[Symbol.dispose](); + } else if (_10 === 2) { + rt.byteVecPush(out, caught$u8_panics_called_closure4((): rt.u8 => { + return data.length & 0xff; + })); + rt.byteVecPush(out, caught$u8_panics_seven(seven)); + rt.byteVecPush(out, seven()); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(caught$Vec_u8_panics_called_closure5((): rt.ByteVec => { + return rt.iterToByteVec(rt.iterRev(rt.iterSlice(rt.byteSliceView(data))), undefined, true); + }))); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(caught$String_panics_called_closure6((): string => { + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(first, f)]; + return rt.fmtFormat([args6[0]]); + }))); + } else if (_10 === 3) { + rt.byteVecPush(out, caught$u8_ref_panics_called_closure7((): rt.u8 => { + return rt.u8WrappingMul(first, 2); + })); + let sum: rt.u8 = 0; + rt.byteVecPush(out, ((): rt.u8 => { + const iterS: Uint8Array = rt.byteSliceView(data); + let iterI: number = 0; + while (true) { + const _59: rt.u8 | rt.Done = iterI < iterS.length ? iterS[iterI++] : rt.DONE; + if (_59 === rt.DONE) { + return sum; + } + sum = rt.u8WrappingAdd(sum, _59); + } + })()); + rt.byteVecPush(out, sum); + } else if (_10 === 4) { + const owned2: Logged.Logged = new Logged.Logged(1, _8); + const captured = owned2._0; + const r3: rt.Result = rt.Ok(((): rt.u8 => { + const _56: rt.RefCell = rt.refCellBorrowMut(_8); + rt.byteVecPush(_56.value, rt.u8Add(10, captured)); + rt.refMutRelease(_56); + return captured; + })()); + const _92: rt.RefCell = rt.refCellBorrowMut(_8); + const _93: rt.u8 = rt.u8Add(30, rt.resultUnwrap(r3, (_value: Any.Any, f: rt.Formatter) => rt.fmtWriteStr(f, "Any { .. }"))); + rt.byteVecPush(_92.value, _93); + rt.refMutRelease(_92); + const captured2 = first; + const boxed: () => rt.u8 = (): rt.u8 => { + return rt.u8WrappingAdd(captured2, 1); + }; + const _102: rt.RefCell = rt.refCellBorrowMut(_8); + const _103: rt.u8 = caught$u8_boxed_Box_dyn_ops_FnOnce_unit_u8_panic_UnwindSafe(boxed); + rt.byteVecPush(_102.value, _103); + rt.refMutRelease(_102); + const _109: rt.RefCell = rt.refCellBorrow(_8); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_109.value))), undefined, true); + rt.refRelease(_109); + owned2[Symbol.dispose](); + } else if (_10 === 5) { + const r4: rt.Result = rt.Ok(((): rt.u8 => { + return data.length & 0xff; + })()); + const _117: boolean = r4.ok; + const _120: rt.u8 = result_mapOr$059pexg(asRef$16amweg(r4), 0, (n2: rt.u8): rt.u8 => { + return n2; + }); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtResult(r4, f, rt.u8Debug, (_value: Any.Any, f: rt.Formatter) => rt.fmtWriteStr(f, "Any { .. }")), (f: rt.Formatter) => rt.fmtBool(_117, f), (f: rt.Formatter) => rt.u8Display(_120, f)]; + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(rt.fmtFormat([args2[0], " ", args2[1], " ", args2[2]]))); + drop$Result_u8_boxed_Box_dyn_Any_marker_Send(r4); + } else { + const owned3: Logged.Logged = new Logged.Logged(1, _8); + rt.byteVecPush(out, ((): rt.u8 => { + if (!(rt.u8Rem(first, 3) === 0)) { + return first; + } + const part = owned3._0; + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(part, f)]; + rt.panicFmt(["caught ", args4[0]]); + })()); + owned3[Symbol.dispose](); + const _147: rt.RefCell = rt.refCellBorrow(_8); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_147.value))), undefined, true); + rt.refRelease(_147); + } +} + +/** `AssertUnwindSafe` of futures, polled by hand and awaited. */ +export function awaited(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + let _22!: rt.Poll; + let _86: boolean = false; + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + const log: rt.RefCell = new rt.RefCell(new rt.ByteVec()); + const cx: rt.Context = rt.NOOP_WAKER; + const _11: rt.u8 = rt.u8Rem(kind, 3); + if (_11 === 0) { + const pinned: Countdown.Countdown = { _0: rt.u8Rem(first, 4) }; + const f: Countdown.Countdown = pinned; + let polls: rt.u8 = 0; + while (true) { + polls = rt.u8Add(polls, 1); + _22 = Countdown.poll$Future(f, cx); + if (_22 !== rt.Pending) { + break; + } + } + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(_22, polls)), undefined, true); + } else if (_11 === 1) { + const pinned2: rt.Future = new awaitedFuture$closure0(rt.u8Rem(first, 3)); + const f2: rt.Future = pinned2; + let polls2: rt.u8 = 0; + while (isPending$0vrdvv1(f2.poll(cx))) { + polls2 = rt.u8Add(polls2, 1); + } + rt.byteVecPush(out, polls2); + rt.drop(pinned2); + } else { + const a: Logged.Logged = new Logged.Logged(1, log); + const b: Logged.Logged = new Logged.Logged(2, log); + const _53: rt.Future = new awaitedFuture$closure1(a._0); + _86 = true; + const unpolled: rt.Future = _53; + const pinned3: rt.Future = new awaitedFuture$closure2(b._0); + const done: rt.Future = pinned3; + while (done.poll(cx) === rt.Pending) { + const _67: rt.RefCell = rt.refCellBorrowMut(log); + rt.byteVecPush(_67.value, 9); + rt.refMutRelease(_67); + } + if (rt.u8Rem(first, 2) === 0) { + _86 = false; + rt.drop(unpolled); + } + const _77: rt.RefCell = rt.refCellBorrowMut(log); + rt.byteVecPush(_77.value, 0); + rt.refMutRelease(_77); + const _84: rt.RefCell = rt.refCellBorrow(log); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_84.value))), undefined, true); + rt.refRelease(_84); + rt.drop(pinned3); + if (_86) { + rt.drop(unpolled); + } + _86 = false; + b[Symbol.dispose](); + a[Symbol.dispose](); + } +} + +export class awaitedFuture$closure0 { + #state: number = 0; + #n: rt.u8; + #inner!: rt.u8; + #awaitee!: rt.Future; + #awaitee$2!: Countdown.Countdown; + + constructor(n: rt.u8) { + this.#n = n; + } + + [Symbol.dispose](): void { + if (this.#state === 3) { + rt.drop(this.#awaitee); + } + } + + poll(_2: rt.Context): rt.Poll { + b0: { + if (this.#state === 0) { + this.#awaitee = new awaitedFuture$closure0_closure0(this.#n); + } else if (this.#state === 1) { + rt.asyncFnResumed(); + } else if (this.#state !== 3) { + break b0; + } + const _7: rt.Poll = this.#awaitee.poll(_2); + if (_7 !== rt.Pending) { + this.#inner = _7; + rt.drop(this.#awaitee); + this.#awaitee$2 = { _0: 0 }; + } else { + this.#state = 3; + return rt.Pending; + } + } + const _18: rt.Poll = Countdown.poll$Future(this.#awaitee$2, _2); + if (_18 !== rt.Pending) { + const _0: rt.Poll = rt.u8Add(_18, this.#inner); + this.#state = 1; + return _0; + } + this.#state = 4; + return rt.Pending; + } +} + +export class awaitedFuture$closure0_closure0 { + #state: number = 0; + #n: rt.u8; + #awaitee!: Countdown.Countdown; + + constructor(n: rt.u8) { + this.#n = n; + } + + [Symbol.dispose](): void { + } + + poll(_2: rt.Context): rt.Poll { + if (this.#state === 0) { + this.#awaitee = { _0: this.#n }; + } else if (this.#state === 1) { + rt.asyncFnResumed(); + } + const _6: rt.Poll = Countdown.poll$Future(this.#awaitee, _2); + if (_6 !== rt.Pending) { + const _0: rt.Poll = rt.u8Add(_6, 1); + this.#state = 1; + return _0; + } + this.#state = 3; + return rt.Pending; + } +} + +export class awaitedFuture$closure1 { + #state: number = 0; + #a0: rt.u8; + + constructor(a0: rt.u8) { + this.#a0 = a0; + } + + [Symbol.dispose](): void { + } + + poll(_2: rt.Context): rt.Poll { + if (this.#state === 0) { + const _0: rt.Poll = this.#a0; + this.#state = 1; + return _0; + } + rt.asyncFnResumed(); + } +} + +export class awaitedFuture$closure2 { + #state: number = 0; + #b0: rt.u8; + #awaitee!: Countdown.Countdown; + + constructor(b0: rt.u8) { + this.#b0 = b0; + } + + [Symbol.dispose](): void { + } + + poll(_2: rt.Context): rt.Poll { + if (this.#state === 0) { + this.#awaitee = { _0: 1 }; + } else if (this.#state === 1) { + rt.asyncFnResumed(); + } + if (Countdown.poll$Future(this.#awaitee, _2) !== rt.Pending) { + const _0: rt.Poll = this.#b0; + this.#state = 1; + return _0; + } + this.#state = 3; + return rt.Pending; + } +} + +/** Panics with a payload, and `Debug` of a `dyn Any`. */ +export function raised(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + const log: rt.RefCell = new rt.RefCell(new rt.ByteVec()); + const _9: rt.u8 = rt.u8Rem(kind, 8); + if (_9 === 0) { + rt.panic("a payload of text"); + } else if (_9 === 1) { + rt.panic(text(data)); + } else if (_9 === 2) { + rt.panic("Box"); + } else if (_9 === 3) { + const _19: rt.RefCell = new rt.RefCell(new rt.ByteVec()); + new Logged.Logged(first, _19)._0; + rt.panic("Box"); + } else if (_9 === 4) { + new of$u8(first); + rt.resumeUnwind(); + } else if (_9 === 5) { + new of$ref_str("text"); + rt.resumeUnwind(); + } else if (_9 === 6) { + const _held: Logged.Logged = new Logged.Logged(1, log); + if (!(rt.u8Rem(first, 2) === 0)) { + rt.byteVecPush(out, first); + _held[Symbol.dispose](); + } else { + rt.panic("Box"); + } + } else { + const boxed: Any.Any = new of$u8(first); + new of$u8(first); + const synced: Any.Any = new of$String(""); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtWriteStr(f, "Any { .. }"), (f: rt.Formatter) => rt.fmtWriteStr(f, "Any { .. }"), (f: rt.Formatter) => rt.fmtWriteStr(f, "Any { .. }"), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtWriteStr(f, "Any { .. }")])]; + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(rt.fmtFormat([args2[0], "|", rt.fmtSpec({ alternate: true }, args2[1]), "|", rt.fmtSpec({ align: ">", width: 20 }, args2[2]), "|", args2[3]]))); + rt.drop(synced); + rt.drop(boxed); + } + const _67: rt.RefCell = rt.refCellBorrow(log); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_67.value))), undefined, true); + rt.refRelease(_67); +} + +//# sourceMappingURL=panics.ts.map diff --git a/ts/fixtures/lang/panics/src/lib.rs b/ts/fixtures/lang/panics/src/lib.rs new file mode 100644 index 000000000..45f096144 --- /dev/null +++ b/ts/fixtures/lang/panics/src/lib.rs @@ -0,0 +1,24 @@ +//! A part of the language the driver translates, exercised against native Rust: what +//! each of its modules says. + +#[path = "../../harness.rs"] +pub mod harness; +mod panics; +#[cfg(target_arch = "wasm32")] +#[path = "../../../wasm.rs"] +mod wasm; +#[cfg(target_arch = "wasm32")] +use harness::run as entry; + +use harness::Case; + +/// Each case's name and what it runs. The first two bytes, big-endian, pick one modulo +/// their count, so a case added at the end moves no other. +fn cases() -> Vec<(&'static str, Case)> { + vec![ + ("panics::wrapped", panics::wrapped), + ("panics::called", panics::called), + ("panics::awaited", panics::awaited), + ("panics::raised", panics::raised), + ] +} diff --git a/ts/fixtures/lang/panics/src/panics.rs b/ts/fixtures/lang/panics/src/panics.rs new file mode 100644 index 000000000..3b72fc853 --- /dev/null +++ b/ts/fixtures/lang/panics/src/panics.rs @@ -0,0 +1,295 @@ +//! `std::panic` under wasm32's `panic=abort`: `AssertUnwindSafe` made, read and written +//! through `Deref`, `DerefMut`, and its field, called, awaited, printed, defaulted, and +//! converted into, dropping what it holds where Rust drops it; `catch_unwind`, which only +//! ever returns `Ok` or panics on, of closures, functions, and wrapped closures that drop +//! what they own; `panic_any`, whose hook writes a payload of text and `Box` for any +//! other; `resume_unwind`, which aborts without the hook; and `Debug` of a `dyn Any`. + +use std::any::Any; +use std::cell::RefCell; +use std::future::Future; +use std::panic::{self, AssertUnwindSafe}; +use std::pin::pin; +use std::task::{Context, Poll, Waker}; + +/// Logs its byte as it drops. +struct Logged<'a>(u8, &'a RefCell>); + +impl Drop for Logged<'_> { + fn drop(&mut self) { + self.1.borrow_mut().push(self.0); + } +} + +/// A point, which JS copies. +#[derive(Clone, Copy, Debug, Default)] +struct Point { + x: u8, + y: u8, +} + +fn seven() -> u8 { + 7 +} + +/// What `f` returns, through `catch_unwind`. +fn caught(f: impl FnOnce() -> R + panic::UnwindSafe) -> R { + match panic::catch_unwind(f) { + Ok(r) => r, + Err(_) => unreachable!("a panic aborts"), + } +} + +/// `AssertUnwindSafe` of values, read and written as what it holds. +pub fn wrapped(kind: u8, data: &[u8], out: &mut Vec) { + let first = data.first().copied().unwrap_or(0); + match kind % 6 { + 0 => { + let mut w = AssertUnwindSafe(data.len()); + *w += 1; + out.push(*w as u8); + w.0 = w.0.wrapping_mul(3); + out.push(w.0 as u8); + let r = &mut *w; + *r += usize::from(first); + out.push(*w as u8); + } + 1 => { + let mut v = AssertUnwindSafe(data.to_vec()); + v.push(first); + v.0.push(9); + v.retain(|b| b % 2 == 0); + out.extend_from_slice(&v); + out.push(v.len() as u8); + } + 2 => { + let mut p = AssertUnwindSafe(Point { x: first, y: 2 }); + p.x = p.x.wrapping_add(1); + p.0.y = p.y.wrapping_mul(first); + let copy = *p; + p.x = 0; + out.extend([copy.x, copy.y, p.x, p.y]); + let mut nested = AssertUnwindSafe(AssertUnwindSafe(first)); + **nested = nested.wrapping_add(5); + out.push(nested.0.0); + } + 3 => { + let text = crate::harness::text(data); + let s = format!( + "{:?}|{:#?}|{:?}|{:5?}|{:?}|{:#?}", + AssertUnwindSafe(first), + AssertUnwindSafe(first), + AssertUnwindSafe(&text), + AssertUnwindSafe(-1i8), + AssertUnwindSafe(AssertUnwindSafe(Point { x: first, y: 0 })), + AssertUnwindSafe((first, "a")), + ); + out.extend_from_slice(s.as_bytes()); + } + 4 => { + let d: AssertUnwindSafe = Default::default(); + let s: AssertUnwindSafe = AssertUnwindSafe::default(); + let p: AssertUnwindSafe = Default::default(); + let v: AssertUnwindSafe> = Default::default(); + let e: AssertUnwindSafe = first.into(); + let f = AssertUnwindSafe::from(data.to_vec()); + out.extend([d.0, s.len() as u8, p.x, p.y, v.len() as u8, *e, f.len() as u8]); + } + _ => { + // Each drops where Rust drops it: moved into a function, replaced, and at the end. + let log = RefCell::new(Vec::new()); + let a = AssertUnwindSafe(Logged(1, &log)); + let mut b = AssertUnwindSafe(Logged(2, &log)); + let c = AssertUnwindSafe(AssertUnwindSafe(Logged(3, &log))); + drop(a); + *b = Logged(4, &log); + b.0 = Logged(5, &log); + if first % 2 == 0 { + drop(c); + } + log.borrow_mut().push(b.0.0); + drop(b); + out.extend(log.borrow().iter()); + } + } +} + +/// `AssertUnwindSafe` of functions called, and `catch_unwind` of each kind of function. +pub fn called(kind: u8, data: &[u8], out: &mut Vec) { + let first = data.first().copied().unwrap_or(0); + let log = &RefCell::new(Vec::new()); + match kind % 7 { + 0 => { + let n = AssertUnwindSafe(|| data.len())(); + let s = AssertUnwindSafe(seven)(); + // A closure that writes what it captured, called once through the wrapper. + let mut count = first; + AssertUnwindSafe(|| count = count.wrapping_add(3))(); + out.extend([n as u8, s, count]); + } + 1 => { + // What the closure owns drops after it returns, as rustc's shim does, or as it moves + // out. + let owned = Logged(1, log); + let kept = Logged(2, log); + let r = AssertUnwindSafe(move || { + log.borrow_mut().push(10 + owned.0); + first + })(); + log.borrow_mut().push(r); + let back = AssertUnwindSafe(move || kept)(); + log.borrow_mut().push(20 + back.0); + drop(back); + out.extend(log.borrow().iter()); + } + 2 => { + out.push(caught(|| data.len() as u8)); + out.push(caught(seven)); + out.push(panic::catch_unwind(seven).unwrap()); + let v = caught(|| data.iter().rev().copied().collect::>()); + out.extend_from_slice(&v); + let s = caught(|| format!("{first}")); + out.extend_from_slice(s.as_bytes()); + } + 3 => { + // A closure borrowed, and one that writes what it borrows, wrapped. + let f = || first.wrapping_mul(2); + out.push(caught(&f)); + let mut sum = 0u8; + let r = panic::catch_unwind(AssertUnwindSafe(|| { + for &b in data { + sum = sum.wrapping_add(b); + } + sum + })); + out.push(r.unwrap()); + out.push(sum); + } + 4 => { + let owned = Logged(1, log); + let r = panic::catch_unwind(AssertUnwindSafe(move || { + log.borrow_mut().push(10 + owned.0); + owned.0 + })); + log.borrow_mut().push(30 + r.unwrap()); + let boxed: Box u8 + panic::UnwindSafe> = Box::new(move || first.wrapping_add(1)); + log.borrow_mut().push(caught(boxed)); + out.extend(log.borrow().iter()); + } + 5 => { + let r = panic::catch_unwind(|| data.len() as u8); + let s = format!("{r:?} {:?} {}", r.is_ok(), r.as_ref().map_or(0, |n| *n)); + out.extend_from_slice(s.as_bytes()); + } + _ => { + // A panic inside aborts, so nothing catches it, and what was owned never drops. + let owned = Logged(1, log); + let r = panic::catch_unwind(AssertUnwindSafe(|| { + if first % 3 == 0 { + panic!("caught {}", owned.0); + } + first + })); + out.push(r.unwrap()); + drop(owned); + out.extend(log.borrow().iter()); + } + } +} + +/// A future to poll: ready after `n` polls, waking itself on each that is pending. +struct Countdown(u8); + +impl Future for Countdown { + type Output = u8; + + fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.0 == 0 { + return Poll::Ready(7); + } + self.0 -= 1; + cx.waker().wake_by_ref(); + Poll::Pending + } +} + +/// `AssertUnwindSafe` of futures, polled by hand and awaited. +pub fn awaited(kind: u8, data: &[u8], out: &mut Vec) { + let first = data.first().copied().unwrap_or(0); + let log = RefCell::new(Vec::new()); + let mut cx = Context::from_waker(Waker::noop()); + match kind % 3 { + 0 => { + let mut f = pin!(AssertUnwindSafe(Countdown(first % 4))); + let mut polls = 0u8; + loop { + polls += 1; + if let Poll::Ready(v) = f.as_mut().poll(&mut cx) { + out.extend([v, polls]); + break; + } + } + } + 1 => { + let n = first % 3; + let fut = async move { + let inner = AssertUnwindSafe(async { Countdown(n).await + 1 }).await; + AssertUnwindSafe(Countdown(0)).await + inner + }; + let mut f = pin!(AssertUnwindSafe(fut)); + let mut polls = 0u8; + while f.as_mut().poll(&mut cx).is_pending() { + polls += 1; + } + out.push(polls); + } + _ => { + // What a future owns drops with it, polled to the end or not. + let a = Logged(1, &log); + let b = Logged(2, &log); + let unpolled = AssertUnwindSafe(async move { a.0 }); + let mut done = pin!(AssertUnwindSafe(async move { + Countdown(1).await; + b.0 + })); + while let Poll::Pending = done.as_mut().poll(&mut cx) { + log.borrow_mut().push(9); + } + if first % 2 == 0 { + drop(unpolled); + } + log.borrow_mut().push(0); + out.extend(log.borrow().iter()); + } + } +} + +/// Panics with a payload, and `Debug` of a `dyn Any`. +pub fn raised(kind: u8, data: &[u8], out: &mut Vec) { + let first = data.first().copied().unwrap_or(0); + let log = RefCell::new(Vec::new()); + match kind % 8 { + 0 => panic::panic_any("a payload of text"), + 1 => panic::panic_any(crate::harness::text(data)), + 2 => panic::panic_any(first), + 3 => panic::panic_any(Logged(first, &RefCell::new(Vec::new())).0), + 4 => panic::resume_unwind(Box::new(first)), + 5 => panic::resume_unwind(Box::new("text")), + 6 => { + // What a panic would drop never drops, as it aborts. + let _held = Logged(1, &log); + if first % 2 == 0 { + panic::panic_any(Point { x: first, y: 0 }); + } + out.push(first); + } + _ => { + let boxed: Box = Box::new(first); + let any: &dyn Any = &first; + let synced: Box = Box::new(String::new()); + let s = format!("{boxed:?}|{any:#?}|{synced:>20?}|{:?}", AssertUnwindSafe(&*boxed)); + out.extend_from_slice(s.as_bytes()); + } + } + out.extend(log.borrow().iter()); +} diff --git a/ts/fixtures/lang/panics/ts.toml b/ts/fixtures/lang/panics/ts.toml new file mode 100644 index 000000000..f13de1b16 --- /dev/null +++ b/ts/fixtures/lang/panics/ts.toml @@ -0,0 +1,2 @@ +package = "lang-panics-fixture" +roots = ["harness::run", "harness::op"] diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 6689f68c1..a841af340 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -97,6 +97,10 @@ fixtures/reject/src/objects.rs:generic_object+0:23: `dyn objects::Scaled`, a fixtures/reject/src/objects.rs:generic_object+1:4: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, whose object would hold the value there as it is made, not as it is read, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet +fixtures/reject/src/panics.rs:caller+1:2: a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet +fixtures/reject/src/panics.rs:caller+1:33: a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet +fixtures/reject/src/panics.rs:hooked+1:13: `panic::take_hook`, whose hook reads each panic's `Location`, which panics don't carry yet +fixtures/reject/src/panics.rs:hooked+2:2: `panic::set_hook`, whose hook reads each panic's `Location`, which panics don't carry yet fixtures/reject/src/parts.rs:array_part+1:9: a raw pointer to `std::cell::UnsafeCell` in `parts::InArray`, which the crate may put another object in place of fixtures/reject/src/parts.rs:array_part_replaced+3:33: a reference through which a raw pointer may be made into `parts::InArray` in `[parts::InArray; 2]`, which the crate may put another object in place of fixtures/reject/src/parts.rs:element_replaced+2:20: a reference through which a raw pointer may be made into `std::cell::UnsafeCell` in `[std::cell::UnsafeCell; 2]`, which the crate may put another object in place of diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index db3809ba0..4a34f24e7 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -99,6 +99,10 @@ fixtures/reject/src/objects.rs:generic_object+0:23: `dyn objects::Scaled`, a fixtures/reject/src/objects.rs:generic_object+1:4: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, whose object would hold the value there as it is made, not as it is read, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet +fixtures/reject/src/panics.rs:caller+1:2: a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet +fixtures/reject/src/panics.rs:caller+1:33: a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet +fixtures/reject/src/panics.rs:hooked+1:13: `panic::take_hook`, whose hook reads each panic's `Location`, which panics don't carry yet +fixtures/reject/src/panics.rs:hooked+2:2: `panic::set_hook`, whose hook reads each panic's `Location`, which panics don't carry yet fixtures/reject/src/parts.rs:array_part+1:9: a raw pointer to `std::cell::UnsafeCell` in `parts::InArray`, which the crate may put another object in place of fixtures/reject/src/parts.rs:array_part_replaced+3:33: a reference through which a raw pointer may be made into `parts::InArray` in `[parts::InArray; 2]`, which the crate may put another object in place of fixtures/reject/src/parts.rs:element_replaced+2:20: a reference through which a raw pointer may be made into `std::cell::UnsafeCell` in `[std::cell::UnsafeCell; 2]`, which the crate may put another object in place of diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index cd0bca5ba..83d03c654 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -14,6 +14,7 @@ mod iters; mod kept; mod keys; mod objects; +mod panics; mod parts; mod places; mod pointers; diff --git a/ts/fixtures/reject/src/panics.rs b/ts/fixtures/reject/src/panics.rs new file mode 100644 index 000000000..f5a04074b --- /dev/null +++ b/ts/fixtures/reject/src/panics.rs @@ -0,0 +1,14 @@ +//! `std::panic` that needs where a panic or a call is: a `Location`, and the hook that reads +//! one. + +/// The caller each `#[track_caller]` function is given. +#[track_caller] +pub fn caller() -> u32 { + std::panic::Location::caller().line() +} + +/// A hook reads each panic's `Location`. +pub fn hooked() { + let hook = std::panic::take_hook(); + std::panic::set_hook(hook); +} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 75edbec3a..04ce14e03 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -83,6 +83,8 @@ roots = [ "objects::generic_object", "objects::raw_dyn_iter", "objects::same_named", + "panics::caller", + "panics::hooked", "parts::array_part_replaced", "parts::element_replaced", "parts::field_part_replaced", diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 2864e9f00..87965a49a 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -43,6 +43,11 @@ export function panic(message: string): never { throw new Panic(message); } +/** `resume_unwind`, which aborts as `panic=abort`'s runtime does, running no panic hook. */ +export function resumeUnwind(): never { + throw new Abort("resume_unwind"); +} + /** * Panic with a formatted message, which keeps what was written before a value failed to * format, as std's does. diff --git a/ts/src/lower/body.rs b/ts/src/lower/body.rs index 09654e3e3..a7ee5ae2b 100644 --- a/ts/src/lower/body.rs +++ b/ts/src/lower/body.rs @@ -201,7 +201,13 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { library::cow_made(elem, variant, values.pop().unwrap()) } (library::Std::Result, i) => Expr::result(i == 0, values.pop().unwrap()), - (library::Std::Reverse | library::Std::Wrapping | library::Std::Saturating, _) => values.pop().unwrap(), + ( + library::Std::Reverse + | library::Std::AssertUnwindSafe + | library::Std::Wrapping + | library::Std::Saturating, + _, + ) => values.pop().unwrap(), (library::Std::Bound | library::Std::TryLockError, _) => { let ts_ty = self.t.ty(Ty::new_adt(tcx, adt, gargs), span); library::tagged(adt.variant(variant).name.as_str(), values.pop(), ts_ty) diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index c674d2f99..0d4956f21 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -47,6 +47,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::Result | Std::ControlFlow | Std::Reverse + | Std::AssertUnwindSafe | Std::Wrapping | Std::Saturating, ) => (false, args.types().collect()), @@ -255,7 +256,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some(Std::Option) => ret(self.copy_unless(self.none(args.type_at(0)), args.type_at(0), span)), Some(Std::Poll) => ret(self.copy_unless(self.pending(args.type_at(0)), args.type_at(0), span)), Some(Std::Result) => ret(self.copy_result(args.type_at(0), args.type_at(1), span)), - Some(Std::Reverse | Std::Wrapping | Std::Saturating) => ret(self.snapshot(v(), args.type_at(0), span)), + Some(Std::Reverse | Std::AssertUnwindSafe | Std::Wrapping | Std::Saturating) => { + ret(self.snapshot(v(), args.type_at(0), span)) + } Some(Std::Bound) => { let ts_ty = self.ty(ty, span); let held = self.snapshot(v().member("_0"), args.type_at(0), span); diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index 7516451cb..15c878685 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -64,6 +64,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::Bound | Std::TryLockError | Std::Reverse + | Std::AssertUnwindSafe | Std::Wrapping | Std::Saturating | Std::Range @@ -174,6 +175,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::Box | Std::Pin | Std::Reverse + | Std::AssertUnwindSafe | Std::Wrapping | Std::Saturating, ) => self.disposable(args.type_at(0), glue), @@ -209,9 +211,15 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }; match Std::of(self.tcx, adt.did()) { _ if self.bound_drops(adt.did()).is_some() => true, - Some(Std::Box | Std::Pin | Std::Reverse | Std::Wrapping | Std::Saturating | Std::PoisonError) => { - self.always_disposes(args.type_at(0)) - } + Some( + Std::Box + | Std::Pin + | Std::Reverse + | Std::AssertUnwindSafe + | Std::Wrapping + | Std::Saturating + | Std::PoisonError, + ) => self.always_disposes(args.type_at(0)), Some( Std::Waker | Std::PollFn @@ -309,6 +317,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::Box | Std::Pin | Std::Reverse + | Std::AssertUnwindSafe | Std::Wrapping | Std::Saturating | Std::PoisonError @@ -354,6 +363,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::Poll | Std::Pin | Std::Reverse + | Std::AssertUnwindSafe | Std::Wrapping | Std::Saturating, ) => args.types().all(|t| self.inert(t)), @@ -412,7 +422,13 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let option = Ty::new_option(self.tcx, args.type_at(0)); return self.drop_value(value.member("v"), option, span); } - Std::Box | Std::Pin | Std::Reverse | Std::Wrapping | Std::Saturating | Std::PoisonError => { + Std::Box + | Std::Pin + | Std::Reverse + | Std::AssertUnwindSafe + | Std::Wrapping + | Std::Saturating + | Std::PoisonError => { return self.drop_value(value, args.type_at(0), span); } // Its value, or the function that never made one. @@ -575,9 +591,13 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ty::Adt(adt, args) => match Std::of(self.tcx, adt.did())? { Std::Vec => Some(args.type_at(0)), Std::Option if self.erases_to(args.type_at(0)).undefined => None, - Std::Option | Std::Box | Std::Pin | Std::Reverse | Std::Wrapping | Std::Saturating => { - self.elements(args.type_at(0)) - } + Std::Option + | Std::Box + | Std::Pin + | Std::Reverse + | Std::AssertUnwindSafe + | Std::Wrapping + | Std::Saturating => self.elements(args.type_at(0)), _ => None, }, _ => None, diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 55c95775c..7b997d48f 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -105,10 +105,10 @@ impl<'tcx> Translator<'_, 'tcx> { } } - /// What `ty` owns through any boxes. + /// What `ty` owns through any boxes and `AssertUnwindSafe`s. pub fn unboxed(&self, mut ty: Ty<'tcx>) -> Ty<'tcx> { while let ty::Adt(adt, args) = ty.kind() - && Std::of(self.tcx, adt.did()) == Some(Std::Box) + && matches!(Std::of(self.tcx, adt.did()), Some(Std::Box | Std::AssertUnwindSafe)) { ty = args.type_at(0); } diff --git a/ts/src/lower/fmt.rs b/ts/src/lower/fmt.rs index 26bac5dda..ffcdbf426 100644 --- a/ts/src/lower/fmt.rs +++ b/ts/src/lower/fmt.rs @@ -278,6 +278,10 @@ pub fn std_impl<'tcx>( ty::Dynamic(preds, ..) if preds.principal_def_id().and_then(|did| Trait::of(tcx, did)) == Some(tr) => { value.call(vec![f]) } + // Its name and no fields, unpadded, whatever it holds. + ty::Dynamic(..) if tr == Trait::Debug && super::dynamic::is_any_object(tcx, ty) => { + rt("writeStr", vec![f, Expr::Str("Any { .. }".into())]) + } // Unpadded, naming its type as `type_name` does. ty::Adt(adt, args) if tr == Trait::Debug && adt.is_phantom_data() => { let name = rustc_const_eval::util::type_name(tcx, args.type_at(0)); @@ -400,12 +404,10 @@ pub fn std_impl<'tcx>( } (Std::Ordering, Trait::Debug) => rt("ordering", vec![value, f]), // Erased to what it holds. - (Std::Reverse, Trait::Debug) => { + (std @ (Std::Reverse | Std::AssertUnwindSafe), Trait::Debug) => { let held = b.fmt_value(tr, args.type_at(0), value, span, out); - rt( - "debugTupleFields", - vec![f, Expr::Str("Reverse".into()), Expr::Array(vec![held])], - ) + let name = format!("{std:?}"); + rt("debugTupleFields", vec![f, Expr::Str(name), Expr::Array(vec![held])]) } (Std::ControlFlow, Trait::Debug) => { let go = b.fmt_fn(tr, args.type_at(1), span); diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 9979bac44..2596ed829 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -141,6 +141,8 @@ pub enum Std { Wrapping, /// What it holds, whose arithmetic saturates at its bounds. Saturating, + /// What it holds, which it only marks as safe to unwind through. + AssertUnwindSafe, /// A `format_args!` argument: a function writing its value, `rt.Fmt`. Argument, /// A raw pointer to its pointee, as `pointer` has it. @@ -355,6 +357,7 @@ impl Std { ["cell", "lazy", "LazyCell"] => Some(Self::LazyCell), ["sync", "lazy_lock", "LazyLock"] => Some(Self::LazyLock), ["cmp", "Reverse"] => Some(Self::Reverse), + ["panic", "unwind_safe", "AssertUnwindSafe"] => Some(Self::AssertUnwindSafe), ["collections", "binary_heap", "PeekMut"] => Some(Self::PeekMut), ["ops", "range", "Bound"] => Some(Self::Bound), ["num", "nonzero", "NonZero"] => Some(Self::NonZero), @@ -468,14 +471,16 @@ pub fn string<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { } /// What a value of `ty` is in JS if `ty` wraps one value it is represented as: `Reverse`'s, -/// `NonZero`'s, `Wrapping`'s, and `Saturating`'s `T`, which differ only in their -/// order, range, and arithmetic, a pattern type's base, and the field of each of core's -/// `niche_types` structs that hold one. +/// `NonZero`'s, `Wrapping`'s, `Saturating`'s, and `AssertUnwindSafe`'s `T`, which +/// differ only in their order, range, arithmetic, and unwind safety, a pattern type's base, +/// and the field of each of core's `niche_types` structs that hold one. pub fn wrapped<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { match ty.kind() { ty::Pat(base, _) => Some(*base), ty::Adt(adt, args) => match Std::of(tcx, adt.did()) { - Some(Std::Reverse | Std::NonZero | Std::Wrapping | Std::Saturating) => Some(args.type_at(0)), + Some(Std::Reverse | Std::NonZero | Std::Wrapping | Std::Saturating | Std::AssertUnwindSafe) => { + Some(args.type_at(0)) + } Some(_) => None, None => { let path = std_path(tcx, adt.did()); @@ -490,6 +495,30 @@ pub fn wrapped<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { } } +/// Why a `Location` is refused. +pub const LOCATION: &str = + "a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet"; + +/// Whether `did` is std's `panic::Location`. +pub fn location(tcx: TyCtxt<'_>, did: DefId) -> bool { + matches!( + std_path(tcx, did) + .iter() + .map(String::as_str) + .collect::>() + .as_slice(), + ["panic", "location", "Location"] + ) +} + +/// What `ty` holds if it is an `AssertUnwindSafe`. +pub fn unwind_safe<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { + match ty.kind() { + ty::Adt(adt, args) if Std::of(tcx, adt.did()) == Some(Std::AssertUnwindSafe) => Some(args.type_at(0)), + _ => None, + } +} + /// A `Bound` or a `TryLockError` of type `ty`, tagged as the crate's own enums are: its /// variant, by name, and the value it holds, if any. pub fn tagged(variant: &str, value: Option, ty: crate::ts::Type) -> Expr { @@ -949,6 +978,8 @@ fn discards(owner: &str, name: &str) -> Option<&'static [usize]> { | ("Result", "unwrap" | "expect" | "unwrap_err") | ("Vec", "insert") | ("RangeInclusive", "new") + | ("panic", "panic_any" | "resume_unwind") + | ("rt", "begin_panic") // Each element moves into the closure. | ("array", "map") => &[], // `Vec` drops each element `retain` rejects, `dedup` removes, and `truncate`, @@ -968,7 +999,7 @@ fn discards(owner: &str, name: &str) -> Option<&'static [usize]> { _ if maps_guard(owner, name) => &[], // A guard it leaks or downgrades keeps its borrow or lock. ("Ref" | "RefMut", "leak") | ("RwLockWriteGuard", "downgrade") => &[], - ("Once", "call_once" | "call_once_force") => &[0], + ("Once", "call_once" | "call_once_force") | ("panic", "catch_unwind") => &[0], ("Arc" | "Rc", "new_cyclic") => &[1], // `pop_if::` drops its test uncalled if there is no element. ("Vec", "pop_if") => &[2], @@ -1141,9 +1172,9 @@ struct Closures<'tcx> { spent: Vec, } -/// What an argument of type `ty` that std calls holds, through any references and boxes, -/// whether a reference lends it, and `value`, the argument, read as that: a `&mut` to a -/// function JS copies is its place, so std calls the function it holds. +/// What an argument of type `ty` that std calls holds, through any references, boxes, and +/// `AssertUnwindSafe`s, whether a reference lends it, and `value`, the argument, read as +/// that: a `&mut` to a function JS copies is its place, so std calls the function it holds. pub fn called_through<'tcx>( t: &Translator<'_, 'tcx>, ty: Ty<'tcx>, @@ -1159,7 +1190,9 @@ pub fn called_through<'tcx>( lent = true; inner } - ty::Adt(adt, args) if Std::of(t.tcx, adt.did()) == Some(Std::Box) => args.type_at(0), + ty::Adt(adt, args) if matches!(Std::of(t.tcx, adt.did()), Some(Std::Box | Std::AssertUnwindSafe)) => { + args.type_at(0) + } _ => break, }; } @@ -1444,6 +1477,13 @@ fn mapping<'tcx>( let range = matches!(inherent_std(tcx, call.def_id), Some(Std::Range | Std::RangeInclusive)); let e = match (owner.as_str(), name.as_str()) { + // An `AssertUnwindSafe` is what it holds. + ("From", "from") if unwind_safe(tcx, targ(gargs, 0)).is_some() => args.pop()?, + ("Default", "default") if let Some(held) = self_ty().and_then(|t| unwind_safe(tcx, t)) => { + let default = super::collections::trait_fn(tcx, "Default", "default"); + return Some(b.call_def(default, tcx.mk_args(&[held.into()]), args, span, out)); + } + // Conversions. ("From", "from") => { // A `NonZero` converts as its integer. @@ -2432,7 +2472,7 @@ fn mapping<'tcx>( match ty.kind() { ty::Adt(adt, vargs) => match Std::of(tcx, adt.did()) { Some(Std::Vec) => vec_view(v, vargs.type_at(0)), - Some(Std::String | Std::Box | Std::ManuallyDrop) => v, + Some(Std::String | Std::Box | Std::ManuallyDrop | Std::AssertUnwindSafe) => v, Some(Std::Cow) if string(tcx, ty) => v, Some(Std::Cow) if let Some(elem) = cow_slice(tcx, ty) => cow_deref(v, elem), // The greatest element of a heap, which `DerefMut` marks to sift once lent. @@ -2606,7 +2646,30 @@ fn mapping<'tcx>( None => Expr::rt("panicFmt", vec![message]), } } - ("panicking", "panic") | ("rt", "begin_panic") => Expr::rt("panic", args), + ("panicking", "panic") => Expr::rt("panic", args), + // The hook writes a payload of text, and `Box` for any other. + ("panic", "panic_any") | ("rt", "begin_panic") => { + let payload = targ(gargs, 0); + match matches!(payload.kind(), ty::Ref(_, s, _) if s.is_str()) || b.t.std_of(payload) == Some(Std::String) { + true => Expr::rt("panic", args), + false => Expr::rt("panic", vec![Expr::Str("Box".into())]), + } + } + // A panic aborts, so `catch_unwind` never catches one. Its call consumes the function, + // so nothing drops it. + ("panic", "catch_unwind") => return Some(Expr::result(true, args.pop()?.call(Vec::new()))), + ("panic", "resume_unwind") => Expr::rt("resumeUnwind", Vec::new()), + ("Location", _) + if impl_self(tcx, call.def_id) + .is_some_and(|t| matches!(t.kind(), ty::Adt(adt, _) if location(tcx, adt.did()))) => + { + return Some(b.err(span, LOCATION)); + } + ("panicking", "set_hook" | "take_hook") => { + let why = + format!("`panic::{name}`, whose hook reads each panic's `Location`, which panics don't carry yet"); + return Some(b.err(span, why)); + } ("panicking", "panic_explicit") => Expr::rt("panic", vec![Expr::Str("explicit panic".into())]), ("panicking", "panic_display" | "unreachable_display") => { let value = args.pop()?; diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 188c80d44..238006291 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -246,7 +246,11 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { .or_else(|| tcx.async_fn_trait_kind_from_def_id(trait_id)) && let [callee, tupled] = args { - let callee_ty = gargs.type_at(0); + // An `AssertUnwindSafe` is the function it holds. + let mut callee_ty = gargs.type_at(0); + while let Some(inner) = library::unwind_safe(tcx, callee_ty) { + callee_ty = inner; + } let values = match self.take_parts(&tupled.node) { Some(parts) => parts, None => { @@ -275,7 +279,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } // A `&mut` to a function JS copies, such as a box of one, or to a reference to one, as // std's impls for a `&mut` and a box forward through, is its place: call what is - // there. A box is what it holds. + // there. A box, or an `AssertUnwindSafe`, is what it holds. let mut through = callee.node.ty(&self.mir.local_decls, tcx); let mut f = match callee.node.place() { Some(p) if let Some(pointee) = self.b.t.place_pointee(through) => { @@ -285,7 +289,8 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { _ => self.operand(&callee.node, span, out), }; loop { - through = match (through.boxed_ty(), self.b.t.place_pointee(through)) { + let held = through.boxed_ty().or_else(|| library::unwind_safe(tcx, through)); + through = match (held, self.b.t.place_pointee(through)) { (Some(inner), _) => inner, (None, Some(inner)) => { f = f.member("v"); @@ -845,10 +850,11 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { { self.same_place(args, dest, span, out) } - // A box is what it holds, so the `&mut` borrows the place holding the box. + // A box is what it holds, so the `&mut` borrows the place holding the box, and so is an + // `AssertUnwindSafe`. ("DerefMut", "deref_mut") | ("AsMut", "as_mut") | ("BorrowMut", "borrow_mut") if targ(0) - .and_then(|t| t.boxed_ty()) + .and_then(|t| t.boxed_ty().or_else(|| library::unwind_safe(tcx, t))) .is_some_and(|t| self.b.t.needs_place(t)) => { self.same_place(args, dest, span, out) diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 74dbca0c2..ee8f33bf2 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -595,6 +595,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { | Std::Box | Std::Pin | Std::Reverse + | Std::AssertUnwindSafe | Std::NonZero | Std::Wrapping | Std::Saturating diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 8a9d98f19..4c43b200e 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -775,7 +775,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { (Std::RangeTo | Std::RangeToInclusive, _) => { crate::lower::library::range_to(values.pop().unwrap(), std == Std::RangeToInclusive) } - (Std::Reverse | Std::Wrapping | Std::Saturating, _) => values.pop().unwrap(), + (Std::Reverse | Std::AssertUnwindSafe | Std::Wrapping | Std::Saturating, _) => values.pop().unwrap(), (Std::Bound | Std::TryLockError, _) => { let ts_ty = self.b.t.ty(ty, span); crate::lower::library::tagged(def.name.as_str(), values.pop(), ts_ty) diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index ee4ca0eb6..1c325c0fd 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1313,6 +1313,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | library::Std::BytesMut | library::Std::ThreadRng, ) => Some(ObjectKind::Class), + // What it holds, which its JS value is. + Some(library::Std::AssertUnwindSafe) => self.object_kind(args.type_at(0)), // A place, unless a pointer to what it holds is that object. Some(library::Std::MaybeUninit | library::Std::ManuallyDrop) => match self.pointee(args.type_at(0)) { pointer::Pointee::Object | pointer::Pointee::Bytes => self.object_kind(args.type_at(0)), @@ -1638,6 +1640,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } library::Std::Box | library::Std::Reverse + | library::Std::AssertUnwindSafe | library::Std::NonZero | library::Std::Wrapping | library::Std::Saturating @@ -1803,6 +1806,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { { return Type::Rt("Iter", vec![self.ty(item, span)]); } + if library::location(tcx, adt.did()) { + self.unrepresented(span, library::LOCATION.into()); + return Type::Builtin("unknown"); + } if !self.crates.translates(adt.did()) { self.unrepresented( span, @@ -2007,6 +2014,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { library::Std::Box | library::Std::Pin | library::Std::Reverse + | library::Std::AssertUnwindSafe | library::Std::NonZero | library::Std::Wrapping | library::Std::Saturating diff --git a/ts/src/lower/task.rs b/ts/src/lower/task.rs index 3e5d019e9..98510a894 100644 --- a/ts/src/lower/task.rs +++ b/ts/src/lower/task.rs @@ -411,8 +411,14 @@ pub fn call<'tcx>( let [future, cx] = two(args); future.member("poll").call(vec![cx]) } - // A pinned pointer or a box polls what it points at, which its JS value is. - Some(ty::Adt(adt, adt_args)) if matches!(Std::of(tcx, adt.did()), Some(Std::Pin | Std::Box)) => { + // A pinned pointer, a box, or an `AssertUnwindSafe` polls what it points at or holds, + // which its JS value is. + Some(ty::Adt(adt, adt_args)) + if matches!( + Std::of(tcx, adt.did()), + Some(Std::Pin | Std::Box | Std::AssertUnwindSafe) + ) => + { let inner = match Std::of(tcx, adt.did()) { Some(Std::Pin) => adt_args.type_at(0).builtin_deref(true), _ => Some(adt_args.type_at(0)), diff --git a/ts/tests/fixtures.rs b/ts/tests/fixtures.rs index 59812356b..c21fd05e0 100644 --- a/ts/tests/fixtures.rs +++ b/ts/tests/fixtures.rs @@ -418,6 +418,7 @@ mod lang { hashes, heap, hints, + panics, holds, ints, iters, From 1d4d13ab1cae794946dd09516b5ecb006cfc97e5 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 10:50:17 -0800 Subject: [PATCH 3/4] fix(ts): keep Location and #[track_caller] in census constructs Co-Authored-By: Claude Opus 5.5 --- ts/src/census.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ts/src/census.rs b/ts/src/census.rs index 44b38836e..a128b0604 100644 --- a/ts/src/census.rs +++ b/ts/src/census.rs @@ -135,6 +135,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]", "()", "+", "-", @@ -154,6 +155,7 @@ const VERBATIM: &[&str] = &[ "From::from", "Hash::hash_slice", "Hasher", + "Location", "LowerExp", "MAY_HAVE_SIDE_EFFECT", "None", From 7394ffcbca4f3f45f09f7c2bdeb8c1ca7e78ba2f Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 10:58:37 -0800 Subject: [PATCH 4/4] fix(ts): erase AssertUnwindSafe where a write in place checks what JS keeps Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/panics/expected/panics.ts | 108 +++++++++++++-------- ts/fixtures/lang/panics/src/panics.rs | 23 ++++- ts/src/lower/mod.rs | 22 +++-- 3 files changed, 99 insertions(+), 54 deletions(-) diff --git a/ts/fixtures/lang/panics/expected/panics.ts b/ts/fixtures/lang/panics/expected/panics.ts index a2c45aab0..5b53c815f 100644 --- a/ts/fixtures/lang/panics/expected/panics.ts +++ b/ts/fixtures/lang/panics/expected/panics.ts @@ -69,13 +69,37 @@ export function caught$u8_ref_panics_called_closure7(f: () => rt.u8): rt.u8 { rt.panicFmt(["internal error: entered unreachable code: a panic aborts"]); } +/** Write `to` over what `at` points at. */ +export function replace$panic_AssertUnwindSafe_Vec_u8(at: rt.ByteVec, to: rt.ByteVec): void { + Object.assign(at, to); +} + +/** Write `to` over what `at` points at. */ +export function replace$panic_AssertUnwindSafe_sync_Arc_u8(at: rt.Place>, to: rt.Arc): void { + at.v[Symbol.dispose](); + at.v = to; +} + /** `AssertUnwindSafe` of values, read and written as what it holds. */ export function wrapped(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { let f!: rt.ByteVec; - let _199: boolean = false; + let _234: boolean = false; const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); - const _7: rt.u8 = rt.u8Rem(kind, 6); - if (_7 === 0) { + const _7: rt.u8 = rt.u8Rem(kind, 7); + if (_7 === 6) { + let a: rt.Arc = new rt.Arc(first); + const shared: rt.Arc = rt.arcClone(a); + const _16: rt.Arc = new rt.Arc(9); + replace$panic_AssertUnwindSafe_sync_Arc_u8(rt.place(() => a, (value) => a = value), _16); + const v: rt.ByteVec = rt.byteSliceToByteVec(data); + const before: rt.usize = v.length; + const _25: rt.ByteVec = rt.byteVecOf(first, 3); + replace$panic_AssertUnwindSafe_Vec_u8(v, _25); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(shared.value, a.value, shared.strong & 0xff, before & 0xff)), undefined, true); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(v)); + shared[Symbol.dispose](); + a[Symbol.dispose](); + } else if (_7 === 0) { let w: rt.usize = data.length; w = rt.usizeAdd(w, 1); rt.byteVecPush(out, w & 0xff); @@ -84,20 +108,20 @@ export function wrapped(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void w = rt.usizeAdd(w, first as rt.usize); rt.byteVecPush(out, w & 0xff); } else if (_7 === 1) { - const v: rt.ByteVec = rt.byteSliceToByteVec(data); - rt.byteVecPush(v, first); - rt.byteVecPush(v, 9); - rt.byteVecRetain(v, (b2: rt.u8): boolean => { + const v2: rt.ByteVec = rt.byteSliceToByteVec(data); + rt.byteVecPush(v2, first); + rt.byteVecPush(v2, 9); + rt.byteVecRetain(v2, (b2: rt.u8): boolean => { return rt.u8Rem(b2, 2) === 0; }); - rt.byteVecExtendFromSlice(out, rt.byteVecSlice(v)); - rt.byteVecPush(out, v.length & 0xff); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(v2)); + rt.byteVecPush(out, v2.length & 0xff); } else if (_7 === 2) { const p: Point.Point = { x: first, y: 2 }; - const _54: rt.u8 = rt.u8WrappingAdd(p.x, 1); - p.x = _54; - const _60: rt.u8 = rt.u8WrappingMul(p.y, first); - p.y = _60; + const _89: rt.u8 = rt.u8WrappingAdd(p.x, 1); + p.x = _89; + const _95: rt.u8 = rt.u8WrappingMul(p.y, first); + p.y = _95; const copy: Point.Point = copy$panics_Point(p); p.x = 0; rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(copy.x, copy.y, p.x, p.y)), undefined, true); @@ -105,52 +129,52 @@ export function wrapped(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void nested = rt.u8WrappingAdd(nested, 5); rt.byteVecPush(out, nested); } else if (_7 === 3) { - const _97: rt.u8 = first; - const _99: rt.u8 = first; - const _101: string = text(data); - const _103: rt.i8 = -1; - const _105: Point.Point = { x: first, y: 0 }; - const _109: [rt.u8, string] = [first, "a"]; - const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.u8Debug(_97, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.u8Debug(_99, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtStr(_101, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.i8Debug(_103, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => Point.fmt$fmt_Debug(_105, f)])]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.u8Debug(_109[0], f), (f: rt.Formatter) => rt.fmtStr(_109[1], f)], f)])]; + const _132: rt.u8 = first; + const _134: rt.u8 = first; + const _136: string = text(data); + const _138: rt.i8 = -1; + const _140: Point.Point = { x: first, y: 0 }; + const _144: [rt.u8, string] = [first, "a"]; + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.u8Debug(_132, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.u8Debug(_134, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtStr(_136, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.i8Debug(_138, f)]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => Point.fmt$fmt_Debug(_140, f)])]), (f: rt.Formatter) => rt.fmtDebugTupleFields(f, "AssertUnwindSafe", [(f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.u8Debug(_144[0], f), (f: rt.Formatter) => rt.fmtStr(_144[1], f)], f)])]; rt.byteVecExtendFromSlice(out, rt.stringAsBytes(rt.fmtFormat([args2[0], "|", rt.fmtSpec({ alternate: true }, args2[1]), "|", args2[2], "|", rt.fmtSpec({ width: 5 }, args2[3]), "|", args2[4], "|", rt.fmtSpec({ alternate: true }, args2[5])]))); } else if (_7 === 4) { const d: rt.u8 = 0; const s2: string = ""; const p2: Point.Point = Point.default$Default(); - const v2: rt.ByteVec = new rt.ByteVec(); + const v3: rt.ByteVec = new rt.ByteVec(); const e: rt.u8 = first; f = rt.byteSliceToByteVec(data); - rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(d, rt.stringLen(s2) & 0xff, p2.x, p2.y, v2.length & 0xff, e, f.length & 0xff)), undefined, true); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(d, rt.stringLen(s2) & 0xff, p2.x, p2.y, v3.length & 0xff, e, f.length & 0xff)), undefined, true); } else { const log: rt.RefCell = new rt.RefCell(new rt.ByteVec()); - const a: Logged.Logged = new Logged.Logged(1, log); + const a2: Logged.Logged = new Logged.Logged(1, log); let b: Logged.Logged = new Logged.Logged(2, log); - const _163: Logged.Logged = new Logged.Logged(3, log); - _199 = true; - const c: Logged.Logged = _163; - a[Symbol.dispose](); - const _167: Logged.Logged = new Logged.Logged(4, log); - const _169: Logged.Logged = b; - _169[Symbol.dispose](); - Object.assign(_169, _167); - const _171: Logged.Logged = new Logged.Logged(5, log); + const _198: Logged.Logged = new Logged.Logged(3, log); + _234 = true; + const c: Logged.Logged = _198; + a2[Symbol.dispose](); + const _202: Logged.Logged = new Logged.Logged(4, log); + const _204: Logged.Logged = b; + _204[Symbol.dispose](); + Object.assign(_204, _202); + const _206: Logged.Logged = new Logged.Logged(5, log); b[Symbol.dispose](); - b = _171; + b = _206; if (rt.u8Rem(first, 2) === 0) { - _199 = false; + _234 = false; c[Symbol.dispose](); } - const _181: rt.RefCell = rt.refCellBorrowMut(log); - rt.byteVecPush(_181.value, b._0); - rt.refMutRelease(_181); + const _216: rt.RefCell = rt.refCellBorrowMut(log); + rt.byteVecPush(_216.value, b._0); + rt.refMutRelease(_216); b[Symbol.dispose](); - const _191: rt.RefCell = rt.refCellBorrow(log); - rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_191.value))), undefined, true); - rt.refRelease(_191); - if (_199) { + const _226: rt.RefCell = rt.refCellBorrow(log); + rt.iterExtendBytes(out, rt.iterSlice(rt.byteSliceView(rt.byteVecSlice(_226.value))), undefined, true); + rt.refRelease(_226); + if (_234) { c[Symbol.dispose](); } - _199 = false; + _234 = false; } } diff --git a/ts/fixtures/lang/panics/src/panics.rs b/ts/fixtures/lang/panics/src/panics.rs index 3b72fc853..e668e96e6 100644 --- a/ts/fixtures/lang/panics/src/panics.rs +++ b/ts/fixtures/lang/panics/src/panics.rs @@ -1,6 +1,6 @@ //! `std::panic` under wasm32's `panic=abort`: `AssertUnwindSafe` made, read and written -//! through `Deref`, `DerefMut`, and its field, called, awaited, printed, defaulted, and -//! converted into, dropping what it holds where Rust drops it; `catch_unwind`, which only +//! through `Deref`, `DerefMut`, and its field, replaced whole through a `&mut`, called, +//! awaited, printed, defaulted, and converted into, dropping what it holds where Rust drops it; `catch_unwind`, which only //! ever returns `Ok` or panics on, of closures, functions, and wrapped closures that drop //! what they own; `panic_any`, whose hook writes a payload of text and `Box` for any //! other; `resume_unwind`, which aborts without the hook; and `Debug` of a `dyn Any`. @@ -10,6 +10,7 @@ use std::cell::RefCell; use std::future::Future; use std::panic::{self, AssertUnwindSafe}; use std::pin::pin; +use std::sync::Arc; use std::task::{Context, Poll, Waker}; /// Logs its byte as it drops. @@ -40,10 +41,26 @@ fn caught(f: impl FnOnce() -> R + panic::UnwindSafe) -> R { } } +/// Write `to` over what `at` points at. +fn replace(at: &mut T, to: T) { + *at = to; +} + /// `AssertUnwindSafe` of values, read and written as what it holds. pub fn wrapped(kind: u8, data: &[u8], out: &mut Vec) { let first = data.first().copied().unwrap_or(0); - match kind % 6 { + match kind % 7 { + 6 => { + // Replaced whole through a `&mut`: a handle another shares, and bytes. + let mut a = AssertUnwindSafe(Arc::new(first)); + let shared = Arc::clone(&a); + replace(&mut a, AssertUnwindSafe(Arc::new(9))); + let mut v = AssertUnwindSafe(data.to_vec()); + let before = v.len(); + replace(&mut v, AssertUnwindSafe(vec![first; 3])); + out.extend([*shared, **a, Arc::strong_count(&shared) as u8, before as u8]); + out.extend_from_slice(&v); + } 0 => { let mut w = AssertUnwindSafe(data.len()); *w += 1; diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 1c325c0fd..af4037350 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1347,8 +1347,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ) } - /// Whether `ty`'s JS value, through the `Pin`s and `Box`es JS erases, is of an `rt` class - /// that keeps its state private, which `rt.ptrWrite` moves in by its `$move`. + /// Whether `ty`'s JS value, through the `Pin`s, `Box`es, and `AssertUnwindSafe`s JS + /// erases, is of an `rt` class that keeps its state private, which `rt.ptrWrite` moves in + /// by its `$move`. pub fn moves_private(&self, ty: Ty<'tcx>) -> bool { use library::Std; matches!( @@ -1357,22 +1358,25 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ) } - /// What JS keeps of a `ty`: what it holds through any number of `Pin`s and `Box`es. + /// What JS keeps of a `ty`: what it holds through any number of `Pin`s, `Box`es, and + /// `AssertUnwindSafe`s. pub fn erased(&self, ty: Ty<'tcx>) -> Ty<'tcx> { use library::Std; let mut erased = ty; while let ty::Adt(adt, args) = erased.kind() - && matches!(Std::of(self.tcx, adt.did()), Some(Std::Pin | Std::Box)) - { + && matches!( + Std::of(self.tcx, adt.did()), + Some(Std::Pin | Std::Box | Std::AssertUnwindSafe) + ) { erased = args.type_at(0); } erased } - /// The kind of a `pointee`'s object, through the `Pin`s and `Box`es JS erases, where a - /// whole value of it can be written into the object in place: one only its owner - /// holds, unlike an `Arc`'s or a `LocalKey`'s, which its copies share, and whose state - /// a write reaches, unlike a binding's, or a future's, or a trait object's, whatever + /// The kind of a `pointee`'s object, through the `Pin`s, `Box`es, and `AssertUnwindSafe`s + /// JS erases, where a whole value of it can be written into the object in place: one only + /// its owner holds, unlike an `Arc`'s or a `LocalKey`'s, which its copies share, and whose + /// state a write reaches, unlike a binding's, or a future's, or a trait object's, whatever /// class it is. pub fn write_in(&self, pointee: Ty<'tcx>) -> Option { use library::Std;