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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ generic std gap is planned here even when moq never calls it.
- [Unsafe code the translator can model](/quest/m3/ts-unsafe.md) - a dependency using only `unsafe` the translator can model translates from source
- [The capacity of a BytesMut](/quest/m3/ts-bytes-capacity.md) - `BytesMut::capacity` and what reads it translate with `bytes`' values
- [Trait objects](/quest/m3/ts-dyn.md) - `dyn Error` and `dyn Iterator` translate, so a consumer neither rewrites nor binds them
- [A trait object in a struct's last field](/quest/m3/ts-dyn-tail.md) - `Rc<RefCell<dyn Trait>>` and the like translate, the last field as the object
- [bytes' Chain as a Buf object](/quest/m3/ts-buf-chain.md) - `Chain::copy_to_bytes` across both halves translates, and with it a `Chain` as a `dyn Buf`
- [A Buf object's size](/quest/m3/ts-dyn-buf-size.md) - a `dyn Buf` object holds only the methods some call dispatches to
- [by_ref of a mutable reference to an iterator](/quest/m3/ts-by-ref-place.md) - `Iterator::by_ref` of a `&mut I` in a local translates
Expand Down
19 changes: 19 additions & 0 deletions quest/m3/ts-dyn-tail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# [M] A trait object in a struct's last field

## Goal

[rust2-ts](/ts/README.md) translates a pointer to a struct whose last field unsizes to a
trait object, such as `Rc<RefCell<dyn Trait>>`, `Arc<Mutex<dyn Trait>>`, or the crate's
own `&Tail<dyn Trait>`, with Rust's dispatch, borrow checks, and drop order, proven by a
differential test against native Rust.

## Plan

Found writing the `Rc`, `Weak`, and `Pin` trait objects on 2026-11-02: these fail as "a
coercion to `Rc<RefCell<dyn Shape>>`, which is not supported yet", and the reject fixture
keeps `tail_read`. `Rc<RefCell<dyn Trait>>` is the common shared, mutable object in Rust.

One shape: the struct's other fields stay as they are, and the last one is the object a box of
its value would be, so a `RefCell`'s and a `Mutex`'s guards lend the object.

Public API: the translator's output.
21 changes: 13 additions & 8 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ crate translation reaches is refused, and so are two versions of one crate it re
| `Layout`, `alloc::alloc` | `rt.Layout`: `{ size, align }`, never changed once made, checked as std's constructors check it; `alloc` gives an `rt.Block` of its bytes, which a cast of its pointer to `*mut T` the body shows came from `alloc` or `realloc` makes a JS array of `T`s, values JS copies, as an `rt.AtIndex` of its first; a cast back to `*mut u8` only as what `dealloc` or `realloc` takes, which free the array or copy it into a new one; its bytes, read, written, or cast to any other type, are refused, and fail at run time through a pointer from elsewhere |
| closure | an arrow function, with `[Symbol.dispose]` if it owns a value that drops, and what makes its clone if a clone can't be itself |
| `fn` pointer, function item as a value | the function, as a closure is typed: a `()` result is `undefined`, where a declared function returns `void` |
| `&dyn Trait`, `&mut dyn Trait`, `Box<dyn Trait>`, `Arc<dyn Trait>` | an object with the trait's methods, its supertraits' included: `shape.area$Shape()`; behind an `Arc`, an `rt.ArcDyn` sharing the count of the `Arc` it was made from |
| `&dyn Trait`, `&mut dyn Trait`, `Box<dyn Trait>`, `Arc<dyn Trait>`, `Rc<dyn Trait>`, `Weak<dyn Trait>`, `Pin<P>` of one | an object with the trait's methods, its supertraits' included: `shape.area$Shape()`; behind an `Arc` or an `Rc`, an `rt.ArcDyn` sharing the count of the `Arc` it was made from |
| `dyn Any` | an instance of the class its value's type was wrapped in, which a downcast checks for: `a instanceof Any.of$dyns_Square ? a.v : undefined` |
| `dyn Fn`, `dyn FnMut`, `dyn FnOnce` | the closure or function it was made of: `(a0: rt.u8) => rt.u32` |
| constant | its value, inlined as Rust inlines it, so each use of one holding a cell is a cell of its own; one with no value tree, or holding a cell, read from the memory rustc evaluated |
Expand Down Expand Up @@ -450,7 +450,7 @@ as its fold ends, before what it wraps, as std's does. A `Vec`'s `drain` leaves
what precedes its range until it drops, then drops what it still holds and moves the tail
back, so one never dropped, as `mem::forget` leaves it, leaks the rest, as std's does.

A trait object of the crate's own trait, a dependency's, or `bytes`' `Buf`, behind `&`, `&mut`, `Box`, or `Arc`, is an object with
A trait object of the crate's own trait, a dependency's, or `bytes`' `Buf`, behind `&`, `&mut`, `Box`, `Arc`, `Rc`, or a `Pin` of one, 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
Expand All @@ -462,14 +462,19 @@ same object. A `Box<dyn Trait>` drops what it holds through its `[Symbol.dispose
a TS caller's object may leave out. An `Arc<T>` unsized to an `Arc<dyn Trait>`, or to an
`Arc<dyn Future>`, is an `rt.ArcDyn`: the object a box of its value would be, and the
`Arc<T>` it was made from, whose one count both sides' clones and drops change, and the
last of which drops the value. `Arc::ptr_eq` compares the `Arc<T>`s. A `dyn Buf`'s object
holds no `chunks_vectored`, whose `IoSlice` has no JS value, so a call of it fails.
last of which drops the value. `Arc::ptr_eq` compares the `Arc<T>`s. An `Rc` is an `Arc`,
and a `Pin` the pointer it holds. A `Weak<T>` unsized to a `Weak<dyn Trait>` keeps the
`Arc<T>` and makes the `rt.ArcDyn` as it upgrades, since the value of `new_cyclic`'s
`Weak` is not there yet as it unsizes. `Arc::from` or `Rc::from` of a box is an `rt.Arc`
of what the box is, which drops it with the last clone. A `dyn Buf`'s object holds no
`chunks_vectored`, whose `IoSlice` has no JS value, so a call of it fails.

A `dyn Any` is such an object with no methods, whose class `is`, `downcast_ref`,
`downcast_mut`, and `Box`'s and `Arc`'s `downcast` check for: `a instanceof Any.of$dyns_Square ?
`downcast_mut`, and `Box`'s, `Arc`'s, and `Rc`'s `downcast` check for: `a instanceof Any.of$dyns_Square ?
a.v : undefined`. A `&mut` one of a value JS copies is the class holding its `rt.Place`,
or the class itself behind a `Box`, as a `&mut dyn Trait`'s is, and `Arc::downcast` gives
back the `Arc<T>` the object was made from.
back the `Arc<T>` the object was made from, or, of one made from a box, an `rt.ArcDyn` of
the value sharing its count.

A `dyn Fn`, `dyn FnMut`, or `dyn FnOnce` is the closure or function it was made of, which
a call calls as it does the closure, and which a box drops as it does the closure. One
Expand All @@ -480,7 +485,7 @@ or a mapped crate's trait other than `dyn Debug` and `dyn Display` behind `&`, `
`Box`, `Arc`, or `Rc`, `dyn Future`,
`dyn Any`, `dyn Buf`, and the function traits, of a generic
trait or one with an associated type, of a byte array, or behind any other pointer, such
as a `Weak` or a struct's last field, is refused, and so are an upcast to `dyn Any` and
as a struct's last field, is refused, and so are an upcast to `dyn Any` and
`Any::type_id`.

An `async fn` returns a future without running anything: an instance of a class of its
Expand Down Expand Up @@ -858,7 +863,7 @@ they need.
Byte reinterpretation, pointer addresses, pointer arithmetic on a value JS keeps as an object (other than wrapping arithmetic on a null or dangling pointer), memory from `alloc` other than an array of values JS copies, and unions whose
fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than
`dyn Debug`, `dyn Display`, `dyn Future`, `dyn Any`, `dyn Fn*`, `dyn Buf`, and the translated crates' traits'
behind `&`, `&mut`, `Box`, or `Arc` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `dyn Error`, binds to
behind `&`, `&mut`, `Box`, `Arc`, `Rc`, `Weak`, or `Pin` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `dyn Error`, binds to
hand-written TS ([bindings](#bindings)).

These fail translation: `f16` and `f128`, a `Poll` of a `Poll` of a `Poll`, a future whose output can be pending, an instance of a generic type whose field is a `Poll` of a type parameter that can be pending, a closure that owns a value
Expand Down
13 changes: 13 additions & 0 deletions ts/fixtures/lang/expected/core/any.Any.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Generated by rust2-ts. Do not edit.

import * as rt from "../rt.ts";
import type * as dyns_Noisy from "../dyns.Noisy.ts";
import type * as Square from "../dyns.Square.ts";
import type * as Tri from "../dyns.Tri.ts";
import type * as fmt_Fault from "../fmt.Fault.ts";
Expand All @@ -15,6 +16,18 @@ export class of$Option_u8 {
}
}

export class of$dyns_Noisy {
v: dyns_Noisy.Noisy;

constructor(v: dyns_Noisy.Noisy) {
this.v = v;
}

[Symbol.dispose](): void {
this.v[Symbol.dispose]();
}
}

export class of$dyns_Square {
v: Square.Square;

Expand Down
9 changes: 8 additions & 1 deletion ts/fixtures/lang/expected/core/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ export function map$1j8xzu6(self: rt.u8 | undefined | rt.None, f: (a0: rt.u8 | u
return undefined;
}

export function map$1i542n2(self: rt.Arc<dyns_Shape.Shape> | undefined, f: (a0: rt.Arc<dyns_Shape.Shape>) => rt.u32): rt.u32 | undefined {
if (self === undefined) {
return undefined;
}
return f(self);
}

export function map$1mp7tga(self: rt.i64[] | undefined, f: (a0: rt.i64[]) => rt.usize): rt.usize | undefined {
if (self === undefined) {
return undefined;
Expand Down Expand Up @@ -715,7 +722,7 @@ export function map$155qrdr(self: rt.Arc<defaults_Made.Made> | undefined, f: (a0
return f(self);
}

export function map$1i542n2(self: rt.Arc<dyns_Shape.Shape> | undefined, f: (a0: rt.Arc<dyns_Shape.Shape>) => rt.u32): rt.u32 | undefined {
export function map$0j05j69(self: rt.Arc<rt.u32> | undefined, f: (a0: rt.Arc<rt.u32>) => rt.u32): rt.u32 | undefined {
if (self === undefined) {
return undefined;
}
Expand Down
16 changes: 15 additions & 1 deletion ts/fixtures/lang/expected/core/result.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Generated by rust2-ts. Do not edit.

import * as rt from "../rt.ts";
import { drop$Result_sync_MutexGuard_locks_Tally_sync_TryLockError_sync_MutexGuard_locks_Tally, drop$Result_sync_RwLockReadGuard_locks_Tally_sync_TryLockError_sync_RwLockReadGuard_locks_Tally, drop$Result_tuple2_usize_u8_sync_Arc_dyn_Any_marker_Sync_marker_Send } from "../lib.ts";
import { drop$Result_sync_Arc_u32_sync_Arc_dyn_Any_marker_Sync_marker_Send, drop$Result_sync_MutexGuard_locks_Tally_sync_TryLockError_sync_MutexGuard_locks_Tally, drop$Result_sync_RwLockReadGuard_locks_Tally_sync_TryLockError_sync_RwLockReadGuard_locks_Tally, drop$Result_tuple2_usize_u8_sync_Arc_dyn_Any_marker_Sync_marker_Send } from "../lib.ts";
import { eq$09knsz6 as string_eq$09knsz6 } from "../alloc/string.ts";
import * as ByRef from "../collections.ByRef.ts";
import * as ByVal from "../collections.ByVal.ts";
Expand Down Expand Up @@ -447,6 +447,20 @@ export function ok$04yj0hm(self: rt.Result<rt.ByteSlice, rt.usize>): rt.ByteSlic
return undefined;
}

export function ok$00p9ap8(self: rt.Result<rt.Arc<rt.u32>, rt.Arc<Any.Any>>): rt.Arc<rt.u32> | undefined {
let _0!: rt.Arc<rt.u32> | undefined;
if (self.ok) {
_0 = self.value;
} else {
_0 = undefined;
}
if (!self.ok) {
drop$Result_sync_Arc_u32_sync_Arc_dyn_Any_marker_Sync_marker_Send(self);
return _0;
}
return _0;
}

export function ok$0lceojf(self: rt.Result<[rt.usize, rt.u8], rt.Arc<Any.Any>>): [rt.usize, rt.u8] | undefined {
let _0!: [rt.usize, rt.u8] | undefined;
if (self.ok) {
Expand Down
50 changes: 50 additions & 0 deletions ts/fixtures/lang/expected/dyns.Looped.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Generated by rust2-ts. Do not edit.

import * as rt from "./rt.ts";
import type * as dyns_Shape from "./dyns.Shape.ts";

/** Provided, and overridden by one impl. */
export function sides$dyns_Shape(_self: Looped): rt.u8 {
return 4;
}

export function intoArea$dyns_Shape(self: Looped): rt.u32 {
const _0: rt.u32 = rt.u32WrappingAdd(area$dyns_Shape(self), 1);
self[Symbol.dispose]();
return _0;
}

/** Over early-bound lifetimes, which its signature takes as arguments too. */
export function larger$dyns_Shape(self: Looped, other: rt.u32): rt.u32 {
return rt.max(area$dyns_Shape(self), other);
}

/** Writes through a `&mut` to a number, which is an `rt.Place`. */
export function tally$dyns_Shape(self: Looped, sum: rt.Place<rt.u32>): void {
sum.v = rt.u32WrappingAdd(sum.v, area$dyns_Shape(self));
}

/** A shape holding a `Weak` of itself as a trait object, made inside `Rc::new_cyclic`. */
export class Looped {
side: rt.u8;
me: rt.Weak<dyns_Shape.Shape>;

constructor(side: rt.u8, me: rt.Weak<dyns_Shape.Shape>) {
this.side = side;
this.me = me;
}

[Symbol.dispose](): void {
this.me[Symbol.dispose]();
}
}

export function area$dyns_Shape(self: Looped): rt.u32 {
return rt.u32Add(self.side as rt.u32, 1);
}

export function grow$dyns_Shape(self: Looped, by: rt.u8): void {
self.side = rt.u8WrappingAdd(self.side, by);
}

//# sourceMappingURL=dyns.Looped.ts.map
56 changes: 56 additions & 0 deletions ts/fixtures/lang/expected/dyns.Noisy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Generated by rust2-ts. Do not edit.

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

/** Provided, and overridden by one impl. */
export function sides$dyns_Shape(_self: Noisy): rt.u8 {
return 4;
}

export function intoArea$dyns_Shape(self: Noisy): rt.u32 {
const _0: rt.u32 = rt.u32WrappingAdd(area$dyns_Shape(self), 1);
self[Symbol.dispose]();
return _0;
}

/** Over early-bound lifetimes, which its signature takes as arguments too. */
export function larger$dyns_Shape(self: Noisy, other: rt.u32): rt.u32 {
return rt.max(area$dyns_Shape(self), other);
}

/** Writes through a `&mut` to a number, which is an `rt.Place`. */
export function tally$dyns_Shape(self: Noisy, sum: rt.Place<rt.u32>): void {
sum.v = rt.u32WrappingAdd(sum.v, area$dyns_Shape(self));
}

/** A shape that notes its id in a log as it drops. */
export class Noisy {
_0: rt.u8;
_1: rt.Arc<rt.RefCell<rt.ByteVec>>;

constructor(_0: rt.u8, _1: rt.Arc<rt.RefCell<rt.ByteVec>>) {
this._0 = _0;
this._1 = _1;
}

[Symbol.dispose](): void {
drop$ops_Drop(this);
this._1[Symbol.dispose]();
}
}

export function area$dyns_Shape(self: Noisy): rt.u32 {
return self._0 as rt.u32;
}

export function grow$dyns_Shape(self: Noisy, by: rt.u8): void {
self._0 = rt.u8WrappingAdd(self._0, by);
}

export function drop$ops_Drop(self: Noisy): void {
const _5: rt.RefCell<rt.ByteVec> = rt.refCellBorrowMut(self._1.value);
rt.byteVecPush(_5.value, self._0);
rt.refMutRelease(_5);
}

//# sourceMappingURL=dyns.Noisy.ts.map
74 changes: 74 additions & 0 deletions ts/fixtures/lang/expected/dyns.Shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import * as rt from "./rt.ts";
import { sides$dyns_Shape_u32, intoArea$dyns_Shape_u32, larger$dyns_Shape_u32, tally$dyns_Shape_u32 as dyns_tally$dyns_Shape_u32, area$dyns_Shape_u32, grow$dyns_Shape_u32 } from "./dyns.ts";
import * as Hits from "./dyns.Hits.ts";
import * as Looped from "./dyns.Looped.ts";
import * as dyns_Noisy from "./dyns.Noisy.ts";
import * as Square from "./dyns.Square.ts";
import * as Tri from "./dyns.Tri.ts";

Expand Down Expand Up @@ -41,6 +43,78 @@ export class of$dyns_Hits {
}
}

export class of$dyns_Looped {
v: Looped.Looped;

constructor(v: Looped.Looped) {
this.v = v;
}

[Symbol.dispose](): void {
this.v[Symbol.dispose]();
}

area$Shape(): rt.u32 {
return Looped.area$dyns_Shape(this.v);
}

grow$Shape(a: rt.u8): void {
return Looped.grow$dyns_Shape(this.v, a);
}

intoArea$Shape(): rt.u32 {
return Looped.intoArea$dyns_Shape(this.v);
}

larger$Shape(a: rt.u32): rt.u32 {
return Looped.larger$dyns_Shape(this.v, a);
}

sides$Shape(): rt.u8 {
return Looped.sides$dyns_Shape(this.v);
}

tally$Shape(a: rt.Place<rt.u32>): void {
return Looped.tally$dyns_Shape(this.v, a);
}
}

export class of$dyns_Noisy {
v: dyns_Noisy.Noisy;

constructor(v: dyns_Noisy.Noisy) {
this.v = v;
}

[Symbol.dispose](): void {
this.v[Symbol.dispose]();
}

area$Shape(): rt.u32 {
return dyns_Noisy.area$dyns_Shape(this.v);
}

grow$Shape(a: rt.u8): void {
return dyns_Noisy.grow$dyns_Shape(this.v, a);
}

intoArea$Shape(): rt.u32 {
return dyns_Noisy.intoArea$dyns_Shape(this.v);
}

larger$Shape(a: rt.u32): rt.u32 {
return dyns_Noisy.larger$dyns_Shape(this.v, a);
}

sides$Shape(): rt.u8 {
return dyns_Noisy.sides$dyns_Shape(this.v);
}

tally$Shape(a: rt.Place<rt.u32>): void {
return dyns_Noisy.tally$dyns_Shape(this.v, a);
}
}

export class of$dyns_Square {
v: Square.Square;

Expand Down
Loading