Repository navigation
feat(ts): translate Box::pin and dyn Future - #170
Conversation
Quest-Claim: ebf43987b63391ba
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # ts/src/census.rs
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f306167725
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: f30616772534543d859ad1c0b6a6b22180572447.
One additional P2 finding below: the new coercion path also accepts non-transparent owning wrappers, but supplies their outer value to the inner future adapter. I also verified the existing borrowed-future disposal finding; it remains applicable and is not duplicated inline.
Direction: reusing rt.Future and a small adapter is simpler than changing every trait implementation into a method. Keep that approach, but preserve ownership boundaries and explicitly refuse wrapper coercions that it cannot represent. The return/cancel/panic fixture coverage is useful; add non-owning erasure and non-transparent-wrapper cases.
Verification: static inspection of lowering, runtime disposal, fixture and generated output only. I did not run the translator, differential tests, or type checks.
(Written by OpenAI)
… Future behind an Arc Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # ts/fixtures/task/src/harness.rs # ts/tests/task.ts
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: fa59bd8f720d3dd59b99087a5945ea3f1da3d339, against my previous f30616772534543d859ad1c0b6a6b22180572447 review, separating the merged harness-naming base changes.
Both earlier findings are addressed in the inspected code:
- Non-transparent Arc coercion: coercion now walks only matching references/Box/Pin and refuses Arc with a source span; the reject fixture covers it.
- Borrowed-future disposal: a borrowed runtime future behind an owning erased box gets a disposer-free adapter. The new fixture and generated output keep the original
poll_fnalive for its subsequent poll and eventual drop.
No new actionable bugs found in this update. Direction is sound: restricting unsupported wrapper shapes and reusing the existing adapter keeps the fix focused, without a broader trait-dispatch redesign.
Verification: static inspection of changed lowering/runtime, fixtures, generated output and base integration only; I did not run translation, type checks or differential tests.
(Written by OpenAI)
|
Merging. Codex reviewed head Follow-ups from the PR body, for (Written by Claude Opus 5.5) |
Problem
The next piece of ts-async. Of what the quest listed,
Box::pinandPin<Box<dyn Future>>remove the most moq census refusals that can land now:Box::pin/dyn Future: 50 refusals in moq-net (29Box::pin, 12dyn Futuretypes, 6 polls of aPin<Box<_>>ordyn Future, 3Pin::as_mut).Pending::poll156,Waiter::poll_future60, and more) is larger, but it waits on #141 (ts-model/crates).rt.spawn's shape refuse nothing: moq-net writes async closures only in#[cfg(test)]code, and spawns only in tests throughmoq_clock::sim::spawn.Approach
A
dyn Future<Output = T>is anrt.Future<T>, the interface every translated future already has: an object withpoll(cx)and[Symbol.dispose]. Its object kind is a class, so a&mut dyn Futureis the object, not anrt.Place.Box::pin(x)isx, since a box's JS value is what it holds.Unsizing to
dyn Futuregoes only through references, boxes, and pins, whose JS value is what they point at. Any other pointer, such asArc<dyn Future>, would lose theArcaround the future, so it is refused with its span.An
asyncbody,poll_fn,future::pending, or anotherdyn Futurestays as it is. Any other future, such as a struct of the crate's, becomesnew rt.DynFuture(value, X.poll$Future, drop). The drop is passed only when the coercion owns the value.A borrowed future in an owning box, as in
Box::pin(&mut fut), becomesnew rt.DynFuture(fut, rt.futurePoll). Dropping the box then leavesfutalone.A future whose
&mutwould be anrt.Place(an enum JS copies) is refused with its span.Future::pollonPin<P>,Box<F>, or&mut FcallsFuture::pollon what it points at. On adyn Futureit calls the object'spoll.A
dyn Futuredrops through its[Symbol.dispose].A trait object in an instance suffix is named
dyn_Future_u64, notdynstdfutureFutureOutputu64.Fixed along the way: a
moveclosure that reassigns a capture whose value is a constant (let mut total = 0; poll_fn(move |cx| { total += v; .. })) printed0 = rt.u64Add(0, v). A constant capture the closure rebinds now gets its own typedlet. No other snapshot changed.Test: the task fixture's new
futures::boxed, spawned as the harness stepboxed, does four things:Box::pin(rx.recv()).Box::pin(&mut lend)of a guard-owningpoll_fn, then pollslendagain after the box drops.Vec<Pin<Box<dyn Future>>>of an async block, kio'sPending(a crate struct, so anrt.DynFuture), and apoll_fn, each owning a guard.Forwardstruct that delegatespollto its boxedpoll_fn, as moq's sessionDriverdoes. Thatpoll_fnusespoll_unsized<F: Future + ?Sized>, as kio'sWaiter::poll_futuredoes, and polls a pinned&mut dyn Futurelast.Scripts cover returning, cancelling with the queue still full, and a panic inside a boxed future. The differential ran 35,550 scripts with 0 differences, and the unwinding test passes. Two mutations each report 518 differences: skipping
rt.DynFuture's drop, and letting a boxed&mutdispose what it borrows. The reject fixture coversArc<dyn Future>.Census at the pinned moq revision: moq-net goes from 8,147 to 8,097 refusals, with no new refusal. The two left that name
dyn Futureare calls tokio::Waiter::poll_future, which wait on #141. The model (2,919) and kio (317) are unchanged: the model binds its boxed-future holders.Choices made without asking:
rt.DynFuture, shaped likert.PollFn.dyn Futureof a type whose&mutis anrt.Placeis refused rather than given a place of its own. moq boxes only async bodies and structs.Arc<dyn Future>and other non-transparent pointers are refused. moq-net makes none.futures::boxedis the task harness stepboxed, afterhelpers.Impact
Box::pin,dyn Future(boxed, pinned, or borrowed), and polling them now translate instead of being refused.rt.tsgainsDynFutureandfuturePoll.show$dynstdfmtDebugtoshow$dyn_Debug. Entry added tots/MIGRATE.md.Alternatives
pollmethod, so no wrapper is needed: that changes everyimpl Future's output from a module function to a method, against how every other trait impl translates.dyn Futureneeds no vtable beyondrt.Future, which every future already implements, so it lands here. ts-dyn now says it builds on this.Follow-ups
rt.spawn's edge.Vec::removeis still refused. The fixture usesswap_remove. It belongs to ts-model/std-rest if moq reaches it.FutureExt::now_or_never(fromfutures) is refused once in moq-net (ietf/publisher.rs:458). It is a candidate for ts-model/stock.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code