await in a functor body compiles to invalid JavaScript

Chiusa
#9,219 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@mununki ci sta già lavorando.

Dal 4/11/2026.

  • #9220 di @mununki — aperta

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
25/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Ferma
Stack tecnologico
javascript
Ambito
compilers

Direzione di ricerca

The issue points to compiler/frontend/bs_builtin_ppx.ml, especially expr_mapper, module_expr_mapper, and structure_mapper. Read the await context handling around Pexp_fun, Pmod_functor, and structure-level module bindings, then check the existing cases described for functors and module M = await List. Done means those forms are rejected outside async contexts and accepted where allowed, without generating invalid JavaScript.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

await in the body of a functor passes the compiler's check, and the generated JavaScript has await in the function that the functor compiles to, which is not async:

module type Source = {
  let value: promise<int>
}

module Make = (Source: Source) => {
  let value = await Source.value
}
function Make(Source) {
  let value = await Source.value;
  return {
    value: value
  };
}

Loading the module fails with SyntaxError: Unexpected reserved word (with CommonJS output, SyntaxError: await is only valid in async functions and the top level bodies of modules); node --check reports the same.

A functor in a plain function is rejected with Await on expression not in an async context, but in an async function it is accepted. A first-class functor returned by an async function compiles to a plain arrow:

module type Make = (Source: Source) => {let value: int}

let make = async () => module((Source: Source) => {
  let value = await Source.value
}: Make)
async function make() {
  return Source => {
    let value = await Source.value;
    return {
      value: value
    };
  };
}

The same happens with for await and module M = await List in a functor body, with a generative functor, a functor with several parameters, and a functor defined in the body of another functor.

The JavaScript is valid only where the optimizer inlines every application of the functor and then drops the functor: a functor defined and applied in an async function, a top-level functor that an interface (.resi) or a module signature hides, and a functor applied where it is written, as in module M = ((X: S) => {...})({...}). This depends on the optimization, not on the code: after module Alias = Make, Alias({...}) is not inlined and Make stays a function with await in it, and an application in a plain function inside the async function moves the await into that plain function.

A dynamic import at the top level of a module that is nested in a plain function is accepted as well, while module L = await List directly in the function body is rejected:

let length = values => {
  module M = {
    module L = await List
    let length = L.length(values)
  }
  M.length
}
function length(values) {
  let L = await import("@rescript/runtime/lib/es6/Stdlib_List.mjs");
  let length$1 = L.length(values);
  return length$1;
}

The await check is in compiler/frontend/bs_builtin_ppx.ml. expr_mapper sets the async context at every Pexp_fun to whether that function is async, which #9169, the fix for #9168, made exact for functions. A functor is a Pmod_functor module expression, and module_expr_mapper leaves the context unchanged, so the functor body keeps the context around the functor: true at the top level of a file, where top-level await is allowed (#5925), and in an async function. In addition, structure_mapper rewrites module M = await List in a structure without checking the context, which expr_mapper checks only for the Pexp_letmodule form in an expression.

Reproduced with 11.1.4, 12.0.0-alpha.1, 12.0.0-alpha.6, 12.3.1, 13.0.0-alpha.6 and master (c10223e80).

Related: #9168, #9169, #5925, #8815.

Lingua principale
OCaml
Stelle
7.5k
Fork
484
Merge medio
17h 9m
PR unite (30g)
264

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di rescript-lang/rescript

Tutte le issue di rescript-lang/rescript

Issue simili

Altre issue su Compilers

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.