Semantic Diff

#9220 Reject await in a functor body

1 commit

227b697763ca1c08773343bd9e4da9629b920037..pr-9220

d8b70ba5c · mununki · Nov 04, 2026, 09:38 UTC

Reject await in a functor body

Why

The built-in PPX checks await against an async context that each Pexp_fun sets to whether its function is async. A functor is a Pmod_functor module expression, which left the context as it was, so its body took the context around it: true at the top level of a file, where await is allowed, and in an async function. A functor compiles to a JavaScript function that is not async, so the output had await in a plain function. A functor body is now not an async context, and the context around the functor is restored after it. In a structure, module M = await List was not checked at all, so it passed in a functor body or a module nested in a plain function; it is now checked as in an expression.

addedDiagnostic await in a functor body

module type S = {  let p: promise<int>}module F = (X: S) => {  let v = await X.p}
js
Before a SyntaxError when the module loads
function F(X) {  let v = await X.p;  return {    v: v  };}
diagnostic
After
A.res:5:11-19Await on expression not in an async context

addedDiagnostic dynamic import in a module in a plain function

let f = values => {  module M = {    module L = await List    let n = L.length(values)  }  M.n}
js
Before a SyntaxError when the module loads
function f(values) {  let L = await import("@rescript/runtime/lib/es6/Stdlib_List.mjs");  let n = L.length(values);  return n;}
diagnostic
After
A.res:3:16-25Await on expression not in an async context

addedDiagnostic functor applied where it is defined

module type S = {  let p: promise<int>}let f = async p => {  module F = (X: S) => {    let v = await X.p  }  module M = F({    let p = p  })  M.v}
js
Before valid, since the optimizer inlines the application
async function f(p) {  let v = await p;  return v;}
diagnostic
After
A.res:6:13-21Await on expression not in an async context

Review

  • Does a working program stop compiling? Yes, where the optimizer inlined every application of the functor and dropped the functor: a functor defined and applied in an async function, as above, a top-level functor that an interface or a signature hides, and a functor applied where it is written. That depends on the optimization: after module G = F, G({...}) is not inlined and F stays a function with await in it. Also rejected are module M = await (List: {...}), which imports nothing dynamically, in a functor body or a module in a plain function, as in an expression already, and await in a functor under module type of. Programs that still compile give the same JavaScript.