await in a functor body compiles to invalid JavaScript

Ouverte
#9,219 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

@mununki y travaille déjà.

Depuis le 4/11/2026.

  • #9220 par @mununki — ouverte

Évaluation

Difficulté
4/5
Temps estimé
3-5 jours
Accessibilité débutants
25/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
À l'abandon
Stack technique
javascript
Domaine
compilers

Piste de recherche

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.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

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.

Langage dominant
OCaml
Étoiles
7.5k
Forks
484
Merge moyen
16 h 57 min
PR mergées (30 j)
258

Préparer son environnement

Ouvrir dans Codespaces

Lance le conteneur de développement du projet dans votre navigateur, avec votre propre compte GitHub.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de rescript-lang/rescript

Toutes les issues de rescript-lang/rescript

Issues similaires

Plus d'issues Compilers

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.