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

Đang mở
#9,230 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
55/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
ocaml
Lĩnh vực
compilers

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.

Ngôn ngữ chính
OCaml
Star
7.5k
Fork
484
Merge trung bình
17 giờ 9 phút
Pull request đã merge (30 ngày)
264

Chuẩn bị môi trường

Mở trong Codespaces

Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của rescript-lang/rescript

Tất cả issue của rescript-lang/rescript

Issue tương tự

Thêm issue về Compilers

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.