Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ 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
- [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
- [A string Cow's variant](/quest/m3/ts-cow-str-variant.md) - matching a `Cow<str>`'s `Borrowed` or `Owned`, and `to_mut`'s change of it, translate as std's
22 changes: 22 additions & 0 deletions quest/m3/ts-panic-hook.md
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions quest/m3/ts-panic-location.md
Original file line number Diff line number Diff line change
@@ -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`
4 changes: 4 additions & 0 deletions ts/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ crate translation reaches is refused, and so are two versions of one crate it re
| `ControlFlow<B, C>` | `rt.Result<C, B>`, `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<T>` | an array; for bytes, `Uint8Array` for `[u8; N]`, `rt.ByteSlice` for `&[u8]`, `&[u8; N]`, and `Box<[u8]>`, and `rt.ByteVec` for `Vec<u8>` |
| `Cow<[T]>` | `rt.Cow<T>`: 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<T>`, `Reverse<T>`, `NonZero<T>` | 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<T>`, which for what a box holds is the place holding the box |
| `&T`, `&mut T`, `Box<T>`, `Reverse<T>`, `NonZero<T>`, `AssertUnwindSafe<T>` | 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<T>`, which for what a box holds is the place holding the box |
| `*const T`, `*mut T`, `NonNull<T>` | 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<T>` 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>` | `T`: `undefined` until written, or an object whose parts kept as objects are there, holding `undefined` |
| `ManuallyDrop<T>` | `T`, which nothing drops but its own `drop`; its `Debug`, comparisons, and constants are refused |
Expand Down Expand Up @@ -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<dyn Any>` 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
Expand Down Expand Up @@ -287,7 +291,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 `<borrowed>` and
Expand Down Expand Up @@ -550,7 +554,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<T>` the object was made from, or, of one made from a box, an `rt.ArcDyn` of
the value sharing its count.
Expand Down
14 changes: 14 additions & 0 deletions ts/fixtures/lang/panics/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions ts/fixtures/lang/panics/expected/core/any.Any.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Generated by rust2-ts. Do not edit.

export type Any = object;

//# sourceMappingURL=any.Any.ts.map
12 changes: 12 additions & 0 deletions ts/fixtures/lang/panics/expected/core/option.ts
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions ts/fixtures/lang/panics/expected/core/result.ts
Original file line number Diff line number Diff line change
@@ -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.u8, Any.Any>): rt.Result<rt.u8, Any.Any> {
if (self.ok) {
return rt.Ok(self.value);
}
return rt.Err(self.error);
}

export function mapOr$059pexg(self: rt.Result<rt.u8, Any.Any>, default_: rt.u8, f: (a0: rt.u8) => rt.u8): rt.u8 {
if (self.ok) {
return f(self.value);
}
return default_;
}

//# sourceMappingURL=result.ts.map
16 changes: 16 additions & 0 deletions ts/fixtures/lang/panics/expected/core/task/poll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Generated by rust2-ts. Do not edit.

import * as rt from "../../rt.ts";

export function isReady$1ntq2i8(self: rt.Poll<rt.u8>): boolean {
if (self !== rt.Pending) {
return true;
}
return false;
}

export function isPending$0vrdvv1(self: rt.Poll<rt.u8>): boolean {
return !isReady$1ntq2i8(self);
}

//# sourceMappingURL=poll.ts.map
58 changes: 58 additions & 0 deletions ts/fixtures/lang/panics/expected/harness.ts
Original file line number Diff line number Diff line change
@@ -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.u16, rt.TryFromIntError> = 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
50 changes: 50 additions & 0 deletions ts/fixtures/lang/panics/expected/lib.ts
Original file line number Diff line number Diff line change
@@ -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<rt.u8, Any.Any>): 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
20 changes: 20 additions & 0 deletions ts/fixtures/lang/panics/expected/panics.Countdown.ts
Original file line number Diff line number Diff line change
@@ -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<rt.u8> {
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
26 changes: 26 additions & 0 deletions ts/fixtures/lang/panics/expected/panics.Logged.ts
Original file line number Diff line number Diff line change
@@ -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<rt.ByteVec>;

constructor(_0: rt.u8, _1: rt.RefCell<rt.ByteVec>) {
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.ByteVec> = rt.refCellBorrowMut(self._1);
rt.byteVecPush(_5.value, self._0);
rt.refMutRelease(_5);
}

//# sourceMappingURL=panics.Logged.ts.map
24 changes: 24 additions & 0 deletions ts/fixtures/lang/panics/expected/panics.Point.ts
Original file line number Diff line number Diff line change
@@ -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<undefined, rt.FmtError> {
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
Loading