From bb5efd42cf2617ffee09ee702dc1643e797484c2 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 02:28:22 -0800 Subject: [PATCH 1/5] chore(quest): claim quest/m3/ts-dyn-tail-rest.md Quest-Claim: dec2c49a9925b67c From 15a83491c6c05d0095f0741ce5d3e23c347ae6bc Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 05:45:34 -0800 Subject: [PATCH 2/5] feat(ts): translate a Cell, UnsafeCell, or ManuallyDrop of a trait object, a dyn Future in a struct's last field, and raw pointers unsized to either Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 - quest/m3/ts-dyn-tail-rest.md | 29 --- ts/README.md | 8 +- ts/fixtures/lang/dyns/expected/dyns.Ticks.ts | 44 ++++ ts/fixtures/lang/dyns/expected/dyns.ts | 238 ++++++++++++++++++- ts/fixtures/lang/dyns/expected/lib.ts | 27 ++- ts/fixtures/lang/dyns/src/dyns.rs | 201 +++++++++++++++- ts/fixtures/lang/dyns/src/lib.rs | 1 + ts/fixtures/reject/census.txt | 3 +- ts/fixtures/reject/expected.txt | 3 +- ts/fixtures/reject/src/objects.rs | 10 +- ts/fixtures/reject/src/values.rs | 6 - ts/fixtures/reject/ts.toml | 3 +- ts/runtime/rt.ts | 5 + ts/src/lower/copy.rs | 7 +- ts/src/lower/dynamic.rs | 77 +++++- ts/src/lower/mir/place.rs | 16 +- ts/src/lower/mir/rvalue.rs | 7 + ts/src/lower/pointer.rs | 40 +++- ts/src/lower/task.rs | 4 +- 20 files changed, 642 insertions(+), 88 deletions(-) delete mode 100644 quest/m3/ts-dyn-tail-rest.md create mode 100644 ts/fixtures/lang/dyns/expected/dyns.Ticks.ts diff --git a/quest/m3/README.md b/quest/m3/README.md index fb78bf64b..3e419f719 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -18,7 +18,6 @@ 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) - std's other traits, generic traits, traits with an associated type, and `Any::type_id` translate, so a consumer neither rewrites nor binds them -- [The rest of a trait object in a struct's last field](/quest/m3/ts-dyn-tail-rest.md) - a `Cell`, `UnsafeCell`, or `ManuallyDrop` of one, a `dyn Future` there, and a raw pointer to one translate - [A Buf object's size](/quest/m3/ts-dyn-buf-size.md) - a `dyn Buf` object holds only the methods some call dispatches to - [Placeholders written to a crate's own fmt::Write](/quest/m3/ts-fmt-calls.md) - `write!` with placeholders into a crate's own writer translates, its `write_str` seeing each call std makes - [Waiting: drop the lang fixture's mul_add allowance](/quest/m3/ts-fma-allowance.md) - the differential fails on any `mul_add` difference again once wasm32's `fma` rounds exactly diff --git a/quest/m3/ts-dyn-tail-rest.md b/quest/m3/ts-dyn-tail-rest.md deleted file mode 100644 index 5429cf42e..000000000 --- a/quest/m3/ts-dyn-tail-rest.md +++ /dev/null @@ -1,29 +0,0 @@ -# [M] The rest of a trait object in a struct's last field - -## Goal - -[rust2-ts](/ts/README.md) translates the pointers to a struct whose last field unsizes to a -trait object that it still refuses: a `Cell`, an `UnsafeCell`, or a `ManuallyDrop` as the -struct, a `dyn Future` in the last field polled through a pin, and a raw pointer unsized to -a trait object or a struct of one. Each keeps Rust's dispatch, aliasing, and drop order, -proven by a differential test against native Rust. - -## Plan - -[#493](https://github.com/moq-dev/rust2/pull/493) translates `Rc>`, -`Arc>`, `Arc>`, and the crate's own structs as an -`rt.tailDyn` view of the struct each was made from, whose last field is the object a -`&mut` to that field would unsize to. It refuses the rest with a span, and the reject -fixture keeps `&Cell`. No consumer reaches these yet; they are std gaps. - -- `Cell`, `UnsafeCell`, and `ManuallyDrop` each have an `rt` value of their own, so a view - of one needs the field it holds its value in, as `RefCell` and `Mutex` name `value`. -- A `dyn Future` in the last field, such as `Mutex + Unpin>` polled - by `Pin::new(&mut *guard)`, would make a new `rt.Future` at each read of the field. Check - that polling it through the view advances the one future, and drops it once. -- A raw pointer unsized to a trait object or a struct of one is refused in `object_of`, - for a plain `*const dyn Trait` as well. [Unsafe code](/quest/m3/ts-unsafe.md) decides - what a raw pointer is; a raw pointer to a view is the view, whose equality `rt.tailSame` - already compares. - -Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index a023991d0..5bd89892a 100644 --- a/ts/README.md +++ b/ts/README.md @@ -123,7 +123,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | closure | an arrow function, with `[Symbol.dispose]` if it owns a value that drops, and what makes its clone if a clone can't be itself | | `fn` pointer, function item as a value | the function, or std's an arrow that calls it as a written call does, as a closure is typed: a `()` result is `undefined`, where a declared function returns `void` | | `&dyn Trait`, `&mut dyn Trait`, `Box`, `Arc`, `Rc`, `Weak`, `Pin

` of one | an object with the trait's methods, its supertraits' included: `shape.area$Shape()`; behind an `Arc` or an `Rc`, an `rt.ArcDyn` sharing the count of the `Arc` it was made from | -| a pointer to a struct whose last field unsizes to a trait object: `Rc>`, `Arc>`, `Arc>`, the crate's own `&Tail` | `rt.tailDyn` of the struct it was made from: the same struct, whose other fields, borrows, and lock are that struct's own, and whose last field is the trait object a `&mut` to that field would be, made where it is read | +| a pointer to a struct whose last field unsizes to a trait object: `Rc>`, `Arc>`, `Arc>`, `&mut Cell`, `Rc>`, `Arc>`, the crate's own `&Tail` | `rt.tailDyn` of the struct it was made from: the same struct, whose other fields, borrows, and lock are that struct's own, and whose last field is the trait object a `&mut` to that field would be, made where it is read; a `dyn Future`'s polls the one future in place and drops it with the struct. A `ManuallyDrop` is the trait object of what it holds, which a box of it never drops | | `dyn Error` | an object of its `source`, `fmt$Debug`, and `fmt$Display`: `e.source$Error()` | | `dyn Any` | an instance of the class its value's type was wrapped in, which a downcast checks for: `a instanceof 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` | @@ -134,7 +134,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | a type's function | a function of the type's module, taking `self` first: `Decoder.slice(dec, 4)` after `import * as Decoder` | | `Arc`, `Rc`, `Weak`, `Mutex` | `rt.Arc` for either, of what a `Box` is for a slice or `str`, with its strong and weak counts, an `Arc<[u8]>` of an `Arc`'s bytes sharing its count, and `new_cyclic`'s `Weak` upgrading only once its value is made, `get_mut` lending its value only while no other `Arc` or `Weak` shares it, `make_mut` cloning it into a new `rt.Arc` while another `Arc` shares it, or moving it there while only `Weak`s do, which then dangle, and `into_inner` giving it only from the last strong `Arc`, after which its `Weak`s no longer upgrade; `rt.Weak`, counted in its `rt.Arc` until it drops, and of no `Arc` once made by `Weak::new`; `rt.Mutex`, its own guard, which a lock gives as `Ok` and the guard's drop unlocks: locking it again while held panics and `try_lock` gives `WouldBlock`, as wasm32's do, and a guard's `map` and `filter_map` make `rt.MutexMap`s, each where the part it lends is, which hold the mutex locked until they drop, and a panic aborts, so nothing poisons it and `thread::panicking` is `false`; a `Mutex`'s, a `RefCell`'s, and an `RwLock`'s `into_inner` gives its value and `get_mut` lends it, or an `rt.Place` of it where JS copies it | | `PoisonError`, `TryLockError` | the `T` it holds; `rt.TryLockError`, tagged as `Bound` is | -| `Cell`, `UnsafeCell`, `RefCell`, `RwLock` | `rt.Cell`, the place holding its value in `v`, for either of the first two: `UnsafeCell::get`'s pointer is the cell where it is an `rt.Place`, and otherwise the object in `v`, refused where the crate may put another cell or object in its place; `rt.RefCell` for either of the others, its own guard, counting its borrows: borrowing it while that conflicts panics as `RefCell`'s does, and locking it so aborts as wasm32's `RwLock` does; `try_borrow` and `try_borrow_mut` fail with `rt.BorrowError` and `rt.BorrowMutError` instead, a clone borrows the cell, `Ref::clone` counts another borrow of it, and a guard's `map`, `filter_map`, `try_map`, and `map_split`, an `RwLock` guard's included, make `rt.RefMap`s, each where a part it lends is, which a write through a mapped `RefMut` reaches in place, sharing the cell's borrow until the last drops; `RwLockWriteGuard::downgrade` keeps the lock as one read, and `Ref::leak` and `RefMut::leak` keep the borrow until `RefCell::undo_leak` ends it; `Cell::update` calls its function on a copy, and `RefCell::replace_with` on the value, borrowed as `borrow_mut` does, or its place where JS copies it; refused for a part of a sized type JS can't write in place, such as a binding's | +| `Cell`, `UnsafeCell`, `RefCell`, `RwLock` | `rt.Cell`, the place holding its value in `v`, for either of the first two: `UnsafeCell::get`'s and `Cell::as_ptr`'s pointer, and `get_mut`'s reference, is the cell where it is an `rt.Place`, and otherwise the object in `v`, refused where the crate may put another cell or object in its place; `rt.RefCell` for either of the others, its own guard, counting its borrows: borrowing it while that conflicts panics as `RefCell`'s does, and locking it so aborts as wasm32's `RwLock` does; `try_borrow` and `try_borrow_mut` fail with `rt.BorrowError` and `rt.BorrowMutError` instead, a clone borrows the cell, `Ref::clone` counts another borrow of it, and a guard's `map`, `filter_map`, `try_map`, and `map_split`, an `RwLock` guard's included, make `rt.RefMap`s, each where a part it lends is, which a write through a mapped `RefMut` reaches in place, sharing the cell's borrow until the last drops; `RwLockWriteGuard::downgrade` keeps the lock as one read, and `Ref::leak` and `RefMut::leak` keep the borrow until `RefCell::undo_leak` ends it; `Cell::update` calls its function on a copy, and `RefCell::replace_with` on the value, borrowed as `borrow_mut` does, or its place where JS copies it; refused for a part of a sized type JS can't write in place, such as a binding's | | `thread_local!`, `LocalKey` | an `rt.LocalKey`, a module's value that each copy of the key is, which makes its value on first use from the `const` or the expression and never drops it, as wasm32's do; `with` calls its function on that value, `try_with` never fails, the `Cell` and `RefCell` helpers lower from std, and a `RefCell` key's `set` and `with_borrow_mut` borrow it as `borrow_mut` does | | `OnceCell`, `OnceLock` | `rt.OnceCell` and `rt.OnceLock`, holding the `Option` in `v`, which is the place `get_mut` lends where JS copies the value; a `OnceLock` initialized from within its own initialization panics, as wasm32's does, and `get_or_try_init`'s error leaves either empty | | `Once`, `OnceState` | `rt.Once`, an `rt.OnceLock` that holds `true` once `call_once` or `call_once_force` ran its function, and panics as wasm32's does if run again from within it; `rt.OnceState`, that `Once`, which a panic never poisons, as it aborts | @@ -509,7 +509,9 @@ and a `Pin` the pointer it holds. A `Weak` unsized to a `Weak` kee of what the box is, which drops it with the last clone. A pointer to a struct whose last field unsizes to a trait object, such as `RefCell`, is an `rt.tailDyn` view of the struct it was made from, behind an `rt.ArcDyn` in an `Arc`, and two such pointers are -equal if their views are of one struct. A `dyn Buf`'s object holds no +equal if their views are of one struct. A raw pointer unsized to a trait object, or to a +struct of one, is what a `&mut` to its pointee unsizes to, or stays null or dangling; +pointers to trait objects don't compare, as each coercion makes an object of its own. 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`, diff --git a/ts/fixtures/lang/dyns/expected/dyns.Ticks.ts b/ts/fixtures/lang/dyns/expected/dyns.Ticks.ts new file mode 100644 index 000000000..47d63a26b --- /dev/null +++ b/ts/fixtures/lang/dyns/expected/dyns.Ticks.ts @@ -0,0 +1,44 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** + * A future of the crate's own, which JS keeps as an object: pending until it has counted + * down, waking its waker each time, and noting its count as it drops. + */ +export class Ticks { + left: rt.u8; + polls: rt.u8; + log: rt.Arc>; + + constructor(left: rt.u8, polls: rt.u8, log: rt.Arc>) { + this.left = left; + this.polls = polls; + this.log = log; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + this.log[Symbol.dispose](); + } +} + +export function poll$Future(self: Ticks, cx: rt.Context): rt.Poll { + const _3: rt.u8 = rt.u8WrappingAdd(self.polls, 1); + self.polls = _3; + const _9: rt.u8 | undefined = rt.u8CheckedSub(self.left, 1); + if (_9 === undefined) { + return self.polls; + } + self.left = _9; + cx.wakeByRef(); + return rt.Pending; +} + +export function drop$ops_Drop(self: Ticks): void { + const _5: rt.RefCell = rt.refCellBorrowMut(self.log.value); + rt.byteVecPush(_5.value, 240 | self.polls); + rt.refMutRelease(_5); +} + +//# sourceMappingURL=dyns.Ticks.ts.map diff --git a/ts/fixtures/lang/dyns/expected/dyns.ts b/ts/fixtures/lang/dyns/expected/dyns.ts index 1aa9af6cf..2d8edc618 100644 --- a/ts/fixtures/lang/dyns/expected/dyns.ts +++ b/ts/fixtures/lang/dyns/expected/dyns.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { drop$Result_Rc_dyns_Noisy_Rc_dyn_Any, drop$Result_Rc_dyns_Square_Rc_dyn_Any, drop$Result_Rc_dyns_Tri_Rc_dyn_Any, drop$Result_cell_RefMut_dyns_Square_cell_BorrowMutError, drop$Result_cell_RefMut_dyns_Tri_cell_BorrowMutError, drop$Result_cell_Ref_dyn_dyns_Shape_cell_BorrowError, drop$Result_sync_MutexGuard_dyn_dyns_Shape_sync_TryLockError_sync_MutexGuard_dyn_dyns_Shape, drop$Result_sync_RwLockWriteGuard_dyn_dyns_Shape_sync_TryLockError_sync_RwLockWriteGuard_dyn_dyns_Shape, drop$boxed_Box_dyns_Guarded_dyn_dyns_Shape, drop$cell_RefCell_dyns_Guarded_dyn_dyns_Shape, drop$cell_RefCell_dyns_Guarded_dyns_Held, drop$dyns_Guarded_dyns_Held, drop$dyns_Guarded_dyns_Noisy, drop$tuple2_dyns_Tagged_dyns_Tagged, of$Option_u8, of$dyns_Hits, of$dyns_Lease, of$dyns_Looped, of$dyns_Noisy, of$dyns_Square, of$dyns_Tri, of$ref_str, of$tuple2_u8_u8, of$u32, of$u8, of$unit, ofPlace$Option_u8, ofPlace$dyns_Held, ofPlace$dyns_Tri, ofPlace$u32, ofPlace$u8, ofPlace$unit } from "./lib.ts"; +import { copy$dyns_Tri, drop$Result_Rc_dyns_Noisy_Rc_dyn_Any, drop$Result_Rc_dyns_Square_Rc_dyn_Any, drop$Result_Rc_dyns_Tri_Rc_dyn_Any, drop$Result_cell_RefMut_dyns_Square_cell_BorrowMutError, drop$Result_cell_RefMut_dyns_Tri_cell_BorrowMutError, drop$Result_cell_Ref_dyn_dyns_Shape_cell_BorrowError, drop$Result_sync_MutexGuard_dyn_dyns_Shape_sync_TryLockError_sync_MutexGuard_dyn_dyns_Shape, drop$Result_sync_RwLockWriteGuard_dyn_dyns_Shape_sync_TryLockError_sync_RwLockWriteGuard_dyn_dyns_Shape, drop$boxed_Box_dyns_Guarded_dyn_dyns_Shape, drop$cell_RefCell_dyns_Guarded_dyn_dyns_Shape, drop$cell_RefCell_dyns_Guarded_dyns_Held, drop$dyns_Guarded_dyns_Held, drop$dyns_Guarded_dyns_Noisy, drop$tuple2_dyns_Tagged_dyns_Tagged, of$Option_u8, of$dyns_Hits, of$dyns_Lease, of$dyns_Looped, of$dyns_Noisy, of$dyns_Square, of$dyns_Tri, of$ref_str, of$tuple2_u8_u8, of$u32, of$u8, of$unit, ofPlace$Option_u8, ofPlace$dyns_Held, ofPlace$dyns_Tri, ofPlace$u32, ofPlace$u8, ofPlace$unit } from "./lib.ts"; import type * as Any from "./core/any.Any.ts"; import { thenSome$0d5am30 } from "./core/bool.ts"; import { isSomeAnd$13v37g1, unwrapOr$1iemzn8 as option_unwrapOr$1iemzn8, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1p9q0qv as option_map$1p9q0qv, map$1i542n2 as option_map$1i542n2, map$0oedb57 as option_map$0oedb57, map$08a6hpg as option_map$08a6hpg, map$1qfllpb as option_map$1qfllpb, map$0cs071d as option_map$0cs071d, map$1qzd1bs as option_map$1qzd1bs, map$0j05j69 as option_map$0j05j69, map$0z7s8q7 as option_map$0z7s8q7, mapOr$1surhce as option_mapOr$1surhce, mapOr$1ttssgs as option_mapOr$1ttssgs, mapOr$06rr3sb as option_mapOr$06rr3sb, andThen$0v0bj8w as option_andThen$0v0bj8w, andThen$0ntiuws as option_andThen$0ntiuws } from "./core/option.ts"; @@ -23,6 +23,7 @@ import * as Shown from "./dyns.Shown.ts"; import * as Square from "./dyns.Square.ts"; import * as Tagged from "./dyns.Tagged.ts"; import * as Tail from "./dyns.Tail.ts"; +import * as Ticks from "./dyns.Ticks.ts"; import * as Tri from "./dyns.Tri.ts"; import { put } from "./harness.ts"; @@ -2011,4 +2012,239 @@ export function tails(data: rt.ByteSlice, out: rt.ByteVec): void { } } +/** Polls `f` until it is ready, at most `max` times: how many it took and its output. */ +export function pollTail(f: rt.Mutex>, max: rt.u8): [rt.u8, rt.u8 | undefined] { + let _8!: rt.u8; + const cx: rt.Context = rt.NOOP_WAKER; + let iterStart: rt.u8 = 0; + const iterEnd: rt.u8 = max; + while ((_8 = iterStart++) < iterEnd) { + const i: rt.u8 = _8; + const _16: rt.Mutex> = rt.mutexLock(f); + const _12: rt.Poll = _16.value.poll(cx); + if (_12 !== rt.Pending) { + const _0: [rt.u8, rt.u8 | undefined] = [rt.u8Add(i, 1), _12]; + rt.mutexUnlock(_16); + return _0; + } + rt.mutexUnlock(_16); + } + return [max, undefined] as [rt.u8, rt.u8 | undefined]; +} + +/** Through a raw pointer to a trait object, as `&mut` to it would. */ +export function growRaw(p: Shape.Shape, by: rt.u8): rt.u32 { + p.grow$Shape(by); + return p.area$Shape(); +} + +export function rests(dataData: Uint8Array, dataStart: number, dataLen: number, out: rt.ByteVec): void { + let ready!: rt.u8 | undefined; + if (dataLen >= 3) { + const _11: [rt.u8, rt.u8, rt.u8] = [dataData[dataStart], dataData[dataStart + 1], dataData[dataStart + 2]]; + const a: rt.u8 = _11[0]; + const b: rt.u8 = _11[1]; + const c: rt.u8 = _11[2]; + const log: rt.Arc> = new rt.Arc(new rt.RefCell(new rt.ByteVec())); + const n: rt.Cell = new rt.Cell(a as rt.u32); + const sq: rt.Cell = new rt.Cell({ side: b as rt.u16 }); + const tri: rt.Cell = new rt.Cell({ tag: "Tall", _0: c } as Tri.Tri); + const iter: rt.Iter<[rt.usize, rt.Cell]> = rt.iterEnumerate(rt.iterSlice([rt.tailDyn(n, { v: (value: rt.Cell) => new ofPlace$u32(value) }), rt.tailDyn(sq, { v: (value2: rt.Cell) => new of$dyns_Square(value2.v) }), rt.tailDyn(tri, { v: (value3: rt.Cell) => new ofPlace$dyns_Tri(value3) })])); + while (true) { + const _36: [rt.usize, rt.Cell] | rt.Done = iter.next(); + if (_36 === rt.DONE) { + break; + } + _36[1].v.grow$Shape(rt.u8WrappingAdd(c, _36[0] & 0xff)); + } + const area: rt.u32 = rt.tailDyn(tri, { v: (value4: rt.Cell) => new ofPlace$dyns_Tri(value4) }).v.area$Shape(); + const _58: rt.u32 = n.v; + const _61: Square.Square = sq.v; + const _64: Tri.Tri = copy$dyns_Tri(tri.v); + const _68: rt.Fmt = (f: rt.Formatter) => rt.u32Display(_58, f); + const arg = _61.side; + const args2: rt.Fmt[] = [_68, (f: rt.Formatter) => rt.u16Display(arg, f), (f: rt.Formatter) => Tri.fmt$fmt_Debug(_64, f), (f: rt.Formatter) => rt.u32Display(area, f)]; + put(out, rt.fmtFormat([args2[0], " ", args2[1], " ", args2[2], " ", args2[3], " "])); + const noisy: rt.Arc> = new rt.Arc(new rt.Cell(new Noisy.Noisy(a, rt.arcClone(log)))); + const _80: rt.Arc> = rt.arcClone(noisy); + const cell: rt.Arc> = new rt.ArcDyn(_80, rt.tailDyn(_80.value, { v: (value5: rt.Cell) => new of$dyns_Noisy(value5.v) }, rt.drop)); + const area2: rt.u32 = growRaw(cell.value.v, b); + noisy[Symbol.dispose](); + const _95: rt.RefCell = rt.refCellBorrow(log.value); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(area2, f), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(_95.value), f, rt.u8Debug)]; + const _91: string = rt.fmtFormat([args4[0], " ", args4[1], " "]); + rt.refRelease(_95); + put(out, _91); + cell[Symbol.dispose](); + const _113: rt.RefCell = rt.refCellBorrow(log.value); + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_113.value), f, rt.u8Debug)]; + const _109: string = rt.fmtFormat([args6[0], " "]); + rt.refRelease(_113); + put(out, _109); + const _123: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecClear(_123.value); + rt.refMutRelease(_123); + const u: rt.Arc> = new rt.Arc(new rt.Cell({ tag: "Flat" } as Tri.Tri)); + const _130: rt.Arc> = rt.arcClone(u); + const shape: rt.Arc> = new rt.ArcDyn(_130, rt.tailDyn(_130.value, { v: (value6: rt.Cell) => new ofPlace$dyns_Tri(value6) })); + shape.value.v.grow$Shape(a); + const area3: rt.u32 = growRaw(shape.value.v, b); + const boxed: rt.Cell = rt.tailDyn(new rt.Cell(new Noisy.Noisy(c, rt.arcClone(log))), { v: (value7: rt.Cell) => new of$dyns_Noisy(value7.v) }, rt.drop); + boxed.v.grow$Shape(a); + const _158: Tri.Tri = copy$dyns_Tri(u.value.v); + const _163: rt.u32 = boxed.v.area$Shape(); + const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(area3, f), (f: rt.Formatter) => Tri.fmt$fmt_Debug(_158, f), (f: rt.Formatter) => rt.u32Display(_163, f)]; + put(out, rt.fmtFormat([args8[0], " ", args8[1], " ", args8[2], " "])); + boxed[Symbol.dispose](); + const _183: rt.RefCell = rt.refCellBorrow(log.value); + const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_183.value), f, rt.u8Debug)]; + const _179: string = rt.fmtFormat([args10[0], " "]); + rt.refRelease(_183); + put(out, _179); + const _193: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecClear(_193.value); + rt.refMutRelease(_193); + const kept: Noisy.Noisy = new Noisy.Noisy(a, rt.arcClone(log)); + let held: Tri.Tri = copy$dyns_Tri({ tag: "Tall", _0: b } as Tri.Tri); + const m: Shape.Shape = new of$dyns_Noisy(kept); + m.grow$Shape(c); + const t: Shape.Shape = new ofPlace$dyns_Tri(rt.place(() => held, (value8) => held = value8)); + t.grow$Shape(a); + const _218: rt.u32 = m.area$Shape(); + const _222: rt.u32 = t.area$Shape(); + const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(_218, f), (f: rt.Formatter) => rt.u32Display(_222, f)]; + put(out, rt.fmtFormat([args12[0], " ", args12[1], " "])); + const _244: rt.u32 = new of$dyns_Noisy(new Noisy.Noisy(b, rt.arcClone(log))).area$Shape(); + const _248: Noisy.Noisy = kept; + const _250: Tri.Tri = held; + const _253: rt.Fmt = (f: rt.Formatter) => rt.u32Display(_244, f); + const arg2 = _248._0; + const args14: rt.Fmt[] = [_253, (f: rt.Formatter) => rt.u8Display(arg2, f), (f: rt.Formatter) => Tri.fmt$fmt_Debug(_250, f)]; + put(out, rt.fmtFormat([args14[0], " ", args14[1], " ", args14[2], " "])); + const _267: rt.RefCell = rt.refCellBorrow(log.value); + const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_267.value), f, rt.u8Debug)]; + const _263: string = rt.fmtFormat([args16[0], " "]); + rt.refRelease(_267); + put(out, _263); + rt.drop(new of$dyns_Noisy(kept)); + const _285: rt.RefCell = rt.refCellBorrow(log.value); + const args18: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_285.value), f, rt.u8Debug)]; + const _281: string = rt.fmtFormat([args18[0], " "]); + rt.refRelease(_285); + put(out, _281); + const _295: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecClear(_295.value); + rt.refMutRelease(_295); + const ticks: rt.Arc> = new rt.Arc(new rt.Mutex(new Ticks.Ticks(rt.u8Rem(a, 4), 0, rt.arcClone(log)))); + const _306: rt.Arc> = rt.arcClone(ticks); + const future: rt.Arc>> = new rt.ArcDyn(_306, rt.tailDyn(_306.value, { value: (value9: rt.Mutex) => new rt.DynFuture(value9.value, Ticks.poll$Future, rt.drop) }, rt.drop)); + const first: [rt.u8, rt.u8 | undefined] = pollTail(future.value, rt.u8Rem(b, 3)); + const _316: rt.Mutex = rt.mutexLock(ticks.value); + const polls: rt.u8 = _316.value.polls; + rt.mutexUnlock(_316); + const rest: [rt.u8, rt.u8 | undefined] = pollTail(future.value, 8); + ticks[Symbol.dispose](); + const _332: rt.RefCell = rt.refCellBorrow(log.value); + const args20: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.u8Debug(first[0], f), (f: rt.Formatter) => rt.fmtOption(first[1], f, rt.u8Debug)], f), (f: rt.Formatter) => rt.u8Display(polls, f), (f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.u8Debug(rest[0], f), (f: rt.Formatter) => rt.fmtOption(rest[1], f, rt.u8Debug)], f), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(_332.value), f, rt.u8Debug)]; + const _328: string = rt.fmtFormat([args20[0], " ", args20[1], " ", args20[2], " ", args20[3], " "]); + rt.refRelease(_332); + put(out, _328); + future[Symbol.dispose](); + const _354: rt.RefCell = rt.refCellBorrow(log.value); + const args22: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_354.value), f, rt.u8Debug)]; + const _350: string = rt.fmtFormat([args22[0], " "]); + rt.refRelease(_354); + put(out, _350); + const _364: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecClear(_364.value); + rt.refMutRelease(_364); + const owned: Noisy.Noisy = new Noisy.Noisy(c, rt.arcClone(log)); + const block: rt.Arc>> = new rt.Arc(new rt.RefCell(new restsFuture$closure0(owned._0))); + const _374: rt.Arc>> = rt.arcClone(block); + const erased: rt.Arc>> = new rt.ArcDyn(_374, rt.tailDyn(_374.value, { value: (value10: rt.RefCell>) => value10.value }, rt.drop)); + const cx: rt.Context = rt.NOOP_WAKER; + const borrowed: rt.RefCell> = rt.refCellBorrowMut(erased.value); + const _382: rt.Poll = borrowed.value.poll(cx); + if (_382 !== rt.Pending) { + ready = _382; + } else { + ready = undefined; + } + rt.refMutRelease(borrowed); + block[Symbol.dispose](); + const _398: rt.RefCell = rt.refCellBorrow(log.value); + const args24: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(ready, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(_398.value), f, rt.u8Debug)]; + const _394: string = rt.fmtFormat([args24[0], " ", args24[1], " "]); + rt.refRelease(_398); + put(out, _394); + erased[Symbol.dispose](); + const _416: rt.RefCell = rt.refCellBorrow(log.value); + const args26: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_416.value), f, rt.u8Debug)]; + const _412: string = rt.fmtFormat([args26[0], " "]); + rt.refRelease(_416); + put(out, _412); + const _426: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecClear(_426.value); + rt.refMutRelease(_426); + const side: Square.Square = { side: a as rt.u16 }; + let num: rt.u32 = b as rt.u32; + const t2: Tail.Tail = new Tail.Tail(c, new Noisy.Noisy(c, rt.arcClone(log)), { tag: "Tall", _0: a } as Tri.Tri); + const _439: [Shape.Shape, Shape.Shape] = [rt.rawUnsize(side, (p2) => new of$dyns_Square(p2)), rt.rawUnsize(rt.place(() => num, (value11) => num = value11), (p3) => new ofPlace$u32(p3))]; + const p: Shape.Shape = _439[0]; + const q: Shape.Shape = _439[1]; + const r: Shape.Shape = rt.rawUnsize(side, (p4) => new of$dyns_Square(p4)); + const tail: Tail.Tail = rt.rawUnsize(t2, (p5) => rt.tailDyn(p5, { value: (value12: Tail.Tail) => new ofPlace$dyns_Tri(rt.place(() => value12.value, (value13) => value12.value = value13)) })); + const areas: [rt.u32, rt.u32, rt.u32] = [growRaw(p, c), growRaw(q, a), r.area$Shape()]; + tail.count = rt.u8WrappingAdd(tail.count, a); + const area4: rt.u32 = growRaw(tail.value, b); + const _471: rt.Fmt = (f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.u32Debug(areas[0], f), (f: rt.Formatter) => rt.u32Debug(areas[1], f), (f: rt.Formatter) => rt.u32Debug(areas[2], f)], f); + const _472: rt.Fmt = (f: rt.Formatter) => rt.u32Display(area4, f); + const arg3 = side.side; + const _473: rt.Fmt = (f: rt.Formatter) => rt.u16Display(arg3, f); + const _474: rt.Fmt = (f: rt.Formatter) => rt.u32Display(num, f); + const arg4 = t2.count; + const _475: rt.Fmt = (f: rt.Formatter) => rt.u8Display(arg4, f); + const arg5 = t2.value; + const args28: rt.Fmt[] = [_471, _472, _473, _474, _475, (f: rt.Formatter) => Tri.fmt$fmt_Debug(arg5, f)]; + put(out, rt.fmtFormat([args28[0], " ", args28[1], " ", args28[2], " ", args28[3], " ", args28[4], " ", args28[5], " "])); + t2[Symbol.dispose](); + const _489: rt.RefCell = rt.refCellBorrow(log.value); + const args30: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_489.value), f, rt.u8Debug)]; + const _485: string = rt.fmtFormat([args30[0], " "]); + rt.refRelease(_489); + put(out, _485); + let range: rt.Range = new rt.Range(rt.u8Rem(a, 8), rt.u8Rem(b, 8)); + const next: rt.u8 | undefined = rt.iterToOption(rt.rawUnsize(rt.place(() => range, (value14) => range = value14), (p6) => rt.iterDyn(rt.iterBorrow(p6.v))).next()); + const shown: rt.Fmt = rt.rawUnsize(side, (p7) => (f: rt.Formatter) => Square.fmt$fmt_Debug(p7, f)); + const _519: boolean = rt.rawUnsize(rt.NULL, (p8) => new of$dyns_Square(p8)) === rt.NULL; + const args32: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(next, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtRange(range, f, rt.u8Debug), (f: rt.Formatter) => shown(f), (f: rt.Formatter) => rt.fmtBool(_519, f)]; + put(out, rt.fmtFormat([args32[0], " ", args32[1], " ", args32[2], " ", args32[3]])); + owned[Symbol.dispose](); + shape[Symbol.dispose](); + u[Symbol.dispose](); + log[Symbol.dispose](); + } +} + +export class restsFuture$closure0 { + #state: number = 0; + #owned0: rt.u8; + + constructor(owned0: rt.u8) { + this.#owned0 = owned0; + } + + [Symbol.dispose](): void { + } + + poll(_2: rt.Context): rt.Poll { + if (this.#state === 0) { + const _0: rt.Poll = this.#owned0; + this.#state = 1; + return _0; + } + rt.asyncFnResumed(); + } +} + //# sourceMappingURL=dyns.ts.map diff --git a/ts/fixtures/lang/dyns/expected/lib.ts b/ts/fixtures/lang/dyns/expected/lib.ts index f85a2bfed..0ae5af5e7 100644 --- a/ts/fixtures/lang/dyns/expected/lib.ts +++ b/ts/fixtures/lang/dyns/expected/lib.ts @@ -2,7 +2,7 @@ import * as rt from "./rt.ts"; import type * as Any from "./core/any.Any.ts"; -import { sides$dyns_Shape_u32, intoArea$dyns_Shape_u32, larger$dyns_Shape_u32, tally$dyns_Shape_u32, area$dyns_Shape_u32, grow$dyns_Shape_u32, dyns, shared, asAny$dyns_Erased_u32, asAnyMut$dyns_Erased_u32, key$dyns_Keyed_u32, anys, fns, iters, pointers, boxes, tails } from "./dyns.ts"; +import { sides$dyns_Shape_u32, intoArea$dyns_Shape_u32, larger$dyns_Shape_u32, tally$dyns_Shape_u32, area$dyns_Shape_u32, grow$dyns_Shape_u32, dyns, shared, asAny$dyns_Erased_u32, asAnyMut$dyns_Erased_u32, key$dyns_Keyed_u32, anys, fns, iters, pointers, boxes, tails, rests } from "./dyns.ts"; import * as Guarded from "./dyns.Guarded.ts"; import * as Held from "./dyns.Held.ts"; import * as Hits from "./dyns.Hits.ts"; @@ -14,6 +14,13 @@ import * as Square from "./dyns.Square.ts"; import type * as Tagged from "./dyns.Tagged.ts"; import * as Tri from "./dyns.Tri.ts"; +export function copy$dyns_Tri(v: Tri.Tri): Tri.Tri { + if (v.tag === "Tall") { + return { tag: "Tall", _0: v._0 }; + } + return v; +} + export function drop$Result_Rc_dyns_Noisy_Rc_dyn_Any(_1: rt.Result, rt.Arc>): void { if (_1.ok) { _1.value[Symbol.dispose](); @@ -600,22 +607,24 @@ export class ofPlace$unit { * their count, so a case added at the end moves no other. */ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] { - return [["dyns::dyns", (_27: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): undefined => { + return [["dyns::dyns", (_210: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): undefined => { dyns(data.data, data.start, data.length, out); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::shared", (_28: rt.u8, data2: rt.ByteSlice, out2: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::shared", (_211: rt.u8, data2: rt.ByteSlice, out2: rt.ByteVec): undefined => { shared(data2.data, data2.start, data2.length, out2); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::anys", (_29: rt.u8, data3: rt.ByteSlice, out3: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::anys", (_212: rt.u8, data3: rt.ByteSlice, out3: rt.ByteVec): undefined => { anys(data3.data, data3.start, data3.length, out3); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::fns", (_210: rt.u8, data4: rt.ByteSlice, out4: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::fns", (_213: rt.u8, data4: rt.ByteSlice, out4: rt.ByteVec): undefined => { fns(data4, out4); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::pointers", (_211: rt.u8, data5: rt.ByteSlice, out5: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::pointers", (_214: rt.u8, data5: rt.ByteSlice, out5: rt.ByteVec): undefined => { pointers(data5.data, data5.start, data5.length, out5); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::iters", (_212: rt.u8, data6: rt.ByteSlice, out6: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::iters", (_215: rt.u8, data6: rt.ByteSlice, out6: rt.ByteVec): undefined => { iters(data6, out6); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::boxes", (_213: rt.u8, data7: rt.ByteSlice, out7: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::boxes", (_216: rt.u8, data7: rt.ByteSlice, out7: rt.ByteVec): undefined => { boxes(data7, out7); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::tails", (_214: rt.u8, data8: rt.ByteSlice, out8: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::tails", (_217: rt.u8, data8: rt.ByteSlice, out8: rt.ByteVec): undefined => { tails(data8, out8); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::rests", (_218: rt.u8, data9: rt.ByteSlice, out9: rt.ByteVec): undefined => { + rests(data9.data, data9.start, data9.length, out9); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } diff --git a/ts/fixtures/lang/dyns/src/dyns.rs b/ts/fixtures/lang/dyns/src/dyns.rs index 5ffd9b28e..256ce0035 100644 --- a/ts/fixtures/lang/dyns/src/dyns.rs +++ b/ts/fixtures/lang/dyns/src/dyns.rs @@ -10,18 +10,24 @@ //! `dyn FusedIterator` of std's iterators or the crate's own, borrowed, boxed, or shared, //! reads by its vtable's methods and folds by `next`, dropping what it owns in std's order. //! A box of an iterator reads by what std's box forwards and folds by `next` too. -//! A struct whose last field is a trait object, a `RefCell`, a `Mutex`, an `RwLock`, or the -//! crate's own, shares its fields, borrows, and lock with the value it was made from. +//! A struct whose last field is a trait object, a `RefCell`, a `Mutex`, an `RwLock`, a +//! `Cell`, an `UnsafeCell`, or the crate's own, shares its fields, borrows, and lock with the +//! value it was made from, a future there polled in place and dropped once. A +//! `ManuallyDrop` of one drops only by its own `drop`, and a raw pointer unsized to one, or +//! to a struct of one, reads and writes what it points at, or stays null. use std::any::Any; -use std::cell::RefCell; +use std::cell::{Cell, RefCell, UnsafeCell}; use std::collections::BTreeMap; use std::fmt::{self, Debug}; +use std::future::Future; use std::iter::FusedIterator; +use std::mem::ManuallyDrop; use std::pin::Pin; use std::rc::{self, Rc}; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex, RwLock, Weak}; +use std::task::{Context, Poll, Waker}; use crate::harness::put; @@ -1535,3 +1541,192 @@ pub fn tails(data: &[u8], out: &mut Vec) { _ => put(out, &format!("{}", n.borrow())), } } + +/// A future of the crate's own, which JS keeps as an object: pending until it has counted +/// down, waking its waker each time, and noting its count as it drops. +struct Ticks { + left: u8, + polls: u8, + log: Rc>>, +} + +impl Future for Ticks { + type Output = u8; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.polls = self.polls.wrapping_add(1); + match self.left.checked_sub(1) { + Some(left) => { + self.left = left; + cx.waker().wake_by_ref(); + Poll::Pending + } + None => Poll::Ready(self.polls), + } + } +} + +impl Drop for Ticks { + fn drop(&mut self) { + self.log.borrow_mut().push(0xf0 | self.polls); + } +} + +/// Polls `f` until it is ready, at most `max` times: how many it took and its output. +fn poll_tail(f: &Mutex + Unpin>, max: u8) -> (u8, Option) { + let mut cx = Context::from_waker(Waker::noop()); + for i in 0..max { + if let Poll::Ready(v) = Pin::new(&mut *f.lock().unwrap()).poll(&mut cx) { + return (i + 1, Some(v)); + } + } + (max, None) +} + +/// Through a raw pointer to a trait object, as `&mut` to it would. +unsafe fn grow_raw(p: *mut dyn Shape, by: u8) -> u32 { + unsafe { + (*p).grow(by); + (*p).area() + } +} + +pub fn rests(data: &[u8], out: &mut Vec) { + let [a, b, c, ..] = data else { + return; + }; + let (a, b, c) = (*a, *b, *c); + let log = Rc::new(RefCell::new(Vec::new())); + + // A `Cell`, changed in place through `get_mut` and read through `as_ptr`, of each kind + // of value, and shared, dropping its value with the last `Rc`. + let mut n = Cell::new(u32::from(a)); + let mut sq = Cell::new(Square { side: u16::from(b) }); + let mut tri = Cell::new(Tri::Tall(c)); + { + let cells: [&mut Cell; 3] = [&mut n, &mut sq, &mut tri]; + for (i, cell) in cells.into_iter().enumerate() { + cell.get_mut().grow(c.wrapping_add(i as u8)); + } + } + let shared: &Cell = &tri; + let area = unsafe { (*shared.as_ptr()).area() }; + put( + out, + &format!("{} {} {:?} {area} ", n.get(), sq.get_mut().side, tri.get()), + ); + let noisy = Rc::new(Cell::new(Noisy(a, log.clone()))); + let cell: Rc> = noisy.clone(); + let area = unsafe { grow_raw(cell.as_ptr(), b) }; + drop(noisy); + put(out, &format!("{area} {:?} ", log.borrow())); + drop(cell); + put(out, &format!("{:?} ", log.borrow())); + log.borrow_mut().clear(); + + // An `UnsafeCell`, written through `get` in the value's own cell, and boxed. + let u = Rc::new(UnsafeCell::new(Tri::Flat)); + let shape: Rc> = u.clone(); + unsafe { (*shape.get()).grow(a) }; + let area = unsafe { grow_raw(shape.get(), b) }; + let mut boxed: Box> = Box::new(UnsafeCell::new(Noisy(c, log.clone()))); + boxed.get_mut().grow(a); + put( + out, + &format!("{area} {:?} {} ", unsafe { *u.get() }, unsafe { (*boxed.get()).area() }), + ); + drop(boxed); + put(out, &format!("{:?} ", log.borrow())); + log.borrow_mut().clear(); + + // A `ManuallyDrop`, changed in place, whose box drops nothing and whose `drop` drops + // the value once. + let mut kept = ManuallyDrop::new(Noisy(a, log.clone())); + let mut held = ManuallyDrop::new(Tri::Tall(b)); + { + let m: &mut ManuallyDrop = &mut kept; + m.grow(c); + let t: &mut ManuallyDrop = &mut held; + t.grow(a); + put(out, &format!("{} {} ", m.area(), t.area())); + } + let boxed: Box> = Box::new(ManuallyDrop::new(Noisy(b, log.clone()))); + put(out, &format!("{} {} {:?} ", boxed.area(), kept.0, *held)); + drop(boxed); + put(out, &format!("{:?} ", log.borrow())); + { + let m: &mut ManuallyDrop = &mut kept; + unsafe { ManuallyDrop::drop(m) }; + } + put(out, &format!("{:?} ", log.borrow())); + log.borrow_mut().clear(); + + // A future in a lock, polled through a pin of the guard: each poll advances the one + // future, which drops once, with the last `Arc`. + let ticks = Arc::new(Mutex::new(Ticks { + left: a % 4, + polls: 0, + log: log.clone(), + })); + let future: Arc + Unpin>> = ticks.clone(); + let first = poll_tail(&future, b % 3); + let polls = ticks.lock().unwrap().polls; + let rest = poll_tail(&future, 8); + drop(ticks); + put(out, &format!("{first:?} {polls} {rest:?} {:?} ", log.borrow())); + drop(future); + put(out, &format!("{:?} ", log.borrow())); + log.borrow_mut().clear(); + // An `async` block in a cell, polled through a pin of its borrow, which drops what it + // owns once, with the last `Rc`. + let owned = Noisy(c, log.clone()); + let block = Rc::new(RefCell::new(async move { owned.0 })); + let erased: Rc>> = block.clone(); + let mut cx = Context::from_waker(Waker::noop()); + let ready = { + let mut borrowed = erased.borrow_mut(); + match unsafe { Pin::new_unchecked(&mut *borrowed) }.poll(&mut cx) { + Poll::Ready(v) => Some(v), + Poll::Pending => None, + } + }; + drop(block); + put(out, &format!("{ready:?} {:?} ", log.borrow())); + drop(erased); + put(out, &format!("{:?} ", log.borrow())); + log.borrow_mut().clear(); + + // A raw pointer unsized to a trait object, and to a struct of one, writing the original. + let mut side = Square { side: u16::from(a) }; + let mut num = u32::from(b); + let mut t = Tail { + count: c, + head: Noisy(c, log.clone()), + value: Tri::Tall(a), + }; + let (p, q): (*mut dyn Shape, *mut dyn Shape) = (&raw mut side, &raw mut num); + let r: *const dyn Shape = &raw const side; + let tail: *mut Tail = &raw mut t; + let areas = unsafe { (grow_raw(p, c), grow_raw(q, a), (*r).area()) }; + let area = unsafe { + (*tail).count = (*tail).count.wrapping_add(a); + grow_raw(&raw mut (*tail).value, b) + }; + put( + out, + &format!("{areas:?} {area} {} {num} {} {:?} ", side.side, t.count, t.value), + ); + drop(t); + put(out, &format!("{:?} ", log.borrow())); + // Of std's traits: an iterator read through it, a `Debug` written, and a null pointer, + // which stays null. + let mut range = a % 8..b % 8; + let it: *mut dyn Iterator = &raw mut range; + let next = unsafe { (*it).next() }; + let shown: *const dyn Debug = &raw const side; + let null: *const dyn Shape = std::ptr::null::(); + put( + out, + &format!("{next:?} {range:?} {:?} {}", unsafe { &*shown }, null.is_null()), + ); +} diff --git a/ts/fixtures/lang/dyns/src/lib.rs b/ts/fixtures/lang/dyns/src/lib.rs index 27be8d691..5f63079ae 100644 --- a/ts/fixtures/lang/dyns/src/lib.rs +++ b/ts/fixtures/lang/dyns/src/lib.rs @@ -24,5 +24,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("dyns::iters", |_, data, out| dyns::iters(data, out)), ("dyns::boxes", |_, data, out| dyns::boxes(data, out)), ("dyns::tails", |_, data, out| dyns::tails(data, out)), + ("dyns::rests", |_, data, out| dyns::rests(data, out)), ] } diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index e3e168825..1ee8540f5 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -88,13 +88,13 @@ fixtures/reject/src/keys.rs:set_keys+1:75: hashing `std::collections::BTreeSet::eq` fixtures/reject/src/objects.rs:any_type_id+1:4: `std::any::Any::type_id` of `dyn std::any::Any`, which is not supported yet fixtures/reject/src/objects.rs:any_type_id+1:17: unsupported call to `std::any::TypeId::of::` +fixtures/reject/src/objects.rs:dyn_compared+3:2: comparing pointers to `dyn objects::Reading`, each an object JS makes of its own as it unsizes fixtures/reject/src/objects.rs:dyn_size+1:2: unsupported call to `std::mem::size_of_val::` fixtures/reject/src/objects.rs:dynamic+0:16: `dyn std::hash::Hasher`, a trait object of another crate's trait, which is not supported yet fixtures/reject/src/objects.rs:dynamic+1:4: `dyn std::hash::Hasher`, a trait object of another crate's trait, which is not supported yet fixtures/reject/src/objects.rs:first_of+1:26: a trait object of `[u8; 4]`, which is not supported yet fixtures/reject/src/objects.rs:generic_object+0:23: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet fixtures/reject/src/objects.rs:generic_object+1:4: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet -fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet fixtures/reject/src/parts.rs:array_part+1:9: a raw pointer to `std::cell::UnsafeCell` in `parts::InArray`, which the crate may put another object in place of fixtures/reject/src/parts.rs:array_part_replaced+3:33: a reference through which a raw pointer may be made into `parts::InArray` in `[parts::InArray; 2]`, which the crate may put another object in place of @@ -236,7 +236,6 @@ fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Addr fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` fixtures/reject/src/values.rs:ascii_octal+1:4: unsupported call to `core::num::::is_ascii_octdigit` -fixtures/reject/src/values.rs:cell_unsized+1:2: a coercion to `&std::cell::Cell`, which is not supported yet fixtures/reject/src/values.rs:dangling_enum+1:11: a dangling pointer to `values::Only`, a value of no bytes made of more than structs and tuples fixtures/reject/src/values.rs:float+0:14: unsupported type `f16` fixtures/reject/src/values.rs:float+0:25: unsupported type `f16` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 2e617701e..a3ddf50ab 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -89,13 +89,13 @@ fixtures/reject/src/keys.rs:set_keys+1:75: hashing `std::collections::BTreeSet::eq` fixtures/reject/src/objects.rs:any_type_id+1:4: `std::any::Any::type_id` of `dyn std::any::Any`, which is not supported yet fixtures/reject/src/objects.rs:any_type_id+1:17: unsupported call to `std::any::TypeId::of::` +fixtures/reject/src/objects.rs:dyn_compared+3:2: comparing pointers to `dyn objects::Reading`, each an object JS makes of its own as it unsizes fixtures/reject/src/objects.rs:dyn_size+1:2: unsupported call to `std::mem::size_of_val::` fixtures/reject/src/objects.rs:dynamic+0:16: `dyn std::hash::Hasher`, a trait object of another crate's trait, which is not supported yet fixtures/reject/src/objects.rs:dynamic+1:4: `dyn std::hash::Hasher`, a trait object of another crate's trait, which is not supported yet fixtures/reject/src/objects.rs:first_of+1:26: a trait object of `[u8; 4]`, which is not supported yet fixtures/reject/src/objects.rs:generic_object+0:23: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet fixtures/reject/src/objects.rs:generic_object+1:4: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet -fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet fixtures/reject/src/parts.rs:array_part+1:9: a raw pointer to `std::cell::UnsafeCell` in `parts::InArray`, which the crate may put another object in place of fixtures/reject/src/parts.rs:array_part_replaced+3:33: a reference through which a raw pointer may be made into `parts::InArray` in `[parts::InArray; 2]`, which the crate may put another object in place of @@ -236,7 +236,6 @@ fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::< fixtures/reject/src/values.rs:ascii_octal+1:4: unsupported call to `core::num::::is_ascii_octdigit` fixtures/reject/src/values.rs:bits+1:10: union `values::Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart fixtures/reject/src/values.rs:block_cloned+0:21: const generic types are not supported: `values::Block` -fixtures/reject/src/values.rs:cell_unsized+1:2: a coercion to `&std::cell::Cell`, which is not supported yet fixtures/reject/src/values.rs:dangling_enum+1:11: a dangling pointer to `values::Only`, a value of no bytes made of more than structs and tuples fixtures/reject/src/values.rs:float+0:14: unsupported type `f16` fixtures/reject/src/values.rs:float+0:25: unsupported type `f16` diff --git a/ts/fixtures/reject/src/objects.rs b/ts/fixtures/reject/src/objects.rs index c27026685..369147161 100644 --- a/ts/fixtures/reject/src/objects.rs +++ b/ts/fixtures/reject/src/objects.rs @@ -94,9 +94,9 @@ pub fn dyn_size(d: &dyn std::fmt::Debug) -> usize { size_of_val(d) } -/// A raw pointer to an iterator as one to a trait object, whose `rt.Place` no `rt.Iter` is. -pub fn raw_dyn_iter(n: u8) -> Option { - let mut range = 0..n; - let p: *mut dyn Iterator = &raw mut range; - unsafe { (&mut *p).next() } +/// Raw pointers to trait objects compared, each an object made where it unsized. +pub fn dyn_compared(n: &mut u8) -> bool { + let p: *mut dyn Reading = n; + let q: *mut dyn Reading = n; + std::ptr::eq(p, q) } diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 5db1dcb81..c886aee17 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -1,7 +1,6 @@ //! Values JS can't represent as Rust does. use crate::clones::Reused; -use crate::objects::Reading; use crate::places::Bare; pub fn float(a: f16) -> f16 { @@ -28,11 +27,6 @@ pub fn addr_parsed(s: &str) -> Result) -> &std::cell::Cell { - c -} - /// A union whose fields overlap in values JS keeps apart. pub union Bits { pub int: u32, diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index d1cc94a7c..fc38cc089 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -76,11 +76,11 @@ roots = [ "lopsided", "missing", "objects::any_type_id", + "objects::dyn_compared", "objects::dyn_size", "objects::dynamic", "objects::first_of", "objects::generic_object", - "objects::raw_dyn_iter", "objects::same_named", "parts::array_part_replaced", "parts::element_replaced", @@ -177,7 +177,6 @@ roots = [ "values::ascii_octal", "values::bits", "values::block_cloned", - "values::cell_unsized", "values::dangling_enum", "values::float", "values::generic_unit", diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 521998afc..a50df3c12 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -130,6 +130,11 @@ export const NULL: never = null as never; /** A dangling raw pointer, `NonNull::dangling`, of any type: nothing reads through it. */ export const DANGLING: never = /* @__PURE__ */ Object.freeze({}) as never; +/** The raw pointer `p` unsized by `make`, or `p` itself if it is null or dangling, which point at nothing. */ +export function rawUnsize(p: P, make: (p: P) => Q): Q { + return p === NULL || p === DANGLING ? (p as never) : make(p); +} + /** A dangling pointer to `()`, std's every one: a place that holds only `()`. */ export const DANGLING_UNIT: Place = { v: undefined }; diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 270d10895..546ee3443 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -886,8 +886,13 @@ impl<'tcx> BodyWrites<'_, '_, '_, 'tcx> { } /// `ty`, and each type it holds by value, as replaced: a write of a whole `ty` gives - /// each part it holds another object, and so each part those hold, at any depth. + /// each part it holds another object, and so each part those hold, at any depth. Nothing + /// writes a whole unsized value, and what a trait object's methods replace counts where + /// it became one. fn replace(&mut self, ty: Ty<'tcx>) { + if !ty.is_sized(self.tcx, ty::TypingEnv::post_analysis(self.tcx, self.def_id)) { + return; + } for ty in self.parts(ty) { self.writes.replaced.insert(self.tcx, self.def_id, ty); } diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 279bcec5d..3d9818f5c 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -7,7 +7,7 @@ //! drops the value. An `Arc` is an `rt.ArcDyn` around the one a box would be. A //! struct whose last field unsizes to one, such as a `RefCell`, is an //! `rt.tailDyn` view of the struct it was made from, whose last field is that field's -//! object. A +//! object. A raw pointer unsizes as a `&mut` to what it points at does. A //! `dyn Any` has no methods, and a downcast, of it or of a `dyn Error`, checks for the //! class of the type it names. A `dyn Fn`, //! `dyn FnMut`, or `dyn FnOnce` is the function it was made of, which a call calls as it @@ -434,6 +434,22 @@ pub fn unsized_object<'tcx>(tcx: TyCtxt<'tcx>, mut ty: Ty<'tcx>) -> bool { ty.is_trait() } +/// What `ty` holds, if it is a `ManuallyDrop`. +fn manually_dropped<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { + match ty.kind() { + ty::Adt(adt, args) if Std::of(tcx, adt.did()) == Some(Std::ManuallyDrop) => Some(args.type_at(0)), + _ => None, + } +} + +/// `ptr`, a reference or box, pointing at `pointee` instead. +fn repoint<'tcx>(tcx: TyCtxt<'tcx>, ptr: Ty<'tcx>, pointee: Ty<'tcx>) -> Ty<'tcx> { + match *ptr.kind() { + ty::Ref(region, _, mutability) => Ty::new_ref(tcx, region, pointee, mutability), + _ => Ty::new_box(tcx, pointee), + } +} + /// The trait of the trait object `ty`. fn principal(ty: Ty<'_>) -> Option { match ty.kind() { @@ -479,15 +495,18 @@ impl<'tcx> Body<'_, '_, 'tcx> { /// `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 tcx = self.t.tcx; 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); }; + // A `ManuallyDrop` is what it holds, which a box of it never drops. + if let (Some(held), Some(object)) = (manually_dropped(tcx, pointee), manually_dropped(tcx, object)) { + let (from, to) = (repoint(tcx, from, held), repoint(tcx, to, object)); + return self.object_of(value, from, to, span); + } if !object.is_trait() { - let view = match from.is_raw_ptr() { - true => None, - false => self.tail_object(value, pointee, object, from.is_box(), span), - }; + let view = self.tail_object(value, pointee, object, from.is_box(), span); return view.unwrap_or_else(|| unsupported(self)); } // A box holds one as a `Uint8Array` and a reference as an `rt.ByteSlice`, which one @@ -550,10 +569,42 @@ impl<'tcx> Body<'_, '_, 'tcx> { } match to.builtin_deref(true) { Some(object) if self.t.dyn_iter(object).is_some() => self.dyn_iter(from, to, value, span), + Some(object) if self.t.dyn_future(object).is_some() => self.dyn_future(from, to, value, span), _ => self.object_of(value, from, to, span), } } + /// `p`, a `from` raw pointer, unsized to `to` as a `&mut` to what it points at is, unless + /// it is null or dangling, which stays itself. + pub fn raw_object(&mut self, p: Expr, from: Ty<'tcx>, to: Ty<'tcx>, span: Span) -> Expr { + let tcx = self.t.tcx; + let (Some(pointee), Some(object)) = (from.builtin_deref(true), to.builtin_deref(true)) else { + return self.err(span, format!("a coercion to `{to}`, which is not supported yet")); + }; + let name = self.fresh("p"); + // An `rt.Place` of a library type, such as an iterator, whose `&mut` is the object. + let lent = match self.t.raw_place(from).is_some() && !self.t.needs_place(pointee) { + true => Expr::ident(&name).member("v"), + false => Expr::ident(&name), + }; + let erased = tcx.lifetimes.re_erased; + let (from, to) = ( + Ty::new_mut_ref(tcx, erased, pointee), + Ty::new_mut_ref(tcx, erased, object), + ); + let mut body = Vec::new(); + let made = self.unsize(lent, from, to, span, &mut body); + let param = Param { name, ty: None }; + let make = match body.is_empty() { + true => Expr::Lambda(vec![param], Box::new(made)), + false => { + body.push(StmtKind::Return(Some(made)).at(None)); + Expr::Arrow(vec![param], self.t.ty(to, span), body) + } + }; + Expr::rt("rawUnsize", vec![p, make]) + } + /// `value`, a `from`, a struct whose last field unsizes, as the struct `to` of the trait /// object: an `rt.tailDyn` view of it, whose last field is the object a `&mut` to that /// field would unsize to, made wherever it is read, so it reads and changes the field in @@ -576,6 +627,11 @@ impl<'tcx> Body<'_, '_, 'tcx> { let place = Expr::rt("valuePlace", vec![v.clone()]); (Key::Name("value".into()), v.clone().member("value"), place, held) } + // `rt.Cell`, a `Cell`'s or an `UnsafeCell`'s, is the place of what it holds. + Some(Std::Cell) => { + let held = (from_args.type_at(0), to_args.type_at(0)); + (Key::Name("v".into()), v.clone().member("v"), v.clone(), held) + } None if adt.is_struct() && self.t.own(adt.did()) => { let last = adt .non_enum_variant() @@ -607,10 +663,6 @@ impl<'tcx> Body<'_, '_, 'tcx> { _ => return None, }; let (field_from, field_to) = held; - // A future polled in place through a struct is not supported yet. - if self.t.dyn_future(field_to).is_some() { - return None; - } let lent = match self.t.needs_place(field_from) { true => lent, false => read, @@ -621,7 +673,12 @@ impl<'tcx> Body<'_, '_, 'tcx> { Ty::new_mut_ref(tcx, erased, field_to), ); let mut body = Vec::new(); - let object = self.unsize(lent, ref_from, ref_to, span, &mut body); + // A future made over the field drops it, as a struct's glue drops its last field, + // and polls it in place. + let object = match self.t.dyn_future(field_to) { + Some(_) => self.future_object(field_from, lent, false, true, span), + None => self.unsize(lent, ref_from, ref_to, span, &mut body), + }; let param = Param { name, ty: Some(self.t.ty(from, span)), diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 8fcf0c666..74dbca0c2 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -2150,9 +2150,23 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { ) -> Option { let tcx = self.tcx; let pointee = tcx.mk_place_deref(reference.place()?); - let cell = library::key(tcx, def_id).is_some_and(|(owner, _)| owner == "UnsafeCell"); + let owner = library::key(tcx, def_id).map(|(owner, _)| owner); + let cell = matches!(owner.as_deref(), Some("UnsafeCell" | "Cell")); let held = gargs.type_at(0); let place = match cell && self.b.t.pointee(held) != crate::lower::pointer::Pointee::Place { + // A `Cell` holds its value in its `UnsafeCell`. + true if owner.as_deref() == Some("Cell") + && let ty::Adt(adt, args) = pointee.ty(&self.mir.local_decls, tcx).ty.kind() => + { + let unsafe_cell = adt.non_enum_variant().fields[FieldIdx::ZERO] + .ty(tcx, args) + .skip_norm_wip(); + tcx.mk_place_field( + tcx.mk_place_field(pointee, FieldIdx::ZERO, unsafe_cell), + FieldIdx::ZERO, + held, + ) + } true => tcx.mk_place_field(pointee, FieldIdx::ZERO, held), false => pointee, }; diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 984761b5a..8a9d98f19 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -359,6 +359,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { num::cast(self.b, operand, from, to, span) } CastKind::PointerCoercion(coercion, _) => match coercion { + // A raw pointer, as the `&mut` it is in JS unsizes. + PointerCoercion::Unsize + if from.is_raw_ptr() && to.builtin_deref(true).is_some_and(|t| dynamic::unsized_object(tcx, t)) => + { + let value = self.operand(op, span, out); + self.b.raw_object(value, from, to, span) + } PointerCoercion::Unsize if let Some(tr) = self.b.fmt_object(to) => { let value = self.operand(op, span, out); let value = self.path(value, "arg", out); diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index fae62f13a..8aaf8ddb1 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -74,6 +74,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some(std) => matches!(std, Std::Context | Std::WakerVTable), None => self.bound_drops(adt.did()).is_none(), }, + // A `&mut` to one is the object too. + (Some(ObjectKind::Class), ty::Dynamic(..)) => true, _ => false, }; match object { @@ -109,6 +111,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { "comparing pointers to `{ty}`, which a slice lends as a copy of its own" )); } + if ty.is_trait() { + return Some(format!( + "comparing pointers to `{ty}`, each an object JS makes of its own as it unsizes" + )); + } match self.pointee(ty) { Pointee::Object | Pointee::Bytes => { self.compare(ty); @@ -267,7 +274,10 @@ pub fn from_reference<'tcx>( return false; }; match (owner.as_str(), name.as_str()) { - ("UnsafeCell", "get") | ("MaybeUninit", "as_ptr" | "as_mut_ptr") | ("ptr", "from_ref" | "from_mut") => true, + ("UnsafeCell", "get") + | ("Cell", "as_ptr") + | ("MaybeUninit", "as_ptr" | "as_mut_ptr") + | ("ptr", "from_ref" | "from_mut") => true, ("From", "from") | ("Into", "into") => { let (to, from) = match owner.as_str() { "From" => (args.type_at(0), args.type_at(1)), @@ -312,6 +322,7 @@ pub fn derives(tcx: ty::TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> Option< | ("MaybeUninit", "new" | "uninit" | "assume_init") | ("ManuallyDrop", "new" | "into_inner") | ("UnsafeCell", "new" | "get" | "raw_get" | "get_mut" | "into_inner") + | ("Cell", "as_ptr" | "get_mut") | ("Box", "new") => Some(Derives::Other), ("ptr", "read" | "read_unaligned" | "read_volatile") | ("NonNull", "read") @@ -369,7 +380,7 @@ pub fn lends(tcx: ty::TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> bool { (owner.as_str(), name.as_str()), ("MaybeUninit", "as_mut_ptr" | "write" | "assume_init_mut" | "assume_init_drop") | ("ManuallyDrop", "drop" | "take") - | ("UnsafeCell", "get_mut") + | ("UnsafeCell" | "Cell", "get_mut") | ("NonNull" | "ptr", "as_mut") | ("Box", "leak") ) @@ -398,6 +409,7 @@ pub fn keeps<'tcx>(tcx: ty::TyCtxt<'tcx>, def_id: rustc_hir::def_id::DefId, args ) | ("Box", "into_raw" | "leak" | "from_raw") | ("ManuallyDrop", "new" | "into_inner" | "drop" | "take") | ("UnsafeCell", "new" | "get" | "get_mut" | "into_inner") + | ("Cell", "as_ptr" | "get_mut") ) || owner == "Atomic" && super::library::is_std(tcx, def_id) ); keeps || from_reference(tcx, def_id, args) @@ -481,10 +493,14 @@ pub fn call<'tcx>( ("ptr", "null" | "null_mut") => Expr::Rt("NULL".into()), // A pointer to `()` is an identity, which the `rt.Place` of a place or a reference // is not. A box of one holds nothing, so its pointer is dangling. - ("MaybeUninit", "as_ptr" | "as_mut_ptr") | ("UnsafeCell", "get") if b.t.identity_pointee(pointee()) => b.err( - span, - "a raw pointer to a place of `()`, which has no address of its own", - ), + ("MaybeUninit", "as_ptr" | "as_mut_ptr") | ("UnsafeCell", "get") | ("Cell", "as_ptr") + if b.t.identity_pointee(pointee()) => + { + b.err( + span, + "a raw pointer to a place of `()`, which has no address of its own", + ) + } ("ptr", "from_ref") if b.t.identity_pointee(pointee()) => b.err( span, "a raw pointer to a place of `()`, which has no address of its own", @@ -706,13 +722,13 @@ pub fn call<'tcx>( }; b.t.snapshot(value, pointee(), span) } - // An `rt.Cell`, which is the place a pointer to a value JS copies is, and holds the - // object a pointer to any other is. + // An `rt.Cell`, a `Cell`'s too, which is the place a pointer to a value JS copies is, + // and holds the object a pointer to any other is. ("UnsafeCell", "new") => Expr::New(Box::new(Expr::Rt("Cell".into())), vec![arg()]), - ("UnsafeCell", "get") if kind(b, pointee()) == Some(Pointee::Place) => arg(), - ("UnsafeCell", "get") if bytes(b, pointee()) => to_pointer(arg().member("v")), - ("UnsafeCell", "get_mut") if b.t.needs_place(pointee()) => arg(), - ("UnsafeCell", "get" | "get_mut" | "into_inner") => arg().member("v"), + ("UnsafeCell", "get") | ("Cell", "as_ptr") if kind(b, pointee()) == Some(Pointee::Place) => arg(), + ("UnsafeCell", "get") | ("Cell", "as_ptr") if bytes(b, pointee()) => to_pointer(arg().member("v")), + ("UnsafeCell" | "Cell", "get_mut") if b.t.needs_place(pointee()) => arg(), + ("UnsafeCell", "get" | "get_mut" | "into_inner") | ("Cell", "as_ptr" | "get_mut") => arg().member("v"), // Pointers into an array: `rt.AtIndex`es, which a `Vec`'s and a slice's give. ("slice" | "Vec", "as_ptr" | "as_mut_ptr") => { let elem = targ(gargs, 0); diff --git a/ts/src/lower/task.rs b/ts/src/lower/task.rs index e99124378..3e5d019e9 100644 --- a/ts/src/lower/task.rs +++ b/ts/src/lower/task.rs @@ -71,7 +71,7 @@ impl<'tcx> Body<'_, '_, 'tcx> { /// `value`, a `from` that is a future, as an `rt.Future`: itself if it is one already, /// and else an `rt.DynFuture` over its `Future` impl, which drops it with itself unless /// `borrowed`. A borrowed one that is `dropped` must not drop what it borrows. - fn future_object(&mut self, from: Ty<'tcx>, value: Expr, borrowed: bool, dropped: bool, span: Span) -> Expr { + pub fn future_object(&mut self, from: Ty<'tcx>, value: Expr, borrowed: bool, dropped: bool, span: Span) -> Expr { let tcx = self.t.tcx; let object = match from.kind() { ty::Dynamic(..) | ty::Coroutine(..) => true, @@ -550,6 +550,8 @@ fn cell<'tcx>( "new" => Expr::New(Box::new(Expr::Rt("Cell".into())), args), "get" => b.t.copied(first(args).member("v"), ty, call.span), "into_inner" => first(args).member("v"), + // As its `UnsafeCell`'s `get` and `get_mut`, the same `rt.Cell`. + "as_ptr" | "get_mut" => return Err(args), "replace" => Expr::rt("cellReplace", args), "update" => Expr::rt("cellUpdate", args), // With what drops the value it replaces. From 3d4b9e77bf023473f3696371a96deea1053e8fba Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 05:54:33 -0800 Subject: [PATCH 3/5] fix(ts): unsize a raw pointer as the box that Box::from_raw makes of it, in the place a raw pointer to a cell is Co-Authored-By: Claude Opus 5.5 --- .../lang/dyns/expected/core/task/poll.ts | 12 ++++ ts/fixtures/lang/dyns/expected/dyns.ts | 58 ++++++++++++++++--- ts/fixtures/lang/dyns/src/dyns.rs | 37 ++++++++++++ ts/src/lower/dynamic.rs | 37 ++++++++---- 4 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 ts/fixtures/lang/dyns/expected/core/task/poll.ts diff --git a/ts/fixtures/lang/dyns/expected/core/task/poll.ts b/ts/fixtures/lang/dyns/expected/core/task/poll.ts new file mode 100644 index 000000000..df9d71c12 --- /dev/null +++ b/ts/fixtures/lang/dyns/expected/core/task/poll.ts @@ -0,0 +1,12 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../../rt.ts"; + +export function isReady$1ntq2i8(self: rt.Poll): boolean { + if (self !== rt.Pending) { + return true; + } + return false; +} + +//# sourceMappingURL=poll.ts.map diff --git a/ts/fixtures/lang/dyns/expected/dyns.ts b/ts/fixtures/lang/dyns/expected/dyns.ts index 2d8edc618..527821601 100644 --- a/ts/fixtures/lang/dyns/expected/dyns.ts +++ b/ts/fixtures/lang/dyns/expected/dyns.ts @@ -6,6 +6,7 @@ import type * as Any from "./core/any.Any.ts"; import { thenSome$0d5am30 } from "./core/bool.ts"; import { isSomeAnd$13v37g1, unwrapOr$1iemzn8 as option_unwrapOr$1iemzn8, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1p9q0qv as option_map$1p9q0qv, map$1i542n2 as option_map$1i542n2, map$0oedb57 as option_map$0oedb57, map$08a6hpg as option_map$08a6hpg, map$1qfllpb as option_map$1qfllpb, map$0cs071d as option_map$0cs071d, map$1qzd1bs as option_map$1qzd1bs, map$0j05j69 as option_map$0j05j69, map$0z7s8q7 as option_map$0z7s8q7, mapOr$1surhce as option_mapOr$1surhce, mapOr$1ttssgs as option_mapOr$1ttssgs, mapOr$06rr3sb as option_mapOr$06rr3sb, andThen$0v0bj8w as option_andThen$0v0bj8w, andThen$0ntiuws as option_andThen$0ntiuws } from "./core/option.ts"; import { ok$00p9ap8, ok$1vbcgd7, ok$0lceojf, ok$1a014tb, map$1v8vvnp as result_map$1v8vvnp, map$1p3mw15 as result_map$1p3mw15, map$1wuk0uc as result_map$1wuk0uc, map$0zujxcv as result_map$0zujxcv, unwrapOr$09hp2h5 as result_unwrapOr$09hp2h5 } from "./core/result.ts"; +import { isReady$1ntq2i8 } from "./core/task/poll.ts"; import type * as Chunks from "./dyns.Chunks.ts"; import * as Countdown from "./dyns.Countdown.ts"; import type * as dyns_Erased from "./dyns.Erased.ts"; @@ -2189,11 +2190,11 @@ export function rests(dataData: Uint8Array, dataStart: number, dataLen: number, const side: Square.Square = { side: a as rt.u16 }; let num: rt.u32 = b as rt.u32; const t2: Tail.Tail = new Tail.Tail(c, new Noisy.Noisy(c, rt.arcClone(log)), { tag: "Tall", _0: a } as Tri.Tri); - const _439: [Shape.Shape, Shape.Shape] = [rt.rawUnsize(side, (p2) => new of$dyns_Square(p2)), rt.rawUnsize(rt.place(() => num, (value11) => num = value11), (p3) => new ofPlace$u32(p3))]; + const _439: [Shape.Shape, Shape.Shape] = [rt.rawUnsize(side, (p3) => new of$dyns_Square(p3)), rt.rawUnsize(rt.place(() => num, (value11) => num = value11), (p4) => new ofPlace$u32(p4))]; const p: Shape.Shape = _439[0]; const q: Shape.Shape = _439[1]; - const r: Shape.Shape = rt.rawUnsize(side, (p4) => new of$dyns_Square(p4)); - const tail: Tail.Tail = rt.rawUnsize(t2, (p5) => rt.tailDyn(p5, { value: (value12: Tail.Tail) => new ofPlace$dyns_Tri(rt.place(() => value12.value, (value13) => value12.value = value13)) })); + const r: Shape.Shape = rt.rawUnsize(side, (p5) => new of$dyns_Square(p5)); + const tail: Tail.Tail = rt.rawUnsize(t2, (p6) => rt.tailDyn(p6, { value: (value12: Tail.Tail) => new ofPlace$dyns_Tri(rt.place(() => value12.value, (value13) => value12.value = value13)) }, rt.drop)); const areas: [rt.u32, rt.u32, rt.u32] = [growRaw(p, c), growRaw(q, a), r.area$Shape()]; tail.count = rt.u8WrappingAdd(tail.count, a); const area4: rt.u32 = growRaw(tail.value, b); @@ -2213,12 +2214,53 @@ export function rests(dataData: Uint8Array, dataStart: number, dataLen: number, const _485: string = rt.fmtFormat([args30[0], " "]); rt.refRelease(_489); put(out, _485); + let sq2: rt.Cell = new rt.Cell({ side: c as rt.u16 }); + let tri2: rt.RefCell = new rt.RefCell({ tag: "Tall", _0: a } as Tri.Tri); + const p2: rt.Place> = rt.rawUnsize(rt.place(() => sq2, (value14) => sq2 = value14), (p7) => ({ v: rt.tailDyn(p7.v, { v: (value15: rt.Cell) => new of$dyns_Square(value15.v) }) })); + const q2: rt.Place> = rt.rawUnsize(rt.place(() => tri2, (value16) => tri2 = value16), (p8) => ({ v: rt.tailDyn(p8.v, { value: (value17: rt.RefCell) => new ofPlace$dyns_Tri(rt.valuePlace(value17)) }) })); + p2.v.v.grow$Shape(b); + const _512: rt.RefCell = rt.refCellBorrowMut(q2.v); + _512.value.grow$Shape(c); + rt.refMutRelease(_512); + const _514: rt.u32 = p2.v.v.area$Shape(); + const _521: rt.RefCell = rt.refCellBorrow(q2.v); + const areas2: [rt.u32, rt.u32] = [_514, _521.value.area$Shape()]; + rt.refRelease(_521); + const _531: Square.Square = sq2.v; + const _534: rt.RefCell = rt.refCellBorrow(tri2); + const _538: rt.Fmt = (f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.u32Debug(areas2[0], f), (f: rt.Formatter) => rt.u32Debug(areas2[1], f)], f); + const arg6 = _531.side; + const args32: rt.Fmt[] = [_538, (f: rt.Formatter) => rt.u16Display(arg6, f), (f: rt.Formatter) => Tri.fmt$fmt_Debug(_534.value, f)]; + const _527: string = rt.fmtFormat([args32[0], " ", args32[1], " ", args32[2], " "]); + rt.refRelease(_534); + put(out, _527); + const _546: [Ticks.Ticks, Noisy.Noisy, Tail.Tail] = [new Ticks.Ticks(rt.u8Rem(b, 3), 0, rt.arcClone(log)), new Noisy.Noisy(a, rt.arcClone(log)), new Tail.Tail(b, new Noisy.Noisy(b, rt.arcClone(log)), new Noisy.Noisy(c, rt.arcClone(log)))]; + const ticks2: Ticks.Ticks = _546[0]; + const noisy2: Noisy.Noisy = _546[1]; + const tail2: Tail.Tail = _546[2]; + const _571: [rt.Future, Shape.Shape, Tail.Tail] = [rt.rawUnsize(ticks2, (p9) => new rt.DynFuture(p9, Ticks.poll$Future, rt.drop)), rt.rawUnsize(noisy2, (p10) => new of$dyns_Noisy(p10)), rt.rawUnsize(tail2, (p11) => rt.tailDyn(p11, { value: (value18: Tail.Tail) => new of$dyns_Noisy(value18.value) }, rt.drop))]; + const ticks3: rt.Future = _571[0]; + const noisy3: Shape.Shape = _571[1]; + const tail3: Tail.Tail = _571[2]; + const polled: boolean = isReady$1ntq2i8(ticks3.poll(rt.NOOP_WAKER)); + noisy3.grow$Shape(c); + rt.drop(ticks3); + rt.drop(noisy3); + tail3[Symbol.dispose](); + const _599: rt.RefCell = rt.refCellBorrow(log.value); + const args34: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtBool(polled, f), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(_599.value), f, rt.u8Debug)]; + const _595: string = rt.fmtFormat([args34[0], " ", args34[1], " "]); + rt.refRelease(_599); + put(out, _595); + const _611: rt.RefCell = rt.refCellBorrowMut(log.value); + rt.byteVecClear(_611.value); + rt.refMutRelease(_611); let range: rt.Range = new rt.Range(rt.u8Rem(a, 8), rt.u8Rem(b, 8)); - const next: rt.u8 | undefined = rt.iterToOption(rt.rawUnsize(rt.place(() => range, (value14) => range = value14), (p6) => rt.iterDyn(rt.iterBorrow(p6.v))).next()); - const shown: rt.Fmt = rt.rawUnsize(side, (p7) => (f: rt.Formatter) => Square.fmt$fmt_Debug(p7, f)); - const _519: boolean = rt.rawUnsize(rt.NULL, (p8) => new of$dyns_Square(p8)) === rt.NULL; - const args32: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(next, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtRange(range, f, rt.u8Debug), (f: rt.Formatter) => shown(f), (f: rt.Formatter) => rt.fmtBool(_519, f)]; - put(out, rt.fmtFormat([args32[0], " ", args32[1], " ", args32[2], " ", args32[3]])); + const next: rt.u8 | undefined = rt.iterToOption(rt.rawUnsize(rt.place(() => range, (value20) => range = value20), (p12) => rt.iterDyn(p12.v)).next()); + const shown: rt.Fmt = rt.rawUnsize(side, (p13) => rt.fmtOwned(p13, (value: Square.Square, f: rt.Formatter) => Square.fmt$fmt_Debug(value, f))); + const _637: boolean = rt.rawUnsize(rt.NULL, (p14) => new of$dyns_Square(p14)) === rt.NULL; + const args36: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(next, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtRange(range, f, rt.u8Debug), (f: rt.Formatter) => shown(f), (f: rt.Formatter) => rt.fmtBool(_637, f)]; + put(out, rt.fmtFormat([args36[0], " ", args36[1], " ", args36[2], " ", args36[3]])); owned[Symbol.dispose](); shape[Symbol.dispose](); u[Symbol.dispose](); diff --git a/ts/fixtures/lang/dyns/src/dyns.rs b/ts/fixtures/lang/dyns/src/dyns.rs index 256ce0035..95b2a8814 100644 --- a/ts/fixtures/lang/dyns/src/dyns.rs +++ b/ts/fixtures/lang/dyns/src/dyns.rs @@ -1718,6 +1718,43 @@ pub fn rests(data: &[u8], out: &mut Vec) { ); drop(t); put(out, &format!("{:?} ", log.borrow())); + // To a cell of one, read and written in place. + let mut sq = Cell::new(Square { side: u16::from(c) }); + let mut tri = RefCell::new(Tri::Tall(a)); + let p: *mut Cell = &raw mut sq; + let q: *mut RefCell = &raw mut tri; + let areas = unsafe { + (*p).get_mut().grow(b); + (*q).borrow_mut().grow(c); + ((*p).get_mut().area(), (*q).borrow().area()) + }; + put(out, &format!("{areas:?} {} {:?} ", sq.get_mut().side, tri.borrow())); + // Owned again by `Box::from_raw`, which drops what it points at once. + let (ticks, noisy, tail) = ( + Box::into_raw(Box::new(Ticks { + left: b % 3, + polls: 0, + log: log.clone(), + })), + Box::into_raw(Box::new(Noisy(a, log.clone()))), + Box::into_raw(Box::new(Tail { + count: b, + head: Noisy(b, log.clone()), + value: Noisy(c, log.clone()), + })), + ); + let (ticks, noisy, tail): (*mut dyn Future, *mut dyn Shape, *mut Tail) = + (ticks, noisy, tail); + let mut cx = Context::from_waker(Waker::noop()); + let polled = unsafe { Pin::new_unchecked(&mut *ticks) }.poll(&mut cx).is_ready(); + unsafe { + (*noisy).grow(c); + drop(Box::from_raw(ticks)); + drop(Box::from_raw(noisy)); + drop(Box::from_raw(tail)); + } + put(out, &format!("{polled} {:?} ", log.borrow())); + log.borrow_mut().clear(); // Of std's traits: an iterator read through it, a `Debug` written, and a null pointer, // which stays null. let mut range = a % 8..b % 8; diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 3d9818f5c..8fb602770 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -574,26 +574,39 @@ impl<'tcx> Body<'_, '_, 'tcx> { } } - /// `p`, a `from` raw pointer, unsized to `to` as a `&mut` to what it points at is, unless - /// it is null or dangling, which stays itself. + /// `p`, a `from` raw pointer, unsized to `to`, unless it is null or dangling, which stays + /// itself: what a box of the object it points at unsizes to, which drops it as the box + /// `Box::from_raw` makes of it does, or else what a `&mut` to its place unsizes to, in the + /// `rt.Place` a raw pointer to `to`'s pointee is, if it is one. pub fn raw_object(&mut self, p: Expr, from: Ty<'tcx>, to: Ty<'tcx>, span: Span) -> Expr { let tcx = self.t.tcx; let (Some(pointee), Some(object)) = (from.builtin_deref(true), to.builtin_deref(true)) else { return self.err(span, format!("a coercion to `{to}`, which is not supported yet")); }; let name = self.fresh("p"); - // An `rt.Place` of a library type, such as an iterator, whose `&mut` is the object. - let lent = match self.t.raw_place(from).is_some() && !self.t.needs_place(pointee) { - true => Expr::ident(&name).member("v"), - false => Expr::ident(&name), - }; let erased = tcx.lifetimes.re_erased; - let (from, to) = ( - Ty::new_mut_ref(tcx, erased, pointee), - Ty::new_mut_ref(tcx, erased, object), - ); + let (lent, ptr_from, ptr_to) = match self.t.needs_place(pointee) { + true => ( + Expr::ident(&name), + Ty::new_mut_ref(tcx, erased, pointee), + Ty::new_mut_ref(tcx, erased, object), + ), + // An `rt.Place` of a library type, such as an iterator or a cell, holds the object. + false => ( + match self.t.raw_place(from) { + Some(_) => Expr::ident(&name).member("v"), + None => Expr::ident(&name), + }, + Ty::new_box(tcx, pointee), + Ty::new_box(tcx, object), + ), + }; let mut body = Vec::new(); - let made = self.unsize(lent, from, to, span, &mut body); + let made = self.unsize(lent, ptr_from, ptr_to, span, &mut body); + let made = match self.t.raw_place(to).is_some() && !self.t.needs_place(object) { + true => Expr::Object(vec![(Key::Name("v".into()), made)]), + false => made, + }; let param = Param { name, ty: None }; let make = match body.is_empty() { true => Expr::Lambda(vec![param], Box::new(made)), From 462b2fd09c0c775df932d5aebcdcceda760f9a58 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:01:43 -0800 Subject: [PATCH 4/5] fix(ts): read a raw pointer's library value as it is read, not as it unsizes Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 8 +++-- ts/fixtures/lang/dyns/expected/dyns.ts | 22 +++++++++---- ts/fixtures/lang/dyns/src/dyns.rs | 25 +++++++++------ ts/fixtures/reject/census.txt | 1 + ts/fixtures/reject/expected.txt | 1 + ts/fixtures/reject/src/objects.rs | 8 +++++ ts/fixtures/reject/ts.toml | 1 + ts/runtime/rt.ts | 9 ++++++ ts/src/lower/dynamic.rs | 44 ++++++++++++++------------ 9 files changed, 79 insertions(+), 40 deletions(-) diff --git a/ts/README.md b/ts/README.md index 1573b98b1..694b5fd48 100644 --- a/ts/README.md +++ b/ts/README.md @@ -524,8 +524,12 @@ of what the box is, which drops it with the last clone. A pointer to a struct wh field unsizes to a trait object, such as `RefCell`, is an `rt.tailDyn` view of the struct it was made from, behind an `rt.ArcDyn` in an `Arc`, and two such pointers are equal if their views are of one struct. A raw pointer unsized to a trait object, or to a -struct of one, is what a `&mut` to its pointee unsizes to, or stays null or dangling; -pointers to trait objects don't compare, as each coercion makes an object of its own. A `dyn Buf`'s object holds no +struct of one, is what a box of the object it points at unsizes to, which drops it as +`Box::from_raw` of it does, or what a `&mut` to its place does; one to a library value, +such as a cell, unsizes as each read finds the value there, and stays null or dangling. +One whose object would hold such a value as it is made, such as a `*mut dyn Iterator` of +a `*mut Range`, is refused. Pointers to trait objects don't compare, as each coercion +makes an object of its own. 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`, diff --git a/ts/fixtures/lang/dyns/expected/dyns.ts b/ts/fixtures/lang/dyns/expected/dyns.ts index 527821601..365a4c0d1 100644 --- a/ts/fixtures/lang/dyns/expected/dyns.ts +++ b/ts/fixtures/lang/dyns/expected/dyns.ts @@ -2216,8 +2216,8 @@ export function rests(dataData: Uint8Array, dataStart: number, dataLen: number, put(out, _485); let sq2: rt.Cell = new rt.Cell({ side: c as rt.u16 }); let tri2: rt.RefCell = new rt.RefCell({ tag: "Tall", _0: a } as Tri.Tri); - const p2: rt.Place> = rt.rawUnsize(rt.place(() => sq2, (value14) => sq2 = value14), (p7) => ({ v: rt.tailDyn(p7.v, { v: (value15: rt.Cell) => new of$dyns_Square(value15.v) }) })); - const q2: rt.Place> = rt.rawUnsize(rt.place(() => tri2, (value16) => tri2 = value16), (p8) => ({ v: rt.tailDyn(p8.v, { value: (value17: rt.RefCell) => new ofPlace$dyns_Tri(rt.valuePlace(value17)) }) })); + const p2: rt.Place> = rt.rawUnsizePlace(rt.place(() => sq2, (value14) => sq2 = value14), (p7) => rt.tailDyn(p7, { v: (value15: rt.Cell) => new of$dyns_Square(value15.v) })); + const q2: rt.Place> = rt.rawUnsizePlace(rt.place(() => tri2, (value16) => tri2 = value16), (p8) => rt.tailDyn(p8, { value: (value17: rt.RefCell) => new ofPlace$dyns_Tri(rt.valuePlace(value17)) })); p2.v.v.grow$Shape(b); const _512: rt.RefCell = rt.refCellBorrowMut(q2.v); _512.value.grow$Shape(c); @@ -2255,12 +2255,20 @@ export function rests(dataData: Uint8Array, dataStart: number, dataLen: number, const _611: rt.RefCell = rt.refCellBorrowMut(log.value); rt.byteVecClear(_611.value); rt.refMutRelease(_611); - let range: rt.Range = new rt.Range(rt.u8Rem(a, 8), rt.u8Rem(b, 8)); - const next: rt.u8 | undefined = rt.iterToOption(rt.rawUnsize(rt.place(() => range, (value20) => range = value20), (p12) => rt.iterDyn(p12.v)).next()); + let slot: rt.Cell = (undefined as unknown) as rt.Cell; + const thin: rt.Place> = rt.place(() => slot, (value20) => slot = value20); + const wide: rt.Place> = rt.rawUnsizePlace(thin, (p12) => rt.tailDyn(p12, { v: (value21: rt.Cell) => new ofPlace$u32(value21) })); + const null_: boolean = wide === rt.NULL; + thin.v = new rt.Cell(a as rt.u32); + wide.v.v.grow$Shape(b); + const area5: rt.u32 = wide.v.v.area$Shape(); + const _637: rt.u32 = slot.v; + const args36: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtBool(null_, f), (f: rt.Formatter) => rt.u32Display(area5, f), (f: rt.Formatter) => rt.u32Display(_637, f)]; + put(out, rt.fmtFormat([args36[0], " ", args36[1], " ", args36[2], " "])); const shown: rt.Fmt = rt.rawUnsize(side, (p13) => rt.fmtOwned(p13, (value: Square.Square, f: rt.Formatter) => Square.fmt$fmt_Debug(value, f))); - const _637: boolean = rt.rawUnsize(rt.NULL, (p14) => new of$dyns_Square(p14)) === rt.NULL; - const args36: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(next, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtRange(range, f, rt.u8Debug), (f: rt.Formatter) => shown(f), (f: rt.Formatter) => rt.fmtBool(_637, f)]; - put(out, rt.fmtFormat([args36[0], " ", args36[1], " ", args36[2], " ", args36[3]])); + const _663: boolean = rt.rawUnsize(rt.NULL, (p14) => new of$dyns_Square(p14)) === rt.NULL; + const args38: rt.Fmt[] = [(f: rt.Formatter) => shown(f), (f: rt.Formatter) => rt.fmtBool(_663, f)]; + put(out, rt.fmtFormat([args38[0], " ", args38[1]])); owned[Symbol.dispose](); shape[Symbol.dispose](); u[Symbol.dispose](); diff --git a/ts/fixtures/lang/dyns/src/dyns.rs b/ts/fixtures/lang/dyns/src/dyns.rs index 95b2a8814..5c8518094 100644 --- a/ts/fixtures/lang/dyns/src/dyns.rs +++ b/ts/fixtures/lang/dyns/src/dyns.rs @@ -14,7 +14,8 @@ //! `Cell`, an `UnsafeCell`, or the crate's own, shares its fields, borrows, and lock with the //! value it was made from, a future there polled in place and dropped once. A //! `ManuallyDrop` of one drops only by its own `drop`, and a raw pointer unsized to one, or -//! to a struct of one, reads and writes what it points at, or stays null. +//! to a struct of one, reads and writes what it points at, drops it as the box made of it, +//! or stays null. use std::any::Any; use std::cell::{Cell, RefCell, UnsafeCell}; @@ -1755,15 +1756,19 @@ pub fn rests(data: &[u8], out: &mut Vec) { } put(out, &format!("{polled} {:?} ", log.borrow())); log.borrow_mut().clear(); - // Of std's traits: an iterator read through it, a `Debug` written, and a null pointer, - // which stays null. - let mut range = a % 8..b % 8; - let it: *mut dyn Iterator = &raw mut range; - let next = unsafe { (*it).next() }; + // To a cell not there yet, read only once it is. + let mut slot = std::mem::MaybeUninit::>::uninit(); + let thin = slot.as_mut_ptr(); + let wide: *mut Cell = thin; + let null = wide.is_null(); + let area = unsafe { + thin.write(Cell::new(u32::from(a))); + (*wide).get_mut().grow(b); + (*wide).get_mut().area() + }; + put(out, &format!("{null} {area} {} ", unsafe { slot.assume_init() }.get())); + // Of std's traits: a `Debug` written, and a null pointer, which stays null. let shown: *const dyn Debug = &raw const side; let null: *const dyn Shape = std::ptr::null::(); - put( - out, - &format!("{next:?} {range:?} {:?} {}", unsafe { &*shown }, null.is_null()), - ); + put(out, &format!("{:?} {}", unsafe { &*shown }, null.is_null())); } diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index b87dd84d2..34c9b6585 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -95,6 +95,7 @@ fixtures/reject/src/objects.rs:dynamic+1:4: `dyn std::hash::Hasher`, a trait obj fixtures/reject/src/objects.rs:first_of+1:26: a trait object of `[u8; 4]`, which is not supported yet fixtures/reject/src/objects.rs:generic_object+0:23: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet fixtures/reject/src/objects.rs:generic_object+1:4: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet +fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, whose object would hold the value there as it is made, not as it is read, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet fixtures/reject/src/parts.rs:array_part+1:9: a raw pointer to `std::cell::UnsafeCell` in `parts::InArray`, which the crate may put another object in place of fixtures/reject/src/parts.rs:array_part_replaced+3:33: a reference through which a raw pointer may be made into `parts::InArray` in `[parts::InArray; 2]`, which the crate may put another object in place of diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index ae56d66f1..01d563514 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -97,6 +97,7 @@ fixtures/reject/src/objects.rs:dynamic+1:4: `dyn std::hash::Hasher`, a trait obj fixtures/reject/src/objects.rs:first_of+1:26: a trait object of `[u8; 4]`, which is not supported yet fixtures/reject/src/objects.rs:generic_object+0:23: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet fixtures/reject/src/objects.rs:generic_object+1:4: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet +fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, whose object would hold the value there as it is made, not as it is read, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet fixtures/reject/src/parts.rs:array_part+1:9: a raw pointer to `std::cell::UnsafeCell` in `parts::InArray`, which the crate may put another object in place of fixtures/reject/src/parts.rs:array_part_replaced+3:33: a reference through which a raw pointer may be made into `parts::InArray` in `[parts::InArray; 2]`, which the crate may put another object in place of diff --git a/ts/fixtures/reject/src/objects.rs b/ts/fixtures/reject/src/objects.rs index 369147161..08095b004 100644 --- a/ts/fixtures/reject/src/objects.rs +++ b/ts/fixtures/reject/src/objects.rs @@ -94,6 +94,14 @@ pub fn dyn_size(d: &dyn std::fmt::Debug) -> usize { size_of_val(d) } +/// A raw pointer to an iterator as one to a trait object, whose object would hold the +/// `Range` there as it is made, not as it is read. +pub fn raw_dyn_iter(n: u8) -> Option { + let mut range = 0..n; + let p: *mut dyn Iterator = &raw mut range; + unsafe { (&mut *p).next() } +} + /// Raw pointers to trait objects compared, each an object made where it unsized. pub fn dyn_compared(n: &mut u8) -> bool { let p: *mut dyn Reading = n; diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 73a622634..4de6da4fc 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -81,6 +81,7 @@ roots = [ "objects::dynamic", "objects::first_of", "objects::generic_object", + "objects::raw_dyn_iter", "objects::same_named", "parts::array_part_replaced", "parts::element_replaced", diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index 9e6eb50a2..00a53fad5 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -135,6 +135,15 @@ export function rawUnsize(p: P, make: (p: P) => Q): Q { return p === NULL || p === DANGLING ? (p as never) : make(p); } +/** + * `rawUnsize` of `p`, the place of a value, to the place of what `make` makes of whatever + * value is there as it is read, so it may be uninitialized or replaced until then. Nothing + * writes an unsized value whole. + */ +export function rawUnsizePlace(p: Place, make: (value: T) => U): Place { + return rawUnsize(p, (p) => place(() => make(p.v), unreachable)); +} + /** A dangling pointer to `()`, std's every one: a place that holds only `()`. */ export const DANGLING_UNIT: Place = { v: undefined }; diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 8fb602770..3d5e8ab5c 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -576,46 +576,48 @@ impl<'tcx> Body<'_, '_, 'tcx> { /// `p`, a `from` raw pointer, unsized to `to`, unless it is null or dangling, which stays /// itself: what a box of the object it points at unsizes to, which drops it as the box - /// `Box::from_raw` makes of it does, or else what a `&mut` to its place unsizes to, in the - /// `rt.Place` a raw pointer to `to`'s pointee is, if it is one. + /// `Box::from_raw` makes of it does, or else what a `&mut` to its place unsizes to. One + /// that is the `rt.Place` of a library value, such as a cell, unsizes to the place of + /// what a box of the value there unsizes to as each read finds it, since it may be + /// uninitialized or replaced until then. pub fn raw_object(&mut self, p: Expr, from: Ty<'tcx>, to: Ty<'tcx>, span: Span) -> Expr { let tcx = self.t.tcx; let (Some(pointee), Some(object)) = (from.builtin_deref(true), to.builtin_deref(true)) else { return self.err(span, format!("a coercion to `{to}`, which is not supported yet")); }; - let name = self.fresh("p"); let erased = tcx.lifetimes.re_erased; - let (lent, ptr_from, ptr_to) = match self.t.needs_place(pointee) { + let (ptr_from, ptr_to) = match self.t.needs_place(pointee) { true => ( - Expr::ident(&name), Ty::new_mut_ref(tcx, erased, pointee), Ty::new_mut_ref(tcx, erased, object), ), - // An `rt.Place` of a library type, such as an iterator or a cell, holds the object. - false => ( - match self.t.raw_place(from) { - Some(_) => Expr::ident(&name).member("v"), - None => Expr::ident(&name), - }, - Ty::new_box(tcx, pointee), - Ty::new_box(tcx, object), - ), + false => (Ty::new_box(tcx, pointee), Ty::new_box(tcx, object)), }; + let read = !self.t.needs_place(pointee) && self.t.raw_place(from).is_some(); + if read && (self.t.raw_place(to).is_none() || self.t.needs_place(object)) { + return self.err( + span, + format!( + "a coercion of `{from}` to `{to}`, whose object would hold the value there as it is made, not as it is read, which is not supported yet" + ), + ); + } + let name = self.fresh("p"); let mut body = Vec::new(); - let made = self.unsize(lent, ptr_from, ptr_to, span, &mut body); - let made = match self.t.raw_place(to).is_some() && !self.t.needs_place(object) { - true => Expr::Object(vec![(Key::Name("v".into()), made)]), - false => made, - }; + let made = self.unsize(Expr::ident(&name), ptr_from, ptr_to, span, &mut body); let param = Param { name, ty: None }; + let ty = self.t.ty(ptr_to, span); let make = match body.is_empty() { true => Expr::Lambda(vec![param], Box::new(made)), false => { body.push(StmtKind::Return(Some(made)).at(None)); - Expr::Arrow(vec![param], self.t.ty(to, span), body) + Expr::Arrow(vec![param], ty, body) } }; - Expr::rt("rawUnsize", vec![p, make]) + match read { + true => Expr::rt("rawUnsizePlace", vec![p, make]), + false => Expr::rt("rawUnsize", vec![p, make]), + } } /// `value`, a `from`, a struct whose last field unsizes, as the struct `to` of the trait From 7f8bc25b6127f58ab78c40e297a3c5d7e740a871 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:07:18 -0800 Subject: [PATCH 5/5] test(ts): drop an iterator in a cell and in a lock of a trait object once Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/dyns/expected/dyns.ts | 29 +++++++++++++++++++------- ts/fixtures/lang/dyns/src/dyns.rs | 11 ++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/ts/fixtures/lang/dyns/expected/dyns.ts b/ts/fixtures/lang/dyns/expected/dyns.ts index 365a4c0d1..1525d625f 100644 --- a/ts/fixtures/lang/dyns/expected/dyns.ts +++ b/ts/fixtures/lang/dyns/expected/dyns.ts @@ -2255,20 +2255,33 @@ export function rests(dataData: Uint8Array, dataStart: number, dataLen: number, const _611: rt.RefCell = rt.refCellBorrowMut(log.value); rt.byteVecClear(_611.value); rt.refMutRelease(_611); + const cell2: rt.Cell> = rt.tailDyn(new rt.Cell(new Countdown.Countdown(rt.u8Rem(a, 8))), { v: (value20: rt.Cell) => rt.iterDyn(rt.iterFrom(value20.v, Countdown.next$iter_Iterator)) }, rt.drop); + const first2: rt.u8 | undefined = rt.iterToOption(cell2.v.next()); + const _624: rt.Arc> = new rt.Arc(new rt.Mutex(new Countdown.Countdown(rt.u8Rem(b, 8)))); + const locked: rt.Arc>> = new rt.ArcDyn(_624, rt.tailDyn(_624.value, { value: (value21: rt.Mutex) => rt.iterDyn(rt.iterFrom(value21.value, Countdown.next$iter_Iterator)) }, rt.drop)); + const _632: rt.Mutex> = rt.mutexLock(locked.value); + const second: rt.u8 | undefined = rt.iterToOption(_632.value.next()); + rt.mutexUnlock(_632); + const args36: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(first2, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(second, f, rt.u8Debug)]; + put(out, rt.fmtFormat([args36[0], " ", args36[1], " "])); + cell2[Symbol.dispose](); + dropped(out); + locked[Symbol.dispose](); + dropped(out); let slot: rt.Cell = (undefined as unknown) as rt.Cell; - const thin: rt.Place> = rt.place(() => slot, (value20) => slot = value20); - const wide: rt.Place> = rt.rawUnsizePlace(thin, (p12) => rt.tailDyn(p12, { v: (value21: rt.Cell) => new ofPlace$u32(value21) })); + const thin: rt.Place> = rt.place(() => slot, (value22) => slot = value22); + const wide: rt.Place> = rt.rawUnsizePlace(thin, (p12) => rt.tailDyn(p12, { v: (value23: rt.Cell) => new ofPlace$u32(value23) })); const null_: boolean = wide === rt.NULL; thin.v = new rt.Cell(a as rt.u32); wide.v.v.grow$Shape(b); const area5: rt.u32 = wide.v.v.area$Shape(); - const _637: rt.u32 = slot.v; - const args36: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtBool(null_, f), (f: rt.Formatter) => rt.u32Display(area5, f), (f: rt.Formatter) => rt.u32Display(_637, f)]; - put(out, rt.fmtFormat([args36[0], " ", args36[1], " ", args36[2], " "])); + const _677: rt.u32 = slot.v; + const args38: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtBool(null_, f), (f: rt.Formatter) => rt.u32Display(area5, f), (f: rt.Formatter) => rt.u32Display(_677, f)]; + put(out, rt.fmtFormat([args38[0], " ", args38[1], " ", args38[2], " "])); const shown: rt.Fmt = rt.rawUnsize(side, (p13) => rt.fmtOwned(p13, (value: Square.Square, f: rt.Formatter) => Square.fmt$fmt_Debug(value, f))); - const _663: boolean = rt.rawUnsize(rt.NULL, (p14) => new of$dyns_Square(p14)) === rt.NULL; - const args38: rt.Fmt[] = [(f: rt.Formatter) => shown(f), (f: rt.Formatter) => rt.fmtBool(_663, f)]; - put(out, rt.fmtFormat([args38[0], " ", args38[1]])); + const _703: boolean = rt.rawUnsize(rt.NULL, (p14) => new of$dyns_Square(p14)) === rt.NULL; + const args40: rt.Fmt[] = [(f: rt.Formatter) => shown(f), (f: rt.Formatter) => rt.fmtBool(_703, f)]; + put(out, rt.fmtFormat([args40[0], " ", args40[1]])); owned[Symbol.dispose](); shape[Symbol.dispose](); u[Symbol.dispose](); diff --git a/ts/fixtures/lang/dyns/src/dyns.rs b/ts/fixtures/lang/dyns/src/dyns.rs index 5c8518094..4264ef209 100644 --- a/ts/fixtures/lang/dyns/src/dyns.rs +++ b/ts/fixtures/lang/dyns/src/dyns.rs @@ -1756,6 +1756,17 @@ pub fn rests(data: &[u8], out: &mut Vec) { } put(out, &format!("{polled} {:?} ", log.borrow())); log.borrow_mut().clear(); + // An iterator of the crate's own in a cell and in a lock, read through each and + // dropped once, with the last owner. + let mut cell: Box>> = Box::new(Cell::new(Countdown(a % 8))); + let first = cell.get_mut().next(); + let locked: Arc>> = Arc::new(Mutex::new(Countdown(b % 8))); + let second = locked.lock().unwrap().next(); + put(out, &format!("{first:?} {second:?} ")); + drop(cell); + dropped(out); + drop(locked); + dropped(out); // To a cell not there yet, read only once it is. let mut slot = std::mem::MaybeUninit::>::uninit(); let thin = slot.as_mut_ptr();