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. diff --git a/quest/m3/ts-dyn.md b/quest/m3/ts-dyn.md index 6d41c50ff..d7198442e 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` and `Box`), 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 other crates' traits), with Rust's +dispatch and drop order, proven by differential tests against native Rust, so a consumer +neither rewrites nor binds them. ## Plan @@ -12,6 +13,16 @@ 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 closures ([bindings](/ts/README.md#bindings)). -- `&dyn Debug`, `&dyn Display`, and `dyn Future` already translate; build on how they do. +- `&dyn Debug` and `&dyn Display` translate as functions writing their value, `dyn Future` + as an `rt.Future`, and `&dyn Trait`, `&mut dyn Trait`, and `Box` of the + crate's own traits as an object with the trait's methods, which a class per type wraps + the value in. Build the rest on those. +- `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. + `Arc` has the same problem. +- Another crate's traits: `Fn*` objects can be the closure itself, as `&dyn Debug` is the + function writing it; `Iterator`, `Error`, `Any`, and `bytes::Buf` need their std + methods mapped onto the object. +- Generic traits and associated types name their arguments in the object's type. Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index cedda1358..df98a462a 100644 --- a/ts/README.md +++ b/ts/README.md @@ -87,6 +87,7 @@ in `rt/str.ts`, collections in `rt/collections.ts`, ownership, atomics, and asyn | `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` | @@ -232,6 +233,19 @@ 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`, `&dyn Display`, and `dyn Future`, of a generic trait or one with an +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 arguments and the locals held across an `.await`, and its `poll` resumes at the state it @@ -446,8 +460,8 @@ they need. ## Not yet `unsafe` and raw pointers ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than -`&dyn Debug`, `&dyn Display`, and `dyn Future` ([trait objects](/quest/m3/ts-dyn.md)), fail -translation. Until they translate, an item that needs one, such as a boxed closure, binds to +`&dyn Debug`, `&dyn Display`, `dyn Future`, and the crate's own traits' behind `&`, `&mut`, +or `Box` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a boxed closure, binds to hand-written TS ([bindings](#bindings)). These fail translation: formatting and parsing an `f64`, and `f64::clamp`, whose panic formats them ([float formatting](/quest/m2/ts-model/floats.md)), `f32` ([`f32`](/quest/m3/ts-f32.md)), statics, async closures, a closure that owns a value diff --git a/ts/fixtures/lang/caller.ts b/ts/fixtures/lang/caller.ts index 06bfceeff..19d8df66e 100644 --- a/ts/fixtures/lang/caller.ts +++ b/ts/fixtures/lang/caller.ts @@ -215,4 +215,12 @@ const utf8 = [ [0x80, 0xf8, 0xff], ].map((bytes) => Uint8Array.of(op("utf8::decodes"), 0, ...bytes)); -export const cases: Uint8Array[] = [...loops, ...durations, ...ints(), ...utf8, ...floats]; +/** + * `dyns::dyns` too short to run, with a `Tri` of each variant, at the ends of each side and + * count, and boxing each kind of shape. + */ +const dyns = [[], [1, 2], [3, 0, 7], [255, 1, 255, 0, 1, 2, 3, 4, 5], [0, 3, 2, 9, 255, 6, 128]].map((bytes) => + Uint8Array.of(op("dyns::dyns"), 0, ...bytes), +); + +export const cases: Uint8Array[] = [...loops, ...durations, ...ints(), ...utf8, ...floats, ...dyns]; diff --git a/ts/fixtures/lang/expected/dyns.Named.ts b/ts/fixtures/lang/expected/dyns.Named.ts new file mode 100644 index 000000000..8bbaa21e9 --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Named.ts @@ -0,0 +1,90 @@ +// 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; larger$Shape: (a0: rt.u32) => rt.u32; tally$Shape: (a0: rt.Place) => void }; + +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); + } + + larger$Shape(a: rt.u32): rt.u32 { + return dyns_Square.larger$Shape(this.v, a); + } + + 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 { + 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); + } + + larger$Shape(a: rt.u32): rt.u32 { + return dyns_Tri.larger$Shape(this.v, a); + } + + 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 new file mode 100644 index 000000000..d0c9ff9d7 --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Shape.ts @@ -0,0 +1,171 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { sides$Shape_u32, intoArea$Shape_u32, larger$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; larger$Shape: (a0: rt.u32) => rt.u32; tally$Shape: (a0: rt.Place) => void }; + +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); + } + + larger$Shape(a: rt.u32): rt.u32 { + return dyns_Square.larger$Shape(this.v, a); + } + + 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 { + 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); + } + + larger$Shape(a: rt.u32): rt.u32 { + return dyns_Tri.larger$Shape(this.v, a); + } + + 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 { + 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); + } + + larger$Shape(a: rt.u32): rt.u32 { + return dyns_Tri.larger$Shape(this.v.v, a); + } + + 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 { + 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); + } + + larger$Shape(a: rt.u32): rt.u32 { + return larger$Shape_u32(this.v, a); + } + + 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 { + 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); + } + + larger$Shape(a: rt.u32): rt.u32 { + return larger$Shape_u32(this.v.v, a); + } + + 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 new file mode 100644 index 000000000..6e169a858 --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Square.ts @@ -0,0 +1,49 @@ +// 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); +} + +/** Over early-bound lifetimes, which its signature takes as arguments too. */ +export function larger$Shape(self: Square, other: rt.u32): rt.u32 { + return rt.max(area$Shape(self), other); +} + +/** 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"; + 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..0a2659656 --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Tri.ts @@ -0,0 +1,62 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** Over early-bound lifetimes, which its signature takes as arguments too. */ +export function larger$Shape(self: Tri, other: rt.u32): rt.u32 { + return rt.max(area$Shape(self), other); +} + +/** 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"); + } + 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..0b9815e7f --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.ts @@ -0,0 +1,197 @@ +// 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 Shown from "./dyns.Shown.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); +} + +/** Over early-bound lifetimes, which its signature takes as arguments too. */ +export function larger$Shape_u32(self: rt.u32, other: rt.u32): rt.u32 { + return rt.max(area$Shape_u32(self), other); +} + +/** 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; +} + +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 iterS2!: Named.Named[]; + let iterI2!: number; + let _232!: boolean; + let unwind: number = 0; + try { + _232 = 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], " "])); + _232 = true; + shapes = []; + unwind = 120; + 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 = 114; + 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 = 120; + const _124: string = _125; + unwind = 113; + put(out, _124); + } + let sum: rt.u32 = 0; + for (const _150 of shapes) { + _150.tally$Shape(rt.place(() => sum, (value4) => sum = value4)); + } + const _167: Tri.Tri = tri; + const at2 = rt.idx(shapes, 0); + const _175: rt.u32 = sum; + const _165: Shown.Shown = { _0: (f: rt.Formatter) => Tri.fmt$Debug(_167, f) }; + const _169: rt.u32 = shapes[at2].larger$Shape(b as rt.u32); + const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(_175, f), (f: rt.Formatter) => Shown.fmt$Debug(_165, f), (f: rt.Formatter) => rt.u32Display(_169, f)]; + const _160: string = rt.fmtFormat([args10[0], " ", args10[1], " ", args10[2], " "]); + unwind = 112; + put(out, _160); + unwind = 120; + _232 = false; + iterS2 = shapes; + iterI2 = 0; + while (true) { + unwind = 111; + const _185: Named.Named | rt.Done = iterI2 < iterS2.length ? iterS2[iterI2++] : rt.DONE; + if (_185 === rt.DONE) { + break; + } + sum = rt.u32WrappingAdd(sum, _185.intoArea$Shape()); + } + unwind = 120; + rt.dropAll(iterS2, rt.drop, iterI2); + const _204: rt.u32 = sum; + const _203: rt.u32 = new Shape.u32(n).intoArea$Shape(); + const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(_204, f), (f: rt.Formatter) => rt.u32Display(_203, f)]; + const _198: string = rt.fmtFormat([args12[0], " ", args12[1]]); + unwind = 110; + put(out, _198); + _232 = false; + } + } catch (e) { + if (unwind === 0) { + throw e; + } + if (unwind !== 110) { + if (unwind === 111) { + rt.dropAll(iterS2, rt.drop, iterI2); + } + } + if (_232) { + 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 9b29c82da..fddc43615 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -8,6 +8,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"; @@ -74,7 +75,9 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) counts(data22, out22); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::rounding", (_232: rt.u8, _310: rt.ByteSlice, out23: rt.ByteVec): void => { rounding(out23); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::skips", skips] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["wrappers::wrappers", wrappers] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["edits::edits", edits] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["utf8::decodes", decodes] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["floats::floats", floats] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["atomics::atomics", atomics] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::overrides", overrides] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void]]; + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::skips", skips] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["wrappers::wrappers", wrappers] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["edits::edits", edits] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["utf8::decodes", decodes] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["floats::floats", floats] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["atomics::atomics", atomics] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::overrides", overrides] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["dyns::dyns", (_233: rt.u8, data23: rt.ByteSlice, out24: rt.ByteVec): void => { + dyns(data23, out24); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void]]; } /** Run one case. */ diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs new file mode 100644 index 000000000..6138edbe5 --- /dev/null +++ b/ts/fixtures/lang/src/dyns.rs @@ -0,0 +1,180 @@ +//! 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::{self, 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) + } + /// 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()); + } +} + +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)); + } +} + +/// 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())) +} + +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 { + s.tally(&mut sum); + } + put( + out, + &format!("{sum} {:?} {} ", Shown(&tri), shapes[0].larger(&u32::from(b))), + ); + 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 c84551946..e4157ed82 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::{ - atomics, chars, chunks, collections, combinators, cows, durations, edits, floats, fmt, generics, ints, iters, keys, - nested, refs, shapes, splits, strs, utf8, wrappers, + atomics, 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 @@ -83,6 +83,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("floats::floats", floats::floats), ("atomics::atomics", atomics::atomics), ("keys::overrides", keys::overrides), + ("dyns::dyns", |_, data, out| dyns::dyns(data, out)), ] } diff --git a/ts/fixtures/lang/src/lib.rs b/ts/fixtures/lang/src/lib.rs index feadbf48f..7084ad1f5 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, `Option`s of values that can -//! already be `undefined`, and atomics. +//! already be `undefined`, atomics, 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. @@ -16,6 +16,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 a5d6361d7..97aadb27f 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,7 +1,7 @@ # Census of `reject-fixture` -- Refusals: 74 -- Function instances: 81 +- Refusals: 79 +- Function instances: 87 - Generic functions no instance reached, lowered over their type parameters: 6 ## By construct @@ -11,14 +11,16 @@ | 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:403: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 | 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:522: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:513: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 | @@ -30,6 +32,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:477: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 | @@ -42,6 +45,7 @@ | 1 | a mutable reference into a `Result`'s payload, passed on or kept | fixtures/reject/src/lib.rs:446: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:539: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 | @@ -60,8 +64,8 @@ The type or module each refused type or path belongs to. | 7 | `std::borrow::Cow` | fixtures/reject/src/lib.rs:403:2 | | 6 | `Option` | fixtures/reject/src/lib.rs:23:15 | | 6 | `f32` | fixtures/reject/src/lib.rs:7:14 | +| 5 | `std::sync::Arc` | fixtures/reject/src/lib.rs:117:2 | | 4 | `Iterator` | fixtures/reject/src/lib.rs:48:42 | -| 4 | `std::sync::Arc` | fixtures/reject/src/lib.rs:117:2 | | 3 | `std::iter::Map` | fixtures/reject/src/lib.rs:63:23 | | 3 | `std::slice` | fixtures/reject/src/lib.rs:28:4 | | 3 | `std::vec::Vec` | fixtures/reject/src/lib.rs:289:52 | @@ -70,6 +74,8 @@ The type or module each refused type or path belongs to. | 2 | `HashMap` | fixtures/reject/src/lib.rs:284:6 | | 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:522:23 | +| 2 | `dyn Scaled` | fixtures/reject/src/lib.rs:522: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 | @@ -83,7 +89,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:549:30 | | 1 | `[u16; 1]` | fixtures/reject/src/lib.rs:261:22 | +| 1 | `[u8; 4]` | fixtures/reject/src/lib.rs:539: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:482:2 | @@ -111,6 +119,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:522:23 | | 2 | a coercion to `_` | `std::sync::Arc` | fixtures/reject/src/lib.rs:362:2 | | 2 | an assignment through a reference to `_` | `std::pin::Pin` | fixtures/reject/src/lib.rs:132:2 | | 2 | replacing the value behind a reference to `_` | `Meters` | fixtures/reject/src/lib.rs:101:2 | @@ -128,14 +137,18 @@ 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:436: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:429: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 `_`, which is not supported yet | `&Tail` | fixtures/reject/src/lib.rs:549:30 | +| 1 | a coercion to `_`, which is not supported yet | `std::sync::Arc` | fixtures/reject/src/lib.rs:513: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:539: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 | @@ -164,7 +177,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 @@ -202,15 +214,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 @@ -227,6 +230,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 @@ -255,6 +266,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:522:23: `dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet +- fixtures/reject/src/lib.rs:523:4: `dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet + +
+
a coercion to `_` (2) - fixtures/reject/src/lib.rs:362:2: a coercion to `std::sync::Arc<[u8]>` @@ -262,6 +280,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:513:39: a coercion to `std::sync::Arc`, which is not supported yet +- fixtures/reject/src/lib.rs:549: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 @@ -332,6 +357,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 @@ -404,6 +435,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:539: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 3584a2b31..e59288e1e 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -6,7 +6,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 @@ -66,6 +66,11 @@ 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:453:2 `f64::mul_add`, which rounds once, where JS would round twice at fixtures/reject/src/lib.rs:477:4 unsupported call to `::fmt` at fixtures/reject/src/lib.rs:482:11 +a coercion to `std::sync::Arc`, which is not supported yet at fixtures/reject/src/lib.rs:513:39 +`dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet at fixtures/reject/src/lib.rs:522:23 +`dyn Scaled`, a trait object of the generic trait `Scaled`, which is not supported yet at fixtures/reject/src/lib.rs:523:4 +a trait object of `[u8; 4]`, which is not supported yet at fixtures/reject/src/lib.rs:539:26 +a coercion to `&Tail`, which is not supported yet at fixtures/reject/src/lib.rs:549: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 5cd1fbe8c..4d4f41ca9 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -497,3 +497,55 @@ impl std::future::Future for Ready { std::task::Poll::Ready(self.0) } } + +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) +} + +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 b75d468a4..a62473744 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -59,6 +59,10 @@ roots = [ "places_equal", "fma", "float_printed", + "shared_object", + "generic_object", + "first_of", + "tail_read", "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 18f6f8b96..a9a6a9ee0 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"; @@ -21,6 +22,11 @@ import type * as Consumer from "./kio.Consumer.ts"; import * as Log from "./log.Log.ts"; import * as Guard from "./model.Guard.ts"; +/** What boxed trait objects drop, panicking at the point `at` names, if any. */ +export function dyns(log: Log.Log, id: rt.u32, at: rt.u8): void { + boxed(log, id, at); +} + /** * Run scenario `arg % 23`, panicking at the point `arg / 23 % 11` names, if any: what * every value std holds drops, where Rust drops it, and in Rust's order. @@ -34,7 +40,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; @@ -179,7 +185,7 @@ export function run(log: Log.Log, id: rt.u32, arg: rt.u8): void { unwind = 215; const _94: Guard.Guard = guard(7); unwind = 235; - boxed = { tag: "Two", _0: _91, _1: _94 } as Held.Held; + boxed_ = { tag: "Two", _0: _91, _1: _94 } as Held.Held; unwind = 233; _383 = true; const _98: Held.Held = { tag: "One", _0: guard(8) }; @@ -191,7 +197,7 @@ 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); @@ -530,7 +536,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..c152bb80b --- /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 5. */ +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 === 5 })); + 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..4f5457c62 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,6 +65,11 @@ pub struct Named<'a>(Text<'a>); /// order, the second after the first panics. pub struct Entries(Vec<(K, V)>); +/// What boxed trait objects drop, panicking at the point `at` names, if any. +pub fn dyns(log: &Log, id: u32, at: u8) { + objects::boxed(log, id, at) +} + /// Run scenario `arg % 23`, panicking at the point `arg / 23 % 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) { diff --git a/ts/fixtures/task/src/drops/objects.rs b/ts/fixtures/task/src/drops/objects.rs new file mode 100644 index 000000000..76a812955 --- /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 5. +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 == 5, + })); + 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/fixtures/task/src/harness.rs b/ts/fixtures/task/src/harness.rs index 5b452c872..356ec3841 100644 --- a/ts/fixtures/task/src/harness.rs +++ b/ts/fixtures/task/src/harness.rs @@ -133,6 +133,9 @@ fn steps() -> Vec<(&'static str, Step)> { exec.spawn(futures::boxed(channels[c].consumer(), guard)); } }), + ("dyns", |log, exec, _, arg| { + drops::dyns(log, 1000 * exec.len() as u32, arg % 6) + }), ] } diff --git a/ts/fixtures/task/ts.toml b/ts/fixtures/task/ts.toml index a59bae47d..41e238a75 100644 --- a/ts/fixtures/task/ts.toml +++ b/ts/fixtures/task/ts.toml @@ -11,6 +11,7 @@ roots = [ "drops::run", "drops::held", "drops::edited", + "drops::dyns", "futures::helpers", "futures::boxed", ] diff --git a/ts/src/lower/body.rs b/ts/src/lower/body.rs index 592f28d22..d95b6dc83 100644 --- a/ts/src/lower/body.rs +++ b/ts/src/lower/body.rs @@ -99,6 +99,11 @@ 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 + && 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. 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 847c8643f..a7300b9f4 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -64,8 +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, - ty::Dynamic(..) => self.dyn_future(ty).is_some(), + // Whatever it was made of may drop. + ty::Coroutine(..) | ty::Param(_) | ty::Dynamic(..) => true, _ => false, }; self.drops_open.remove(&ty); @@ -137,7 +137,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } None => false, }, - // A closure's `[Symbol.dispose]` drops what it owns, and a `dyn Future`'s what it is. + // A closure's `[Symbol.dispose]` drops what it owns, a `dyn Future`'s what it is, and + // another trait object's what it wraps. 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..489d8e7b1 --- /dev/null +++ b/ts/src/lower/dynamic.rs @@ -0,0 +1,345 @@ +//! 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, GenericArgsRef, Instance, Interner as _, Ty, TyCtxt}; +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 = 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); + 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) + } + + /// 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, and a `dyn Future` an `rt.Future`. + pub fn dispatches(&self, instance: Instance<'tcx>) -> bool { + let ty::InstanceKind::Virtual(..) = instance.def else { + return false; + }; + let object = instance.args.type_at(0); + let ty::Dynamic(preds, ..) = object.kind() else { + return false; + }; + self.dyn_future(object).is_none() + && 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 { + 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 = 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()) { + 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, + } + } +} + +/// 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. + 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); + }; + // 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(); + } + // 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 bb12772cc..64582314f 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -212,7 +212,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let value = self.operand(op, span, out); self.b.dyn_future(from, to, value, span) } - 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 0a9953372..6fb9e1e17 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. @@ -725,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, } } @@ -745,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, } } @@ -975,8 +984,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::Dynamic(..) if self.dyn_future(ty).is_some() => Some(ObjectKind::Class), + ty::Coroutine(..) | ty::Dynamic(..) => Some(ObjectKind::Class), _ if self.deferred(ty) => Some(ObjectKind::Class), _ => None, } @@ -1081,6 +1089,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}`")); diff --git a/ts/tests/task.ts b/ts/tests/task.ts index 50edc9308..03b0351a8 100644 --- a/ts/tests/task.ts +++ b/ts/tests/task.ts @@ -50,6 +50,7 @@ export interface Fixture { run(log: Log, id: number, arg: number): void; held(rx: Consumer, guard: Guard): Future; edited(log: Log, id: number, at: number): void; + dyns(log: Log, id: number, at: number): void; }; futures: { helpers(rx: Consumer, guard: Guard): Future; @@ -285,6 +286,7 @@ export const steps: [string, (r: Run, arg: number) => void][] = [ if (c !== undefined) r.exec.spawn(r.f.futures.boxed(r.f.Channel.consumer(r.channels[c]), guard(r))); }, ], + ["dyns", (r, arg) => r.f.drops.dyns(r.log, 1000 * r.exec.tasks.length, arg % 6)], ]; /** @@ -369,4 +371,6 @@ export const cases: Uint8Array[] = [ ...Array.from({ length: 253 }, (_, arg) => script(["drops", arg])), // Each of a `Vec`'s edits, its and a tuple's `clone`, and `Ord`'s defaults, panicking at each point or none. ...Array.from({ length: 8 }, (_, at) => script(["edited", at])), + // What boxed trait objects drop, panicking at each point, in a `Drop`, or none. + ...Array.from({ length: 6 }, (_, at) => script(["dyns", at])), ];