A dynamic import `module M = await List` fails with `Unbound module type __List__` after a statement, in a callback and in other positions

Aberta
#9,230 0 comentários 0 reações 0 responsáveis Ver no GitHub

Mantenedores costumam responder em até 1 dia

Ninguém assumiu esta issue ainda.

Avaliação

Dificuldade
4/5
Tempo estimado
3-5 dias
Facilidade para iniciantes
55/100
Tipo de issue
Bug
Clareza
Claramente especificada
Status de atividade
Ativa
Stack de tecnologia
ocaml
Domínio
compilers

Direção de pesquisa

Start by reading compiler/frontend/bs_builtin_ppx.ml, especially the Pexp_letmodule case in expr_mapper and aux in spelunk_vbs; then trace Ast_await.create_await_module_expression and structure_mapper's Pstr_value case. Reproduce the listed failing and working positions, including a Pstr_eval expression, and inspect existing coverage around #6431 and #7783. Done means every import rewritten by expr_mapper has its module type declared, with regression tests covering the reported positions.

Escrita pelo modelo de indexação a partir do texto da issue.

Descrição

A dynamic import of a module, module M = await List, is rejected with Unbound module type __List__ depending on where it appears in the async function. A statement before it is enough:

let f = async () => {
  Console.log("start")
  module M = await List
  M.length(list{1, 2})
}
f()->Promise.thenResolve(Console.log)->ignore
  We've found a bug for you!
  Repro.res:3:14-23

  1 │ let f = async () => {
  2 │   Console.log("start")
  3 │   module M = await List
  4 │   M.length(list{1, 2})
  5 │ }

  Unbound module type __List__

The source is valid and should log start and then 2. __List__ is a name generated by the compiler, not one the source uses. Without the Console.log("start") line, the same function compiles to a dynamic import and logs 2:

async function f() {
  let M = await import("@rescript/runtime/lib/js/Stdlib_List.cjs");
  return M.length({hd: 1, tl: {hd: 2, tl: /* [] */0}});
}

The same error (__List__, or __Array__ for await Array) comes from an import

  • after any statement (e; module M = await List), also in a switch arm,
  • in an async function passed as an argument, e.g. [1]->Array.map(async _ => {...}) or p->Promise.then(async _ => {...}), or written in a record field, an array or a tuple,
  • in the body of a try, a for or a while, or in an if without else,
  • after open U, a local exception E, a plain local module module L = List, or a constrained import module L = await (List: ListT),
  • in an expression at the top level of a file, e.g. run(async () => { module M = await List; ... })->ignore, or of a nested module.

It compiles directly in the body of a function bound by let, after a let, in a nested block, in both branches of if/else and of a ternary, as the first expression of a switch arm, under a return type annotation, in an async function bound by let inside another function, in Some(async () => ...), and after another unconstrained import.

Whether an import compiles also depends on the rest of the file. The function from the first example compiles when an earlier binding imports List in one of the positions that work:

let g = async () => {
  module M = await List
  M.length(list{1})
}
let f = async () => {
  Console.log("start")
  module M = await List
  M.length(list{1, 2})
}
f()->Promise.thenResolve(Console.log)->ignore
async function f() {
  console.log("start");
  let M = await import("@rescript/runtime/lib/js/Stdlib_List.cjs");
  return M.length({hd: 1, tl: {hd: 2, tl: /* [] */0}});
}

This logs start and 2. With g placed after f, f fails again with Unbound module type __List__.

In compiler/frontend/bs_builtin_ppx.ml, the Pexp_letmodule case of expr_mapper rewrites every module M = await List into an unpack of import at the package type module(__List__) (Ast_await.create_await_module_expression), wherever the expression is. The declaration module type __List__ = module type of List is added separately, before the binding, by the Pstr_value case of structure_mapper. To find the imports, aux in spelunk_vbs follows only Pexp_letmodule, Pexp_let, Pexp_braces, Pexp_ifthenelse with an else, Pexp_ternary, Pexp_construct with one argument, Pexp_fun, Pexp_constraint and Pexp_match. Every other expression ends the search (| _ -> acc), among them Pexp_sequence, Pexp_apply, Pexp_try, Pexp_for, Pexp_while, Pexp_open, Pexp_letexception, Pexp_record, Pexp_array and Pexp_tuple. A Pexp_letmodule that is not an unconstrained import ends it too, without its body being searched. A Pstr_eval item, an expression at the top level of a file or of a module, is not searched at all. local_module_type_decl declares each name once per structure, so a declaration found for an earlier binding also serves every later import of the same module, which is why f compiles when g comes before it. Searching every subexpression, and Pstr_eval items too, would declare the module type for each import that expr_mapper rewrites.

Writing the module type out avoids the hoisting: with module type ListT = module type of List at the top level, module M = await (List: ListT) compiles to await import(...) in all of the positions above.

Reproduced with 11.1.4 (with Js.log for Console.log), 12.0.0-alpha.6, 12.3.1, 13.0.0-alpha.6 and master (fc7444266). The search has followed a fixed list of expression shapes since #6431 (11.0.0-rc.5). #7783, the fix for #7173, added Pexp_constraint and Pexp_match to it in 12.0.0-beta.7, so 12.0.0-alpha.6 also fails in a switch arm and under a return type annotation.

Related: #7173, #7783.

Linguagem predominante
OCaml
Estrelas
7.5k
Forks
485
Merge médio
17h 9min
PRs com merge (30d)
264

Preparar o ambiente

Abrir no Codespaces

Inicia o contêiner de desenvolvimento do projeto no navegador, com a sua própria conta do GitHub.

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Mais de rescript-lang/rescript

Todas as issues de rescript-lang/rescript

Issues semelhantes

Mais issues de Compilers

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.