Repository navigation
feat(ts): type a function value's () result as undefined - #303
Conversation
Quest-Claim: f38be08b6246ac3b
A closure, fn pointer, dyn Fn, or function item used as a value returns undefined for (), so a Vec<()> collected from one type-checks. A function item read back from a generic's parameter names the function. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou'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 20 minutes. View limit details
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. Comment |
# Conflicts: # ts/fixtures/lang/expected/durations.ts # ts/fixtures/lang/expected/harness.ts # ts/fixtures/lang/expected/nested.ts # ts/fixtures/lang/expected/orders.ts # ts/fixtures/lang/expected/statics.ts # ts/fixtures/task/expected/drops/iters.ts
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: cdb2189
No actionable bugs found. The direction is sound: function-value signatures consistently represent () as undefined, while declared functions and trait-object methods retain void. Casting named unit-returning functions preserves identity without allocating wrappers; the fallback arrow correctly discards a unit call's result. Recovering FnDef locals is a small, relevant fix, and the added fixture covers function items, pointers, generic callbacks, dynamic calls, and unit-bearing containers. The migration note documents the TypeScript caller impact.
Verification limits: static review of the lowering changes, relevant surrounding code, fixture source, and generated snapshot diffs only. I did not run the translator, TypeScript checks, or differential tests; the PR's reported test results were not independently reproduced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s done Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
A function value returning
()was typed=> void, so TypeScript inferredvoidfor what it yields.tscthen refused theundefinedthe output annotates()with. Reproduced:data.iter().map(|_| ()).collect::<Vec<()>>()failed withnested.ts(661,2): Type 'void[]' is not assignable to type 'undefined[]', andVecDeque<()>failed withType 'Iter<void>' is not assignable to type 'Iter<undefined>'.The quest's plan (retype only a closure's return) doesn't cover every case. The same
voidalso leaks through:fnpointer, such asdata.iter().copied().map(f)withf: fn(u8);each<T>(f: impl Fn(u8) -> T)atT = ();dyn Fn;.map(ignore), which is declared: void.Auditing that last case turned up a pre-existing bug: a function item read back from a generic's parameter lowered to
undefined.each(data, half)becamert.iterMap(..., undefined)for any return type, andtscrefused it.Approach
fn_ty(closure,fnpointer,FnDef, anddyn Fntypes) and closure arrows type a()result asundefined.fn_valuecasts a declared function that returns()to that type, as inignore as (a0: rt.u8) => undefined. The arrow fallback calls it withoutreturn.: void. So do the member types of a trait object, whose methods are declared functions.local_exprreads aFnDeflocal asfn_valuerather thanundefined.nestedgains aunitscase:Vec<()>,VecDeque<()>,Option<()>,Result<(), E>, andResult<T, ()>from closures. It also covers afnpointer, a function item, a generic atT = ()given a closure and a function item, adyn Fn, and a method returning an associated type= (). It runs differentially against native wasm32.Snapshots: 47 files changed, all derived from this change. I checked this with a script that maps
=> undefined/): undefined =>back tovoidand strips the new casts; afterwards only these differences remain:fn_valuearrow that no longerreturns a unit call (bounds.ts);Impact
fnpointer,dyn Fn, or function item used as a value returnsundefinedfor()instead ofvoid. This breaks a TS caller passing avoid-typed function, so there is aMIGRATE.mdentry.ts/README.md's type table gains a row.undefined.Alternatives
()returnundefined, declared functions included. That needs no casts and deletesret_ty's special case, but it churns roughly 770 signatures in 157 snapshot files. Rejected per the quest plan.fnpointer identity.Choices made without asking
T = ()hits it.voidon trait objects' member types.Follow-ups
{:?}of anyVecDequeis refused.filter(|u| **u == ())folds totrueand leavesuunread (TS6133).Review
gpt-6.1-sol, medium effort, round 1, on headcdb2189138d3482c8c92555576e38729da53d184againstorigin/main: 0 findings.just cipassed (158 tests), andjust ts moqtranslates and type-checks moq at the pin.Merge of main at
82fb1fc8(#294, #299) intocdb21891, then60cfcefe: took main's generated snapshots and regenerated them (the one interaction is this PR's=> undefinedcast now applied to #299's boundhash$Hashpassed tort.hashing), and repointed #294's newts-zero-sizedquest from the deletedts-unit-closureslink to this PR. Recorded skip, too trivial to review;just cipassed on60cfcefe.🤖 Generated with Claude Code
(written by Claude Opus 5.5)