Skip to content

emitter: a tail-position try returns in place - #310

Merged
shreeve merged 1 commit into
mainfrom
tail-try
Sep 16, 2026
Merged

shreeve merged 1 commit into
mainfrom
tail-try

Conversation

@shreeve

@shreeve shreeve commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Summary

A try that is the last statement of a function now emits as a statement whose branches return their values. It used to be wrapped in an arrow IIFE, created and called on every invocation:

// before
return (() => { try { return parse(x); } catch (e) { return null; } })();
// after
try { return parse(x); } catch (e) { return null; }

The value form keeps its wrapper in true expression positions such as x = try ..., where there is no function to return from.

Same values. The wrapper only turned each branch's last expression into a return; the statement form does that in place. this and arguments were already preserved by the arrow. finally runs after the return either way.

Two things improve. An async tail try drops the async wrapper and the extra await around it. A generator may now yield inside a tail try, which the wrapper rejected.

Reach. 130 tail-position tries across packages and src now emit in place, 27 of them in the sites manager. The 28 wrappers that remain are all assignments.

Artifacts. lowerings.js changes one site, three lines; 184 corpus artifacts unchanged; parser untouched; browser bundle regenerated under Bun 1.4.0.

Test

Twelve new rows in errors.rip; two sweep.rip rows that pinned the wrapper now pin the statement. Language battery 3270 passing, toolchain gates 661 passing, full test:all suite green across all 26 lanes.

A try that is the last statement of a function now emits as a statement
whose branches return their values, instead of an arrow IIFE created
and called on every invocation. The value form keeps its wrapper for
true expression positions such as `x = try ...`, where there is no
function to return from.

Values are identical: the wrapper only turned each branch's last
expression into a return, and the statement form does the same in
place. `this` and `arguments` were already preserved by the arrow.
An async tail try loses the async wrapper and its extra await; a
generator may now yield inside a tail try, which the wrapper rejected.

Of 130 tail-position tries across packages and src, every one now
emits in place; the 28 wrappers that remain are assignments. Corpus:
lowerings.js changes one site, three lines, from the wrapper to the
statement; 184 artifacts unchanged. The parser is untouched. The
browser bundle is regenerated under the pinned Bun.

Battery: twelve rows in errors.rip pin the body and handler values,
the handler-less form, finally ordering, an explicit inner return, a
catch pattern, a try after other statements, yield and await inside,
and the emitted code for the tail, async and value forms. Two sweep
rows that pinned the wrapper shape now pin the statement.
@shreeve
shreeve merged commit d1001a5 into main Sep 16, 2026
2 checks passed
@shreeve
shreeve deleted the tail-try branch September 16, 2026 07:22
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