Skip to content

feat(ts): translate the rest of a trait object in a struct's last field - #519

Merged
kixelated merged 9 commits into
mainfrom
quest/m3/ts-dyn-tail-rest
Nov 3, 2026
Merged

kixelated merged 9 commits into
mainfrom
quest/m3/ts-dyn-tail-rest

Conversation

@kixelated

@kixelated kixelated commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

#493 translates Rc<RefCell<dyn Trait>>, Arc<Mutex<dyn Trait>>, and the crate's own &Tail<dyn Trait> as an rt.tailDyn view, but still refused the rest of quest/m3/ts-dyn-tail-rest.md: a Cell, UnsafeCell, or ManuallyDrop of a trait object, a dyn Future in a struct's last field, and raw pointers unsized to a trait object or a struct of one. No consumer reaches these yet; they are std gaps.

Approach

  • Cell and UnsafeCell views name rt.Cell's v; the cell is the place of what it holds.
  • ManuallyDrop<T> is T in JS, so a pointer to one unsizes as a pointer to what it holds; a box of it still drops nothing.
  • A dyn Future in the last field is an rt.DynFuture over the field, made where it is read, so each poll advances the one future in place; it drops the future, as a struct's glue drops its last field, so the struct's drop drops it once.
  • A raw pointer unsizes as a box of the object it points at does, so Box::from_raw of it drops the concrete value once, or as a &mut to its place where JS copies the value, through rt.rawUnsize, which keeps a null or dangling pointer as itself so is_null holds. This covers crate traits, dyn Future, dyn Debug, and structs of one. A raw pointer that is the rt.Place of a library value, such as a cell, unsizes through rt.rawUnsizePlace, which makes the view as each read finds the value, so uninitialized or replaced storage is never read early; where the result would be an object made eagerly (*mut Range to *mut dyn Iterator), it is refused with a span.
  • A pointer to a trait object is Pointee::Object (a &mut to one is the object), so *mut dyn Trait is typed as the object; comparing pointers to trait objects stays refused, now with its own reason.
  • Cell::get_mut and Cell::as_ptr translate as their UnsafeCell's get_mut and get (same rt.Cell), in every table that judges those; without them nothing reads a Cell<dyn Trait>. This also fills the gap for sized cells.
  • Root cause fix in the write analysis: a &mut to an unsized type reaching std marked the type as replaced, which made a raw pointer into UnsafeCell<dyn Trait> look out of step. Nothing writes a whole unsized value, and what a trait object's methods replace already counts where it unsized, so replace skips unsized types.

Tests: a new dyns::rests differential case covers dispatch through each view, writes landing in the original, a future advancing across polls and dropping once (in a Mutex and an async block in a RefCell), drop order with ManuallyDrop, raw pointers to crate traits, a struct of one, cells, dyn Debug, a null pointer, uninitialized storage, and ownership rebuilt by Box::from_raw, plus an iterator in a boxed Cell and an Arc<Mutex<..>> dropping once. The reject fixture's cell_unsized now translates; raw_dyn_iter keeps its refusal with the new reason, and dyn_compared covers comparing pointers to trait objects.

Impact

  • Public API: the translator's output. New runtime exports rt.rawUnsize and rt.rawUnsizePlace. No breaking change.

Alternatives

  • Canonicalizing Cell::as_ptr/get_mut to UnsafeCell's in library::key: rejected, since made_out_of_step must project through Cell's inner UnsafeCell.
  • Keeping raw pointers refused outside object_of: the same gap reached dyn Iterator and dyn Future, so the raw case is handled once at the coercion.

Follow-ups

  • Out of scope gaps hit while writing the fixture, reported for planning: std::future::ready and Debug of Poll are unsupported calls (avoided in the fixture), and a raw pointer to a library value unsized to an object made eagerly (*mut Range to *mut dyn Iterator) stays refused.

Review

Codex (quest review, gpt-6.1-sol, medium), 5 rounds; round 5 on head b83fde6 reported no findings. That head merges origin/main (#516, #521, #524), with a ts/README.md prose conflict against #521 resolved by keeping both texts; just ci passes on it.

  • Round 1: raw pointer to a cell typed as the view, not its rt.Place: fixed. Raw pointer objects made as borrowed, so Box::from_raw skipped the destructor: fixed.
  • Round 2: eager read of uninitialized storage through a raw pointer's place: fixed (rt.rawUnsizePlace, eager-object case refused).
  • Round 3: iterator in a boxed cell skips its destructor: refuted, the view's dispose drops the concrete cell (expected dyns.ts), and the added differential case matches native.
  • Rounds 4 and 5: no findings.

🤖 Generated with Claude Code

kixelated and others added 2 commits November 3, 2026 02:28
…ject, a dyn Future in a struct's last field, and raw pointers unsized to either

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title The rest of a trait object in a struct's last field feat(ts): translate the rest of a trait object in a struct's last field Nov 3, 2026
kixelated and others added 5 commits November 3, 2026 05:54
…it, in the place a raw pointer to a cell is

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unsizes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…once

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 3, 2026 14:13
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 18 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 16b28134-4a18-412c-8831-c2f337f56dcc
Commits

Reviewing files that changed from the base of the PR and between 307df7e and b83fde6.

Files selected for processing: 21
  • quest/m3/README.md
  • quest/m3/ts-dyn-tail-rest.md
  • ts/README.md
  • ts/fixtures/lang/dyns/expected/core/task/poll.ts
  • ts/fixtures/lang/dyns/expected/dyns.Ticks.ts
  • ts/fixtures/lang/dyns/expected/dyns.ts
  • ts/fixtures/lang/dyns/expected/lib.ts
  • ts/fixtures/lang/dyns/src/dyns.rs
  • ts/fixtures/lang/dyns/src/lib.rs
  • ts/fixtures/reject/census.txt
  • ts/fixtures/reject/expected.txt
  • ts/fixtures/reject/src/objects.rs
  • ts/fixtures/reject/src/values.rs
  • ts/fixtures/reject/ts.toml
  • ts/runtime/rt.ts
  • ts/src/lower/copy.rs
  • ts/src/lower/dynamic.rs
  • ts/src/lower/mir/place.rs
  • ts/src/lower/mir/rvalue.rs
  • ts/src/lower/pointer.rs
  • ts/src/lower/task.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant