Semantic Diff

#8931 Add try..finally

1 commit, drawn from its message.

8556f0ad72e0fd3b308ff5cd9688c9338ca1a37e..pr-8931

636637bea · Christoph Knittel · Oct 20, 2026

Add try..finally

Why

JavaScript code cleans up with finally blocks: after an await, around break and continue, and in generators, where cancellation is only seen by finally. ReScript had no way to express them.

try takes finally { ... } after its body or after its catch. finally stays an identifier: after a catch it only starts a finally block when a brace follows. The block is a statement and must not break or continue out of it. Supersedes #7903.

modifiedAST Pexp_try

let f = g =>  try g() catch {  | Not_found => 0  } finally {    Console.log("done")  }
diagnostic
Before
`finally` was not valid after a catch.
parse
After
expression├─ Pexp_try├─ expression│  ├─ Pexp_apply│  ├─ expression│  │  └─ Pexp_ident "g"│  ├─ …│  └─ <case>│     ├─ pattern│     │  └─ Ppat_construct "Not_found"│     └─ expression│        └─ Pexp_constant PConst_int (0,None)└─ Some   └─ expression      ├─ Pexp_apply      ├─ expression      │  └─ Pexp_ident "Console.log"      └─ …

addedLambda Ltryfinally

lambda
After
(try  (try (apply g/1002 unit) with raw_exn/1062    …) finally (seq (js_call[console.log] "done") unit))

addedJS try..catch..finally

js
After
function f(g) {  try {    return g();  } catch (raw_exn) {    let exn = Primitive_exceptions.internalToException(raw_exn);    if (exn.RE_EXN_ID === "Not_found") {      return 0;    }    throw exn;  } finally {    console.log("done");  }}

addedDiagnostic break out of finally

while true {  try {    ()  } finally {    break  }}
diagnostic
After
`break` cannot leave a `finally` block. Only a loop inside the block can be broken out of.

Review

  • Why a separate Ltryfinally instead of an optional finally on Ltrywith? Ltrywith keeps its meaning in every pass, a try without catch needs no exception ident, and try..catch..finally is an Ltryfinally around an Ltrywith that the JS backend prints as one statement.
  • Why is the catch handler no longer in tail position? With a finally, a self tail call in the handler would become a loop continue that runs the finally before the recursive call instead of after it.
  • Why reject break and continue in finally? JavaScript allows them, but they silently drop a pending exception or return value.
with Claude Opus 5.5