Skip to content

Falling off the end of a function returns undefined - #430

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-fall-off-end-undefined
Oct 1, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-fall-off-end-undefined

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Follow-up to #429. A function that can fall off its end beside value returns still inferred T, and falling off returned the return slot as it was left:

function f(c: boolean) { if (c) return "a"; }
f(false) === undefined;   // before: false, type string   after: true, type string | undefined

Change

  • bodyFallsOffEnd checks whether the end of the body is reachable, by AST analysis (canCompleteNormally). It's exact for return, throw, blocks, if/else, try/catch/finally, a switch with a default (the end of its last clause, or a break), and while (true) / for (;;) / do .. while (true) without a break that leaves them (BreakOutVisitor: an unlabeled break outside nested loops/switches, or any labeled break). Any other statement is taken to complete. MLIRGen's IR can't answer this: ts.ReturnVal sits inside ts.If regions and isn't a terminator.
  • Discovery adds undefined to an inferred type when the end is reachable, as An inferred return type keeps undefined and null #429 does for a bare return;. An explicit return type is never changed.
  • A function whose result is T | undefined (inferred or declared), any or unknown now generates return undefined at a reachable end. Explicit T | undefined functions that fell off used to return garbage there.
function type
if (c) return "a"; string | undefined
if (c) return "a"; else return "b"; string
switch (n) { case 1: return "one"; default: return "many"; } string
switch (n) { case 1: return "one"; case 2: return "two"; } string | undefined
while (true) { if (n > 0) return n; n++; } number
try { ...; throw 1; } catch { return 2; } s32
if (c) return "x"; throw "e"; string
for (const k of a) { if (k == n) return k; } s32 | undefined

If the analysis is unsure it says "reachable", which adds | undefined, so a call into a function that never returns (where TypeScript would know never) still counts as completing.

Checked

Tests

  • New 00return_falls_off_end.ts (compile + jit): each row above, plus a declared number | undefined.
  • Release suite: 3343/3343 passed.
  • DefaultLib (main b67a213) rebuilt release/gc with this compiler: 158/158 compile, 158/158 jit.
  • The new test passes under gc, rc and none.
  • Not run: Debug, x86, -mm=own.

🤖 Generated with Claude Code

…a";` is `string | undefined`

A function whose end is reachable beside value returns inferred T, and falling
off returned the return slot as it was left. bodyFallsOffEnd asks whether the
end of the body can be reached - exact for return, throw, blocks, if/else,
try/catch/finally, a switch with a default, and `while (true)` / `for (;;)` /
`do .. while (true)` without a break out (BreakOutVisitor); anything else is
taken to complete. Such a function infers T | undefined, and one returning
T | undefined (inferred or declared), any or unknown returns undefined there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit cb5136d into main Oct 1, 2026
2 of 3 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-fall-off-end-undefined branch October 1, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant