From 70ecfb862aa007107055d55e24ed342c69fd7bba Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:01:46 -0700 Subject: [PATCH 1/6] chore(quest): claim quest/m3/ts-dyn.md Quest-Claim: 6f2b4aad0c0ddd9f From 17886708e7f64dbea78ed85401ba1f4f0cbee803 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:20:39 -0700 Subject: [PATCH 2/6] feat(ts): translate trait objects of the crate's own traits A `&dyn Trait`, `&mut dyn Trait`, or `Box` is an object with the trait's methods, its supertraits' included, named for their trait. A coercion wraps the value in a class of its type's whose methods call that type's impl, holding the `rt.Place` of a `&mut` to a value JS copies, and whose `[Symbol.dispose]` drops a boxed value. Every other unsizing to a trait object, such as an `Arc`'s, is refused rather than passed through. Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-dyn.md | 17 +- ts/README.md | 13 + ts/fixtures/lang/caller.ts | 2 +- ts/fixtures/lang/expected/dyns.Named.ts | 74 +++++ ts/fixtures/lang/expected/dyns.Shape.ts | 131 ++++++++ ts/fixtures/lang/expected/dyns.Square.ts | 39 +++ ts/fixtures/lang/expected/dyns.Tri.ts | 52 +++ ts/fixtures/lang/expected/dyns.ts | 173 ++++++++++ ts/fixtures/lang/expected/harness.ts | 7 +- ts/fixtures/lang/src/dyns.rs | 156 +++++++++ ts/fixtures/lang/src/harness.rs | 9 +- ts/fixtures/lang/src/lib.rs | 3 +- ts/fixtures/reject/census.md | 53 ++- ts/fixtures/reject/expected.txt | 5 +- ts/fixtures/reject/src/lib.rs | 25 ++ ts/fixtures/reject/ts.toml | 2 + ts/fixtures/task/expected/core/option.ts | 15 + ts/fixtures/task/expected/drops.Bomb.ts | 11 + ts/fixtures/task/expected/drops.Held.ts | 32 +- ts/fixtures/task/expected/drops.ts | 209 ++++++------ .../task/expected/drops/objects.Part.ts | 88 +++++ ts/fixtures/task/expected/drops/objects.ts | 155 +++++++++ ts/fixtures/task/expected/model.Guard.ts | 22 ++ ts/fixtures/task/src/drops.rs | 8 +- ts/fixtures/task/src/drops/objects.rs | 89 +++++ ts/src/lower/body.rs | 3 + ts/src/lower/drop.rs | 7 +- ts/src/lower/dynamic.rs | 307 ++++++++++++++++++ ts/src/lower/fmt.rs | 6 - ts/src/lower/mir/rvalue.rs | 5 +- ts/src/lower/mod.rs | 17 +- 31 files changed, 1590 insertions(+), 145 deletions(-) create mode 100644 ts/fixtures/lang/expected/dyns.Named.ts create mode 100644 ts/fixtures/lang/expected/dyns.Shape.ts create mode 100644 ts/fixtures/lang/expected/dyns.Square.ts create mode 100644 ts/fixtures/lang/expected/dyns.Tri.ts create mode 100644 ts/fixtures/lang/expected/dyns.ts create mode 100644 ts/fixtures/lang/src/dyns.rs create mode 100644 ts/fixtures/task/expected/drops/objects.Part.ts create mode 100644 ts/fixtures/task/expected/drops/objects.ts create mode 100644 ts/fixtures/task/src/drops/objects.rs create mode 100644 ts/src/lower/dynamic.rs diff --git a/quest/m3/ts-dyn.md b/quest/m3/ts-dyn.md index 1e8c6009a..b53aef36d 100644 --- a/quest/m3/ts-dyn.md +++ b/quest/m3/ts-dyn.md @@ -2,9 +2,10 @@ ## Goal -[rust2-ts](/ts/README.md) translates trait objects (`&dyn Trait`, `Box`, and -`Box`, `Pin`ned or not), with Rust's dispatch and drop order, proven by -differential tests against native Rust, so a consumer neither rewrites nor binds them. +[rust2-ts](/ts/README.md) translates the trait objects moq uses that it still refuses +(`Arc`, `Box` and `&dyn Fn`, and `Box`, `Pin`ned or +not), with Rust's dispatch and drop order, proven by differential tests against native +Rust, so a consumer neither rewrites nor binds them. ## Plan @@ -12,7 +13,15 @@ Decided with the maintainer on 2026-10-27: a stock crate translates with no anno config, so trait objects move from never to not yet. Until this lands, moq rewrites `dyn Expiry` as an enum and binds its boxed futures ([bindings](/ts/README.md#bindings)). -- `&dyn Debug` and `&dyn Display` already translate; build on how they do. +- `&dyn Trait`, `&mut dyn Trait`, and `Box` of the crate's own traits + translate: an object with the trait's methods, which a class per type wraps the value + in. Build the rest on that. +- `Arc`: an `Arc` unsized from an `Arc` shares its count, so the wrapper + can't replace the `Arc`'s value; its clone, drop, and deref must still see one count. +- Another crate's traits: `Fn*` objects can be the closure itself, as `&dyn Debug` is the + function writing it; `Future`, `Iterator`, `Error`, and `Any` need their std methods + mapped onto the object. +- Generic traits and associated types name their arguments in the object's type. - A boxed future must keep Rust's laziness and cancellation as [async](/quest/m2/ts-async.md) defines them. diff --git a/ts/README.md b/ts/README.md index 41283a87d..3eb43a33a 100644 --- a/ts/README.md +++ b/ts/README.md @@ -87,6 +87,7 @@ in `rt/str.ts`, collections in `rt/collections.ts`, ownership and async in | `Ordering`, `DefaultHasher` | `rt.Ordering`, which is `-1`, `0`, or `1`; `rt.Hasher` | | `Duration` | `rt.Duration`: `{ secs, nanos }`, std's whole seconds as an `rt.u64` and the nanoseconds past them, never changed once made | | closure | an arrow function, with `[Symbol.dispose]` if it owns a value that drops | +| `&dyn Trait`, `&mut dyn Trait`, `Box` | an object with the trait's methods, its supertraits' included: `shape.area$Shape()` | | constant | its value, inlined as Rust inlines it | | a type that drops | a class with `[Symbol.dispose]`: its `Drop` impl, then its fields | | a type's function | a function of the type's module, taking `self` first: `Decoder.slice(dec, 4)` after `import * as Decoder` | @@ -230,6 +231,18 @@ panics leaves the rest to drop as Rust's does: a class's glue and the runtime's go on to the rest of the value and its container, but a hash table leaks its other entries, as std's does. +A trait object of the crate's own trait, behind `&`, `&mut`, or `Box`, is an object with +a method for each function a call can dispatch to, its supertraits' included, named for +its trait as an impl's function is and taking what follows `self`: `shape.area$Shape()`. +The trait's module exports its type, `Shape.Shape` from `dyns.Shape.ts`, which a TS +caller can implement. A coercion wraps the value in a class of its type's, `new +Shape.Square(sq)`, whose methods call that type's impl; one from a `&mut` to a value JS +copies holds its `rt.Place`, so `&mut self` writes where the value is. An upcast is the +same object. A `Box` drops what it holds through its `[Symbol.dispose]`, which +a TS caller's object may leave out. A trait object of another crate's trait, but for +`&dyn Debug` and `&dyn Display`, of a generic trait or one with an associated type, or +behind any other pointer, such as an `Arc`, is refused. + An `async fn` returns a future without running anything: an instance of a class of its own (`sumFuture`), the state machine MIR's coroutine transform makes. Its fields are the arguments and the locals held across an `.await`, and its `poll` resumes at the state it diff --git a/ts/fixtures/lang/caller.ts b/ts/fixtures/lang/caller.ts index 8916fd4a8..78d842a32 100644 --- a/ts/fixtures/lang/caller.ts +++ b/ts/fixtures/lang/caller.ts @@ -102,7 +102,7 @@ const floats = [ const ULPS = 2n; export function within(input: Uint8Array, native: Uint8Array, translated: Uint8Array): boolean { - if (input[0] % 59 !== 58 || input[1] % 10 !== 7 || native.length !== translated.length) return false; + if (input[0] % 60 !== 58 || input[1] % 10 !== 7 || native.length !== translated.length) return false; const [a, b] = [native, translated].map((v) => new DataView(v.buffer, v.byteOffset, v.length)); // Bits as a line through zero, so the distance between two is how many doubles apart they are. const line = (v: bigint) => (v < 0n ? -(v & 0x7fffffffffffffffn) : v); diff --git a/ts/fixtures/lang/expected/dyns.Named.ts b/ts/fixtures/lang/expected/dyns.Named.ts new file mode 100644 index 000000000..60cb9e93c --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Named.ts @@ -0,0 +1,74 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import * as dyns_Square from "./dyns.Square.ts"; +import * as dyns_Tri from "./dyns.Tri.ts"; + +export type Named = + | { label$Named: () => string; fmt$Debug: (a0: rt.Formatter) => rt.Result; area$Shape: () => rt.u32; grow$Shape: (a0: rt.u8) => void; sides$Shape: () => rt.u8; intoArea$Shape: () => rt.u32 }; + +export class Square { + v: dyns_Square.Square; + + constructor(v: dyns_Square.Square) { + this.v = v; + } + + area$Shape(): rt.u32 { + return dyns_Square.area$Shape(this.v); + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return dyns_Square.fmt$Debug(this.v, a); + } + + grow$Shape(a: rt.u8): void { + return dyns_Square.grow$Shape(this.v, a); + } + + intoArea$Shape(): rt.u32 { + return dyns_Square.intoArea$Shape(this.v); + } + + label$Named(): string { + return dyns_Square.label$Named(this.v); + } + + sides$Shape(): rt.u8 { + return dyns_Square.sides$Shape(this.v); + } +} + +export class Tri { + v: dyns_Tri.Tri; + + constructor(v: dyns_Tri.Tri) { + this.v = v; + } + + area$Shape(): rt.u32 { + return dyns_Tri.area$Shape(this.v); + } + + fmt$Debug(a: rt.Formatter): rt.Result { + return dyns_Tri.fmt$Debug(this.v, a); + } + + grow$Shape(a: rt.u8): void { + return dyns_Tri.grow$Shape(this, a); + } + + intoArea$Shape(): rt.u32 { + return dyns_Tri.intoArea$Shape(this.v); + } + + label$Named(): string { + return dyns_Tri.label$Named(this.v); + } + + sides$Shape(): rt.u8 { + return dyns_Tri.sides$Shape(this.v); + } +} + +//# sourceMappingURL=dyns.Named.ts.map diff --git a/ts/fixtures/lang/expected/dyns.Shape.ts b/ts/fixtures/lang/expected/dyns.Shape.ts new file mode 100644 index 000000000..e37ef6b91 --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Shape.ts @@ -0,0 +1,131 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { sides$Shape_u32, intoArea$Shape_u32, area$Shape_u32, grow$Shape_u32 } from "./dyns.ts"; +import * as dyns_Square from "./dyns.Square.ts"; +import * as dyns_Tri from "./dyns.Tri.ts"; + +export type Shape = + | { area$Shape: () => rt.u32; grow$Shape: (a0: rt.u8) => void; sides$Shape: () => rt.u8; intoArea$Shape: () => rt.u32 }; + +export class Square { + v: dyns_Square.Square; + + constructor(v: dyns_Square.Square) { + this.v = v; + } + + area$Shape(): rt.u32 { + return dyns_Square.area$Shape(this.v); + } + + grow$Shape(a: rt.u8): void { + return dyns_Square.grow$Shape(this.v, a); + } + + intoArea$Shape(): rt.u32 { + return dyns_Square.intoArea$Shape(this.v); + } + + sides$Shape(): rt.u8 { + return dyns_Square.sides$Shape(this.v); + } +} + +export class Tri { + v: dyns_Tri.Tri; + + constructor(v: dyns_Tri.Tri) { + this.v = v; + } + + area$Shape(): rt.u32 { + return dyns_Tri.area$Shape(this.v); + } + + grow$Shape(a: rt.u8): void { + return dyns_Tri.grow$Shape(this, a); + } + + intoArea$Shape(): rt.u32 { + return dyns_Tri.intoArea$Shape(this.v); + } + + sides$Shape(): rt.u8 { + return dyns_Tri.sides$Shape(this.v); + } +} + +export class Tri$Place { + v: rt.Place; + + constructor(v: rt.Place) { + this.v = v; + } + + area$Shape(): rt.u32 { + return dyns_Tri.area$Shape(this.v.v); + } + + grow$Shape(a: rt.u8): void { + return dyns_Tri.grow$Shape(this.v, a); + } + + intoArea$Shape(): rt.u32 { + return dyns_Tri.intoArea$Shape(this.v.v); + } + + sides$Shape(): rt.u8 { + return dyns_Tri.sides$Shape(this.v.v); + } +} + +export class u32 { + v: rt.u32; + + constructor(v: rt.u32) { + this.v = v; + } + + area$Shape(): rt.u32 { + return area$Shape_u32(this.v); + } + + grow$Shape(a: rt.u8): void { + return grow$Shape_u32(this, a); + } + + intoArea$Shape(): rt.u32 { + return intoArea$Shape_u32(this.v); + } + + sides$Shape(): rt.u8 { + return sides$Shape_u32(this.v); + } +} + +export class u32$Place { + v: rt.Place; + + constructor(v: rt.Place) { + this.v = v; + } + + area$Shape(): rt.u32 { + return area$Shape_u32(this.v.v); + } + + grow$Shape(a: rt.u8): void { + return grow$Shape_u32(this.v, a); + } + + intoArea$Shape(): rt.u32 { + return intoArea$Shape_u32(this.v.v); + } + + sides$Shape(): rt.u8 { + return sides$Shape_u32(this.v.v); + } +} + +//# sourceMappingURL=dyns.Shape.ts.map diff --git a/ts/fixtures/lang/expected/dyns.Square.ts b/ts/fixtures/lang/expected/dyns.Square.ts new file mode 100644 index 000000000..842fe6b6a --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Square.ts @@ -0,0 +1,39 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** Provided, and overridden by one impl. */ +export function sides$Shape(_self: Square): rt.u8 { + return 4; +} + +export function intoArea$Shape(self: Square): rt.u32 { + return rt.u32WrappingAdd(area$Shape(self), 1); +} + +export function fmt$Debug(self: Square, f: rt.Formatter): rt.Result { + const _3: string = "Square"; + const _4: string = "side"; + const _5: rt.Fmt = (f: rt.Formatter) => rt.u16Debug(self.side, f); + return rt.debugStructFinish(rt.debugStructField(rt.fmtDebugStruct(f, _3), _4, _5)); +} + +export interface Square { + side: rt.u16; +} + +export function area$Shape(self: Square): rt.u32 { + return rt.u32Mul(self.side as rt.u32, self.side as rt.u32); +} + +export function grow$Shape(self: Square, by: rt.u8): void { + self.side = rt.u16WrappingAdd(self.side, by as rt.u16); +} + +export function label$Named(self: Square): string { + const arg = self.side; + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u16Display(arg, f)]; + return rt.fmtFormat(["square ", args2[0]]); +} + +//# sourceMappingURL=dyns.Square.ts.map diff --git a/ts/fixtures/lang/expected/dyns.Tri.ts b/ts/fixtures/lang/expected/dyns.Tri.ts new file mode 100644 index 000000000..0569b006a --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Tri.ts @@ -0,0 +1,52 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export function fmt$Debug(self: Tri, f: rt.Formatter): rt.Result { + if (self.tag === "Flat") { + return rt.fmtWriteStr(f, "Flat"); + } + const _6: string = "Tall"; + const _7: rt.Fmt = (f: rt.Formatter) => rt.u8Debug(self._0, f); + return rt.debugTupleFinish(rt.debugTupleField(rt.fmtDebugTuple(f, _6), _7)); +} + +/** An enum, which JS copies, so `&mut self` reaches it through a place. */ +export type Tri = { tag: "Flat" } | { tag: "Tall"; _0: rt.u8 }; + +export function area$Shape(self: Tri): rt.u32 { + if (self.tag === "Flat") { + return 0; + } + return rt.u32Mul(self._0 as rt.u32, 3); +} + +export function grow$Shape(self: rt.Place, by: rt.u8): void { + let _3!: Tri; + if (self.v.tag === "Flat") { + if (!(rt.u8Rem(by, 2) === 0)) { + _3 = { tag: "Tall", _0: by } as Tri; + } else { + _3 = { tag: "Flat" } as Tri; + } + } else { + _3 = { tag: "Tall", _0: rt.u8WrappingAdd(self.v._0, by) } as Tri; + } + self.v = _3; +} + +/** Provided, and overridden by one impl. */ +export function sides$Shape(_self: Tri): rt.u8 { + return 3; +} + +export function intoArea$Shape(self: Tri): rt.u32 { + return rt.u32WrappingMul(area$Shape(self), 2); +} + +export function label$Named(self: Tri): string { + const args2: rt.Fmt[] = [(f: rt.Formatter) => fmt$Debug(self, f)]; + return rt.fmtFormat(["tri ", args2[0]]); +} + +//# sourceMappingURL=dyns.Tri.ts.map diff --git a/ts/fixtures/lang/expected/dyns.ts b/ts/fixtures/lang/expected/dyns.ts new file mode 100644 index 000000000..7f2996dfb --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.ts @@ -0,0 +1,173 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import * as Named from "./dyns.Named.ts"; +import * as Shape from "./dyns.Shape.ts"; +import * as Square from "./dyns.Square.ts"; +import * as Tri from "./dyns.Tri.ts"; +import { put } from "./fmt.ts"; + +/** Provided, and overridden by one impl. */ +export function sides$Shape_u32(_self: rt.u32): rt.u8 { + return 4; +} + +export function intoArea$Shape_u32(self: rt.u32): rt.u32 { + return rt.u32WrappingAdd(area$Shape_u32(self), 1); +} + +export function area$Shape_u32(self: rt.u32): rt.u32 { + return self; +} + +export function grow$Shape_u32(self: rt.Place, by: rt.u8): void { + self.v = rt.u32WrappingMul(self.v, by as rt.u32); +} + +export function total(shapes: Shape.Shape[]): rt.u32 { + return rt.iterFold(rt.iterSlice(shapes), 0, (sum: rt.u32, s: Shape.Shape): rt.u32 => { + return rt.u32WrappingAdd(sum, s.area$Shape()); + }); +} + +export function growTwice(s: Shape.Shape, by: rt.u8): void { + s.grow$Shape(by); + s.grow$Shape(rt.u8WrappingAdd(by, 1)); +} + +/** Over a type parameter that the trait object instantiates. */ +export function sidesOf$Square(s: Square.Square): rt.u8 { + return Square.sides$Shape(s); +} + +/** Over a type parameter that the trait object instantiates. */ +export function sidesOf$dyn_Shape(s: Shape.Shape): rt.u8 { + return s.sides$Shape(); +} + +export function upcast(n: Named.Named): Shape.Shape { + return n; +} + +export function make(kind: rt.u8, n: rt.u8): Named.Named { + let _10!: Tri.Tri; + if (rt.u8Rem(kind, 2) === 0) { + return new Named.Square({ side: n as rt.u16 }); + } + if (!(rt.u8Rem(n, 3) === 0)) { + _10 = { tag: "Tall", _0: n } as Tri.Tri; + } else { + _10 = { tag: "Flat" } as Tri.Tri; + } + return new Named.Tri(_10); +} + +export function dyns(data: rt.ByteSlice, out: rt.ByteVec): void { + let tri!: Tri.Tri; + let shapes!: Named.Named[]; + let _109!: rt.usize; + let iterS!: Named.Named[]; + let iterI!: number; + let _192!: boolean; + let unwind: number = 0; + try { + _192 = false; + if (data.length >= 3) { + const _12: [rt.u8, rt.u8, rt.u8] = [data.data[data.start], data.data[data.start + 1], data.data[data.start + 2]]; + const a: rt.u8 = _12[0]; + const b: rt.u8 = _12[1]; + const c: rt.u8 = _12[2]; + const sq: Square.Square = { side: a as rt.u16 }; + if (!(rt.u8Rem(b, 2) === 0)) { + tri = { tag: "Tall", _0: b } as Tri.Tri; + } else { + tri = { tag: "Flat" } as Tri.Tri; + } + let n: rt.u32 = c as rt.u32; + const part = total([new Shape.Square(sq), new Shape.Tri(tri), new Shape.u32(n)]); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part, f)]; + put(out, rt.fmtFormat([args2[0], " "])); + growTwice(new Shape.Square(sq), b); + growTwice(new Shape.Tri$Place(rt.place(() => tri, (value) => tri = value)), c); + growTwice(new Shape.u32$Place(rt.place(() => n, (value2) => n = value2)), a); + const arg = sq.side; + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.u16Display(arg, f), (f: rt.Formatter) => Tri.fmt$Debug(tri, f), (f: rt.Formatter) => rt.u32Display(n, f)]; + put(out, rt.fmtFormat([args4[0], " ", args4[1], " ", args4[2], " "])); + const _78: rt.u8 = sidesOf$dyn_Shape(new Shape.Tri(tri)); + const _82: rt.u8 = sidesOf$Square(sq); + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(_78, f), (f: rt.Formatter) => rt.u8Display(_82, f)]; + put(out, rt.fmtFormat([args6[0], " ", args6[1], " "])); + _192 = true; + shapes = []; + unwind = 102; + const iter: rt.Iter<[rt.usize, rt.u8]> = rt.iterEnumerate(rt.iterSlice(rt.byteSliceView(new rt.ByteSlice(data.data, data.start + 3, data.length - 3)))); + while (true) { + const _94: [rt.usize, rt.u8] | rt.Done = iter.next(); + if (_94 === rt.DONE) { + break; + } + const i: rt.usize = _94[0]; + const x: rt.u8 = _94[1]; + const _101: Named.Named = make(rt.u8WrappingAdd(x, i & 0xff), x); + shapes.push(_101); + } + const _106: rt.usize = shapes.length; + let iterStart: rt.usize = 0; + const iterEnd: rt.usize = _106; + while ((_109 = iterStart++) < iterEnd) { + const i2: rt.usize = _109; + const at = rt.idx(shapes, i2); + const s: rt.Place = rt.place(() => shapes[at], (value3) => shapes[at] = value3); + if (rt.usizeRem(i2, 2) === 0) { + s.v.grow$Shape(a); + } + const _129: string = s.v.label$Named(); + unwind = 96; + const _132: rt.u32 = s.v.area$Shape(); + const _135: rt.u8 = sidesOf$dyn_Shape(upcast(s.v)); + const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, _129), (f: rt.Formatter) => rt.u32Display(_132, f), (f: rt.Formatter) => rt.u8Display(_135, f), (f: rt.Formatter) => s.v.fmt$Debug(f)]; + const _125: string = rt.fmtFormat([args8[0], ":", args8[1], ":", args8[2], ":", args8[3], " "]); + unwind = 102; + const _124: string = _125; + unwind = 95; + put(out, _124); + } + let sum: rt.u32 = 0; + _192 = false; + iterS = shapes; + iterI = 0; + while (true) { + unwind = 94; + const _150: Named.Named | rt.Done = iterI < iterS.length ? iterS[iterI++] : rt.DONE; + if (_150 === rt.DONE) { + break; + } + sum = rt.u32WrappingAdd(sum, _150.intoArea$Shape()); + } + unwind = 102; + rt.dropAll(iterS, rt.drop, iterI); + const _169: rt.u32 = sum; + const _168: rt.u32 = new Shape.u32(n).intoArea$Shape(); + const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(_169, f), (f: rt.Formatter) => rt.u32Display(_168, f)]; + const _163: string = rt.fmtFormat([args10[0], " ", args10[1]]); + unwind = 93; + put(out, _163); + _192 = false; + } + } catch (e) { + if (unwind === 0) { + throw e; + } + if (unwind !== 93) { + if (unwind === 94) { + rt.dropAll(iterS, rt.drop, iterI); + } + } + if (_192) { + rt.dropAll(shapes); + } + throw e; + } +} + +//# sourceMappingURL=dyns.ts.map diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 3658e9650..c7f04c68f 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -7,6 +7,7 @@ import { hashMaps, sets, btreeMaps, deques, vecs, stores } from "./collections.t import { combinators } from "./combinators.ts"; import { cows } from "./cows.ts"; import { durations } from "./durations.ts"; +import { dyns } from "./dyns.ts"; import { edits } from "./edits.ts"; import { floats } from "./floats.ts"; import { ints, strings, derived, written, counts, rounding, panics } from "./fmt.ts"; @@ -31,7 +32,7 @@ export function run(input: rt.ByteSlice): rt.ByteVec { const _10: [rt.u8, rt.u8] = [input.data[input.start], input.data[input.start + 1]]; const op: rt.u8 = _10[0]; const kind: rt.u8 = _10[1]; - const _14: rt.u8 = rt.u8Rem(op, 59); + const _14: rt.u8 = rt.u8Rem(op, 60); if (_14 === 0) { u8Op(kind, new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out); } else if (_14 === 1) { @@ -165,8 +166,10 @@ export function run(input: rt.ByteSlice): rt.ByteVec { edits(kind, new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out); } else if (_14 === 57) { decodes(kind, new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out); - } else { + } else if (_14 === 58) { floats(kind, new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out); + } else { + dyns(new rt.ByteSlice(input.data, input.start + 2, input.length - 2), out); } return out; } diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs new file mode 100644 index 000000000..cda8ac98e --- /dev/null +++ b/ts/fixtures/lang/src/dyns.rs @@ -0,0 +1,156 @@ +//! Trait objects of the crate's own traits, behind `&`, `&mut`, and `Box`: each call runs +//! the impl of the value's own type, a struct, an enum, or a number, changing it in place +//! through `&mut self` wherever it is, a provided method, a supertrait's, `Debug` among +//! them, `self: Box`, and an upcast to the supertrait. + +use std::fmt::Debug; + +use crate::fmt::put; + +trait Shape { + fn area(&self) -> u32; + fn grow(&mut self, by: u8); + /// Provided, and overridden by one impl. + fn sides(&self) -> u8 { + 4 + } + fn into_area(self: Box) -> u32 { + self.area().wrapping_add(1) + } +} + +trait Named: Shape + Debug { + fn label(&self) -> String; +} + +#[derive(Debug)] +struct Square { + side: u16, +} + +impl Shape for Square { + fn area(&self) -> u32 { + u32::from(self.side) * u32::from(self.side) + } + fn grow(&mut self, by: u8) { + self.side = self.side.wrapping_add(u16::from(by)); + } +} + +impl Named for Square { + fn label(&self) -> String { + format!("square {}", self.side) + } +} + +/// An enum, which JS copies, so `&mut self` reaches it through a place. +#[derive(Debug, Clone, Copy)] +enum Tri { + Flat, + Tall(u8), +} + +impl Shape for Tri { + fn area(&self) -> u32 { + match self { + Tri::Flat => 0, + Tri::Tall(h) => u32::from(*h) * 3, + } + } + fn grow(&mut self, by: u8) { + *self = match *self { + Tri::Flat if by % 2 == 0 => Tri::Flat, + Tri::Flat => Tri::Tall(by), + Tri::Tall(h) => Tri::Tall(h.wrapping_add(by)), + }; + } + fn sides(&self) -> u8 { + 3 + } + fn into_area(self: Box) -> u32 { + self.area().wrapping_mul(2) + } +} + +impl Named for Tri { + fn label(&self) -> String { + format!("tri {self:?}") + } +} + +/// A number, which JS copies too. +impl Shape for u32 { + fn area(&self) -> u32 { + *self + } + fn grow(&mut self, by: u8) { + *self = self.wrapping_mul(u32::from(by)); + } +} + +fn total(shapes: &[&dyn Shape]) -> u32 { + shapes.iter().fold(0, |sum, s| sum.wrapping_add(s.area())) +} + +fn grow_twice(s: &mut dyn Shape, by: u8) { + s.grow(by); + s.grow(by.wrapping_add(1)); +} + +/// Over a type parameter that the trait object instantiates. +fn sides_of(s: &T) -> u8 { + s.sides() +} + +fn upcast(n: &dyn Named) -> &dyn Shape { + n +} + +fn make(kind: u8, n: u8) -> Box { + match kind % 2 { + 0 => Box::new(Square { side: u16::from(n) }), + _ => Box::new(if n % 3 == 0 { Tri::Flat } else { Tri::Tall(n) }), + } +} + +pub fn dyns(data: &[u8], out: &mut Vec) { + let [a, b, c, rest @ ..] = data else { + return; + }; + let (a, b, c) = (*a, *b, *c); + + // Borrowed, of each type. + let mut sq = Square { side: u16::from(a) }; + let mut tri = if b % 2 == 0 { Tri::Flat } else { Tri::Tall(b) }; + let mut n = u32::from(c); + put(out, &format!("{} ", total(&[&sq, &tri, &n]))); + + // Changed in place through `&mut dyn`, a local each. + grow_twice(&mut sq, b); + grow_twice(&mut tri, c); + grow_twice(&mut n, a); + put(out, &format!("{} {tri:?} {n} ", sq.side)); + put(out, &format!("{} {} ", sides_of::(&tri), sides_of(&sq))); + + // Owned, changed through the box, then consumed by `self: Box`. + let mut shapes: Vec> = Vec::new(); + for (i, &x) in rest.iter().enumerate() { + shapes.push(make(x.wrapping_add(i as u8), x)); + } + for i in 0..shapes.len() { + let s = &mut shapes[i]; + if i % 2 == 0 { + s.grow(a); + } + put( + out, + &format!("{}:{}:{}:{:?} ", s.label(), s.area(), sides_of(upcast(&**s)), s), + ); + } + let mut sum = 0u32; + for s in shapes { + sum = sum.wrapping_add(s.into_area()); + } + let boxed: Box = Box::new(n); + put(out, &format!("{sum} {}", boxed.into_area())); +} diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 0f2a6f0ea..455dd301f 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -2,8 +2,8 @@ //! case and the rest is its input. use crate::{ - chars, chunks, collections, combinators, cows, durations, edits, floats, fmt, generics, ints, iters, keys, nested, - refs, shapes, splits, strs, utf8, wrappers, + chars, chunks, collections, combinators, cows, durations, dyns, edits, floats, fmt, generics, ints, iters, keys, + nested, refs, shapes, splits, strs, utf8, wrappers, }; /// The native side's entry: [`run`], except that a case starting with 0xff prints a @@ -22,7 +22,7 @@ pub fn run(input: &[u8]) -> Vec { return out; }; let (op, kind) = (*op, *kind); - match op % 59 { + match op % 60 { 0 => ints::u8_op(kind, data, &mut out), 1 => ints::u16_op(kind, data, &mut out), 2 => ints::u32_op(kind, data, &mut out), @@ -91,7 +91,8 @@ pub fn run(input: &[u8]) -> Vec { 55 => wrappers::wrappers(kind, data, &mut out), 56 => edits::edits(kind, data, &mut out), 57 => utf8::decodes(kind, data, &mut out), - _ => floats::floats(kind, data, &mut out), + 58 => floats::floats(kind, data, &mut out), + _ => dyns::dyns(data, &mut out), } out } diff --git a/ts/fixtures/lang/src/lib.rs b/ts/fixtures/lang/src/lib.rs index 1f6545e6b..9ea9fe9f2 100644 --- a/ts/fixtures/lang/src/lib.rs +++ b/ts/fixtures/lang/src/lib.rs @@ -4,7 +4,7 @@ //! aggregates, closures, traits, generic types, formatting, collections, UTF-8 //! decoding, `&mut` to values JS copies, the standard library calls it maps, //! `Option`'s and `Result`'s combinators from core's MIR, and `Option`s of values that -//! can already be `undefined`. +//! can already be `undefined`, and trait objects. //! //! `harness::run` is the entry point both sides of the differential test run, and //! `fmt::Fault` an error only `caller.ts` prints. @@ -15,6 +15,7 @@ mod collections; mod combinators; mod cows; mod durations; +mod dyns; mod edits; mod floats; mod fmt; diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 6a5110a07..7d6a6a53a 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,7 +1,7 @@ # Census of `reject-fixture` -- Refusals: 76 -- Function instances: 82 +- Refusals: 79 +- Function instances: 85 - Generic functions no instance reached, lowered over their type parameters: 6 ## By construct @@ -11,14 +11,15 @@ | 11 | unsupported call to `_` | fixtures/reject/src/lib.rs:28:4 | | 4 | a variant of `_`, which is not supported: either is the same JS string | fixtures/reject/src/lib.rs:424:2 | | 4 | an assignment through a reference to `_` | fixtures/reject/src/lib.rs:117:2 | -| 4 | unsupported type `_` | fixtures/reject/src/lib.rs:7:14 | | 3 | `_` with a mutable reference to a value JS copies, which its mapping would copy | fixtures/reject/src/lib.rs:17:2 | | 3 | comparing `_` through its own `_`, which is not supported yet | fixtures/reject/src/lib.rs:381:4 | | 3 | replacing the value behind a reference to `_` | fixtures/reject/src/lib.rs:101:2 | +| 3 | unsupported type `_` | fixtures/reject/src/lib.rs:7:14 | | 2 | `_` is not supported: it would be pending two ways with one value | fixtures/reject/src/lib.rs:121:20 | | 2 | `_` is not supported: its `None` and `Some` would both be `rt.None` | fixtures/reject/src/lib.rs:23:15 | | 2 | `_` of `_`, whose `None` is `rt.None`, which is not supported yet | fixtures/reject/src/lib.rs:48:42 | | 2 | `_` of a reference to an array of bytes it is generic over, which its mapping would take as the array, not as a slice of it | fixtures/reject/src/lib.rs:268:16 | +| 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | fixtures/reject/src/lib.rs:527:23 | | 2 | an iterator of the crate implementing `_`, which is not supported yet | fixtures/reject/src/lib.rs:175:1 | | 2 | constructing `_` from another crate | fixtures/reject/src/lib.rs:161:23 | | 2 | overriding `_`, which std's adaptors call in place of `next` or beside it | fixtures/reject/src/lib.rs:155:2 | @@ -30,6 +31,7 @@ | 1 | `_` of `_`, which drops `_`, which is not supported yet | fixtures/reject/src/lib.rs:69:13 | | 1 | `_` of `_`, which folds a closure that drops beside what else drops, which is not supported yet | fixtures/reject/src/lib.rs:63:23 | | 1 | `_` through a held type's own, which is not supported yet | fixtures/reject/src/lib.rs:339:2 | +| 1 | `_`, a trait object of another crate's trait, which is not supported yet | fixtures/reject/src/lib.rs:11:16 | | 1 | `_`, which is not supported yet | fixtures/reject/src/lib.rs:315:9 | | 1 | `f64::mul_add`, which rounds once, where JS would round twice | fixtures/reject/src/lib.rs:498:4 | | 1 | `fold` of `_`, which folds a `flatten` whose source and iterators both drop, which is not supported yet | fixtures/reject/src/lib.rs:237:26 | @@ -37,6 +39,7 @@ | 1 | a `zip` of `_`, whose closures std calls one item further through `next` than through `fold` | fixtures/reject/src/lib.rs:188:51 | | 1 | a cast from `_` to `_` | fixtures/reject/src/lib.rs:112:2 | | 1 | a coercion to `_` | fixtures/reject/src/lib.rs:362:2 | +| 1 | a coercion to `_`, which is not supported yet | fixtures/reject/src/lib.rs:518:39 | | 1 | a constant of type `_` | fixtures/reject/src/lib.rs:96:2 | | 1 | a copy of a closure that changes what it captured | fixtures/reject/src/lib.rs:278:14 | | 1 | a mutable borrow of a subslice of `_`, which JS would copy, having no views | fixtures/reject/src/lib.rs:247:13 | @@ -62,10 +65,10 @@ The type or module each refused type or path belongs to. | 6 | `Option` | fixtures/reject/src/lib.rs:23:15 | | 6 | `f32` | fixtures/reject/src/lib.rs:7:14 | | 4 | `Iterator` | fixtures/reject/src/lib.rs:48:42 | +| 4 | `std::sync::Arc` | fixtures/reject/src/lib.rs:117:2 | | 3 | `PartialOrd` | fixtures/reject/src/lib.rs:381:4 | | 3 | `std::iter::Map` | fixtures/reject/src/lib.rs:63:23 | | 3 | `std::slice` | fixtures/reject/src/lib.rs:28:4 | -| 3 | `std::sync::Arc` | fixtures/reject/src/lib.rs:117:2 | | 3 | `std::vec::Vec` | fixtures/reject/src/lib.rs:289:52 | | 3 | `u8` | fixtures/reject/src/lib.rs:96:2 | | 3 | `{closure}` | fixtures/reject/src/lib.rs:411:13 | @@ -73,6 +76,8 @@ The type or module each refused type or path belongs to. | 2 | `Lopsided` | fixtures/reject/src/lib.rs:381:4 | | 2 | `Meters` | fixtures/reject/src/lib.rs:101:2 | | 2 | `Poll` | fixtures/reject/src/lib.rs:121:20 | +| 2 | `Scaled` | fixtures/reject/src/lib.rs:527:23 | +| 2 | `dyn Scaled` | fixtures/reject/src/lib.rs:527:23 | | 2 | `std::collections::HashMap` | fixtures/reject/src/lib.rs:69:13 | | 2 | `std::iter::Iterator` | fixtures/reject/src/lib.rs:268:16 | | 2 | `std::ops::Range` | fixtures/reject/src/lib.rs:196:8 | @@ -115,6 +120,7 @@ The type or module each refused type or path belongs to. | 3 | unsupported type `_` | `f32` | fixtures/reject/src/lib.rs:7:14 | | 2 | `_` is not supported: it would be pending two ways with one value | `Poll` | fixtures/reject/src/lib.rs:121:20 | | 2 | `_` is not supported: its `None` and `Some` would both be `rt.None` | `Option` | fixtures/reject/src/lib.rs:23:15 | +| 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | `dyn Scaled`, `Scaled` | fixtures/reject/src/lib.rs:527:23 | | 2 | an assignment through a reference to `_` | `std::pin::Pin` | fixtures/reject/src/lib.rs:132:2 | | 2 | comparing `_` through its own `_`, which is not supported yet | `Lopsided`, `PartialOrd::lt` | fixtures/reject/src/lib.rs:381:4 | | 2 | replacing the value behind a reference to `_` | `Meters` | fixtures/reject/src/lib.rs:101:2 | @@ -132,12 +138,14 @@ The type or module each refused type or path belongs to. | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::collections::hash_map::Entry::and_modify` | fixtures/reject/src/lib.rs:457:15 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::iter::IntoIterator::into_iter` | fixtures/reject/src/lib.rs:17:2 | | 1 | `_` with a mutable reference to a value JS copies, which its mapping would copy | `std::slice::last_mut` | fixtures/reject/src/lib.rs:450:21 | +| 1 | `_`, a trait object of another crate's trait, which is not supported yet | `dyn std::ops::Fn(u8) -> u8` | fixtures/reject/src/lib.rs:11:16 | | 1 | `_`, which is not supported yet | `Hasher::write` | fixtures/reject/src/lib.rs:315:9 | | 1 | `fold` of `_`, which folds a `flatten` whose source and iterators both drop, which is not supported yet | `std::iter::Flatten` | fixtures/reject/src/lib.rs:237:26 | | 1 | a `zip` of `_` and `_`, which std reads by index, of items that drop, which is not supported yet | `std::iter::Map`, `std::ops::Range` | fixtures/reject/src/lib.rs:242:30 | | 1 | a `zip` of `_`, whose closures std calls one item further through `next` than through `fold` | `std::iter::Chain` | fixtures/reject/src/lib.rs:188:51 | | 1 | a cast from `_` to `_` | `u64`, `f32` | fixtures/reject/src/lib.rs:112:2 | | 1 | a coercion to `_` | `std::sync::Arc` | fixtures/reject/src/lib.rs:362:2 | +| 1 | a coercion to `_`, which is not supported yet | `std::sync::Arc` | fixtures/reject/src/lib.rs:518:39 | | 1 | a constant of type `_` | `&u8` | fixtures/reject/src/lib.rs:96:2 | | 1 | a mutable borrow of a subslice of `_`, which JS would copy, having no views | `u32` | fixtures/reject/src/lib.rs:247:13 | | 1 | a scalar constant of type `_` | `f32` | fixtures/reject/src/lib.rs:8:2 | @@ -170,7 +178,6 @@ The type or module each refused type or path belongs to. | 1 | unsupported call to `_` | `std::ops::Range::contains` | fixtures/reject/src/lib.rs:196:8 | | 1 | unsupported call to `_` | `std::slice::copy_within` | fixtures/reject/src/lib.rs:357:4 | | 1 | unsupported call to `_` | `std::slice::sort` | fixtures/reject/src/lib.rs:28:4 | -| 1 | unsupported type `_` | `dyn std::ops::Fn(u8) -> u8` | fixtures/reject/src/lib.rs:11:16 | ## Spans @@ -208,15 +215,6 @@ The type or module each refused type or path belongs to. -
unsupported type `_` (4) - -- fixtures/reject/src/lib.rs:7:14: unsupported type `f32` -- fixtures/reject/src/lib.rs:7:25: unsupported type `f32` -- fixtures/reject/src/lib.rs:11:16: unsupported type `dyn std::ops::Fn(u8) -> u8` -- fixtures/reject/src/lib.rs:110:51: unsupported type `f32` - -
-
`_` with a mutable reference to a value JS copies, which its mapping would copy (3) - fixtures/reject/src/lib.rs:17:2: `std::iter::IntoIterator::into_iter` with a mutable reference to a value JS copies, which its mapping would copy @@ -241,6 +239,14 @@ The type or module each refused type or path belongs to.
+
unsupported type `_` (3) + +- fixtures/reject/src/lib.rs:7:14: unsupported type `f32` +- fixtures/reject/src/lib.rs:7:25: unsupported type `f32` +- fixtures/reject/src/lib.rs:110:51: unsupported type `f32` + +
+
`_` is not supported: it would be pending two ways with one value (2) - fixtures/reject/src/lib.rs:121:20: `Poll>>` is not supported: it would be pending two ways with one value @@ -269,6 +275,13 @@ The type or module each refused type or path belongs to.
+
`_`, a trait object of the generic trait `_`, which is not supported yet (2) + +- fixtures/reject/src/lib.rs:527:23: `dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet +- fixtures/reject/src/lib.rs:528:4: `dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet + +
+
an iterator of the crate implementing `_`, which is not supported yet (2) - fixtures/reject/src/lib.rs:175:1: an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet @@ -339,6 +352,12 @@ The type or module each refused type or path belongs to.
+
`_`, a trait object of another crate's trait, which is not supported yet (1) + +- fixtures/reject/src/lib.rs:11:16: `dyn std::ops::Fn(u8) -> u8`, a trait object of another crate's trait, which is not supported yet + +
+
`_`, which is not supported yet (1) - fixtures/reject/src/lib.rs:315:9: `Hasher::write`, which is not supported yet @@ -381,6 +400,12 @@ The type or module each refused type or path belongs to.
+
a coercion to `_`, which is not supported yet (1) + +- fixtures/reject/src/lib.rs:518:39: a coercion to `std::sync::Arc`, which is not supported yet + +
+
a constant of type `_` (1) - fixtures/reject/src/lib.rs:96:2: a constant of type `&u8` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 67f082f1a..39525efd6 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -5,7 +5,7 @@ unsupported type `f32` at fixtures/reject/src/lib.rs:7:14 a scalar constant of type `f32` at fixtures/reject/src/lib.rs:8:2 arithmetic on `f32` at fixtures/reject/src/lib.rs:8:2 unsupported type `f32` at fixtures/reject/src/lib.rs:7:25 -unsupported type `dyn std::ops::Fn(u8) -> u8` at fixtures/reject/src/lib.rs:11:16 +`dyn std::ops::Fn(u8) -> u8`, a trait object of another crate's trait, which is not supported yet at fixtures/reject/src/lib.rs:11:16 `std::iter::IntoIterator::into_iter` with a mutable reference to a value JS copies, which its mapping would copy at fixtures/reject/src/lib.rs:17:11 `Option>>` is not supported: its `None` and `Some` would both be `rt.None` at fixtures/reject/src/lib.rs:24:2 `Option>>` is not supported: its `None` and `Some` would both be `rt.None` at fixtures/reject/src/lib.rs:23:15 @@ -67,6 +67,9 @@ a mutable reference into a `Result`'s payload, passed on or kept at fixtures/rej unsupported call to ` as std::cmp::PartialEq>::eq` at fixtures/reject/src/lib.rs:474:2 `f64::mul_add`, which rounds once, where JS would round twice at fixtures/reject/src/lib.rs:498:4 unsupported call to `::fmt` at fixtures/reject/src/lib.rs:503:11 +a coercion to `std::sync::Arc`, which is not supported yet at fixtures/reject/src/lib.rs:518:39 +`dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet at fixtures/reject/src/lib.rs:527:23 +`dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet at fixtures/reject/src/lib.rs:528:4 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:155:2 an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet at fixtures/reject/src/lib.rs:175:1 overriding `Iterator::size_hint`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:209:2 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index 16ca6043b..3ac88b595 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -502,3 +502,28 @@ pub fn fma(a: f64) -> f64 { pub fn float_printed(a: f64) -> String { format!("{a}") } + +pub trait Reading { + fn read(&self) -> u8; +} + +impl Reading for u8 { + fn read(&self) -> u8 { + *self + } +} + +/// An `Arc` of a trait object, whose count the `Arc` it was made from shares. +pub fn shared_object(n: u8) -> u8 { + let r: std::sync::Arc = std::sync::Arc::new(n); + r.read() +} + +pub trait Scaled { + fn scale(&self, by: T) -> T; +} + +/// A trait object of a generic trait. +pub fn generic_object(s: &dyn Scaled) -> u8 { + s.scale(2) +} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index d52c4d686..a9fac7eeb 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -58,6 +58,8 @@ roots = [ "places_equal", "fma", "float_printed", + "shared_object", + "generic_object", "missing", ] printable = ["Meters"] diff --git a/ts/fixtures/task/expected/core/option.ts b/ts/fixtures/task/expected/core/option.ts index 642357007..6b39f6503 100644 --- a/ts/fixtures/task/expected/core/option.ts +++ b/ts/fixtures/task/expected/core/option.ts @@ -1,6 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rt from "../rt.ts"; +import type * as Part from "../drops/objects.Part.ts"; import type * as Key from "../drops.Key.ts"; import type * as Guard from "../model.Guard.ts"; @@ -35,6 +36,13 @@ export function unwrapOr$Option_u64(self: rt.u64 | undefined, default_: rt.u64): return self; } +export function map$Option_Box_dyn_Part_u32_take(self: Part.Part | undefined, _: (a0: Part.Part) => rt.u32): rt.u32 | undefined { + if (self === undefined) { + return undefined; + } + return self.take$Part(); +} + export function map$Option_Guard_u32_iterated_closure16(self: Guard.Guard | undefined, f: (a0: Guard.Guard) => rt.u32): rt.u32 | undefined { if (self === undefined) { return undefined; @@ -78,6 +86,13 @@ export function map$Option_u32_u32_discarded_closure16(self: rt.u32 | undefined, return _0; } +export function mapOr$Option_Box_dyn_Part_u32_boxed_closure3(self: Part.Part | undefined, default_: rt.u32, f: (a0: Part.Part) => rt.u32): rt.u32 { + if (self === undefined) { + return default_; + } + return f(self); +} + export function mapOr$Option_Guard_u32_drained_closure4(self: Guard.Guard | undefined, default_: rt.u32, f: (a0: Guard.Guard) => rt.u32): rt.u32 { if (self === undefined) { return default_; diff --git a/ts/fixtures/task/expected/drops.Bomb.ts b/ts/fixtures/task/expected/drops.Bomb.ts index 52c803ab9..82f06785c 100644 --- a/ts/fixtures/task/expected/drops.Bomb.ts +++ b/ts/fixtures/task/expected/drops.Bomb.ts @@ -42,4 +42,15 @@ export function drop$Drop(self: Bomb): void { } } +export function id$Part(self: Bomb): rt.u32 { + return self.id; +} + +/** Consumes the box, dropping it on return. */ +export function take$Part(self: Bomb): rt.u32 { + const id: rt.u32 = self.id; + rt.drop(self); + return rt.u32WrappingMul(id, 2); +} + //# sourceMappingURL=drops.Bomb.ts.map diff --git a/ts/fixtures/task/expected/drops.Held.ts b/ts/fixtures/task/expected/drops.Held.ts index e14100773..5ea722036 100644 --- a/ts/fixtures/task/expected/drops.Held.ts +++ b/ts/fixtures/task/expected/drops.Held.ts @@ -1,6 +1,8 @@ // Generated by rust2-ts. Do not edit. -import type * as Guard from "./model.Guard.ts"; +import * as rt from "./rt.ts"; +import { drop$Held } from "./lib.ts"; +import * as Guard from "./model.Guard.ts"; /** A value of a type that drops, with no `[Symbol.dispose]` of its own. */ export type Held = @@ -18,4 +20,32 @@ export function isSome(self: Held): boolean { return !_2; } +/** Consumes the box, dropping it on return. */ +export function take$Part(self: Held): rt.u32 { + let unwind: number = 0; + try { + unwind = 5; + const _0: rt.u32 = rt.u32WrappingAdd(id$Part(self), 1); + unwind = 0; + drop$Held(self); + return _0; + } catch (e) { + if (unwind === 0) { + throw e; + } + drop$Held(self); + throw e; + } +} + +export function id$Part(self: Held): rt.u32 { + if (self.tag === "One") { + return Guard.id(self._0); + } + if (self.tag === "Two") { + return rt.u32Add(Guard.id(self._0), Guard.id(self._1)); + } + return 0; +} + //# sourceMappingURL=drops.Held.ts.map diff --git a/ts/fixtures/task/expected/drops.ts b/ts/fixtures/task/expected/drops.ts index a1db9f3be..5f9764827 100644 --- a/ts/fixtures/task/expected/drops.ts +++ b/ts/fixtures/task/expected/drops.ts @@ -6,6 +6,7 @@ import { isSomeAnd$Option_Guard_options_closure7, unwrapOr$Option_Guard, unwrapO import { ok$Result_Guard_Guard, err$Result_Guard_Guard, mapOrElse$Result_Guard_Guard_u32_options_closure9_options_closure10, inspectErr$Result_u8_Key_tripped_closure6, andThen$Result_Guard_Guard_Guard_options_closure8, unwrapOr$Result_Guard_Guard } from "./core/result.ts"; import { scoped, discarded, iterated, blasted } from "./drops/closures.ts"; import { looped, collected, drained, extended, bombed, spent } from "./drops/iters.ts"; +import { boxed } from "./drops/objects.ts"; import * as Bomb from "./drops.Bomb.ts"; import * as Entries from "./drops.Entries.ts"; import type * as Fault from "./drops.Fault.ts"; @@ -22,7 +23,7 @@ import * as Log from "./log.Log.ts"; import * as Guard from "./model.Guard.ts"; /** - * Run scenario `arg % 23`, panicking at the point `arg / 23 % 11` names, if any: what + * Run scenario `arg % 24`, panicking at the point `arg / 24 % 11` names, if any: what * every value std holds drops, where Rust drops it, and in Rust's order. */ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { @@ -34,7 +35,7 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { let cloned!: rt.Arc; let mutex!: rt.Mutex; let v2!: Held.Held[]; - let boxed!: Held.Held; + let boxed_!: Held.Held; let _91!: Guard.Guard; let maybe!: Held.Held | undefined; let wrapped!: Wrap.Wrap; @@ -85,7 +86,7 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { _380 = false; _379 = false; _378 = false; - const at: rt.u8 = rt.u8Rem(rt.u8Div(arg, 23), 11); + const at: rt.u8 = rt.u8Rem(rt.u8Div(arg, 24), 11); const guard: (a0: rt.u32) => Guard.Guard = (n: rt.u32): Guard.Guard => { return Guard.new(rt.u32Add(id, n), Log.clone$Clone(log)); }; @@ -95,8 +96,10 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { rt.panicFmt(["d", args18[0], " at ", args18[1]]); } }; - const _13: rt.u8 = rt.u8Rem(arg, 23); - if (_13 === 22) { + const _13: rt.u8 = rt.u8Rem(arg, 24); + if (_13 === 23) { + boxed(log, id, at); + } else if (_13 === 22) { iterated(log, id, at); } else if (_13 === 19) { scoped(log, id, at); @@ -136,23 +139,23 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { grown(log, id, at, false); } else if (_13 === 0) { v = []; - unwind = 220; + unwind = 221; const _19 = { tag: "One", _0: guard(1) } as Held.Held; v.push(_19); v.push({ tag: "Empty" } as Held.Held); _29 = guard(2); - unwind = 219; - const _32: Guard.Guard = guard(3); unwind = 220; + const _32: Guard.Guard = guard(3); + unwind = 221; v.push({ tag: "Two", _0: _29, _1: _32 } as Held.Held); - unwind = 218; + unwind = 219; held_ = v; check(1); arc = new rt.Arc({ tag: "One", _0: guard(4) } as Held.Held); - unwind = 239; + unwind = 240; _386 = true; shared = rt.arcClone(arc); - unwind = 217; + unwind = 218; _386 = false; rt.arcDrop(arc, drop$Held); const part4 = shared.strong; @@ -160,7 +163,7 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { Log.note(log, rt.fmtFormat(["arc ", args2[0]])); const part5 = shared.strong; cloned = rt.arcClone(shared); - unwind = 237; + unwind = 238; _385 = true; const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.usizeDisplay(part5, f)]; Log.note(log, rt.fmtFormat(["counted ", args4[0]])); @@ -168,22 +171,22 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { rt.arcDrop(cloned, drop$Held); check(2); v2 = []; - unwind = 216; + unwind = 217; const _85 = { tag: "One", _0: guard(5) } as Held.Held; v2.push(_85); - unwind = 237; + unwind = 238; mutex = new rt.Mutex(v2); - unwind = 235; + unwind = 236; _384 = true; _91 = guard(6); - unwind = 215; + unwind = 216; const _94: Guard.Guard = guard(7); - unwind = 235; - boxed = { tag: "Two", _0: _91, _1: _94 } as Held.Held; - unwind = 233; + unwind = 236; + boxed_ = { tag: "Two", _0: _91, _1: _94 } as Held.Held; + unwind = 234; _383 = true; const _98: Held.Held = { tag: "One", _0: guard(8) }; - unwind = 214; + unwind = 215; maybe = _98; check(3); const _112: rt.usize = held_.length; @@ -191,14 +194,14 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.usizeDisplay(_112, f), (f: rt.Formatter) => rt.fmtBool(_115, f)]; Log.note(log, rt.fmtFormat(["held ", args6[0], " ", args6[1]])); _383 = false; - drop$Held(boxed); + drop$Held(boxed_); check(4); _384 = false; rt.dropAll(mutex.value, drop$Held); check(5); - unwind = 233; + unwind = 234; drop$Option_Box_Held(maybe); - unwind = 239; + unwind = 240; _383 = false; _384 = false; _385 = false; @@ -209,57 +212,57 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { } else if (_13 === 1) { const _133: rt.u32 = rt.u32Add(id, 10); _135 = Log.clone$Clone(log); - unwind = 213; + unwind = 214; const _137: Guard.Guard = guard(11); - unwind = 212; + unwind = 213; wrapped = new Wrap.Wrap({ id: _133, log: _135, inner: { tag: "One", _0: _137 } as Held.Held }); const _141: rt.u32 = rt.u32Add(id, 20); _143 = Log.clone$Clone(log); - unwind = 211; + unwind = 212; const _144: Guard.Guard = guard(21); - unwind = 210; + unwind = 211; guarded = new Wrap.Wrap({ id: _141, log: _143, inner: _144 }); check(1); _152 = guard(31); - unwind = 209; + unwind = 210; const _155: Guard.Guard = guard(32); - unwind = 208; + unwind = 209; _151 = { tag: "Two", _0: _152, _1: _155 } as Held.Held; const _158: Guard.Guard = guard(33); - unwind = 231; + unwind = 232; _382 = true; pair = new Pair.Pair({ first: _151, second: _158 }); v3 = []; - unwind = 207; + unwind = 208; _166 = guard(41); - unwind = 206; - const _169: Guard.Guard = guard(42); unwind = 207; + const _169: Guard.Guard = guard(42); + unwind = 208; v3.push(new Pair.Pair({ first: _166, second: _169 })); _175 = guard(43); - unwind = 205; + unwind = 206; const _178: Guard.Guard = guard(44); - unwind = 207; + unwind = 208; v3.push(new Pair.Pair({ first: _175, second: _178 })); - unwind = 203; + unwind = 204; pairs = v3; _borrowed = new Named.Named({ tag: "Borrowed", _0: "text" } as Text.Text); const _186: Guard.Guard = guard(45); - unwind = 202; + unwind = 203; _owned = new Named.Named({ tag: "Owned", _0: _186 } as Text.Text); check(2); const _193: rt.u32 = rt.u32Add(id, 50); _195 = Log.clone$Clone(log); - unwind = 201; + unwind = 202; v4 = []; - unwind = 200; + unwind = 201; const _201: rt.u32 = rt.u32Add(id, 60); _203 = Log.clone$Clone(log); - unwind = 199; - const _205: Guard.Guard = guard(61); unwind = 200; + const _205: Guard.Guard = guard(61); + unwind = 201; v4.push(new Wrap.Wrap({ id: _201, log: _203, inner: { tag: "One", _0: _205 } as Held.Held })); - unwind = 198; + unwind = 199; nested = new Wrap.Wrap({ id: _193, log: _195, inner: v4 }); check(3); _382 = false; @@ -270,15 +273,15 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { const _228: rt.u32 = rt.u32Add(rt.u32Add(Guard.id(pairs[rt.idx(pairs, 1)].first), Guard.id(pairs[rt.idx(pairs, 0)].second)), nested.inner.length as rt.u32); const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtBool(_222, f), (f: rt.Formatter) => rt.u32Display(_225, f), (f: rt.Formatter) => rt.u32Display(_228, f)]; Log.note(log, rt.fmtFormat(["wraps ", args8[0], " ", args8[1], " ", args8[2]])); - unwind = 202; - drop$Wrap_Vec_Wrap_Held(nested); unwind = 203; - rt.drop(_owned); + drop$Wrap_Vec_Wrap_Held(nested); unwind = 204; + rt.drop(_owned); + unwind = 205; rt.drop(_borrowed); - unwind = 231; + unwind = 232; rt.dropAll(pairs); - unwind = 212; + unwind = 213; _382 = false; drop$Wrap_Guard(guarded); unwind = 0; @@ -286,22 +289,22 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { } else if (_13 === 2) { const _251: rt.u32 = rt.u32Add(id, 70); _253 = Log.clone$Clone(log); - unwind = 197; + unwind = 198; const _256: Guard.Guard = guard(71); - unwind = 196; + unwind = 197; _255 = { tag: "One", _0: _256 } as Held.Held; const _259: Guard.Guard = guard(72); unwind = 0; shared2 = new rt.Arc(new Wrap.Wrap({ id: _251, log: _253, inner: new Pair.Pair({ first: _255, second: _259 }) })); - unwind = 229; + unwind = 230; _381 = true; v5 = []; - unwind = 195; + unwind = 196; const _266: rt.Arc>> = rt.arcClone(shared2); v5.push(_266); const _270: rt.Arc>> = rt.arcClone(shared2); v5.push(_270); - unwind = 194; + unwind = 195; clones = v5; check(1); _381 = false; @@ -313,36 +316,36 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { const part3 = !bail(log, id, at).ok; const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtBool(part3, f)]; Log.note(log, rt.fmtFormat(["bailed ", args12[0]])); - unwind = 229; + unwind = 230; rt.dropAll(clones, drop$Arc_Wrap_Pair_Held); _381 = false; } else { _308 = guard(81); - unwind = 193; + unwind = 194; const _311: Guard.Guard = guard(82); unwind = 0; arc2 = new rt.Arc(new rt.Mutex({ tag: "Two", _0: _308, _1: _311 } as Held.Held)); - unwind = 227; + unwind = 228; _380 = true; other = rt.arcClone(arc2); - unwind = 192; + unwind = 193; check(1); _380 = false; rt.arcDrop(arc2, drop$Mutex_Held); check(2); const _327: rt.Mutex = other.value; - unwind = 191; - const held2: boolean = Held.isSome(_327.value); unwind = 192; + const held2: boolean = Held.isSome(_327.value); + unwind = 193; const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtBool(held2, f)]; Log.note(log, rt.fmtFormat(["locked ", args14[0]])); lent = guard(83); - unwind = 190; + unwind = 191; borrowed = new rt.Arc(lent); - unwind = 225; + unwind = 226; _379 = true; again = rt.arcClone(borrowed); - unwind = 223; + unwind = 224; _378 = true; _379 = false; rt.arcDrop(borrowed); @@ -352,11 +355,11 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { const part = Guard.id(lent); const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part, f)]; Log.note(log, rt.fmtFormat(["lent ", args16[0]])); - unwind = 192; + unwind = 193; _378 = false; _379 = false; rt.drop(lent); - unwind = 227; + unwind = 228; rt.arcDrop(other, drop$Mutex_Held); _380 = false; } @@ -386,136 +389,136 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { if (unwind === 0) { throw e; } - if (unwind === 190) { - break b4; - } if (unwind === 191) { - break b3; + break b4; } if (unwind === 192) { break b3; } if (unwind === 193) { + break b3; + } + if (unwind === 194) { rt.drop(_308); break b1; } - if (unwind === 194) { + if (unwind === 195) { rt.dropAll(clones, drop$Arc_Wrap_Pair_Held); break b5; } - if (unwind === 195) { + if (unwind === 196) { rt.dropAll(v5, drop$Arc_Wrap_Pair_Held); break b5; } - if (unwind === 196) { + if (unwind === 197) { drop$Held(_255); break b6; } - if (unwind === 197) { + if (unwind === 198) { break b6; } - if (unwind === 198) { + if (unwind === 199) { drop$Wrap_Vec_Wrap_Held(nested); break b12; } - if (unwind === 199) { + if (unwind === 200) { rt.drop(_203); break b14; } - if (unwind === 200) { + if (unwind === 201) { break b14; } - if (unwind === 201) { + if (unwind === 202) { break b13; } - if (unwind === 202) { + if (unwind === 203) { break b12; } - if (unwind === 203) { + if (unwind === 204) { break b11; } - if (unwind === 204) { + if (unwind === 205) { break b10; } - if (unwind === 205) { + if (unwind === 206) { rt.drop(_175); break b15; } - if (unwind === 206) { + if (unwind === 207) { rt.drop(_166); break b15; } - if (unwind === 207) { + if (unwind === 208) { break b15; } - if (unwind === 208) { + if (unwind === 209) { drop$Held(_151); break b8; } - if (unwind === 209) { + if (unwind === 210) { rt.drop(_152); break b8; } - if (unwind === 210) { + if (unwind === 211) { break b8; } - if (unwind === 211) { + if (unwind === 212) { rt.drop(_143); break b7; } - if (unwind === 212) { + if (unwind === 213) { break b7; } - if (unwind === 213) { + if (unwind === 214) { rt.drop(_135); break b1; } - if (unwind === 214) { + if (unwind === 215) { drop$Option_Box_Held(maybe); break b21; } - if (unwind === 215) { + if (unwind === 216) { rt.drop(_91); break b20; } - if (unwind === 216) { + if (unwind === 217) { rt.dropAll(v2, drop$Held); break b19; } - if (unwind === 217) { + if (unwind === 218) { break b18; } - if (unwind === 218) { + if (unwind === 219) { break b16; } - if (unwind === 219) { + if (unwind === 220) { rt.drop(_29); break b22; } - if (unwind === 220) { + if (unwind === 221) { break b22; } - if (unwind === 223) { + if (unwind === 224) { if (_378) { rt.arcDrop(again); } - } else if (unwind !== 225) { - if (unwind === 227) { + } else if (unwind !== 226) { + if (unwind === 228) { break b2; } - if (unwind === 229) { + if (unwind === 230) { break b5; } - if (unwind === 231) { + if (unwind === 232) { break b9; } - if (unwind === 233) { + if (unwind === 234) { break b21; } - if (unwind === 235) { + if (unwind === 236) { break b20; } - if (unwind === 237) { + if (unwind === 238) { break b19; } break b17; @@ -530,7 +533,7 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { break b1; } if (_383) { - drop$Held(boxed); + drop$Held(boxed_); } } if (_384) { diff --git a/ts/fixtures/task/expected/drops/objects.Part.ts b/ts/fixtures/task/expected/drops/objects.Part.ts new file mode 100644 index 000000000..527d8cefb --- /dev/null +++ b/ts/fixtures/task/expected/drops/objects.Part.ts @@ -0,0 +1,88 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { drop$Held } from "../lib.ts"; +import { take$Part_u32, id$Part_u32 } from "../drops/objects.ts"; +import * as drops_Bomb from "../drops.Bomb.ts"; +import * as drops_Held from "../drops.Held.ts"; +import * as model_Guard from "../model.Guard.ts"; + +export class Bomb { + v: drops_Bomb.Bomb; + + constructor(v: drops_Bomb.Bomb) { + this.v = v; + } + + [Symbol.dispose](): void { + rt.drop(this.v); + } + + id$Part(): rt.u32 { + return drops_Bomb.id$Part(this.v); + } + + take$Part(): rt.u32 { + return drops_Bomb.take$Part(this.v); + } +} + +export class Guard { + v: model_Guard.Guard; + + constructor(v: model_Guard.Guard) { + this.v = v; + } + + [Symbol.dispose](): void { + rt.drop(this.v); + } + + id$Part(): rt.u32 { + return model_Guard.id$Part(this.v); + } + + take$Part(): rt.u32 { + return model_Guard.take$Part(this.v); + } +} + +export class Held { + v: drops_Held.Held; + + constructor(v: drops_Held.Held) { + this.v = v; + } + + [Symbol.dispose](): void { + drop$Held(this.v); + } + + id$Part(): rt.u32 { + return drops_Held.id$Part(this.v); + } + + take$Part(): rt.u32 { + return drops_Held.take$Part(this.v); + } +} + +export type Part = { id$Part: () => rt.u32; take$Part: () => rt.u32 }; + +export class u32 { + v: rt.u32; + + constructor(v: rt.u32) { + this.v = v; + } + + id$Part(): rt.u32 { + return id$Part_u32(this.v); + } + + take$Part(): rt.u32 { + return take$Part_u32(this.v); + } +} + +//# sourceMappingURL=objects.Part.ts.map diff --git a/ts/fixtures/task/expected/drops/objects.ts b/ts/fixtures/task/expected/drops/objects.ts new file mode 100644 index 000000000..14c526463 --- /dev/null +++ b/ts/fixtures/task/expected/drops/objects.ts @@ -0,0 +1,155 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; +import { map$Option_Box_dyn_Part_u32_take, mapOr$Option_Box_dyn_Part_u32_boxed_closure3 } from "../core/option.ts"; +import * as Part from "../drops/objects.Part.ts"; +import * as Bomb from "../drops.Bomb.ts"; +import type * as Held from "../drops.Held.ts"; +import * as Log from "../log.Log.ts"; +import * as Guard from "../model.Guard.ts"; + +/** Consumes the box, dropping it on return. */ +export function take$Part_u32(self: rt.u32): rt.u32 { + return rt.u32WrappingAdd(id$Part_u32(self), 1); +} + +export function id$Part_u32(self: rt.u32): rt.u32 { + return self; +} + +/** Boxed parts of each kind, panicking at point `at`, if any, or in a `Drop` at 9. */ +export function boxed(log: Log.Log, id: rt.u32, at: rt.u8): void { + let first!: Part.Part; + let parts!: Part.Part[]; + let _20!: Guard.Guard; + let slot!: Part.Part; + let _85!: Held.Held; + let _99!: rt.u32; + let kept!: Part.Part | undefined; + let _166!: boolean; + let _167!: boolean; + let unwind: number = 0; + try { + _167 = false; + _166 = false; + const guard: (a0: rt.u32) => Guard.Guard = (n: rt.u32): Guard.Guard => { + return Guard.new(rt.u32Add(id, n), Log.clone$Clone(log)); + }; + const check: (a0: rt.u8) => void = (point: rt.u8): void => { + if (point === at) { + const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(id, f), (f: rt.Formatter) => rt.u8Display(point, f)]; + rt.panicFmt(["d", args14[0], " at ", args14[1]]); + } + }; + const _10: Guard.Guard = guard(1); + unwind = 83; + _167 = true; + first = new Part.Guard(_10); + parts = []; + unwind = 86; + _166 = true; + _20 = guard(2); + unwind = 82; + const _23: Guard.Guard = guard(3); + unwind = 86; + const _17: Part.Part = new Part.Held({ tag: "Two", _0: _20, _1: _23 } as Held.Held); + parts.push(_17); + const _28: Part.Part = new Part.u32(7); + parts.push(_28); + const _32: Part.Part = new Part.Bomb(new Bomb.Bomb({ id: rt.u32Add(id, 4), log: Log.clone$Clone(log), blows: at === 9 })); + parts.push(_32); + const _41: Part.Part = new Part.Held({ tag: "One", _0: guard(5) } as Held.Held); + parts.push(_41); + rt.sliceSwap(parts, 2, 3); + check(1); + const part = rt.iterFold(rt.iterMap(rt.iterSlice(parts), (p: Part.Part): rt.u32 => { + return p.id$Part(); + }), 0, (a0: rt.u32, a1: rt.u32): rt.u32 => { + return rt.u32WrappingAdd(a0, a1); + }); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part, f)]; + Log.note(log, rt.fmtFormat(["ids ", args2[0]])); + const _69: Guard.Guard = guard(6); + unwind = 81; + slot = new Part.Guard(_69); + const part2 = slot.id$Part(); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part2, f)]; + Log.note(log, rt.fmtFormat(["slot ", args4[0]])); + _85 = { tag: "Empty" } as Held.Held; + unwind = 40; + rt.drop(slot); + unwind = 81; + slot = new Part.Held(_85); + const part3 = slot.id$Part(); + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part3, f)]; + Log.note(log, rt.fmtFormat(["slot ", args6[0]])); + _99 = 7; + unwind = 49; + rt.drop(slot); + unwind = 81; + slot = new Part.u32(_99); + check(2); + _167 = false; + const part4 = first.take$Part(); + const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(part4, f)]; + Log.note(log, rt.fmtFormat(["took ", args8[0]])); + const part5 = map$Option_Box_dyn_Part_u32_take(parts.pop(), (a02: Part.Part): rt.u32 => { + return a02.take$Part(); + }); + const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(part5, f, rt.u32Debug)]; + Log.note(log, rt.fmtFormat(["last ", args10[0]])); + const _130: Guard.Guard = guard(7); + unwind = 80; + kept = new Part.Guard(_130); + check(3); + _166 = false; + rt.dropAll(parts); + check(4); + const _148: rt.u32 = mapOr$Option_Box_dyn_Part_u32_boxed_closure3(kept, 0, (p2: Part.Part): rt.u32 => { + return p2.id$Part(); + }); + const _152: rt.u32 = slot.id$Part(); + const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(_148, f), (f: rt.Formatter) => rt.u32Display(_152, f)]; + Log.note(log, rt.fmtFormat(["kept ", args12[0], " ", args12[1]])); + unwind = 81; + rt.drop(kept); + unwind = 86; + rt.drop(slot); + _166 = false; + _167 = false; + } catch (e) { + b0: { + b1: { + if (unwind === 0) { + throw e; + } + if (unwind === 40) { + slot = new Part.Held(_85); + } else if (unwind === 49) { + slot = new Part.u32(_99); + } else if (unwind === 80) { + rt.drop(kept); + } else if (unwind !== 81) { + if (unwind === 82) { + rt.drop(_20); + break b1; + } + if (unwind === 83) { + break b0; + } + break b1; + } + rt.drop(slot); + } + if (_166) { + rt.dropAll(parts); + } + } + if (_167) { + rt.drop(first); + } + throw e; + } +} + +//# sourceMappingURL=objects.ts.map diff --git a/ts/fixtures/task/expected/model.Guard.ts b/ts/fixtures/task/expected/model.Guard.ts index 3820492bd..7b93231f6 100644 --- a/ts/fixtures/task/expected/model.Guard.ts +++ b/ts/fixtures/task/expected/model.Guard.ts @@ -3,6 +3,28 @@ import * as rt from "./rt.ts"; import * as Log from "./log.Log.ts"; +/** Consumes the box, dropping it on return. */ +export function take$Part(self: Guard): rt.u32 { + let unwind: number = 0; + try { + unwind = 5; + const _0: rt.u32 = rt.u32WrappingAdd(id$Part(self), 1); + unwind = 0; + rt.drop(self); + return _0; + } catch (e) { + if (unwind === 0) { + throw e; + } + rt.drop(self); + throw e; + } +} + +export function id$Part(self: Guard): rt.u32 { + return id(self); +} + /** Logs its own drop. */ export class Guard { id: rt.u32; diff --git a/ts/fixtures/task/src/drops.rs b/ts/fixtures/task/src/drops.rs index 47708fcd9..c4a8700ec 100644 --- a/ts/fixtures/task/src/drops.rs +++ b/ts/fixtures/task/src/drops.rs @@ -23,6 +23,7 @@ macro_rules! list { mod closures; mod iters; +mod objects; /// A value of a type that drops, with no `[Symbol.dispose]` of its own. pub enum Held { @@ -64,17 +65,18 @@ pub struct Named<'a>(Text<'a>); /// order, the second after the first panics. pub struct Entries(Vec<(K, V)>); -/// Run scenario `arg % 23`, panicking at the point `arg / 23 % 11` names, if any: what +/// Run scenario `arg % 24`, panicking at the point `arg / 24 % 11` names, if any: what /// every value std holds drops, where Rust drops it, and in Rust's order. pub fn run(log: &Log, id: u32, arg: u8) { - let at = arg / 23 % 11; + let at = arg / 24 % 11; let guard = |n: u32| Guard::new(id + n, log.clone()); let check = |point: u8| { if point == at { panic!("d{id} at {point}"); } }; - match arg % 23 { + match arg % 24 { + 23 => objects::boxed(log, id, at), 22 => closures::iterated(log, id, at), 19 => closures::scoped(log, id, at), 20 => closures::discarded(log, id, at), diff --git a/ts/fixtures/task/src/drops/objects.rs b/ts/fixtures/task/src/drops/objects.rs new file mode 100644 index 000000000..7cc94eac8 --- /dev/null +++ b/ts/fixtures/task/src/drops/objects.rs @@ -0,0 +1,89 @@ +//! What a `Box` drops: the value it was made of, by its own type's drop, a +//! struct's, an enum's glue, or nothing for a number, where the box goes out of scope, +//! is replaced, or is consumed by `self: Box`, a panic included. + +use super::{Bomb, Held}; +use crate::log::Log; +use crate::model::Guard; + +trait Part { + fn id(&self) -> u32; + /// Consumes the box, dropping it on return. + fn take(self: Box) -> u32 { + self.id().wrapping_add(1) + } +} + +impl Part for Guard { + fn id(&self) -> u32 { + Guard::id(self) + } +} + +impl Part for Held { + fn id(&self) -> u32 { + match self { + Held::One(g) => g.id(), + Held::Two(a, b) => a.id() + b.id(), + Held::Empty => 0, + } + } +} + +impl Part for Bomb { + fn id(&self) -> u32 { + self.id + } + fn take(self: Box) -> u32 { + let id = self.id; + drop(self); + id.wrapping_mul(2) + } +} + +impl Part for u32 { + fn id(&self) -> u32 { + *self + } +} + +/// Boxed parts of each kind, panicking at point `at`, if any, or in a `Drop` at 9. +pub(super) fn boxed(log: &Log, id: u32, at: u8) { + let guard = |n: u32| Guard::new(id + n, log.clone()); + let check = |point: u8| { + if point == at { + panic!("d{id} at {point}"); + } + }; + let first: Box = Box::new(guard(1)); + let mut parts: Vec> = Vec::new(); + parts.push(Box::new(Held::Two(guard(2), guard(3)))); + parts.push(Box::new(7u32)); + parts.push(Box::new(Bomb { + id: id + 4, + log: log.clone(), + blows: at == 9, + })); + parts.push(Box::new(Held::One(guard(5)))); + parts.swap(2, 3); + check(1); + log.note(format!( + "ids {}", + parts.iter().map(|p| p.id()).fold(0u32, u32::wrapping_add) + )); + // The old value drops as the new one takes its place. + let mut slot: Box = Box::new(guard(6)); + log.note(format!("slot {}", slot.id())); + slot = Box::new(Held::Empty); + log.note(format!("slot {}", slot.id())); + slot = Box::new(7u32); + check(2); + log.note(format!("took {}", first.take())); + let last = parts.pop().map(Part::take); + log.note(format!("last {last:?}")); + let kept: Option> = Some(Box::new(guard(7))); + check(3); + drop(parts); + check(4); + log.note(format!("kept {} {}", kept.as_ref().map_or(0, |p| p.id()), slot.id())); +} diff --git a/ts/src/lower/body.rs b/ts/src/lower/body.rs index 592f28d22..1773382c7 100644 --- a/ts/src/lower/body.rs +++ b/ts/src/lower/body.rs @@ -99,6 +99,9 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { if let Some(sym) = self.t.bound_fn(instance, span) { return Expr::Sym(sym).call(args.into_iter().map(Expr::typed).collect()); } + if let ty::InstanceKind::Virtual(method, _) = instance.def { + return self.virtual_call(method, gargs.type_at(0), args, span); + } // An `async` body's `Future::poll`, which its future has itself. if let ty::InstanceKind::Item(did) = instance.def && tcx.is_coroutine(did) diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index 97f311b00..a31905322 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -64,7 +64,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ty::Tuple(tys) => tys.iter().any(|t| self.drops(t)), ty::Array(elem, _) => self.drops(*elem), ty::Closure(_, args) => args.as_closure().upvar_tys().iter().any(|t| self.drops(t)), - ty::Coroutine(..) | ty::Param(_) => true, + // Whatever it was made of may drop. + ty::Coroutine(..) | ty::Param(_) | ty::Dynamic(..) => true, _ => false, }; self.drops_open.remove(&ty); @@ -136,8 +137,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } None => false, }, - // A closure's `[Symbol.dispose]` drops what it owns. - ty::Closure(..) | ty::Coroutine(..) | ty::Param(_) => true, + // A closure's `[Symbol.dispose]` drops what it owns, as a trait object's does. + ty::Closure(..) | ty::Coroutine(..) | ty::Param(_) | ty::Dynamic(..) => true, _ => false, } } diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs new file mode 100644 index 000000000..586b381da --- /dev/null +++ b/ts/src/lower/dynamic.rs @@ -0,0 +1,307 @@ +//! Trait objects. A `dyn Trait` is a JS object with a method for each of the trait's +//! dispatchable functions, its supertraits' included, named as an impl's function is, +//! for its trait (`area$Shape`), and taking the arguments after `self`. That is the +//! trait's type, which a TS caller can implement. A coercion to one wraps the value in a +//! class of its type's own, whose methods call that type's impl and whose +//! `[Symbol.dispose]`, which a `Box` drops it by, drops the value. + +use rustc_hir::def_id::DefId; +use rustc_middle::ty::{self, Interner as _, Ty}; +use rustc_span::Span; + +use super::Translator; +use super::body::Body; +use crate::program::{SymKind, Symbol}; +use crate::ts::{self, Expr, FieldInit, Item, Param, StmtKind, SymId, Type}; + +/// A trait that objects of it translate for: its type, and its methods in vtable order. +#[derive(Clone)] +pub struct Object { + pub sym: SymId, + pub methods: Vec, +} + +#[derive(Clone)] +pub struct Method { + pub def_id: DefId, + pub name: String, +} + +/// How a wrapper class holds the value a pointer to it was unsized from. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub enum Held { + /// Its value, from `&T` or `Box`: the class is an `rt.Place` of it, as a `v`. + Value, + /// The `rt.Place` a `&mut T` to a value JS copies is. + Place, +} + +impl<'tcx> Translator<'_, 'tcx> { + /// The trait of the trait object `ty`, a `dyn Trait`, made the first time. + pub fn object(&mut self, ty: Ty<'tcx>, span: Span) -> Option { + let tcx = self.tcx; + let ty::Dynamic(preds, ..) = ty.kind() else { + return None; + }; + let Some(principal) = preds.principal_def_id() else { + self.unrepresented( + span, + format!("`{ty}`, a trait object of no trait with methods, which is not supported yet"), + ); + return None; + }; + if let Some(object) = self.objects.get(&principal) { + return Some(object.clone()); + } + if !principal.is_local() { + self.unrepresented( + span, + format!("`{ty}`, a trait object of another crate's trait, which is not supported yet"), + ); + return None; + } + if preds.projection_bounds().next().is_some() { + self.unrepresented( + span, + format!("`{ty}`, a trait object with an associated type, which is not supported yet"), + ); + return None; + } + let traits: Vec = tcx.supertrait_def_ids(principal).collect(); + if let Some(&generic) = traits.iter().find(|&&tr| tcx.generics_of(tr).count() > 1) { + let msg = format!( + "`{ty}`, a trait object of the generic trait `{}`, which is not supported yet", + tcx.def_path_str(generic) + ); + self.unrepresented(span, msg); + return None; + } + let name = tcx.item_name(principal).to_string(); + let sym = self.program.sym(Symbol { + module: crate::program::type_module(&self.module_of(principal), &name), + base: name, + suffix: String::new(), + owner: None, + kind: SymKind::Union, + order: self.order(tcx.def_span(principal)), + instance: String::new(), + name: String::new(), + alias: None, + }); + let mut methods = Vec::new(); + for &tr in &traits { + for &def_id in tcx.own_existential_vtable_entries(tr) { + 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}`")); + return None; + } + methods.push(Method { def_id, name }); + } + } + let object = Object { sym, methods }; + // Before its methods' types, which may name it. + self.objects.insert(principal, object.clone()); + let mut members = Vec::new(); + for method in &object.methods { + let args = tcx.mk_args(&[ty.into()]); + let sig = tcx.fn_sig(method.def_id).instantiate(tcx, args).skip_norm_wip(); + let sig = tcx.instantiate_bound_regions_with_erased(sig); + let func = self.fn_ty(&sig.inputs()[1..], sig.output(), span); + members.push((method.name.clone(), func)); + } + self.program.items.push(Item::Union { + sym, + params: Vec::new(), + ty: Type::Object(members), + doc: self.doc(principal), + }); + Some(object) + } + + /// The class wrapping a `from` as the trait object `to`, held as `held`, made the first + /// time. + pub fn object_class(&mut self, from: Ty<'tcx>, to: Ty<'tcx>, held: Held, span: Span) -> Option { + let tcx = self.tcx; + let from = tcx.erase_and_anonymize_regions(from); + let to = tcx.erase_and_anonymize_regions(to); + let object = self.object(to, span)?; + let key = (from, object.sym, held); + if let Some(&sym) = self.object_classes.get(&key) { + return Some(sym); + } + let trait_sym = &self.program.syms[object.sym]; + let base = super::short_name(tcx, from); + let sym = self.program.sym(Symbol { + module: trait_sym.module.clone(), + base, + suffix: match held { + Held::Value => String::new(), + Held::Place => "Place".into(), + }, + owner: None, + kind: SymKind::Struct, + order: trait_sym.order, + instance: String::new(), + name: String::new(), + alias: None, + }); + self.object_classes.insert(key, sym); + let v = self.program.field(sym, "v".into()); + let ty = self.ty(from, span); + let field_ty = match held { + Held::Value => ty, + Held::Place => Type::Rt("Place", vec![ty]), + }; + self.program.items.push(Item::Struct { + sym, + params: Vec::new(), + fields: vec![ts::Field { + id: v, + ty: field_ty, + doc: None, + init: FieldInit::Ctor, + }], + ctor: ts::Ctor::Positional, + doc: None, + }); + let value = Expr::This.field(v); + for method in &object.methods { + let args = tcx.mk_args(&[from.into()]); + let sig = tcx.fn_sig(method.def_id).instantiate(tcx, args).skip_norm_wip(); + let sig = tcx.instantiate_bound_regions_with_erased(sig); + let receiver = match self.receiver(sig.inputs()[0], from, held, value.clone()) { + Some(receiver) => receiver, + None => { + let msg = format!( + "`{to}` with the receiver `{}`, which is not supported yet", + sig.inputs()[0] + ); + self.err(span, msg); + continue; + } + }; + let mut b = Body::new(self, args); + let params: Vec = sig.inputs()[1..] + .iter() + .map(|input| Param { + name: b.fresh("a"), + ty: Some(b.t.ty(*input, span)), + }) + .collect(); + let values = params.iter().map(|p| Expr::ident(&p.name)); + let mut body = Vec::new(); + let call = b.call_def( + method.def_id, + args, + [receiver].into_iter().chain(values).collect(), + span, + &mut body, + ); + let ret = self.ret_ty(sig.output(), span); + body.push(StmtKind::Return(Some(call.typed())).at(None)); + let method = self.method(sym, method.name.clone()); + self.program.methods.entry(sym).or_default().push(ts::Function { + sym: method, + params, + ret, + body, + loc: None, + doc: None, + }); + } + if held == Held::Value + && let Some(dropped) = self.drop_value(value, from, span) + { + let dispose = self.method(sym, "[Symbol.dispose]".into()); + self.program.methods.entry(sym).or_default().push(ts::Function { + sym: dispose, + params: Vec::new(), + ret: Type::Builtin("void"), + body: vec![StmtKind::Expr(dropped).at(None)], + loc: None, + doc: None, + }); + } + Some(sym) + } + + /// A method `base` of the class `owner`. + fn method(&mut self, owner: SymId, base: String) -> SymId { + let module = self.program.syms[owner].module.clone(); + self.program.sym(Symbol { + module, + base, + suffix: String::new(), + owner: Some(owner), + kind: SymKind::Method, + order: 0, + instance: String::new(), + name: String::new(), + alias: None, + }) + } + + /// What a wrapper of a `from`, holding `value` as `held`, passes as `self` of type + /// `receiver`, if it can. A `&mut` wrapper is never a `Box`'s, which it reads anyway. + fn receiver(&self, receiver: Ty<'tcx>, from: Ty<'tcx>, held: Held, value: Expr) -> Option { + let read = match held { + Held::Place => value.clone().member("v"), + Held::Value => value.clone(), + }; + match receiver.kind() { + ty::Ref(_, _, ty::Mutability::Not) => Some(read), + ty::Ref(_, _, ty::Mutability::Mut) => Some(match held { + // The class is a place of its value. + Held::Value if self.needs_place(from) => Expr::This, + _ => value, + }), + _ if receiver.is_box() => Some(read), + _ => None, + } + } +} + +impl<'tcx> Body<'_, '_, 'tcx> { + /// A call of the trait method `def_id` on a trait object, `args[0]`, through the method + /// its object has. + pub fn virtual_call(&mut self, def_id: DefId, object: Ty<'tcx>, mut args: Vec, span: Span) -> Expr { + let Some(found) = self.t.object(object, span) else { + return Expr::undefined(); + }; + let Some(method) = found.methods.iter().find(|m| m.def_id == def_id) else { + return self.err( + span, + format!("`{}` is not a method of `{object}`", self.t.tcx.def_path_str(def_id)), + ); + }; + let receiver = args.remove(0); + receiver.member(method.name.clone()).call(args) + } + + /// `value`, a `&T`, `&mut T`, or `Box`, unsized to `to`, the same pointer to a trait + /// object. + pub fn object_of(&mut self, value: Expr, from: Ty<'tcx>, to: Ty<'tcx>, span: Span) -> Expr { + let unsupported = |b: &mut Self| b.err(span, format!("a coercion to `{to}`, which is not supported yet")); + let (Some(pointee), Some(object)) = (from.builtin_deref(true), to.builtin_deref(true)) else { + return unsupported(self); + }; + if self.t.object(object, span).is_none() { + return Expr::undefined(); + } + // An upcast, from a trait object, which has its supertraits' methods already. + if pointee.is_trait() { + return value; + } + let held = match from.kind() { + ty::Ref(_, _, ty::Mutability::Mut) if self.t.needs_place(pointee) => Held::Place, + ty::Ref(..) => Held::Value, + _ if from.is_box() => Held::Value, + _ => return unsupported(self), + }; + match self.t.object_class(pointee, object, held, span) { + Some(class) => Expr::New(Box::new(Expr::Sym(class)), vec![value]), + None => Expr::undefined(), + } + } +} diff --git a/ts/src/lower/fmt.rs b/ts/src/lower/fmt.rs index f4414f933..d7b5446e3 100644 --- a/ts/src/lower/fmt.rs +++ b/ts/src/lower/fmt.rs @@ -113,12 +113,6 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { Trait::of(self.t.tcx, preds.principal_def_id()?) } - /// Whether `ty` points to a trait object. - pub fn trait_object(&self, ty: Ty<'tcx>) -> bool { - ty.builtin_deref(true) - .is_some_and(|pointee| matches!(pointee.kind(), ty::Dynamic(..))) - } - /// `value`, a `ty`, written by `tr` to `f`, through the impl for `ty`. pub fn fmt_call(&mut self, tr: Trait, ty: Ty<'tcx>, value: Expr, f: Expr, span: Span) -> Expr { let tcx = self.t.tcx; diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index e2cd7f978..998ccbba2 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -205,7 +205,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let value = self.path(value, "arg", out); self.b.fmt_value(tr, from, value, span, out) } - PointerCoercion::Unsize if self.b.trait_object(to) => self.err(span, format!("a coercion to `{to}`")), + PointerCoercion::Unsize if unsized_to(from, to).is_some_and(|t| t.is_trait()) => { + let value = self.operand(op, span, out); + self.b.object_of(value, from, to, span) + } // An array's bytes, as a slice of all of it: a reference to them is one already. PointerCoercion::Unsize if unsized_to(from, to).is_some_and(super::place::byte_slice) => { match to.builtin_deref(true) { diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 6199bde5a..0b6eb6638 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -12,6 +12,7 @@ mod collections; mod consts; mod copy; mod drop; +mod dynamic; mod float; mod fmt; mod iter; @@ -184,6 +185,10 @@ pub struct Translator<'a, 'tcx> { copy_shims: rustc_data_structures::fx::FxHashMap, SymId>, /// The types the crate writes into in place, which alone are copied. writes: copy::Writes<'tcx>, + /// The trait of each trait object, by its principal trait. + objects: rustc_data_structures::fx::FxHashMap, + /// The class wrapping each type as each trait object. + object_classes: rustc_data_structures::fx::FxHashMap<(Ty<'tcx>, SymId, dynamic::Held), SymId>, /// The class of each coroutine instance. pub coroutines: rustc_data_structures::fx::FxHashMap, mir::coroutine::Info<'tcx>>, /// Whether [`Self::ty`] is typing a variable: see [`Self::held_ty`]. @@ -274,6 +279,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { drops_cycle: false, glued: Default::default(), coroutines: Default::default(), + objects: Default::default(), + object_classes: Default::default(), drop_shims: Default::default(), copy_shims: Default::default(), // Once the bindings are known. @@ -974,7 +981,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }, // Its captures change through it, as a JS function's do. ty::Closure(..) => Some(ObjectKind::Closure), - ty::Coroutine(..) => Some(ObjectKind::Class), + ty::Coroutine(..) | ty::Dynamic(..) => Some(ObjectKind::Class), _ if self.deferred(ty) => Some(ObjectKind::Class), _ => None, } @@ -1076,6 +1083,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { { Type::Rt("Fmt", vec![]) } + ty::Dynamic(..) => match self.object(ty, span) { + Some(object) => Type::Sym(object.sym, Vec::new()), + None => Type::Builtin("unknown"), + }, _ if self.deferred(ty) => Type::Builtin("unknown"), _ => { self.unrepresented(span, format!("unsupported type `{ty}`")); @@ -1521,6 +1532,10 @@ pub fn short_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> String { ty::Never => "never".into(), ty::Tuple(tys) => tys.iter().map(|t| short_name(tcx, t)).collect::>().join("_"), ty::Array(elem, _) | ty::Slice(elem) => format!("{}s", short_name(tcx, *elem)), + ty::Dynamic(preds, ..) => match preds.principal_def_id() { + Some(principal) => format!("dyn_{}", tcx.item_name(principal)), + None => "dyn".into(), + }, // By where it is defined, not by its span, which code above it moves. ty::Closure(def_id, _) | ty::Coroutine(def_id, _) | ty::CoroutineClosure(def_id, _) | ty::FnDef(def_id, _) => { def_name(tcx, *def_id) From 84c1b696c78d88401285aa82c4b0f63739306b11 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:26:23 -0700 Subject: [PATCH 3/6] chore(quest): replacing an indexed element whose drop may panic Co-Authored-By: Claude Opus 5.5 --- quest/m2/README.md | 1 + quest/m2/ts-index-cleanup.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 quest/m2/ts-index-cleanup.md diff --git a/quest/m2/README.md b/quest/m2/README.md index 02702f43e..672017ab4 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -27,6 +27,7 @@ The fork's feature quests folded into the [exporter](/quest/m1/ffi-export/README ## Required - [Byte-slice locals that crash the translator](/quest/m2/ts-scalar-ice.md) - `take_apart` never reaches the arm `find_scalars` promised it wouldn't +- [Replacing an indexed element whose drop may panic](/quest/m2/ts-index-cleanup.md) - `v[i] = x` type-checks and unwinds as Rust does when the old element's drop may panic - [Byte slices without objects](/quest/m2/ts-byte-views/README.md) - reading through a translated cursor allocates no object per read - [Async in the TypeScript translator](/quest/m2/ts-async.md) - moq-net's and kio's async code translates with Rust's lazy futures - [Shared ownership](/quest/m2/ts-shared.md) - `Arc`/`Rc`/`Weak` keep exact counts, and locks and atomics become plain values diff --git a/quest/m2/ts-index-cleanup.md b/quest/m2/ts-index-cleanup.md new file mode 100644 index 000000000..3b62fc2a8 --- /dev/null +++ b/quest/m2/ts-index-cleanup.md @@ -0,0 +1,28 @@ +# [S] Replacing an indexed element whose drop may panic + +## Goal + +rust2-ts translates `v[i] = x`, where dropping the old element may panic, into +TypeScript that `tsc` accepts and that drops and replaces as native Rust does on every +path, a panic in the old element's `Drop` included, proven by differential tests. + +## Plan + +Found while testing [trait objects](/quest/m3/ts-dyn.md). The drop-and-replace through +`IndexMut` lowers the borrowed element as `v[at2]`, where `at2` is a `const` the body +declares inside the `try`. The unwind cleanup in the `catch` replays the assignment as +`v[at2] = x`, out of `at2`'s scope, so `tsc` rejects the output. A `Vec` of a type with +a `Drop` impl reproduces it, no trait object needed: + +```rust +let mut v = vec![Bomb::new()]; +v[0] = Bomb::new(); +``` + +kio's `self.children[index] = Some(Occupant { .. })` and `waiters[i] = Waiter::noop()` +have that shape. + +- Fix it where the cleanup reads what the body spilled, so every temporary an aliased + place names is in scope in the `catch`, not by special-casing indexing. + +Public API: none. From 12819a19b6ecd440bb3b1a1eb1e95e83d63b3366 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:34:22 -0700 Subject: [PATCH 4/6] fix(ts): keep `&dyn Debug`'s calls, and pass places to trait object methods A virtual call of a formatting trait object stays on the function it is, and the others take a `&mut` to a value JS copies as its `rt.Place`, as the crate's own functions do, not as std's mappings take it. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/dyns.Named.ts | 10 +++- ts/fixtures/lang/expected/dyns.Shape.ts | 24 +++++++- ts/fixtures/lang/expected/dyns.Shown.ts | 14 +++++ ts/fixtures/lang/expected/dyns.Square.ts | 5 ++ ts/fixtures/lang/expected/dyns.Tri.ts | 5 ++ ts/fixtures/lang/expected/dyns.ts | 75 +++++++++++++++--------- ts/fixtures/lang/src/dyns.rs | 19 +++++- ts/src/lower/body.rs | 4 +- ts/src/lower/dynamic.rs | 17 +++++- ts/src/lower/mod.rs | 6 +- 10 files changed, 142 insertions(+), 37 deletions(-) create mode 100644 ts/fixtures/lang/expected/dyns.Shown.ts diff --git a/ts/fixtures/lang/expected/dyns.Named.ts b/ts/fixtures/lang/expected/dyns.Named.ts index 60cb9e93c..e5a6fad2d 100644 --- a/ts/fixtures/lang/expected/dyns.Named.ts +++ b/ts/fixtures/lang/expected/dyns.Named.ts @@ -5,7 +5,7 @@ import * as dyns_Square from "./dyns.Square.ts"; import * as dyns_Tri from "./dyns.Tri.ts"; export type Named = - | { label$Named: () => string; fmt$Debug: (a0: rt.Formatter) => rt.Result; area$Shape: () => rt.u32; grow$Shape: (a0: rt.u8) => void; sides$Shape: () => rt.u8; intoArea$Shape: () => rt.u32 }; + | { label$Named: () => string; fmt$Debug: (a0: rt.Formatter) => rt.Result; area$Shape: () => rt.u32; grow$Shape: (a0: rt.u8) => void; sides$Shape: () => rt.u8; intoArea$Shape: () => rt.u32; tally$Shape: (a0: rt.Place) => void }; export class Square { v: dyns_Square.Square; @@ -37,6 +37,10 @@ export class Square { sides$Shape(): rt.u8 { return dyns_Square.sides$Shape(this.v); } + + tally$Shape(a: rt.Place): void { + return dyns_Square.tally$Shape(this.v, a); + } } export class Tri { @@ -69,6 +73,10 @@ export class Tri { sides$Shape(): rt.u8 { return dyns_Tri.sides$Shape(this.v); } + + tally$Shape(a: rt.Place): void { + return dyns_Tri.tally$Shape(this.v, a); + } } //# sourceMappingURL=dyns.Named.ts.map diff --git a/ts/fixtures/lang/expected/dyns.Shape.ts b/ts/fixtures/lang/expected/dyns.Shape.ts index e37ef6b91..9b5a9293c 100644 --- a/ts/fixtures/lang/expected/dyns.Shape.ts +++ b/ts/fixtures/lang/expected/dyns.Shape.ts @@ -1,12 +1,12 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { sides$Shape_u32, intoArea$Shape_u32, area$Shape_u32, grow$Shape_u32 } from "./dyns.ts"; +import { sides$Shape_u32, intoArea$Shape_u32, tally$Shape_u32, area$Shape_u32, grow$Shape_u32 } from "./dyns.ts"; import * as dyns_Square from "./dyns.Square.ts"; import * as dyns_Tri from "./dyns.Tri.ts"; export type Shape = - | { area$Shape: () => rt.u32; grow$Shape: (a0: rt.u8) => void; sides$Shape: () => rt.u8; intoArea$Shape: () => rt.u32 }; + | { area$Shape: () => rt.u32; grow$Shape: (a0: rt.u8) => void; sides$Shape: () => rt.u8; intoArea$Shape: () => rt.u32; tally$Shape: (a0: rt.Place) => void }; export class Square { v: dyns_Square.Square; @@ -30,6 +30,10 @@ export class Square { sides$Shape(): rt.u8 { return dyns_Square.sides$Shape(this.v); } + + tally$Shape(a: rt.Place): void { + return dyns_Square.tally$Shape(this.v, a); + } } export class Tri { @@ -54,6 +58,10 @@ export class Tri { sides$Shape(): rt.u8 { return dyns_Tri.sides$Shape(this.v); } + + tally$Shape(a: rt.Place): void { + return dyns_Tri.tally$Shape(this.v, a); + } } export class Tri$Place { @@ -78,6 +86,10 @@ export class Tri$Place { sides$Shape(): rt.u8 { return dyns_Tri.sides$Shape(this.v.v); } + + tally$Shape(a: rt.Place): void { + return dyns_Tri.tally$Shape(this.v.v, a); + } } export class u32 { @@ -102,6 +114,10 @@ export class u32 { sides$Shape(): rt.u8 { return sides$Shape_u32(this.v); } + + tally$Shape(a: rt.Place): void { + return tally$Shape_u32(this.v, a); + } } export class u32$Place { @@ -126,6 +142,10 @@ export class u32$Place { sides$Shape(): rt.u8 { return sides$Shape_u32(this.v.v); } + + tally$Shape(a: rt.Place): void { + return tally$Shape_u32(this.v.v, a); + } } //# sourceMappingURL=dyns.Shape.ts.map diff --git a/ts/fixtures/lang/expected/dyns.Shown.ts b/ts/fixtures/lang/expected/dyns.Shown.ts new file mode 100644 index 000000000..39897e87d --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Shown.ts @@ -0,0 +1,14 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** Writes a `&dyn Debug` by calling its `fmt`, not through a placeholder. */ +export interface Shown { + _0: rt.Fmt; +} + +export function fmt$Debug(self: Shown, f: rt.Formatter): rt.Result { + return self._0(f); +} + +//# sourceMappingURL=dyns.Shown.ts.map diff --git a/ts/fixtures/lang/expected/dyns.Square.ts b/ts/fixtures/lang/expected/dyns.Square.ts index 842fe6b6a..baf6c7b00 100644 --- a/ts/fixtures/lang/expected/dyns.Square.ts +++ b/ts/fixtures/lang/expected/dyns.Square.ts @@ -11,6 +11,11 @@ export function intoArea$Shape(self: Square): rt.u32 { return rt.u32WrappingAdd(area$Shape(self), 1); } +/** Writes through a `&mut` to a number, which is an `rt.Place`. */ +export function tally$Shape(self: Square, sum: rt.Place): void { + sum.v = rt.u32WrappingAdd(sum.v, area$Shape(self)); +} + export function fmt$Debug(self: Square, f: rt.Formatter): rt.Result { const _3: string = "Square"; const _4: string = "side"; diff --git a/ts/fixtures/lang/expected/dyns.Tri.ts b/ts/fixtures/lang/expected/dyns.Tri.ts index 0569b006a..6511154eb 100644 --- a/ts/fixtures/lang/expected/dyns.Tri.ts +++ b/ts/fixtures/lang/expected/dyns.Tri.ts @@ -2,6 +2,11 @@ import * as rt from "./rt.ts"; +/** Writes through a `&mut` to a number, which is an `rt.Place`. */ +export function tally$Shape(self: Tri, sum: rt.Place): void { + sum.v = rt.u32WrappingAdd(sum.v, area$Shape(self)); +} + export function fmt$Debug(self: Tri, f: rt.Formatter): rt.Result { if (self.tag === "Flat") { return rt.fmtWriteStr(f, "Flat"); diff --git a/ts/fixtures/lang/expected/dyns.ts b/ts/fixtures/lang/expected/dyns.ts index 7f2996dfb..d45a2820d 100644 --- a/ts/fixtures/lang/expected/dyns.ts +++ b/ts/fixtures/lang/expected/dyns.ts @@ -3,6 +3,7 @@ import * as rt from "./rt.ts"; import * as Named from "./dyns.Named.ts"; import * as Shape from "./dyns.Shape.ts"; +import * as Shown from "./dyns.Shown.ts"; import * as Square from "./dyns.Square.ts"; import * as Tri from "./dyns.Tri.ts"; import { put } from "./fmt.ts"; @@ -16,6 +17,11 @@ export function intoArea$Shape_u32(self: rt.u32): rt.u32 { return rt.u32WrappingAdd(area$Shape_u32(self), 1); } +/** Writes through a `&mut` to a number, which is an `rt.Place`. */ +export function tally$Shape_u32(self: rt.u32, sum: rt.Place): void { + sum.v = rt.u32WrappingAdd(sum.v, area$Shape_u32(self)); +} + export function area$Shape_u32(self: rt.u32): rt.u32 { return self; } @@ -66,12 +72,12 @@ export function dyns(data: rt.ByteSlice, out: rt.ByteVec): void { let tri!: Tri.Tri; let shapes!: Named.Named[]; let _109!: rt.usize; - let iterS!: Named.Named[]; - let iterI!: number; - let _192!: boolean; + let iterS2!: Named.Named[]; + let iterI2!: number; + let _222!: boolean; let unwind: number = 0; try { - _192 = false; + _222 = false; if (data.length >= 3) { const _12: [rt.u8, rt.u8, rt.u8] = [data.data[data.start], data.data[data.start + 1], data.data[data.start + 2]]; const a: rt.u8 = _12[0]; @@ -97,9 +103,9 @@ export function dyns(data: rt.ByteSlice, out: rt.ByteVec): void { const _82: rt.u8 = sidesOf$Square(sq); const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(_78, f), (f: rt.Formatter) => rt.u8Display(_82, f)]; put(out, rt.fmtFormat([args6[0], " ", args6[1], " "])); - _192 = true; + _222 = true; shapes = []; - unwind = 102; + unwind = 116; const iter: rt.Iter<[rt.usize, rt.u8]> = rt.iterEnumerate(rt.iterSlice(rt.byteSliceView(new rt.ByteSlice(data.data, data.start + 3, data.length - 3)))); while (true) { const _94: [rt.usize, rt.u8] | rt.Done = iter.next(); @@ -122,48 +128,59 @@ export function dyns(data: rt.ByteSlice, out: rt.ByteVec): void { s.v.grow$Shape(a); } const _129: string = s.v.label$Named(); - unwind = 96; + unwind = 110; const _132: rt.u32 = s.v.area$Shape(); const _135: rt.u8 = sidesOf$dyn_Shape(upcast(s.v)); const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, _129), (f: rt.Formatter) => rt.u32Display(_132, f), (f: rt.Formatter) => rt.u8Display(_135, f), (f: rt.Formatter) => s.v.fmt$Debug(f)]; const _125: string = rt.fmtFormat([args8[0], ":", args8[1], ":", args8[2], ":", args8[3], " "]); - unwind = 102; + unwind = 116; const _124: string = _125; - unwind = 95; + unwind = 109; put(out, _124); } let sum: rt.u32 = 0; - _192 = false; - iterS = shapes; - iterI = 0; + for (const _150 of shapes) { + _150.tally$Shape(rt.place(() => sum, (value4) => sum = value4)); + } + const _167: Tri.Tri = tri; + const _168: rt.u32 = sum; + const _165: Shown.Shown = { _0: (f: rt.Formatter) => Tri.fmt$Debug(_167, f) }; + const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(_168, f), (f: rt.Formatter) => Shown.fmt$Debug(_165, f)]; + const _160: string = rt.fmtFormat([args10[0], " ", args10[1], " "]); + unwind = 108; + put(out, _160); + unwind = 116; + _222 = false; + iterS2 = shapes; + iterI2 = 0; while (true) { - unwind = 94; - const _150: Named.Named | rt.Done = iterI < iterS.length ? iterS[iterI++] : rt.DONE; - if (_150 === rt.DONE) { + unwind = 107; + const _177: Named.Named | rt.Done = iterI2 < iterS2.length ? iterS2[iterI2++] : rt.DONE; + if (_177 === rt.DONE) { break; } - sum = rt.u32WrappingAdd(sum, _150.intoArea$Shape()); + sum = rt.u32WrappingAdd(sum, _177.intoArea$Shape()); } - unwind = 102; - rt.dropAll(iterS, rt.drop, iterI); - const _169: rt.u32 = sum; - const _168: rt.u32 = new Shape.u32(n).intoArea$Shape(); - const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(_169, f), (f: rt.Formatter) => rt.u32Display(_168, f)]; - const _163: string = rt.fmtFormat([args10[0], " ", args10[1]]); - unwind = 93; - put(out, _163); - _192 = false; + unwind = 116; + rt.dropAll(iterS2, rt.drop, iterI2); + const _196: rt.u32 = sum; + const _195: rt.u32 = new Shape.u32(n).intoArea$Shape(); + const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(_196, f), (f: rt.Formatter) => rt.u32Display(_195, f)]; + const _190: string = rt.fmtFormat([args12[0], " ", args12[1]]); + unwind = 106; + put(out, _190); + _222 = false; } } catch (e) { if (unwind === 0) { throw e; } - if (unwind !== 93) { - if (unwind === 94) { - rt.dropAll(iterS, rt.drop, iterI); + if (unwind !== 106) { + if (unwind === 107) { + rt.dropAll(iterS2, rt.drop, iterI2); } } - if (_192) { + if (_222) { rt.dropAll(shapes); } throw e; diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs index cda8ac98e..bbbdd63f6 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -3,7 +3,7 @@ //! through `&mut self` wherever it is, a provided method, a supertrait's, `Debug` among //! them, `self: Box`, and an upcast to the supertrait. -use std::fmt::Debug; +use std::fmt::{self, Debug}; use crate::fmt::put; @@ -17,6 +17,10 @@ trait Shape { fn into_area(self: Box) -> u32 { self.area().wrapping_add(1) } + /// Writes through a `&mut` to a number, which is an `rt.Place`. + fn tally(&self, sum: &mut u32) { + *sum = sum.wrapping_add(self.area()); + } } trait Named: Shape + Debug { @@ -88,6 +92,15 @@ impl Shape for u32 { } } +/// Writes a `&dyn Debug` by calling its `fmt`, not through a placeholder. +struct Shown<'a>(&'a dyn Debug); + +impl Debug for Shown<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + fn total(shapes: &[&dyn Shape]) -> u32 { shapes.iter().fold(0, |sum, s| sum.wrapping_add(s.area())) } @@ -148,6 +161,10 @@ pub fn dyns(data: &[u8], out: &mut Vec) { ); } let mut sum = 0u32; + for s in &shapes { + s.tally(&mut sum); + } + put(out, &format!("{sum} {:?} ", Shown(&tri))); for s in shapes { sum = sum.wrapping_add(s.into_area()); } diff --git a/ts/src/lower/body.rs b/ts/src/lower/body.rs index 1773382c7..d95b6dc83 100644 --- a/ts/src/lower/body.rs +++ b/ts/src/lower/body.rs @@ -99,7 +99,9 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { if let Some(sym) = self.t.bound_fn(instance, span) { return Expr::Sym(sym).call(args.into_iter().map(Expr::typed).collect()); } - if let ty::InstanceKind::Virtual(method, _) = instance.def { + if let ty::InstanceKind::Virtual(method, _) = instance.def + && self.t.dispatches(instance) + { return self.virtual_call(method, gargs.type_at(0), args, span); } // An `async` body's `Future::poll`, which its future has itself. diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 586b381da..6255627d0 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -6,7 +6,7 @@ //! `[Symbol.dispose]`, which a `Box` drops it by, drops the value. use rustc_hir::def_id::DefId; -use rustc_middle::ty::{self, Interner as _, Ty}; +use rustc_middle::ty::{self, Instance, Interner as _, Ty}; use rustc_span::Span; use super::Translator; @@ -119,6 +119,21 @@ impl<'tcx> Translator<'_, 'tcx> { Some(object) } + /// Whether `instance` is a call through a trait object's method, whose wrapper takes + /// its arguments as the crate's functions do. A `&dyn Debug` or `&dyn Display` is a + /// function writing its value instead. + pub fn dispatches(&self, instance: Instance<'tcx>) -> bool { + let ty::InstanceKind::Virtual(..) = instance.def else { + return false; + }; + let ty::Dynamic(preds, ..) = instance.args.type_at(0).kind() else { + return false; + }; + preds + .principal_def_id() + .is_none_or(|principal| super::fmt::Trait::of(self.tcx, principal).is_none()) + } + /// The class wrapping a `from` as the trait object `to`, held as `held`, made the first /// time. pub fn object_class(&mut self, from: Ty<'tcx>, to: Ty<'tcx>, held: Held, span: Span) -> Option { diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 0b6eb6638..c3bed3d15 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -732,7 +732,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// crate's own or std's from its MIR, rather than a mapping. pub fn translated(&self, def_id: DefId, args: GenericArgsRef<'tcx>) -> bool { match Instance::try_resolve(self.tcx, self.typing_env(), def_id, args) { - Ok(Some(instance)) => self.lowers(instance), + Ok(Some(instance)) => self.lowers(instance) || self.dispatches(instance), _ => false, } } @@ -752,7 +752,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// parameter as what it points at, rather than a body that translates or a binding's export. pub fn mapped(&self, def_id: DefId, args: GenericArgsRef<'tcx>) -> bool { match Instance::try_resolve(self.tcx, self.typing_env(), def_id, args) { - Ok(Some(instance)) => !self.lowers(instance) && self.bound_module(instance).is_none(), + Ok(Some(instance)) => { + !self.lowers(instance) && !self.dispatches(instance) && self.bound_module(instance).is_none() + } _ => true, } } From 6945be486c36e2dd1274604d0602a4d0373ba93a Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:40:20 -0700 Subject: [PATCH 5/6] fix(ts): instantiate trait object methods over their own lifetimes A dispatchable method's early-bound lifetimes are arguments of its signature too, so instantiating it with `Self` alone panicked in rustc. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/src/dyns.rs | 9 ++++++++- ts/src/lower/dynamic.rs | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs index bbbdd63f6..6138edbe5 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -17,6 +17,10 @@ trait Shape { fn into_area(self: Box) -> u32 { self.area().wrapping_add(1) } + /// Over early-bound lifetimes, which its signature takes as arguments too. + fn larger<'a: 'b, 'b>(&'a self, other: &'b u32) -> u32 { + self.area().max(*other) + } /// Writes through a `&mut` to a number, which is an `rt.Place`. fn tally(&self, sum: &mut u32) { *sum = sum.wrapping_add(self.area()); @@ -164,7 +168,10 @@ pub fn dyns(data: &[u8], out: &mut Vec) { for s in &shapes { s.tally(&mut sum); } - put(out, &format!("{sum} {:?} ", Shown(&tri))); + put( + out, + &format!("{sum} {:?} {} ", Shown(&tri), shapes[0].larger(&u32::from(b))), + ); for s in shapes { sum = sum.wrapping_add(s.into_area()); } diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 6255627d0..c048757e9 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -6,7 +6,7 @@ //! `[Symbol.dispose]`, which a `Box` drops it by, drops the value. use rustc_hir::def_id::DefId; -use rustc_middle::ty::{self, Instance, Interner as _, Ty}; +use rustc_middle::ty::{self, GenericArgsRef, Instance, Interner as _, Ty, TyCtxt}; use rustc_span::Span; use super::Translator; @@ -104,7 +104,7 @@ impl<'tcx> Translator<'_, 'tcx> { self.objects.insert(principal, object.clone()); let mut members = Vec::new(); for method in &object.methods { - let args = tcx.mk_args(&[ty.into()]); + let args = method_args(tcx, method.def_id, ty); let sig = tcx.fn_sig(method.def_id).instantiate(tcx, args).skip_norm_wip(); let sig = tcx.instantiate_bound_regions_with_erased(sig); let func = self.fn_ty(&sig.inputs()[1..], sig.output(), span); @@ -182,7 +182,7 @@ impl<'tcx> Translator<'_, 'tcx> { }); let value = Expr::This.field(v); for method in &object.methods { - let args = tcx.mk_args(&[from.into()]); + let args = method_args(tcx, method.def_id, from); let sig = tcx.fn_sig(method.def_id).instantiate(tcx, args).skip_norm_wip(); let sig = tcx.instantiate_bound_regions_with_erased(sig); let receiver = match self.receiver(sig.inputs()[0], from, held, value.clone()) { @@ -277,6 +277,15 @@ impl<'tcx> Translator<'_, 'tcx> { } } +/// The arguments of a trait object's method `def_id` for `self_ty`: the trait's `Self`, +/// then its own early-bound lifetimes, erased, which are all a dispatchable method takes. +fn method_args<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) -> GenericArgsRef<'tcx> { + ty::GenericArgs::for_item(tcx, def_id, |param, _| match param.index { + 0 => self_ty.into(), + _ => tcx.lifetimes.re_erased.into(), + }) +} + impl<'tcx> Body<'_, '_, 'tcx> { /// A call of the trait method `def_id` on a trait object, `args[0]`, through the method /// its object has. From fbc4932b3c1a7745e5a95ea5b057589ce9336c7c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:58:36 -0700 Subject: [PATCH 6/6] fix(ts): refuse byte arrays and struct tails as trait objects A boxed `[u8; N]` is a `Uint8Array` and a borrowed one an `rt.ByteSlice`, which one wrapper class can't both hold, and a coercion of a struct whose last field unsizes made no object at all: both now fail with their span. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 3 ++- ts/fixtures/reject/census.md | 30 +++++++++++++++++++++--------- ts/fixtures/reject/expected.txt | 2 ++ ts/fixtures/reject/src/lib.rs | 27 +++++++++++++++++++++++++++ ts/fixtures/reject/ts.toml | 2 ++ ts/src/lower/dynamic.rs | 12 ++++++++++++ 6 files changed, 66 insertions(+), 10 deletions(-) diff --git a/ts/README.md b/ts/README.md index 0d000262e..13498cdcb 100644 --- a/ts/README.md +++ b/ts/README.md @@ -242,7 +242,8 @@ copies holds its `rt.Place`, so `&mut self` writes where the value is. An upcast same object. A `Box` drops what it holds through its `[Symbol.dispose]`, which a TS caller's object may leave out. A trait object of another crate's trait, but for `&dyn Debug`, `&dyn Display`, and `dyn Future`, of a generic trait or one with an -associated type, or behind any other pointer, such as an `Arc`, is refused. +associated type, of a byte array, or behind any other pointer, such as an `Arc` or a +struct's last field, is refused. An `async fn` returns a future without running anything: an instance of a class of its own (`sumFuture`), the state machine MIR's coroutine transform makes. Its fields are the diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 5085073fb..1dc6e3ad2 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,7 +1,7 @@ # Census of `reject-fixture` -- Refusals: 80 -- Function instances: 87 +- Refusals: 82 +- Function instances: 90 - Generic functions no instance reached, lowered over their type parameters: 6 ## By construct @@ -21,6 +21,7 @@ | 2 | `_` of a reference to an array of bytes it is generic over, which its mapping would take as the array, not as a slice of it | fixtures/reject/src/lib.rs:268:16 | | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | fixtures/reject/src/lib.rs:543:23 | | 2 | a coercion to `_` | fixtures/reject/src/lib.rs:362:2 | +| 2 | a coercion to `_`, which is not supported yet | fixtures/reject/src/lib.rs:534:39 | | 2 | an iterator of the crate implementing `_`, which is not supported yet | fixtures/reject/src/lib.rs:175:1 | | 2 | constructing `_` from another crate | fixtures/reject/src/lib.rs:161:23 | | 2 | overriding `_`, which std's adaptors call in place of `next` or beside it | fixtures/reject/src/lib.rs:155:2 | @@ -39,13 +40,13 @@ | 1 | a `zip` of `_` and `_`, which std reads by index, of items that drop, which is not supported yet | fixtures/reject/src/lib.rs:242:30 | | 1 | a `zip` of `_`, whose closures std calls one item further through `next` than through `fold` | fixtures/reject/src/lib.rs:188:51 | | 1 | a cast from `_` to `_` | fixtures/reject/src/lib.rs:112:2 | -| 1 | a coercion to `_`, which is not supported yet | fixtures/reject/src/lib.rs:534:39 | | 1 | a constant of type `_` | fixtures/reject/src/lib.rs:96:2 | | 1 | a copy of a closure that changes what it captured | fixtures/reject/src/lib.rs:278:14 | | 1 | a mutable borrow of a subslice of `_`, which JS would copy, having no views | fixtures/reject/src/lib.rs:247:13 | | 1 | a mutable reference into a `Result`'s payload, passed on or kept | fixtures/reject/src/lib.rs:467:12 | | 1 | a printable type must implement `Display` or `Debug` | fixtures/reject/src/lib.rs:108:1 | | 1 | a scalar constant of type `_` | fixtures/reject/src/lib.rs:8:2 | +| 1 | a trait object of `_`, which is not supported yet | fixtures/reject/src/lib.rs:560:26 | | 1 | a zero-sized constant of type `_` | fixtures/reject/src/lib.rs:78:2 | | 1 | arithmetic on `_` | fixtures/reject/src/lib.rs:8:2 | | 1 | collecting `_`, whose `None` is `rt.None`, which is not supported yet | fixtures/reject/src/lib.rs:43:43 | @@ -92,7 +93,9 @@ The type or module each refused type or path belongs to. | 1 | `Maybe` | fixtures/reject/src/lib.rs:38:2 | | 1 | `Noisy` | fixtures/reject/src/lib.rs:69:13 | | 1 | `T \| undefined` | fixtures/reject/src/lib.rs:38:2 | +| 1 | `Tail` | fixtures/reject/src/lib.rs:570:30 | | 1 | `[u16; 1]` | fixtures/reject/src/lib.rs:261:22 | +| 1 | `[u8; 4]` | fixtures/reject/src/lib.rs:560:26 | | 1 | `count` | fixtures/reject/src/lib.rs:63:23 | | 1 | `dyn std::ops::Fn(u8) -> u8` | fixtures/reject/src/lib.rs:11:16 | | 1 | `f64` | fixtures/reject/src/lib.rs:503:2 | @@ -145,10 +148,12 @@ The type or module each refused type or path belongs to. | 1 | a `zip` of `_` and `_`, which std reads by index, of items that drop, which is not supported yet | `std::iter::Map`, `std::ops::Range` | fixtures/reject/src/lib.rs:242:30 | | 1 | a `zip` of `_`, whose closures std calls one item further through `next` than through `fold` | `std::iter::Chain` | fixtures/reject/src/lib.rs:188:51 | | 1 | a cast from `_` to `_` | `u64`, `f32` | fixtures/reject/src/lib.rs:112:2 | +| 1 | a coercion to `_`, which is not supported yet | `&Tail` | fixtures/reject/src/lib.rs:570:30 | | 1 | a coercion to `_`, which is not supported yet | `std::sync::Arc` | fixtures/reject/src/lib.rs:534:39 | | 1 | a constant of type `_` | `&u8` | fixtures/reject/src/lib.rs:96:2 | | 1 | a mutable borrow of a subslice of `_`, which JS would copy, having no views | `u32` | fixtures/reject/src/lib.rs:247:13 | | 1 | a scalar constant of type `_` | `f32` | fixtures/reject/src/lib.rs:8:2 | +| 1 | a trait object of `_`, which is not supported yet | `[u8; 4]` | fixtures/reject/src/lib.rs:560:26 | | 1 | a zero-sized constant of type `_` | `{async closure}` | fixtures/reject/src/lib.rs:78:2 | | 1 | an assignment through a reference to `_` | `std::sync::Arc` | fixtures/reject/src/lib.rs:117:2 | | 1 | an assignment through a reference to `_` | `std::sync::MutexGuard` | fixtures/reject/src/lib.rs:137:2 | @@ -289,6 +294,13 @@ The type or module each refused type or path belongs to. +
a coercion to `_`, which is not supported yet (2) + +- fixtures/reject/src/lib.rs:534:39: a coercion to `std::sync::Arc`, which is not supported yet +- fixtures/reject/src/lib.rs:570:30: a coercion to `&Tail`, which is not supported yet + +
+
an iterator of the crate implementing `_`, which is not supported yet (2) - fixtures/reject/src/lib.rs:175:1: an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet @@ -401,12 +413,6 @@ The type or module each refused type or path belongs to.
-
a coercion to `_`, which is not supported yet (1) - -- fixtures/reject/src/lib.rs:534:39: a coercion to `std::sync::Arc`, which is not supported yet - -
-
a constant of type `_` (1) - fixtures/reject/src/lib.rs:96:2: a constant of type `&u8` @@ -443,6 +449,12 @@ The type or module each refused type or path belongs to.
+
a trait object of `_`, which is not supported yet (1) + +- fixtures/reject/src/lib.rs:560:26: a trait object of `[u8; 4]`, which is not supported yet + +
+
a zero-sized constant of type `_` (1) - fixtures/reject/src/lib.rs:78:2: a zero-sized constant of type `{async closure@fixtures/reject/src/lib.rs:77:10: 77:18}` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index d0121c8b1..2754ecc9d 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -71,6 +71,8 @@ unsupported call to `::fmt` at fixtures/reject/src/lib a coercion to `std::sync::Arc`, which is not supported yet at fixtures/reject/src/lib.rs:534:39 `dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet at fixtures/reject/src/lib.rs:543:23 `dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet at fixtures/reject/src/lib.rs:544:4 +a trait object of `[u8; 4]`, which is not supported yet at fixtures/reject/src/lib.rs:560:26 +a coercion to `&Tail`, which is not supported yet at fixtures/reject/src/lib.rs:570:30 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:155:2 an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet at fixtures/reject/src/lib.rs:175:1 overriding `Iterator::size_hint`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:209:2 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index 752945cd4..5f23b933c 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -543,3 +543,30 @@ pub trait Scaled { pub fn generic_object(s: &dyn Scaled) -> u8 { s.scale(2) } + +pub trait First { + fn first(&self) -> u8; +} + +impl First for [u8; 4] { + fn first(&self) -> u8 { + self[0] + } +} + +/// A trait object of a byte array, which a box holds as a `Uint8Array` and a reference as +/// an `rt.ByteSlice`. +pub fn first_of(n: u8) -> u8 { + let b: Box = Box::new([n, 8, 9, 10]); + b.first() +} + +pub struct Tail { + pub value: T, +} + +/// A struct whose last field unsizes to a trait object. +pub fn tail_read(t: &Tail) -> u8 { + let r: &Tail = t; + r.value.read() +} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index ae871c2c7..a62473744 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -61,6 +61,8 @@ roots = [ "float_printed", "shared_object", "generic_object", + "first_of", + "tail_read", "missing", ] printable = ["Meters"] diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index bbdccec65..489d8e7b1 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -312,6 +312,18 @@ impl<'tcx> Body<'_, '_, 'tcx> { let (Some(pointee), Some(object)) = (from.builtin_deref(true), to.builtin_deref(true)) else { return unsupported(self); }; + // The pointer itself, not a struct whose last field unsizes. + if !object.is_trait() { + return unsupported(self); + } + // A box holds one as a `Uint8Array` and a reference as an `rt.ByteSlice`, which one + // class's methods can't both take. + if super::byte_array(pointee) { + return self.err( + span, + format!("a trait object of `{pointee}`, which is not supported yet"), + ); + } if self.t.object(object, span).is_none() { return Expr::undefined(); }