diff --git a/quest/m3/README.md b/quest/m3/README.md index 7ab70819d..6d46a895c 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -17,7 +17,7 @@ generic std gap is planned here even when moq never calls it. - [Relink without rebuilding](/quest/m3/relink.md) - measure upstream's skip of dependency-only rebuilds on moq's edits once it reaches nightly - [Unsafe code the translator can model](/quest/m3/ts-unsafe.md) - a dependency using only `unsafe` the translator can model translates from source - [The capacity of a BytesMut](/quest/m3/ts-bytes-capacity.md) - `BytesMut::capacity` and what reads it translate with `bytes`' values -- [Trait objects](/quest/m3/ts-dyn.md) - `dyn Error` and `dyn Iterator` translate, so a consumer neither rewrites nor binds them +- [Trait objects](/quest/m3/ts-dyn.md) - `dyn Iterator` and `dyn Error`'s downcasts translate, so a consumer neither rewrites nor binds them - [A trait object in a struct's last field](/quest/m3/ts-dyn-tail.md) - `Rc>` and the like translate, the last field as the object - [bytes' Chain as a Buf object](/quest/m3/ts-buf-chain.md) - `Chain::copy_to_bytes` across both halves translates, and with it a `Chain` as a `dyn Buf` - [A Buf object's size](/quest/m3/ts-dyn-buf-size.md) - a `dyn Buf` object holds only the methods some call dispatches to diff --git a/quest/m3/ts-dyn.md b/quest/m3/ts-dyn.md index bef2ca0ae..a6f7406ba 100644 --- a/quest/m3/ts-dyn.md +++ b/quest/m3/ts-dyn.md @@ -3,7 +3,7 @@ ## Goal [rust2-ts](/ts/README.md) translates the trait objects of std's and mapped crates' traits -that moq still refuses (`dyn Error` and `dyn Iterator`), and `dyn FusedIterator`, behind +that moq still refuses (`dyn Iterator`), and `dyn FusedIterator`, behind any pointer std coerces to one, such as `&mut dyn Debug` and `Arc`, with Rust's dispatch and drop order, proven by differential tests against native Rust, so a consumer neither rewrites nor binds them. @@ -18,8 +18,10 @@ config, so trait objects move from never to not yet. as an `rt.Future`, `dyn Any` as the class a downcast checks for, `dyn Fn*` as the closure itself, and the translated crates' traits as an object with the trait's methods. Build the rest on those. -- `Error` needs `source` and its supertraits' `Debug` and `Display` on the object, and its - `downcast_ref` can check classes as `dyn Any`'s does; moq's are thiserror's `source`. +- `dyn Error` translates as an object of `source`, `Debug`, and `Display`. Its downcasts + remain: checking the class of `Error.of$T` as `dyn Any`'s do would miss a value upcast + from the crate's own trait over `Error`, whose object is that trait's class, so a + downcast needs the class of the value's own type, as an upcast to `dyn Any` does. - `Iterator` can be an `rt.Iter`. - `bytes::Buf` is an object of its methods, as the crate's own traits are, decided with the maintainer on 2026-11-02, so it works for any implementor. diff --git a/ts/README.md b/ts/README.md index e07b3f402..131f71d07 100644 --- a/ts/README.md +++ b/ts/README.md @@ -123,6 +123,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | closure | an arrow function, with `[Symbol.dispose]` if it owns a value that drops, and what makes its clone if a clone can't be itself | | `fn` pointer, function item as a value | the function, as a closure is typed: a `()` result is `undefined`, where a declared function returns `void` | | `&dyn Trait`, `&mut dyn Trait`, `Box`, `Arc`, `Rc`, `Weak`, `Pin

` of one | an object with the trait's methods, its supertraits' included: `shape.area$Shape()`; behind an `Arc` or an `Rc`, an `rt.ArcDyn` sharing the count of the `Arc` it was made from | +| `dyn Error` | an object of its `source`, `fmt$Debug`, and `fmt$Display`: `e.source$Error()` | | `dyn Any` | an instance of the class its value's type was wrapped in, which a downcast checks for: `a instanceof Any.of$dyns_Square ? a.v : undefined` | | `dyn Fn`, `dyn FnMut`, `dyn FnOnce` | the closure or function it was made of: `(a0: rt.u8) => rt.u32` | | constant | its value, inlined as Rust inlines it, so each use of one holding a cell is a cell of its own; one with no value tree, or holding a cell, read from the memory rustc evaluated | @@ -481,10 +482,18 @@ A `dyn Fn`, `dyn FnMut`, or `dyn FnOnce` is the closure or function it was made a call calls as it does the closure, and which a box drops as it does the closure. One that `Fn` or `FnMut` calls, boxed as a `dyn FnOnce`, is `rt.once` of it, which drops it after the call, as an `FnOnce` closure's body drops what it owns, and a box of a -reference to one is `rt.lent` of it, which never drops it. A trait object of std's +reference to one is `rt.lent` of it, which never drops it. + +A `dyn Error` is such an object of `source` and its supertraits' `Debug` and `Display`, +`Error.of$errors_Leaf` from `core/error.Error.ts`, whose `source` is `None` for a type +that keeps `Error`'s own. `From` an error into a `Box`, which `?` converts by, +makes one. Its deprecated `description` and `cause`, and its downcasts, are refused, and so +is `From` a string, which boxes std's own private error type. + +A trait object of std's or a mapped crate's trait other than `dyn Debug` and `dyn Display` behind `&`, `&mut`, `Box`, `Arc`, or `Rc`, `dyn Future`, -`dyn Any`, `dyn Buf`, and the function traits, of a generic +`dyn Any`, `dyn Error`, `dyn Buf`, and the function traits, of a generic trait or one with an associated type, of a byte array, or behind any other pointer, such as a struct's last field, is refused, and so are an upcast to `dyn Any` and `Any::type_id`. @@ -863,8 +872,8 @@ they need. Byte reinterpretation, pointer addresses, pointer arithmetic on a value JS keeps as an object (other than wrapping arithmetic on a null or dangling pointer), memory from `alloc` other than an array of values JS copies, and unions whose fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than -`dyn Debug`, `dyn Display`, `dyn Future`, `dyn Any`, `dyn Fn*`, `dyn Buf`, and the translated crates' traits' -behind `&`, `&mut`, `Box`, `Arc`, `Rc`, `Weak`, or `Pin` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `dyn Error`, binds to +`dyn Debug`, `dyn Display`, `dyn Future`, `dyn Any`, `dyn Error`, `dyn Fn*`, `dyn Buf`, and the translated crates' traits' +behind `&`, `&mut`, `Box`, `Arc`, `Rc`, `Weak`, or `Pin` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `Box` made `From` a string, binds to hand-written TS ([bindings](#bindings)). These fail translation: `f16` and `f128`, a `Poll` of a `Poll` of a `Poll`, a future whose output can be pending, an instance of a generic type whose field is a `Poll` of a type parameter that can be pending, a closure that owns a value diff --git a/ts/fixtures/lang/expected/core/error.Error.ts b/ts/fixtures/lang/expected/core/error.Error.ts new file mode 100644 index 000000000..b0affc059 --- /dev/null +++ b/ts/fixtures/lang/expected/core/error.Error.ts @@ -0,0 +1,168 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { drop$errors_Failure } from "../lib.ts"; +import * as Failure from "../errors.Failure.ts"; +import * as Leaf from "../errors.Leaf.ts"; +import * as Transported from "../errors.Transported.ts"; + +export type Error = + | { source$Error: () => Error | undefined; fmt$Display: (a0: rt.Formatter) => rt.Result; fmt$Debug: (a0: rt.Formatter) => rt.Result }; + +export class of$boxed_Box_errors_Failure { + v: Failure.Failure; + + constructor(v: Failure.Failure) { + this.v = v; + } + + [Symbol.dispose](): void { + drop$errors_Failure(this.v); + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return Failure.fmt$fmt_Debug(this.v, a); + } + + fmt$Display(a: rt.Formatter): rt.Result { + return Failure.fmt$fmt_Display(this.v, a); + } + + source$Error(): Error | undefined { + return Failure.source$Error(this.v); + } +} + +export class of$errors_Failure { + v: Failure.Failure; + + constructor(v: Failure.Failure) { + this.v = v; + } + + [Symbol.dispose](): void { + drop$errors_Failure(this.v); + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return Failure.fmt$fmt_Debug(this.v, a); + } + + fmt$Display(a: rt.Formatter): rt.Result { + return Failure.fmt$fmt_Display(this.v, a); + } + + source$Error(): Error | undefined { + return Failure.source$Error(this.v); + } +} + +export class of$errors_Leaf { + v: Leaf.Leaf; + + constructor(v: Leaf.Leaf) { + this.v = v; + } + + [Symbol.dispose](): void { + this.v[Symbol.dispose](); + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return Leaf.fmt$fmt_Debug(this.v, a); + } + + fmt$Display(a: rt.Formatter): rt.Result { + return Leaf.fmt$fmt_Display(this.v, a); + } + + source$Error(): Error | undefined { + return undefined; + } +} + +export class of$errors_Transported { + v: Transported.Transported; + + constructor(v: Transported.Transported) { + this.v = v; + } + + [Symbol.dispose](): void { + this.v[Symbol.dispose](); + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return Transported.fmt$fmt_Debug(this.v, a); + } + + fmt$Display(a: rt.Formatter): rt.Result { + return Transported.fmt$fmt_Display(this.v, a); + } + + source$Error(): Error | undefined { + return Transported.source$Error(this.v); + } +} + +export class of$num_ParseIntError { + v: rt.ParseIntError; + + constructor(v: rt.ParseIntError) { + this.v = v; + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return rt.fmtParseIntErrorDebug(this.v, a); + } + + fmt$Display(a: rt.Formatter): rt.Result { + return rt.fmtParseIntErrorDisplay(this.v, a); + } + + source$Error(): Error | undefined { + return undefined; + } +} + +export class of$ref_errors_Failure { + v: Failure.Failure; + + constructor(v: Failure.Failure) { + this.v = v; + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return Failure.fmt$fmt_Debug(this.v, a); + } + + fmt$Display(a: rt.Formatter): rt.Result { + return Failure.fmt$fmt_Display(this.v, a); + } + + source$Error(): Error | undefined { + return Failure.source$Error(this.v); + } +} + +export class of$ref_errors_Leaf { + v: Leaf.Leaf; + + constructor(v: Leaf.Leaf) { + this.v = v; + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return Leaf.fmt$fmt_Debug(this.v, a); + } + + fmt$Display(a: rt.Formatter): rt.Result { + return Leaf.fmt$fmt_Display(this.v, a); + } + + source$Error(): Error | undefined { + return undefined; + } +} + +//# sourceMappingURL=error.Error.ts.map diff --git a/ts/fixtures/lang/expected/core/result.ts b/ts/fixtures/lang/expected/core/result.ts index 3d34e4825..6985b2a6a 100644 --- a/ts/fixtures/lang/expected/core/result.ts +++ b/ts/fixtures/lang/expected/core/result.ts @@ -13,6 +13,7 @@ import { eq$1horntk as float_parse_eq$1horntk } from "../core/num/float_parse.ts import { eq$1v93ipo as str_error_eq$1v93ipo, eq$1f3e51k as str_error_eq$1f3e51k } from "../core/str/error.ts"; import { cmp$175ka5q as poll_cmp$175ka5q } from "../core/task/poll.ts"; import { eq$0dhyfbb as time_eq$0dhyfbb } from "../core/time.ts"; +import type * as Failure from "../errors.Failure.ts"; import type * as heap_Fault from "../heap.Fault.ts"; import type * as locks_Tally from "../locks.Tally.ts"; import * as Part from "../orders.Part.ts"; @@ -782,6 +783,13 @@ export function mapErr$1lsqfgi(self: rt.Result, op: (a0: rt.u16) return rt.Err(op(self.error)); } +export function mapErr$1ff8zoy(self: rt.Result, _: (a0: rt.ParseIntError) => Failure.Failure): rt.Result { + if (self.ok) { + return self as rt.Result; + } + return rt.Err({ tag: "Parse", _0: self.error } as Failure.Failure); +} + export function mapErr$0u5yxjj(self: rt.Result, op: (a0: rt.ParseIntError) => rt.IntErrorKind): rt.Result { if (self.ok) { return self as rt.Result; diff --git a/ts/fixtures/lang/expected/errors.Coded.ts b/ts/fixtures/lang/expected/errors.Coded.ts new file mode 100644 index 000000000..4eced1b6a --- /dev/null +++ b/ts/fixtures/lang/expected/errors.Coded.ts @@ -0,0 +1,40 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { drop$errors_Failure } from "./lib.ts"; +import type * as error_Error from "./core/error.Error.ts"; +import * as Failure from "./errors.Failure.ts"; + +/** The crate's own trait over `Error`, as moq's transports' errors are. */ +export type Coded = + | { code$Coded: () => rt.u8; source$Error: () => error_Error.Error | undefined; fmt$Display: (a0: rt.Formatter) => rt.Result; fmt$Debug: (a0: rt.Formatter) => rt.Result }; + +export class of$errors_Failure { + v: Failure.Failure; + + constructor(v: Failure.Failure) { + this.v = v; + } + + [Symbol.dispose](): void { + drop$errors_Failure(this.v); + } + + code$Coded(): rt.u8 { + return Failure.code$errors_Coded(this.v); + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return Failure.fmt$fmt_Debug(this.v, a); + } + + fmt$Display(a: rt.Formatter): rt.Result { + return Failure.fmt$fmt_Display(this.v, a); + } + + source$Error(): error_Error.Error | undefined { + return Failure.source$Error(this.v); + } +} + +//# sourceMappingURL=errors.Coded.ts.map diff --git a/ts/fixtures/lang/expected/errors.Failure.ts b/ts/fixtures/lang/expected/errors.Failure.ts new file mode 100644 index 000000000..6b95b8923 --- /dev/null +++ b/ts/fixtures/lang/expected/errors.Failure.ts @@ -0,0 +1,61 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import * as error_Error from "./core/error.Error.ts"; +import * as Leaf from "./errors.Leaf.ts"; + +export function fmt$fmt_Debug(self: Failure, f: rt.Formatter): rt.Result { + if (self.tag === "Short") { + const _5: string = "Short"; + const _6: rt.Fmt = (f: rt.Formatter) => rt.u8Debug(self._0, f); + return rt.debugTupleFinish(rt.debugTupleField(rt.fmtDebugTuple(f, _5), _6)); + } + if (self.tag === "Wrapped") { + const _9: string = "Wrapped"; + const _10: rt.Fmt = (f: rt.Formatter) => Leaf.fmt$fmt_Debug(self._0, f); + return rt.debugTupleFinish(rt.debugTupleField(rt.fmtDebugTuple(f, _9), _10)); + } + const _13: string = "Parse"; + const _14: rt.Fmt = (f: rt.Formatter) => rt.fmtParseIntErrorDebug(self._0, f); + return rt.debugTupleFinish(rt.debugTupleField(rt.fmtDebugTuple(f, _13), _14)); +} + +/** An error whose source is the crate's, std's, or none. */ +export type Failure = + | { tag: "Short"; _0: rt.u8 } + | { tag: "Wrapped"; _0: Leaf.Leaf } + | { tag: "Parse"; _0: rt.ParseIntError }; + +export function fmt$fmt_Display(self: Failure, f: rt.Formatter): rt.Result { + if (self.tag === "Short") { + const arg = self._0; + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(arg, f)]; + return rt.fmtWriteFmt(f, ["short by ", args2[0]]); + } + if (self.tag === "Wrapped") { + return rt.fmtWriteStr(f, "wrapped"); + } + return rt.fmtWriteStr(f, "unparsed"); +} + +export function source$Error(self: Failure): error_Error.Error | undefined { + if (self.tag === "Short") { + return undefined; + } + if (self.tag === "Wrapped") { + return new error_Error.of$errors_Leaf(self._0); + } + return new error_Error.of$num_ParseIntError(self._0); +} + +export function code$errors_Coded(self: Failure): rt.u8 { + if (self.tag === "Short") { + return self._0; + } + if (self.tag === "Wrapped") { + return self._0.code; + } + return 0; +} + +//# sourceMappingURL=errors.Failure.ts.map diff --git a/ts/fixtures/lang/expected/errors.Leaf.ts b/ts/fixtures/lang/expected/errors.Leaf.ts new file mode 100644 index 000000000..a66e0f4eb --- /dev/null +++ b/ts/fixtures/lang/expected/errors.Leaf.ts @@ -0,0 +1,37 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { DROPPED } from "./errors.ts"; +import { set$0st49po as local_set$0st49po, get$1ei2eoj as local_get$1ei2eoj } from "./std/thread/local.ts"; + +export function fmt$fmt_Debug(self: Leaf, f: rt.Formatter): rt.Result { + const _3: string = "Leaf"; + const _4: string = "code"; + const _5: rt.Fmt = (f: rt.Formatter) => rt.u8Debug(self.code, f); + return rt.debugStructFinish(rt.debugStructField(rt.fmtDebugStruct(f, _3), _4, _5)); +} + +/** The crate's error without a source of its own, which counts its drops. */ +export class Leaf { + code: rt.u8; + + constructor(code: rt.u8) { + this.code = code; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function fmt$fmt_Display(self: Leaf, f: rt.Formatter): rt.Result { + const arg = self.code; + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(arg, f)]; + return rt.fmtWriteFmt(f, ["leaf ", args2[0]]); +} + +export function drop$ops_Drop(self: Leaf): void { + local_set$0st49po(DROPPED, rt.u32WrappingAdd(rt.u32WrappingMul(local_get$1ei2eoj(DROPPED), 10), rt.u8Rem(self.code, 10) as rt.u32)); +} + +//# sourceMappingURL=errors.Leaf.ts.map diff --git a/ts/fixtures/lang/expected/errors.Transported.ts b/ts/fixtures/lang/expected/errors.Transported.ts new file mode 100644 index 000000000..1046b1fc8 --- /dev/null +++ b/ts/fixtures/lang/expected/errors.Transported.ts @@ -0,0 +1,34 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import type * as error_Error from "./core/error.Error.ts"; +import type * as Coded from "./errors.Coded.ts"; + +export function fmt$fmt_Debug(self: Transported, f: rt.Formatter): rt.Result { + const _3: string = "Transported"; + const _4: rt.Fmt = (f: rt.Formatter) => self._0.fmt$Debug(f); + return rt.debugTupleFinish(rt.debugTupleField(rt.fmtDebugTuple(f, _3), _4)); +} + +/** A boxed `Coded`, forwarding `Error` to it, as moq's boxed transport error does. */ +export class Transported { + _0: Coded.Coded; + + constructor(_0: Coded.Coded) { + this._0 = _0; + } + + [Symbol.dispose](): void { + rt.drop(this._0); + } +} + +export function fmt$fmt_Display(self: Transported, f: rt.Formatter): rt.Result { + return self._0.fmt$Display(f); +} + +export function source$Error(self: Transported): error_Error.Error | undefined { + return self._0.source$Error(); +} + +//# sourceMappingURL=errors.Transported.ts.map diff --git a/ts/fixtures/lang/expected/errors.ts b/ts/fixtures/lang/expected/errors.ts index 10150aca7..94b3a2dd2 100644 --- a/ts/fixtures/lang/expected/errors.ts +++ b/ts/fixtures/lang/expected/errors.ts @@ -1,12 +1,21 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; +import { drop$Result_u8_boxed_Box_dyn_Error_marker_Sync_marker_Send, drop$errors_Failure } from "./lib.ts"; import { then$0afp4no } from "./core/bool.ts"; import { eq$09vw48j as convert_eq$09vw48j } from "./core/char/convert.ts"; +import * as error_Error from "./core/error.Error.ts"; +import { fromStr$1hq0zfa as num_fromStr$1hq0zfa } from "./core/num.ts"; import { eq$18sin5z as num_error_eq$18sin5z } from "./core/num/error.ts"; -import { eq$04qb8k3 as result_eq$04qb8k3, eq$10fppe8 as result_eq$10fppe8, eq$0ecfqx9 as result_eq$0ecfqx9, eq$0nlhjpf as result_eq$0nlhjpf, eq$04iua4k as result_eq$04iua4k, eq$1dhtbkj as result_eq$1dhtbkj, eq$0pe0qmq as result_eq$0pe0qmq, eq$1mpp8xa as result_eq$1mpp8xa, eq$0grmb7g as result_eq$0grmb7g, eq$19kc15f as result_eq$19kc15f, eq$0rj0czq as result_eq$0rj0czq, eq$1hq3neq as result_eq$1hq3neq, eq$08241oi as result_eq$08241oi, eq$0bl30vt as result_eq$0bl30vt, eq$1fkhuzl as result_eq$1fkhuzl, eq$0p1bb4y as result_eq$0p1bb4y, eq$03gbayb as result_eq$03gbayb, err$1ujom0k, err$02r96h1, err$0jf6smd, map$0fm1w6u as result_map$0fm1w6u, map$0r57egs as result_map$0r57egs, mapErr$0u5yxjj as result_mapErr$0u5yxjj } from "./core/result.ts"; +import { eq$04qb8k3 as result_eq$04qb8k3, eq$10fppe8 as result_eq$10fppe8, eq$0ecfqx9 as result_eq$0ecfqx9, eq$0nlhjpf as result_eq$0nlhjpf, eq$04iua4k as result_eq$04iua4k, eq$1dhtbkj as result_eq$1dhtbkj, eq$0pe0qmq as result_eq$0pe0qmq, eq$1mpp8xa as result_eq$1mpp8xa, eq$0grmb7g as result_eq$0grmb7g, eq$19kc15f as result_eq$19kc15f, eq$0rj0czq as result_eq$0rj0czq, eq$1hq3neq as result_eq$1hq3neq, eq$08241oi as result_eq$08241oi, eq$0bl30vt as result_eq$0bl30vt, eq$1fkhuzl as result_eq$1fkhuzl, eq$0p1bb4y as result_eq$0p1bb4y, eq$03gbayb as result_eq$03gbayb, err$1ujom0k, err$02r96h1, err$0jf6smd, map$0fm1w6u as result_map$0fm1w6u, map$0r57egs as result_map$0r57egs, mapErr$1ff8zoy as result_mapErr$1ff8zoy, mapErr$0u5yxjj as result_mapErr$0u5yxjj } from "./core/result.ts"; import { parse$0vh4cry, parse$082ztwj, parse$1ci7nyc, parse$1s5pqso, parse$0o4ono0, parse$0at0fdl } from "./core/str.ts"; import { eq$1f3e51k as str_error_eq$1f3e51k } from "./core/str/error.ts"; +import * as Coded from "./errors.Coded.ts"; +import type * as Failure from "./errors.Failure.ts"; +import * as Leaf from "./errors.Leaf.ts"; +import * as Transported from "./errors.Transported.ts"; +import { put as fmt_put } from "./fmt.ts"; +import { set$0st49po as local_set$0st49po, get$1ei2eoj as local_get$1ei2eoj } from "./std/thread/local.ts"; /** Each pair's `==` and `!=`, a bit each. */ export function pairs$Option_Result_u8_num_ParseIntError(values: Array | undefined>, out: rt.ByteVec): void { @@ -533,4 +542,129 @@ export function eqs(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { } } +export const DROPPED: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey(DROPPED$init, false); + +export function DROPPED$init(): rt.Cell { + return new rt.Cell(0); +} + +/** Each error in the chain, by `Display` and `Debug`. */ +export function chain(e: error_Error.Error): string { + const args2: rt.Fmt[] = [(f: rt.Formatter) => e.fmt$Display(f), (f: rt.Formatter) => e.fmt$Debug(f)]; + let s: string = rt.fmtFormat([args2[0], " ", args2[1]]); + let cur: error_Error.Error | undefined = e.source$Error(); + while (true) { + if (cur !== undefined) { + const c: error_Error.Error = cur; + const args4: rt.Fmt[] = [(f: rt.Formatter) => c.fmt$Display(f), (f: rt.Formatter) => c.fmt$Debug(f)]; + const _17: string = rt.fmtFormat([" <- ", args4[0], " ", args4[1]]); + s = s + _17; + cur = c.source$Error(); + } else { + return s; + } + } +} + +export function failure(kind: rt.u8, n: rt.u8): Failure.Failure { + const _3: rt.u8 = rt.u8Rem(kind, 3); + if (_3 === 0) { + return { tag: "Short", _0: n } as Failure.Failure; + } + if (_3 === 1) { + return { tag: "Wrapped", _0: new Leaf.Leaf(n) } as Failure.Failure; + } + const _11: rt.usize = rt.u8Rem(n, 3) as rt.usize; + if (!(_11 < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + _11); + } + return { tag: "Parse", _0: rt.resultUnwrapErr(num_fromStr$1hq0zfa(["", "x", "999"][_11]), rt.u8Debug) } as Failure.Failure; +} + +/** `?` boxes a `Failure` as a `dyn Error`, as does `into`. */ +export function checked(kind: rt.u8, n: rt.u8): rt.Result { + if (!(rt.u8Rem(n, 4) === 0)) { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(n, f)]; + const _8: rt.Result = result_mapErr$1ff8zoy(num_fromStr$1hq0zfa(rt.fmtFormat([args2[0], args2[0]])), (a0: rt.ParseIntError): Failure.Failure => { + return { tag: "Parse", _0: a0 }; + }); + if (_8.ok) { + return rt.Ok(_8.value); + } + return rt.Err(new error_Error.of$errors_Failure((_8 as { ok: false; error: Failure.Failure }).error)); + } + return rt.Err(new error_Error.of$errors_Failure(failure(kind, n))); +} + +export function objects(dataData: Uint8Array, dataStart: number, dataLen: number, out: rt.ByteVec): void { + if (dataLen >= 2) { + const _10: [rt.u8, rt.u8] = [dataData[dataStart], dataData[dataStart + 1]]; + const a: rt.u8 = _10[0]; + const b: rt.u8 = _10[1]; + const _24: Failure.Failure = failure(a, b); + const _21: string = chain(new error_Error.of$errors_Failure(_24)); + const _29: Leaf.Leaf = new Leaf.Leaf(a); + const _26: string = chain(new error_Error.of$errors_Leaf(_29)); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, _21), (f: rt.Formatter) => rt.fmtPad(f, _26)]; + const _17: string = rt.fmtFormat([args2[0], " | ", args2[1], " | "]); + _29[Symbol.dispose](); + drop$errors_Failure(_24); + fmt_put(out, _17); + const part = chain(new error_Error.of$num_ParseIntError(rt.resultUnwrapErr(num_fromStr$1hq0zfa("x"), rt.u8Debug))); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, part)]; + fmt_put(out, rt.fmtFormat([args4[0], " | "])); + const held: Failure.Failure = failure(a, b); + const _64: string = chain(new error_Error.of$boxed_Box_errors_Failure(held)); + const _68: string = chain(new error_Error.of$ref_errors_Failure(held)); + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, _64), (f: rt.Formatter) => rt.fmtPad(f, _68)]; + fmt_put(out, rt.fmtFormat([args6[0], " | ", args6[1], " | "])); + const coded: Coded.Coded = new Coded.of$errors_Failure(failure(b, a)); + const up: error_Error.Error = coded; + const _89: rt.u8 = coded.code$Coded(); + const _92: string = chain(up); + const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(_89, f), (f: rt.Formatter) => rt.fmtPad(f, _92)]; + fmt_put(out, rt.fmtFormat([args8[0], " ", args8[1], " | "])); + const boxed: Transported.Transported = new Transported.Transported(coded); + const _107: string = chain(new error_Error.of$errors_Transported(boxed)); + const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, _107), (f: rt.Formatter) => Transported.fmt$fmt_Debug(boxed, f)]; + fmt_put(out, rt.fmtFormat([args10[0], " ", rt.fmtSpec({ alternate: true }, args10[1]), " | "])); + local_set$0st49po(DROPPED, 0); + const results: rt.Result[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(new rt.ByteSlice(dataData, dataStart + 2, dataLen - 2))), (_2: rt.u8): rt.Result => { + const n: rt.u8 = _2; + return checked(rt.u8WrappingAdd(a, n), n); + }), undefined, 8, true); + for (const _127 of results) { + if (_127.ok) { + const arg = _127.value; + const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(arg, f)]; + fmt_put(out, rt.fmtFormat([args12[0], " "])); + } else { + const part2 = chain(_127.error); + const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, part2)]; + fmt_put(out, rt.fmtFormat([args14[0], " "])); + } + } + rt.dropAll(results, drop$Result_u8_boxed_Box_dyn_Error_marker_Sync_marker_Send); + const part3 = local_get$1ei2eoj(DROPPED); + const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part3, f)]; + fmt_put(out, rt.fmtFormat(["| ", args16[0], " "])); + local_set$0st49po(DROPPED, 0); + const leaf: Leaf.Leaf = new Leaf.Leaf(b); + const borrowed: error_Error.Error = new error_Error.of$ref_errors_Leaf(leaf); + const part4 = chain(borrowed); + const args18: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, part4)]; + fmt_put(out, rt.fmtFormat([args18[0], " "])); + rt.drop(borrowed); + const part5 = local_get$1ei2eoj(DROPPED); + const args20: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part5, f)]; + fmt_put(out, rt.fmtFormat([args20[0], " "])); + leaf[Symbol.dispose](); + const part6 = local_get$1ei2eoj(DROPPED); + const args22: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part6, f)]; + fmt_put(out, rt.fmtFormat([args22[0]])); + boxed[Symbol.dispose](); + drop$errors_Failure(held); + } +} + //# sourceMappingURL=errors.ts.map diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 8b91942a9..da044d898 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -26,7 +26,7 @@ import { dyns, shared as dyns_shared, anys, fns, pointers } from "./dyns.ts"; import { edits as edits_edits, bytes as edits_bytes } from "./edits.ts"; import { vecs as elements_vecs, arrays as elements_arrays, segments as elements_segments, inline, edges as elements_edges, rawSlices, emptyParts } from "./elements.ts"; import { ends as ends_ends } from "./ends.ts"; -import { eqs } from "./errors.ts"; +import { eqs, objects as errors_objects } from "./errors.ts"; import { fields as fields_fields } from "./fields.ts"; import { floats, mulAdds as floats_mulAdds, formats as floats_formats, parses as floats_parses } from "./floats.ts"; import { ints, strings, derived, written as fmt_written, counts as fmt_counts, rounding, panics as fmt_panics, wrapped as fmt_wrapped, collections, enums, iters as fmt_iters, writes as fmt_writes, objects as fmt_objects } from "./fmt.ts"; @@ -325,7 +325,9 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) sorts_btrees(a0165, a1165.data, a1165.start, a1165.length, a2165); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::pointers", (_2212: rt.u8, data112: rt.ByteSlice, out113: rt.ByteVec): undefined => { pointers(data112.data, data112.start, data112.length, out113); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["searches::searches", searches as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["searches::texts", texts as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["searches::searches", searches as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["searches::texts", texts as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["errors::objects", (_2213: rt.u8, data113: rt.ByteSlice, out114: rt.ByteVec): undefined => { + errors_objects(data113.data, data113.start, data113.length, out114); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } /** Run one case. */ diff --git a/ts/fixtures/lang/expected/lib.ts b/ts/fixtures/lang/expected/lib.ts index 0a418097b..24c845a55 100644 --- a/ts/fixtures/lang/expected/lib.ts +++ b/ts/fixtures/lang/expected/lib.ts @@ -10,6 +10,7 @@ import type * as Quirky from "./collections.Quirky.ts"; import type * as collections_Tally from "./collections.Tally.ts"; import type * as conversions_Logged from "./conversions.Logged.ts"; import type * as Any from "./core/any.Any.ts"; +import type * as error_Error from "./core/error.Error.ts"; import type * as ArcReleasing from "./counts.ArcReleasing.ts"; import type * as ArcShared from "./counts.ArcShared.ts"; import type * as counts_Noisy from "./counts.Noisy.ts"; @@ -20,6 +21,7 @@ import type * as dyns_Noisy from "./dyns.Noisy.ts"; import type * as Square from "./dyns.Square.ts"; import type * as Tri from "./dyns.Tri.ts"; import * as Inline from "./elements.Inline.ts"; +import type * as Failure from "./errors.Failure.ts"; import type * as Couple from "./fields.Couple.ts"; import type * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; @@ -1204,6 +1206,12 @@ export function drop$Result_tuple2_usize_u8_sync_Arc_dyn_Any_marker_Sync_marker_ } } +export function drop$Result_u8_boxed_Box_dyn_Error_marker_Sync_marker_Send(_1: rt.Result): void { + if (!_1.ok) { + rt.drop(_1.error); + } +} + export function drop$Vec_Rc_counts_Noisy(value: rt.Arc[]): void { rt.dropAll(value); } @@ -1244,6 +1252,12 @@ export function drop$elements_Inline_sync_Arc_u8(_1: Inline.Inline Inline.drop$ops_Drop_sync_Arc_u8(_1); } +export function drop$errors_Failure(_1: Failure.Failure): void { + if (_1.tag === "Wrapped") { + _1._0[Symbol.dispose](); + } +} + export function drop$generics_Run_generics_Ticked(_1: generics_Run.Run): void { if (_1.tag === "Listed") { rt.dropAll(_1._0 as Ticked.Ticked[]); diff --git a/ts/fixtures/lang/src/errors.rs b/ts/fixtures/lang/src/errors.rs index 2f077e1b0..e3cc7253a 100644 --- a/ts/fixtures/lang/src/errors.rs +++ b/ts/fixtures/lang/src/errors.rs @@ -1,11 +1,19 @@ //! `==` and `!=` of a `Result` and of std's errors: every pair of a type's values at its -//! edges and of the input, so `Ok` meets `Err` and each error kind meets every other. +//! edges and of the input, so `Ok` meets `Err` and each error kind meets every other. A +//! `dyn Error` of the crate's errors and std's, borrowed, boxed by `?`, and upcast from the +//! crate's own trait, written by its `Display` and `Debug` and walked by `source`, and +//! dropping what it owns as its box drops. +use std::cell::Cell; use std::convert::Infallible; +use std::error::Error; +use std::fmt; use std::num::{IntErrorKind, NonZero, ParseIntError, TryFromIntError}; use std::str::FromStr; use std::time::Duration; +use crate::fmt::put; + /// Strings that parse into one of the types, or fail each way it can: empty, an invalid /// digit, past either end, zero, more than one char, a sign alone, and NaN. const EDGES: &[&str] = &[ @@ -157,3 +165,162 @@ pub fn eqs(kind: u8, data: &[u8], out: &mut Vec) { } } } + +thread_local! { + static DROPPED: Cell = const { Cell::new(0) }; +} + +/// The crate's error without a source of its own, which counts its drops. +#[derive(Debug)] +struct Leaf { + code: u8, +} + +impl fmt::Display for Leaf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "leaf {}", self.code) + } +} + +impl Error for Leaf {} + +impl Drop for Leaf { + fn drop(&mut self) { + DROPPED.set(DROPPED.get().wrapping_mul(10).wrapping_add(u32::from(self.code % 10))); + } +} + +/// An error whose source is the crate's, std's, or none. +#[derive(Debug)] +enum Failure { + Short(u8), + Wrapped(Leaf), + Parse(ParseIntError), +} + +impl fmt::Display for Failure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Failure::Short(n) => write!(f, "short by {n}"), + Failure::Wrapped(_) => f.write_str("wrapped"), + Failure::Parse(_) => f.write_str("unparsed"), + } + } +} + +impl Error for Failure { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Failure::Short(_) => None, + Failure::Wrapped(leaf) => Some(leaf), + Failure::Parse(e) => Some(e), + } + } +} + +/// The crate's own trait over `Error`, as moq's transports' errors are. +trait Coded: Error { + fn code(&self) -> u8; +} + +impl Coded for Failure { + fn code(&self) -> u8 { + match self { + Failure::Short(n) => *n, + Failure::Wrapped(leaf) => leaf.code, + Failure::Parse(_) => 0, + } + } +} + +/// A boxed `Coded`, forwarding `Error` to it, as moq's boxed transport error does. +#[derive(Debug)] +struct Transported(Box); + +impl fmt::Display for Transported { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Error for Transported { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.0.source() + } +} + +/// Each error in the chain, by `Display` and `Debug`. +fn chain(e: &dyn Error) -> String { + let mut s = format!("{e} {e:?}"); + let mut cur = e.source(); + while let Some(c) = cur { + s += &format!(" <- {c} {c:?}"); + cur = c.source(); + } + s +} + +fn failure(kind: u8, n: u8) -> Failure { + match kind % 3 { + 0 => Failure::Short(n), + 1 => Failure::Wrapped(Leaf { code: n }), + _ => Failure::Parse(u8::from_str(["", "x", "999"][usize::from(n % 3)]).unwrap_err()), + } +} + +/// `?` boxes a `Failure` as a `dyn Error`, as does `into`. +fn checked(kind: u8, n: u8) -> Result> { + if n % 4 == 0 { + return Err(failure(kind, n).into()); + } + let parsed: u8 = u8::from_str(&format!("{n}{n}")).map_err(Failure::Parse)?; + Ok(parsed) +} + +pub fn objects(data: &[u8], out: &mut Vec) { + let [a, b, rest @ ..] = data else { + return; + }; + let (a, b) = (*a, *b); + + // Borrowed, of the crate's and std's errors. + put( + out, + &format!("{} | {} | ", chain(&failure(a, b)), chain(&Leaf { code: a })), + ); + let parse = u8::from_str("x").unwrap_err(); + put(out, &format!("{} | ", chain(&parse))); + // A box and a reference are errors too, whose `source` is what they point at's. + let held = Box::new(failure(a, b)); + let lent = &*held; + put(out, &format!("{} | {} | ", chain(&held), chain(&lent))); + + // Through the crate's trait, boxed, and upcast to `dyn Error`. + let coded: Box = Box::new(failure(b, a)); + let up: &dyn Error = &*coded; + put(out, &format!("{} {} | ", coded.code(), chain(up))); + let boxed = Transported(coded); + put(out, &format!("{} {:#?} | ", chain(&boxed), boxed)); + + // Boxed by `?` and `into`, then dropped: the box drops what it owns. + DROPPED.set(0); + let results: Vec<_> = rest.iter().map(|&n| checked(a.wrapping_add(n), n)).collect(); + for r in &results { + match r { + Ok(n) => put(out, &format!("{n} ")), + Err(e) => put(out, &format!("{} ", chain(&**e))), + } + } + drop(results); + put(out, &format!("| {} ", DROPPED.get())); + + // A box of a reference drops nothing; the leaf drops with its owner. + DROPPED.set(0); + let leaf = Leaf { code: b }; + let borrowed: Box = (&leaf).into(); + put(out, &format!("{} ", chain(&*borrowed))); + drop(borrowed); + put(out, &format!("{} ", DROPPED.get())); + drop(leaf); + put(out, &format!("{}", DROPPED.get())); +} diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 3d09a565a..69b1cd5c6 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -311,6 +311,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("dyns::pointers", |_, data, out| dyns::pointers(data, out)), ("searches::searches", searches::searches), ("searches::texts", searches::texts), + ("errors::objects", |_, data, out| errors::objects(data, out)), ] } diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 2c0a6d026..65f9ab8e5 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -110,7 +110,7 @@ impl<'tcx> Translator<'_, 'tcx> { return Some(object.clone()); } // A trait of a crate translated from source is as the crate's own. - if !self.own(principal) && !is_any(tcx, principal) && !is_buf(tcx, principal) { + if !self.own(principal) && !is_any(tcx, principal) && !is_error(tcx, principal) && !is_buf(tcx, principal) { self.unrepresented( span, format!("`{ty}`, a trait object of another crate's trait, which is not supported yet"), @@ -150,6 +150,11 @@ impl<'tcx> Translator<'_, 'tcx> { // `Any`'s object is its class, which a downcast checks for, so it needs no method. for &tr in traits.iter().filter(|&&tr| !is_any(tcx, tr)) { for &def_id in tcx.own_existential_vtable_entries(tr) { + // Of `Error`, only `source`: `type_id`'s `TypeId` has no JS value, `provide` is + // unstable, and `description` and `cause` are deprecated. + if is_error(tcx, tr) && tcx.item_name(def_id).as_str() != "source" { + continue; + } // `Buf::chunks_vectored`, whose `IoSlice` has no JS value. if is_buf(tcx, tr) && tcx.item_name(def_id).as_str() == "chunks_vectored" { continue; @@ -355,6 +360,16 @@ fn is_any(tcx: TyCtxt<'_>, def_id: DefId) -> bool { tcx.is_diagnostic_item(rustc_span::Symbol::intern("Any"), def_id) } +/// Whether `def_id` is std's `Error`. +pub fn is_error(tcx: TyCtxt<'_>, def_id: DefId) -> bool { + tcx.is_diagnostic_item(rustc_span::Symbol::intern("Error"), def_id) +} + +/// Whether `ty` is a `dyn Error`. +pub fn is_error_object(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool { + principal(ty).is_some_and(|p| is_error(tcx, p)) +} + /// Whether `def_id` is `bytes`' `Buf`, whose provided methods translate from its MIR for /// a buffer the mapping doesn't name. fn is_buf(tcx: TyCtxt<'_>, def_id: DefId) -> bool { diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 1bba257ac..32ef9aeee 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -1448,6 +1448,15 @@ fn mapping<'tcx>( { return Some(Expr::New(Box::new(Expr::Rt("Arc".into())), vec![value])); } + // An error, boxed as a `dyn Error`, which `?` converts it by. One of a string boxes + // std's own private error type. + let error = targ(gargs, 1); + if let Some(object) = to.boxed_ty() + && super::dynamic::is_error_object(tcx, object) + && !string(tcx, error.peel_refs()) + { + return Some(b.object_of(value, Ty::new_box(tcx, error), to, span)); + } // A heap's `Vec` is its elements. if let ty::Adt(adt, args) = from.kind() && Std::of(tcx, adt.did()) == Some(Std::BinaryHeap) @@ -2508,6 +2517,27 @@ fn mapping<'tcx>( let [value, f] = <[Expr; 2]>::try_from(args).ok()?; fmt::std_impl(b, tr, targ(gargs, 0), value, f, span, out)? } + // `Error`'s own `source`, which a type that has none keeps, and std's for a reference + // or a box, which asks what it points at, its JS value. + ("Error", "source") + if tcx + .trait_of_assoc(call.def_id) + .is_some_and(|tr| super::dynamic::is_error(tcx, tr)) => + { + let self_ty = targ(gargs, 0); + if let Some(inner) = self_ty.builtin_deref(true).filter(|_| !self_ty.is_raw_ptr()) { + return Some(b.call_def(call.def_id, tcx.mk_args(&[inner.into()]), args, span, out)); + } + let instance = Instance::try_resolve(tcx, b.t.typing_env_pub(), call.def_id, gargs).ok()??; + if instance.def_id() != call.def_id { + return None; + } + let sig = b.t.sig(call.def_id, gargs); + match sig.output().kind() { + ty::Adt(_, option) => b.t.none(option.type_at(0)), + _ => return None, + } + } ("ToString", "to_string") => { let ty = targ(gargs, 0); let value = args.pop()?;