From 58fdd4db2949121285a5ff91085a7ca886436c92 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 05:46:20 -0800 Subject: [PATCH 1/3] feat(ts): translate dyn Error as an object of source, Debug, and Display Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 2 +- quest/m3/ts-dyn.md | 8 +- ts/README.md | 18 ++- ts/fixtures/lang/expected/core/error.Error.ts | 104 ++++++++++++ ts/fixtures/lang/expected/core/result.ts | 8 + ts/fixtures/lang/expected/errors.Coded.ts | 40 +++++ ts/fixtures/lang/expected/errors.Failure.ts | 61 +++++++ ts/fixtures/lang/expected/errors.Leaf.ts | 37 +++++ .../lang/expected/errors.Transported.ts | 34 ++++ ts/fixtures/lang/expected/errors.ts | 116 ++++++++++++- ts/fixtures/lang/expected/harness.ts | 8 +- ts/fixtures/lang/expected/lib.ts | 14 ++ ts/fixtures/lang/src/errors.rs | 152 +++++++++++++++++- ts/fixtures/lang/src/harness.rs | 1 + ts/src/lower/dynamic.rs | 17 +- ts/src/lower/library.rs | 16 ++ 16 files changed, 621 insertions(+), 15 deletions(-) create mode 100644 ts/fixtures/lang/expected/core/error.Error.ts create mode 100644 ts/fixtures/lang/expected/errors.Coded.ts create mode 100644 ts/fixtures/lang/expected/errors.Failure.ts create mode 100644 ts/fixtures/lang/expected/errors.Leaf.ts create mode 100644 ts/fixtures/lang/expected/errors.Transported.ts diff --git a/quest/m3/README.md b/quest/m3/README.md index 5c3286027..f7c425442 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`, `dyn Iterator`, `dyn Buf`, and `Arc` translate, so a consumer neither rewrites nor binds them +- [Trait objects](/quest/m3/ts-dyn.md) - `dyn Iterator`, `dyn Buf`, and `Arc` translate, so a consumer neither rewrites nor binds them - [Placeholders written to a crate's own fmt::Write](/quest/m3/ts-fmt-calls.md) - `write!` with placeholders into a crate's own writer translates, its `write_str` seeing each call std makes - [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library - [Drop order in std's folds and iterators](/quest/m3/ts-drop-order.md) - the values that drop still refused in std's consumers, flattens, and collects drop as std's do diff --git a/quest/m3/ts-dyn.md b/quest/m3/ts-dyn.md index 681418e6a..1dfaa78c3 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`, `dyn Iterator`, and `dyn bytes::Buf`), and behind any +that moq still refuses (`dyn Iterator` and `dyn bytes::Buf`), and behind any pointer std coerces to one, such as `Arc`, with Rust's dispatch and drop order, proven by differential tests against native Rust, so a consumer neither rewrites nor binds them. @@ -16,8 +16,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`, and `bytes::Buf` the `rt.ByteSlice` it reads. - `Arc` is refused where it is built, found by [#247](https://github.com/moq-dev/rust2/pull/247) on 2026-10-29: `Arc`, `Rc`, and `Box` diff --git a/ts/README.md b/ts/README.md index cadf317f4..efa34b999 100644 --- a/ts/README.md +++ b/ts/README.md @@ -121,6 +121,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` | an object with the trait's methods, its supertraits' included: `shape.area$Shape()`; behind an `Arc`, 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 | @@ -465,9 +466,16 @@ 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 -or a mapped crate's trait other than `&dyn Debug`, `&dyn Display`, `dyn Future`, -`dyn Any`, and the function traits, of a generic +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`, `&dyn Display`, +`dyn Future`, `dyn Any`, `dyn Error`, 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 `Weak` or a struct's last field, is refused, and so are an upcast to `dyn Any` and `Any::type_id`. @@ -837,8 +845,8 @@ they need. Byte reinterpretation, pointer arithmetic and addresses, raw allocation, 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*`, and the translated crates' traits' -behind `&`, `&mut`, `Box`, or `Arc` ([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*`, and the translated crates' traits' +behind `&`, `&mut`, `Box`, or `Arc` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `dyn bytes::Buf`, 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..800563f7d --- /dev/null +++ b/ts/fixtures/lang/expected/core/error.Error.ts @@ -0,0 +1,104 @@ +// 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$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; + } +} + +//# sourceMappingURL=error.Error.ts.map diff --git a/ts/fixtures/lang/expected/core/result.ts b/ts/fixtures/lang/expected/core/result.ts index c684cb71a..9661c71c7 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 locks_Tally from "../locks.Tally.ts"; import * as Part from "../orders.Part.ts"; import type * as returns_Error from "../returns.Error.ts"; @@ -760,6 +761,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..8c2887575 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,109 @@ 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 coded: Coded.Coded = new Coded.of$errors_Failure(failure(b, a)); + const up: error_Error.Error = coded; + const _66: rt.u8 = coded.code$Coded(); + const _69: string = chain(up); + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(_66, f), (f: rt.Formatter) => rt.fmtPad(f, _69)]; + fmt_put(out, rt.fmtFormat([args6[0], " ", args6[1], " | "])); + const boxed: Transported.Transported = new Transported.Transported(coded); + const _84: string = chain(new error_Error.of$errors_Transported(boxed)); + const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, _84), (f: rt.Formatter) => Transported.fmt$fmt_Debug(boxed, f)]; + fmt_put(out, rt.fmtFormat([args8[0], " ", rt.fmtSpec({ alternate: true }, args8[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 _104 of results) { + if (_104.ok) { + const arg = _104.value; + const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(arg, f)]; + fmt_put(out, rt.fmtFormat([args10[0], " "])); + } else { + const part2 = chain(_104.error); + const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, part2)]; + fmt_put(out, rt.fmtFormat([args12[0], " "])); + } + } + rt.dropAll(results, drop$Result_u8_boxed_Box_dyn_Error_marker_Sync_marker_Send); + const part3 = local_get$1ei2eoj(DROPPED); + const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part3, f)]; + fmt_put(out, rt.fmtFormat(["| ", args14[0]])); + boxed[Symbol.dispose](); + } +} + //# sourceMappingURL=errors.ts.map diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index a4b115d32..3d4b5bb7d 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -24,7 +24,7 @@ import { defaults as defaults_defaults } from "./defaults.ts"; import { durations } from "./durations.ts"; import { dyns, shared as dyns_shared, anys, fns } from "./dyns.ts"; import { edits as edits_edits, bytes as edits_bytes } from "./edits.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 } from "./fmt.ts"; @@ -43,7 +43,7 @@ import { locals } from "./locals.ts"; import { locks, relock, rwlocks, mapped, mutexMapped, printed as locks_printed, downgraded } from "./locks.ts"; import { u8s as methods_u8s, u16s as methods_u16s, u32s as methods_u32s, u64s as methods_u64s, u128s as methods_u128s, usizes as methods_usizes, i8s as methods_i8s, i16s as methods_i16s, i32s as methods_i32s, i64s as methods_i64s, i128s as methods_i128s, isizes as methods_isizes } from "./methods.ts"; import { nested as nested_nested } from "./nested.ts"; -import { objects } from "./objects.ts"; +import { objects as objects_objects } from "./objects.ts"; import { orders, bytes as orders_bytes, tuples as orders_tuples, held as orders_held, btrees } from "./orders.ts"; import { lists as pointers_lists, shared as pointers_shared, places as pointers_places, unchecked, nested as pointers_nested, copies as pointers_copies, sentinels, cells as pointers_cells, rebound as pointers_rebound, lapsed, writers, made as pointers_made, segments, uninitRuns, iteratedRuns, besideWritten, waiters, taken as pointers_taken, bytes as pointers_bytes, loaded, reattached, partWritten, aimed } from "./pointers.ts"; import { refs, toRefs, adapted as refs_adapted } from "./refs.ts"; @@ -165,7 +165,7 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["edits::bytes", edits_bytes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["arrays::from_fns", (_2147: rt.u8, data47: rt.ByteSlice, out48: rt.ByteVec): undefined => { fromFns(data47, out48); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["collections::optionals", optionals as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["objects::objects", (_2148: rt.u8, data48: rt.ByteSlice, out49: rt.ByteVec): undefined => { - objects(data48, out49); + objects_objects(data48, out49); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["holds::holds", holds as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::wrapped", (_2149: rt.u8, data49: rt.ByteSlice, out50: rt.ByteVec): undefined => { fmt_wrapped(data49, out50); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["refs::to_refs", (_2150: rt.u8, data50: rt.ByteSlice, out51: rt.ByteVec): undefined => { @@ -280,6 +280,8 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) downgraded(data97, out98); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["borrows::leaks", (_2198: rt.u8, data98: rt.ByteSlice, out99: rt.ByteVec): undefined => { leaks(data98, out99); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["errors::objects", (_2199: rt.u8, data99: rt.ByteSlice, out100: rt.ByteVec): undefined => { + errors_objects(data99.data, data99.start, data99.length, out100); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } diff --git a/ts/fixtures/lang/expected/lib.ts b/ts/fixtures/lang/expected/lib.ts index 6a4a3a663..3234e3775 100644 --- a/ts/fixtures/lang/expected/lib.ts +++ b/ts/fixtures/lang/expected/lib.ts @@ -9,12 +9,14 @@ import type * as collections_Picky from "./collections.Picky.ts"; import type * as Quirky from "./collections.Quirky.ts"; import type * as collections_Tally from "./collections.Tally.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"; import type * as RcReleasing from "./counts.RcReleasing.ts"; import type * as RcShared from "./counts.RcShared.ts"; import type * as durations_Window from "./durations.Window.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"; @@ -1130,6 +1132,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); } @@ -1162,6 +1170,12 @@ export function drop$cell_RefCell_Vec_sync_Arc_counts_ArcReleasing(value: rt.Ref rt.dropAll(value.value); } +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..1a0b9581e 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,145 @@ 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))); + + // 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())); +} diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 0a4253ba2..617f19912 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -288,6 +288,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("capacity::maps", capacity::maps), ("locks::downgraded", |_, data, out| locks::downgraded(data, out)), ("borrows::leaks", |_, data, out| borrows::leaks(data, out)), + ("errors::objects", |_, data, out| errors::objects(data, out)), ] } diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 2d73493a2..031f5754f 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) { + if !self.own(principal) && !is_any(tcx, principal) && !is_error(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; + } let name = format!("{}${}", super::camel(tcx.item_name(def_id).as_str()), tcx.item_name(tr)); if methods.iter().any(|m: &Method| m.name == name) { self.err(span, format!("`{ty}`, whose traits have two methods named `{name}`")); @@ -351,6 +356,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 `ty` is a `dyn Any`. pub fn is_any_object(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool { principal(ty).is_some_and(|p| is_any(tcx, p)) diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 7bb8a1e28..af10796a5 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -1400,6 +1400,14 @@ fn mapping<'tcx>( { return Some(boxed_vec(value, elem)); } + // An error, boxed as a `dyn Error`, which `?` converts it by. One of a string boxes + // std's own private error type. + if let Some(object) = to.boxed_ty() + && super::dynamic::is_error_object(tcx, object) + && !string(tcx, from.peel_refs()) + { + return Some(b.object_of(value, Ty::new_box(tcx, from), 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) @@ -2410,6 +2418,14 @@ 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. + ("Error", "source") if tcx.trait_of_assoc(call.def_id).is_some_and(|tr| super::dynamic::is_error(tcx, tr)) => { + 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()?; From b9ea8148eee1b3121b3e407aec83da0fd0b2771a Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 05:54:09 -0800 Subject: [PATCH 2/3] style: format Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/src/errors.rs | 5 ++++- ts/src/lower/library.rs | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ts/fixtures/lang/src/errors.rs b/ts/fixtures/lang/src/errors.rs index 1a0b9581e..3d996cfc4 100644 --- a/ts/fixtures/lang/src/errors.rs +++ b/ts/fixtures/lang/src/errors.rs @@ -284,7 +284,10 @@ pub fn objects(data: &[u8], out: &mut Vec) { 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 }))); + put( + out, + &format!("{} | {} | ", chain(&failure(a, b)), chain(&Leaf { code: a })), + ); let parse = u8::from_str("x").unwrap_err(); put(out, &format!("{} | ", chain(&parse))); diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index af10796a5..b147eefea 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -2419,7 +2419,11 @@ fn mapping<'tcx>( fmt::std_impl(b, tr, targ(gargs, 0), value, f, span, out)? } // `Error`'s own `source`, which a type that has none keeps. - ("Error", "source") if tcx.trait_of_assoc(call.def_id).is_some_and(|tr| super::dynamic::is_error(tcx, tr)) => { + ("Error", "source") + if tcx + .trait_of_assoc(call.def_id) + .is_some_and(|tr| super::dynamic::is_error(tcx, tr)) => + { let sig = b.t.sig(call.def_id, gargs); match sig.output().kind() { ty::Adt(_, option) => b.t.none(option.type_at(0)), From c496b85269419f28c6c699065674e6658d54b39a Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 06:25:58 -0800 Subject: [PATCH 3/3] fix(ts): forward a reference's and a box's Error::source, and box a borrowed error as borrowed Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/core/error.Error.ts | 64 +++++++++++++++++++ ts/fixtures/lang/expected/errors.ts | 54 +++++++++++----- ts/fixtures/lang/src/errors.rs | 16 ++++- ts/src/lower/library.rs | 16 ++++- 4 files changed, 129 insertions(+), 21 deletions(-) diff --git a/ts/fixtures/lang/expected/core/error.Error.ts b/ts/fixtures/lang/expected/core/error.Error.ts index 800563f7d..b0affc059 100644 --- a/ts/fixtures/lang/expected/core/error.Error.ts +++ b/ts/fixtures/lang/expected/core/error.Error.ts @@ -9,6 +9,30 @@ 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; @@ -101,4 +125,44 @@ export class of$num_ParseIntError { } } +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/errors.ts b/ts/fixtures/lang/expected/errors.ts index 8c2887575..94b3a2dd2 100644 --- a/ts/fixtures/lang/expected/errors.ts +++ b/ts/fixtures/lang/expected/errors.ts @@ -613,37 +613,57 @@ export function objects(dataData: Uint8Array, dataStart: number, dataLen: number 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 _66: rt.u8 = coded.code$Coded(); - const _69: string = chain(up); - const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(_66, f), (f: rt.Formatter) => rt.fmtPad(f, _69)]; - fmt_put(out, rt.fmtFormat([args6[0], " ", args6[1], " | "])); + 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 _84: string = chain(new error_Error.of$errors_Transported(boxed)); - const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, _84), (f: rt.Formatter) => Transported.fmt$fmt_Debug(boxed, f)]; - fmt_put(out, rt.fmtFormat([args8[0], " ", rt.fmtSpec({ alternate: true }, args8[1]), " | "])); + 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 _104 of results) { - if (_104.ok) { - const arg = _104.value; - const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(arg, f)]; - fmt_put(out, rt.fmtFormat([args10[0], " "])); - } else { - const part2 = chain(_104.error); - const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, part2)]; + 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 args14: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part3, f)]; - fmt_put(out, rt.fmtFormat(["| ", args14[0]])); + 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); } } diff --git a/ts/fixtures/lang/src/errors.rs b/ts/fixtures/lang/src/errors.rs index 3d996cfc4..e3cc7253a 100644 --- a/ts/fixtures/lang/src/errors.rs +++ b/ts/fixtures/lang/src/errors.rs @@ -290,6 +290,10 @@ pub fn objects(data: &[u8], out: &mut Vec) { ); 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)); @@ -308,5 +312,15 @@ pub fn objects(data: &[u8], out: &mut Vec) { } } drop(results); - put(out, &format!("| {}", DROPPED.get())); + 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/src/lower/library.rs b/ts/src/lower/library.rs index b147eefea..3e4063d09 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -1402,11 +1402,12 @@ fn mapping<'tcx>( } // 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, from.peel_refs()) + && !string(tcx, error.peel_refs()) { - return Some(b.object_of(value, Ty::new_box(tcx, from), to, span)); + 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() @@ -2418,12 +2419,21 @@ 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. + // `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)),