mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
615 lines
30 KiB
Rust
615 lines
30 KiB
Rust
//! 模块链接面的 IR 级断言:内存版 `ModuleSourceLoader` +
|
||
//! `emit_program_module`,钉死 import prelude / export 就地注册 / star 再导出 /
|
||
//! 循环导入编译期报错 / 数据模块 / defer 依赖(不求值、mixed eager 序、
|
||
//! 自导入与祖先回边哨兵)的现状行为。
|
||
//!
|
||
//! 路径口径:加载器 `path` 回填表键原样字符串,与入口 `module_path` 入参逐字符
|
||
//! 一致(虚拟路径 canonicalize 失败保留原路径),自导入身份比较与循环检测依赖
|
||
//! 此口径。断言不依赖寄存器号与指令序。
|
||
|
||
use std::collections::HashMap;
|
||
use std::collections::HashSet;
|
||
|
||
use oxide_bytecode::opcode::OpCode;
|
||
use oxide_emit::module::{ModuleKind, ModuleSourceLoader, ResolvedModule};
|
||
use oxide_emit::{Constant, Emitter};
|
||
use oxide_ir::IRFunction;
|
||
|
||
/// 内存版加载器:specifier 精确查表;`path` 原样回填表键。
|
||
/// `resolved` 记录每次 resolve 的 (specifier, 属性对),供链接期观察。
|
||
struct MemLoader {
|
||
table: HashMap<String, (ModuleKind, String)>,
|
||
resolved: Vec<(String, Vec<(String, String)>)>,
|
||
}
|
||
|
||
impl ModuleSourceLoader for MemLoader {
|
||
fn resolve(
|
||
&mut self, _base_dir: &str, specifier: &str, attributes: &[(&str, &str)],
|
||
) -> Result<ResolvedModule, String> {
|
||
self.resolved.push((
|
||
specifier.to_string(),
|
||
attributes.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(),
|
||
));
|
||
self.table
|
||
.get(specifier)
|
||
.cloned()
|
||
.map(|(kind, source)| ResolvedModule {
|
||
source,
|
||
path: specifier.to_string(),
|
||
kind,
|
||
})
|
||
.ok_or_else(|| format!("MemLoader 无 {specifier}"))
|
||
}
|
||
}
|
||
|
||
/// 夹具:parse_module + emit_program_module,返回 IR 与加载器。
|
||
fn emit_module(src: &str, path: &str, deps: &[(&str, ModuleKind, &str)]) -> Result<(IRFunction, MemLoader), String> {
|
||
let alloc = oxide_parser::Allocator::default();
|
||
let program =
|
||
oxide_parser::parse_module(&alloc, src).map_err(|errs| format!("parse_module 失败 {src:?}: {errs:?}"))?;
|
||
let mut loader = MemLoader {
|
||
table: deps.iter().map(|(s, k, t)| (s.to_string(), (*k, t.to_string()))).collect(),
|
||
resolved: Vec::new(),
|
||
};
|
||
let ir = Emitter::new().emit_program_module(&program, path, &mut loader)?;
|
||
Ok((ir, loader))
|
||
}
|
||
|
||
fn builtin_names(ir: &IRFunction) -> HashSet<String> {
|
||
ir.builtin_reg_map.iter().map(|(n, _)| n.clone()).collect()
|
||
}
|
||
|
||
fn builtin_reg(ir: &IRFunction, name: &str) -> Option<u32> {
|
||
ir.builtin_reg_map.iter().find(|(n, _)| n == name).map(|(_, r)| *r)
|
||
}
|
||
|
||
/// 以某 built-in 为 callee(rd 槽)的 CALL_NATIVE 计数。
|
||
fn native_calls_to(ir: &IRFunction, name: &str) -> usize {
|
||
let Some(reg) = builtin_reg(ir, name) else {
|
||
return 0;
|
||
};
|
||
ir.insts
|
||
.iter()
|
||
.filter(|i| i.op == OpCode::CALL_NATIVE && matches!(i.rd, oxide_ir::operand::Operand::Reg(r) if r == reg))
|
||
.count()
|
||
}
|
||
|
||
fn pool_strings(ir: &IRFunction) -> HashSet<String> {
|
||
ir.constants
|
||
.iter()
|
||
.filter_map(|c| if let Constant::String(s) = c { Some(s.clone()) } else { None })
|
||
.collect()
|
||
}
|
||
|
||
/// 在顶层 nested 中按 `function_name` 查找模块。
|
||
fn named_module<'a>(ir: &'a IRFunction, name: &str) -> Option<&'a IRFunction> {
|
||
ir.nested.iter().find(|m| m.function_name.as_deref() == Some(name))
|
||
}
|
||
|
||
/// 面 1:import 绑定 prelude 经 `__moduleLinkGet` 链接,依赖模块编入子模块树。
|
||
#[test]
|
||
fn import_binding_prelude() {
|
||
let (ir, _) = emit_module(
|
||
"import { x } from \"./dep.js\"; var y = x;",
|
||
"./entry.js",
|
||
&[("./dep.js", ModuleKind::Js, "export var x = 42;")],
|
||
)
|
||
.expect("模块编译应成功");
|
||
assert!(builtin_names(&ir).contains("__moduleLinkGet"), "prelude 应链接 __moduleLinkGet");
|
||
assert!(native_calls_to(&ir, "__moduleLinkGet") >= 1, "命名导入应发 __moduleLinkGet 调用");
|
||
assert_eq!(ir.nested.len(), 1, "依赖模块应编入子模块树");
|
||
let dep = &ir.nested[0];
|
||
assert!(!dep.insts.is_empty());
|
||
assert!(builtin_names(dep).contains("__moduleSet"), "依赖的 export 应就地注册");
|
||
}
|
||
|
||
/// 面 2:export 声明就地注册导出值到命名空间(`__moduleSet` 逐名成对)。
|
||
#[test]
|
||
fn export_in_place_register() {
|
||
let (ir, _) = emit_module("export function f() {} export const c = 2;", "./entry.js", &[]).expect("模块编译应成功");
|
||
assert!(builtin_names(&ir).contains("__moduleSet"));
|
||
assert_eq!(native_calls_to(&ir, "__moduleSet"), 2, "f 与 c 各注册一次");
|
||
let pool = pool_strings(&ir);
|
||
assert!(pool.contains("f") && pool.contains("c"), "导出名应成对入池: {pool:?}");
|
||
}
|
||
|
||
/// 面 3:`export * from` 匿名 star 再导出经 `__moduleStar` 整体转发(现状口径)。
|
||
#[test]
|
||
fn export_all_from() {
|
||
let (ir, _) = emit_module(
|
||
"export * from \"./dep.js\";",
|
||
"./entry.js",
|
||
&[("./dep.js", ModuleKind::Js, "export var z = 1;")],
|
||
)
|
||
.expect("star 再导出应编译通过");
|
||
assert!(builtin_names(&ir).contains("__moduleStar"), "匿名 star 应走 __moduleStar");
|
||
assert_eq!(ir.nested.len(), 1);
|
||
}
|
||
|
||
/// 面 4:循环导入在编译期报错,错误文本即循环检测点产物。
|
||
#[test]
|
||
fn circular_import_compile_error() {
|
||
let src_a = "import { y } from \"./b.js\"; export var x = 1;";
|
||
let result = emit_module(
|
||
src_a,
|
||
"./a.js",
|
||
&[
|
||
("./a.js", ModuleKind::Js, src_a),
|
||
("./b.js", ModuleKind::Js, "import { x } from \"./a.js\"; export var y = 2;"),
|
||
],
|
||
);
|
||
assert_eq!(
|
||
result.err().as_deref(),
|
||
Some("circular module import not supported: ./a.js"),
|
||
"Err 应来自循环检测点(自导入身份比较命中)"
|
||
);
|
||
}
|
||
|
||
/// 面 5:json 数据模块经 `__moduleData` 物化,数据源文本入子模块常量池。
|
||
#[test]
|
||
fn json_data_module() {
|
||
let json = r#"{"k":1}"#;
|
||
let (ir, loader) = emit_module(
|
||
"import data from \"./d.json\" assert { type: \"json\" }; data;",
|
||
"./entry.js",
|
||
&[("./d.json", ModuleKind::Json, json)],
|
||
)
|
||
.expect("数据模块编译应成功");
|
||
// import 属性应原样传给加载器。
|
||
let attrs = loader
|
||
.resolved
|
||
.iter()
|
||
.find(|(s, _)| s == "./d.json")
|
||
.map(|(_, a)| a.clone())
|
||
.expect("d.json 应被 resolve");
|
||
assert_eq!(attrs, vec![("type".to_string(), "json".to_string())]);
|
||
assert_eq!(ir.nested.len(), 1);
|
||
let data = &ir.nested[0];
|
||
assert!(builtin_names(data).contains("__moduleData"), "数据模块应链接 __moduleData");
|
||
let pool = pool_strings(data);
|
||
assert!(pool.contains("json") && pool.contains(json), "json 源文本应入池: {pool:?}");
|
||
}
|
||
|
||
/// 面 6:`export default` 匿名函数/生成器/箭头/类经 SetFunctionName 落隐式名 "default"。
|
||
/// 带方法的类必须命中构造器,而不是构造器之后 push 的方法子模块。
|
||
#[test]
|
||
fn export_default_anonymous_implicit_name() {
|
||
let (ir, _) = emit_module("export default function() {}", "./entry.js", &[]).expect("匿名函数声明应编译成功");
|
||
assert!(named_module(&ir, "default").is_some(), "匿名函数声明应落隐式名");
|
||
|
||
let (ir, _) = emit_module("export default function* () {}", "./entry.js", &[]).expect("匿名生成器声明应编译成功");
|
||
assert!(named_module(&ir, "default").is_some_and(|m| m.is_generator), "匿名生成器应落隐式名");
|
||
|
||
let (ir, _) = emit_module("export default (function() {})", "./entry.js", &[]).expect("括号函数表达式应编译成功");
|
||
assert!(named_module(&ir, "default").is_some(), "匿名函数表达式应落隐式名");
|
||
|
||
let (ir, _) = emit_module("export default (() => {})", "./entry.js", &[]).expect("箭头表达式应编译成功");
|
||
assert!(named_module(&ir, "default").is_some_and(|m| m.is_arrow), "匿名箭头应落隐式名");
|
||
|
||
let (ir, _) = emit_module("export default class { m() {} }", "./entry.js", &[]).expect("匿名类声明应编译成功");
|
||
let ctor = ir.nested.iter().find(|m| m.is_class_constructor).expect("类构造器应在 nested");
|
||
assert_eq!(ctor.function_name.as_deref(), Some("default"), "匿名类构造器应落隐式名");
|
||
assert!(named_module(&ir, "m").is_some(), "方法模块应保留自身名");
|
||
|
||
let (ir, _) = emit_module("export default (class { m() {} })", "./entry.js", &[]).expect("括号类表达式应编译成功");
|
||
let ctor = ir.nested.iter().find(|m| m.is_class_constructor).expect("类构造器应在 nested");
|
||
assert_eq!(ctor.function_name.as_deref(), Some("default"), "匿名类表达式构造器应落隐式名");
|
||
assert!(named_module(&ir, "m").is_some(), "方法模块应保留自身名");
|
||
}
|
||
|
||
/// 面 7:`export default` 具名变体与非常量表达式不被隐式名覆盖。
|
||
#[test]
|
||
fn export_default_implicit_name_named_unchanged() {
|
||
let (ir, _) = emit_module("export default class Foo { m() {} }", "./entry.js", &[]).expect("具名类声明应编译成功");
|
||
let ctor = ir.nested.iter().find(|m| m.is_class_constructor).expect("类构造器应在 nested");
|
||
assert_eq!(ctor.function_name.as_deref(), Some("Foo"));
|
||
|
||
let (ir, _) =
|
||
emit_module("export default (class Bar { m() {} })", "./entry.js", &[]).expect("具名类表达式应编译成功");
|
||
let ctor = ir.nested.iter().find(|m| m.is_class_constructor).expect("类构造器应在 nested");
|
||
assert_eq!(ctor.function_name.as_deref(), Some("Bar"));
|
||
|
||
let (ir, _) = emit_module("export default (function f() {})", "./entry.js", &[]).expect("具名函数表达式应编译成功");
|
||
assert!(named_module(&ir, "f").is_some(), "具名函数表达式应保留自身名");
|
||
|
||
let (ir, _) = emit_module("export default 42", "./entry.js", &[]).expect("字面量应编译成功");
|
||
assert!(named_module(&ir, "default").is_none(), "非函数值不应注入隐式名");
|
||
}
|
||
|
||
/// STORE_VAR 指令计数(合成绑定惰性化守卫用)。
|
||
fn store_var_count(ir: &IRFunction) -> usize {
|
||
ir.insts.iter().filter(|i| i.op == OpCode::STORE_VAR).count()
|
||
}
|
||
|
||
/// 面 8:无 self-import 的 default 表达式模块零 diff。
|
||
/// `export default 42` 不建合成 `*default*` 绑定,故除 export 注册外无任何
|
||
/// STORE_VAR(字面量经 LOAD_CONST 直接注册)。若合成绑定被无条件建立,会多出
|
||
/// 一条 STORE_VAR,本守卫即失败。
|
||
#[test]
|
||
fn default_expression_module_has_no_synthetic_binding() {
|
||
let (ir, _) = emit_module("export default 42", "./entry.js", &[]).expect("字面量应编译成功");
|
||
assert_eq!(store_var_count(&ir), 0, "default 表达式模块不应建合成绑定槽");
|
||
assert_eq!(native_calls_to(&ir, "__moduleSet"), 1, "default 只注册一次导出");
|
||
}
|
||
|
||
/// 面 8b:无 self-import 的 `export let x = 1` 不因 default 面多出槽位/指令。
|
||
/// 该模块唯一 STORE_VAR 是 x 自身的声明初始化;零 diff 守卫要求不出现第二个槽。
|
||
#[test]
|
||
fn named_export_without_self_import_keeps_single_store() {
|
||
let (ir, _) = emit_module("export let x = 1;", "./entry.js", &[]).expect("导出声明应编译成功");
|
||
assert_eq!(store_var_count(&ir), 1, "x 声明只应有一条 STORE_VAR");
|
||
assert_eq!(native_calls_to(&ir, "__moduleSet"), 1, "x 只注册一次导出");
|
||
}
|
||
|
||
/// 面 8c:无 source 再导出 `export { x }` 不引入合成绑定。
|
||
/// 局部 x 由普通声明承载,再导出只读值注册;无 self-import 时零额外 STORE_VAR。
|
||
#[test]
|
||
fn local_reexport_without_self_import_keeps_single_store() {
|
||
let (ir, _) = emit_module("let x = 1; export { x };", "./entry.js", &[]).expect("本地再导出应编译成功");
|
||
assert_eq!(store_var_count(&ir), 1, "x 声明只应有一条 STORE_VAR");
|
||
}
|
||
|
||
/// 面 9:self-import 别名以源绑定槽位承载,导出注册不再回写占位槽。
|
||
/// `export default 42` 有 self-import default 时合成绑定存在,但别名与源共享
|
||
/// 槽位:export 语句只发一次 LOAD_CONST + `__moduleSet`,无额外的回写 STORE_VAR。
|
||
#[test]
|
||
fn self_import_default_alias_has_no_placeholder_writeback() {
|
||
let (ir, _) = emit_module(
|
||
"import d from './self.js'; export default 42;",
|
||
"./self.js",
|
||
&[("./self.js", ModuleKind::Js, "export default 42;")],
|
||
)
|
||
.expect("self-import default 应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__moduleSet"), 1, "default 只注册一次导出");
|
||
// 别名与合成源绑定共享槽位:仅 default 表达式的绑定写入一条 STORE_VAR,
|
||
// 不出现旧占位路径的额外回写。
|
||
assert_eq!(store_var_count(&ir), 1, "不应出现占位回写带来的第二条 STORE_VAR");
|
||
}
|
||
|
||
/// 面 10:零 diff 守卫 G1——非 live 模块不发射 `__modulePreRegister`。
|
||
/// live 门控为自导入 `import * as ns from './self'`;纯导出、default 表达式与
|
||
/// 普通外部命名空间导入均不得出现预注册调用。
|
||
#[test]
|
||
fn no_pre_register_for_non_live_modules() {
|
||
let (ir, _) = emit_module("export let x = 1;", "./entry.js", &[]).expect("导出声明应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 0, "纯导出模块不得预注册");
|
||
|
||
let (ir, _) = emit_module("export default 42;", "./entry.js", &[]).expect("default 应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 0, "default 表达式模块不得预注册");
|
||
|
||
let (ir, _) = emit_module(
|
||
"import * as ns from './dep.js'; ns;",
|
||
"./entry.js",
|
||
&[("./dep.js", ModuleKind::Js, "export var z = 1;")],
|
||
)
|
||
.expect("外部命名空间导入应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 0, "外部 ns 导入不得预注册");
|
||
}
|
||
|
||
/// 面 10b:live 模块按静态导出名逐个预注册,`__moduleObject` 仍为零参调用。
|
||
#[test]
|
||
fn pre_register_for_self_namespace_import() {
|
||
let (ir, _) = emit_module(
|
||
"import * as ns from './self.js'; export const a = 1; export default 2;",
|
||
"./self.js",
|
||
&[("./self.js", ModuleKind::Js, "export const a = 1;")],
|
||
)
|
||
.expect("self-ns 模块应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 2, "a 与 default 各预注册一次");
|
||
assert!(pool_strings(&ir).contains("a") && pool_strings(&ir).contains("default"), "导出名应入池");
|
||
|
||
// __moduleObject 保持零实参签名:仅一次调用且压缩实参计数为 0。
|
||
let reg = builtin_reg(&ir, "__moduleObject").expect("应链接 __moduleObject");
|
||
let object_calls: Vec<_> = ir
|
||
.insts
|
||
.iter()
|
||
.filter(|i| i.op == OpCode::CALL_NATIVE && matches!(i.rd, oxide_ir::operand::Operand::Reg(r) if r == reg))
|
||
.collect();
|
||
assert_eq!(object_calls.len(), 1, "__moduleObject 只应调用一次");
|
||
assert_eq!(object_calls[0].ext.first().copied(), Some(0), "__moduleObject 应保持零实参");
|
||
}
|
||
|
||
/// 面 11:写穿发射门控——live 模块顶层重赋按导出名集合追加 `__moduleSet`;
|
||
/// 非 live 模块(无自导入 ns)重赋不得产生任何额外注册,IR 零变化。
|
||
#[test]
|
||
fn write_through_only_for_live_modules() {
|
||
// 一个源绑定 a 背两个导出名 a/b:export 声明注册一次、再导出注册一次,
|
||
// a = 2 的写穿各写 a、b 一次,共 4 次 `__moduleSet`。
|
||
let (ir, _) = emit_module(
|
||
"import * as ns from './self.js'; export let a = 1; export { a as b }; a = 2;",
|
||
"./self.js",
|
||
&[("./self.js", ModuleKind::Js, "export let a = 1;")],
|
||
)
|
||
.expect("live 模块应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__moduleSet"), 4, "live 模块重赋应写穿两个导出名");
|
||
|
||
// 非 live:重赋只写绑定槽,不得追加 `__moduleSet`。
|
||
let (ir, _) = emit_module("export let a = 1; a = 2;", "./entry.js", &[]).expect("非 live 模块应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__moduleSet"), 1, "非 live 模块重赋不得写穿");
|
||
}
|
||
|
||
/// 面 12:可重赋依赖激活活读与 Cell——依赖预注册并挂共享 cell,导入方顶层读走
|
||
/// `__moduleGet`;const-only 依赖零变化,导入方仍走链接期快照、依赖不预注册。
|
||
#[test]
|
||
fn reassignable_dep_activates_live_read_and_cell() {
|
||
let (ir, _) = emit_module(
|
||
"import { x } from './dep.js'; x;",
|
||
"./entry.js",
|
||
&[("./dep.js", ModuleKind::Js, "export const x = 1;")],
|
||
)
|
||
.expect("const-only 依赖应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__moduleGet"), 0, "const-only 依赖导入方不得活读");
|
||
assert!(native_calls_to(&ir, "__moduleLinkGet") >= 1, "导入绑定仍走链接期快照");
|
||
let dep = &ir.nested[0];
|
||
assert_eq!(native_calls_to(dep, "__modulePreRegister"), 0, "const-only 依赖不得预注册");
|
||
assert_eq!(native_calls_to(dep, "__moduleSetCell"), 0, "const-only 依赖不得挂 cell");
|
||
|
||
let (ir, _) = emit_module(
|
||
"import { x } from './dep.js'; x;",
|
||
"./entry.js",
|
||
&[(
|
||
"./dep.js",
|
||
ModuleKind::Js,
|
||
"var x = 1; export { x }; globalThis.f = function(){ x = 2; };",
|
||
)],
|
||
)
|
||
.expect("可重赋依赖应编译成功");
|
||
let dep = &ir.nested[0];
|
||
assert!(native_calls_to(dep, "__modulePreRegister") >= 1, "可重赋依赖应预注册导出");
|
||
assert!(native_calls_to(dep, "__moduleSetCell") >= 1, "被捕获导出应挂共享 cell");
|
||
assert!(native_calls_to(&ir, "__moduleGet") >= 1, "导入方顶层读应活读");
|
||
}
|
||
|
||
/// 面 13:入口模块的可重赋导出不激活预注册(`module_live_dep` 仅依赖模块成立)。
|
||
#[test]
|
||
fn entry_module_reassignable_export_not_pre_registered() {
|
||
let (ir, _) = emit_module("export let x = 1; x = 2;", "./entry.js", &[]).expect("入口模块应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 0, "入口模块不得预注册");
|
||
assert_eq!(native_calls_to(&ir, "__moduleSetCell"), 0, "入口模块不得挂 cell");
|
||
}
|
||
|
||
/// 面 14:defer-only 依赖不求值——无 `__moduleEval`,发 `__moduleDeferObject`
|
||
/// 构造 deferred 对象,逐名 `__modulePreRegister` 预注册静态导出名。
|
||
#[test]
|
||
fn defer_only_dep_not_evaluated() {
|
||
let (ir, _) = emit_module(
|
||
"import defer * as ns from \"./dep.js\"; ns;",
|
||
"./entry.js",
|
||
&[("./dep.js", ModuleKind::Js, "export var a = 1; export const b = 2;")],
|
||
)
|
||
.expect("defer-only 依赖应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__moduleEval"), 0, "defer-only 依赖不得求值");
|
||
assert_eq!(native_calls_to(&ir, "__moduleDeferObject"), 1, "deferred 对象应构造一次");
|
||
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 2, "a 与 b 各预注册一次");
|
||
let pool = pool_strings(&ir);
|
||
assert!(pool.contains("a") && pool.contains("b"), "导出名应入池: {pool:?}");
|
||
assert_eq!(ir.nested.len(), 1, "依赖模块应编入子模块树");
|
||
}
|
||
|
||
/// 面 15:mixed 依赖按 eager 语句序求值,defer 语句不计入求值序。
|
||
/// 入口的 eager 序为 [setup, dep-2, dep-1](dep-1 为 mixed);dep-1 内的
|
||
/// defer-only dep-1.2 不求值。
|
||
#[test]
|
||
fn mixed_dep_evaluates_in_eager_order() {
|
||
let (ir, _) = emit_module(
|
||
"import \"./setup.js\"; import defer * as ns1 from \"./dep-1.js\"; import \"./dep-2.js\"; import \"./dep-1.js\";",
|
||
"./entry.js",
|
||
&[
|
||
("./setup.js", ModuleKind::Js, "globalThis.evaluations = [];"),
|
||
(
|
||
"./dep-1.js",
|
||
ModuleKind::Js,
|
||
"import \"./dep-1.1.js\"; import defer * as ns_1_2 from \"./dep-1.2.js\"; globalThis.evaluations.push(1); export { ns_1_2 };",
|
||
),
|
||
("./dep-2.js", ModuleKind::Js, "globalThis.evaluations.push(2);"),
|
||
("./dep-1.1.js", ModuleKind::Js, "globalThis.evaluations.push(1.1);"),
|
||
("./dep-1.2.js", ModuleKind::Js, "globalThis.evaluations.push(1.2);"),
|
||
],
|
||
)
|
||
.expect("mixed 依赖应编译成功");
|
||
// 入口:3 个 eager 依赖(setup, dep-2, dep-1)各求值一次。
|
||
assert_eq!(native_calls_to(&ir, "__moduleEval"), 3, "入口应求值 3 个 eager 依赖");
|
||
// 入口:dep-1 为 mixed,deferred 对象构造一次。
|
||
assert_eq!(native_calls_to(&ir, "__moduleDeferObject"), 1, "mixed dep-1 应构造 deferred 对象一次");
|
||
// dep-1 是第 3 个 nested 模块(eager_order = [setup, dep-2, dep-1])。
|
||
let dep1 = &ir.nested[2];
|
||
// dep-1:1 个 eager 依赖(dep-1.1)求值,defer-only 依赖(dep-1.2)不求值。
|
||
assert_eq!(native_calls_to(dep1, "__moduleEval"), 1, "dep-1 应只求值 dep-1.1");
|
||
// dep-1:dep-1.2 为 defer-only,deferred 对象构造一次。
|
||
assert_eq!(native_calls_to(dep1, "__moduleDeferObject"), 1, "dep-1.2 应构造 deferred 对象一次");
|
||
// dep-1.2 是 dep-1 的第 2 个 nested 模块(eager_order = [dep-1.1], defer_order = [dep-1.1, dep-1.2])。
|
||
let dep12 = &dep1.nested[1];
|
||
assert_eq!(native_calls_to(dep12, "__moduleEval"), 0, "dep-1.2 不得求值");
|
||
}
|
||
|
||
/// 面 16:自导入 defer 走哨兵分支——不递归、无环检测 Err、发
|
||
/// `__moduleDeferObject(undefined, undefined)`。
|
||
#[test]
|
||
fn self_import_defer_sentinel() {
|
||
let (ir, _) = emit_module(
|
||
"import defer * as self from \"./self.js\"; self;",
|
||
"./self.js",
|
||
&[("./self.js", ModuleKind::Js, "export var foo = 1;")],
|
||
)
|
||
.expect("自导入 defer 应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__moduleEval"), 0, "自导入 defer 不得求值");
|
||
assert_eq!(native_calls_to(&ir, "__moduleDeferObject"), 1, "哨兵 deferred 对象应构造一次");
|
||
assert_eq!(ir.nested.len(), 0, "自导入 defer 不得递归");
|
||
}
|
||
|
||
/// 面 17:祖先 defer 回边(dep → main)走哨兵分支——编译成功、无环检测 Err、
|
||
/// 不递归。main eager 导入 dep,dep defer 导入 main(导入图含环)。
|
||
#[test]
|
||
fn ancestor_defer_back_edge_sentinel() {
|
||
let main_src = "import \"./dep.js\"; globalThis.result = 1;";
|
||
let dep_src = "import defer * as main from \"./main.js\"; main.foo;";
|
||
let (ir, _) = emit_module(
|
||
main_src,
|
||
"./main.js",
|
||
&[("./main.js", ModuleKind::Js, main_src), ("./dep.js", ModuleKind::Js, dep_src)],
|
||
)
|
||
.expect("祖先 defer 回边应编译成功");
|
||
// main:1 个 eager 依赖(dep)求值。
|
||
assert_eq!(native_calls_to(&ir, "__moduleEval"), 1, "main 应求值 dep");
|
||
// main:无 defer,无 deferred 对象。
|
||
assert_eq!(native_calls_to(&ir, "__moduleDeferObject"), 0, "main 不得构造 deferred 对象");
|
||
// dep(nested):1 个 defer-only 依赖(main)哨兵,不求值。
|
||
let dep = &ir.nested[0];
|
||
assert_eq!(native_calls_to(dep, "__moduleEval"), 0, "dep 不得求值 main");
|
||
assert_eq!(native_calls_to(dep, "__moduleDeferObject"), 1, "dep 应为 main 构造哨兵 deferred 对象");
|
||
// dep:不递归(main 未编入 dep 的 nested)。
|
||
assert_eq!(dep.nested.len(), 0, "dep 不得递归进 main");
|
||
}
|
||
|
||
/// 面 18:零 diff 守卫——非 defer 模块无 `__moduleDeferObject`、无依赖预注册。
|
||
#[test]
|
||
fn no_defer_object_for_non_defer_modules() {
|
||
let (ir, _) = emit_module(
|
||
"import * as ns from \"./dep.js\"; ns;",
|
||
"./entry.js",
|
||
&[("./dep.js", ModuleKind::Js, "export var z = 1;")],
|
||
)
|
||
.expect("非 defer 模块应编译成功");
|
||
assert_eq!(native_calls_to(&ir, "__moduleDeferObject"), 0, "非 defer 模块不得构造 deferred 对象");
|
||
assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 0, "非 defer 模块不得预注册依赖");
|
||
}
|
||
|
||
/// 面 19:模块自身顶层 `await` 使 `is_async` 为真,体 await 编译为 AWAIT;
|
||
/// 无异步依赖时不发聚合 gate。
|
||
#[test]
|
||
fn tla_module_compiles_async_without_gate() {
|
||
let (ir, _) = emit_module("const x = await Promise.resolve(1); export { x };", "./entry.js", &[])
|
||
.expect("TLA 模块应编译成功");
|
||
assert!(ir.is_async, "含顶层 await 的模块应标记异步求值");
|
||
assert_eq!(
|
||
ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(),
|
||
1,
|
||
"顶层 await 应编译为一条 AWAIT"
|
||
);
|
||
assert_eq!(native_calls_to(&ir, "__moduleWaitAll"), 0, "无异步依赖不得发聚合 gate");
|
||
}
|
||
|
||
/// 面 20:无顶层 `await` 的模块保持同步编译(`is_async` 假、主身零 AWAIT);
|
||
/// 异步函数体内的 await 不使模块变异步(await 落在嵌套子模块)。
|
||
#[test]
|
||
fn non_tla_module_stays_sync() {
|
||
let (ir, _) =
|
||
emit_module("async function f() { return await Promise.resolve(1); } export { f };", "./entry.js", &[])
|
||
.expect("非 TLA 模块应编译成功");
|
||
assert!(!ir.is_async, "无顶层 await 的模块不得标记异步");
|
||
assert_eq!(ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(), 0, "主身不得含 AWAIT");
|
||
}
|
||
|
||
/// 面 21:`for await…of` 在模块代码使模块异步(规范强制模块异步求值,
|
||
/// `await` 关键字触发 [[HasTLA]];右值与体照常游走)。
|
||
#[test]
|
||
fn for_await_of_makes_module_async() {
|
||
let (ir, _) = emit_module("for await (const x of iter) { globalThis.v = x; }", "./entry.js", &[])
|
||
.expect("for await…of 模块应编译成功");
|
||
assert!(ir.is_async, "for await…of 应使模块标记异步");
|
||
}
|
||
|
||
/// 面 22:普通 `for…of`(无 `await`)不使模块异步,主身零 AWAIT。
|
||
#[test]
|
||
fn for_of_is_not_tla() {
|
||
let (ir, _) =
|
||
emit_module("for (const x of iter) { globalThis.v = x; }", "./entry.js", &[]).expect("for…of 模块应编译成功");
|
||
assert!(!ir.is_async, "普通 for…of 不得使模块标记异步");
|
||
assert_eq!(ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(), 0, "主身不得含 AWAIT");
|
||
}
|
||
|
||
/// 面 23:异步 eager 依赖聚合 gate——依赖含顶层 `await` 时 `__moduleEval` 返回
|
||
/// capability promise,导入方发 `__moduleWaitAll` 加单 AWAIT 挂起,模块标记异步。
|
||
#[test]
|
||
fn async_dep_emits_wait_all_gate() {
|
||
let (ir, _) = emit_module(
|
||
"import { x } from \"./dep.js\"; export { x };",
|
||
"./entry.js",
|
||
&[("./dep.js", ModuleKind::Js, "const v = await Promise.resolve(1); export const x = v;")],
|
||
)
|
||
.expect("含异步依赖的模块应编译成功");
|
||
assert!(ir.is_async, "含异步依赖的导入方应标记异步求值");
|
||
assert_eq!(native_calls_to(&ir, "__moduleWaitAll"), 1, "聚合 gate 应发一次");
|
||
assert_eq!(ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(), 1, "gate 应以单 AWAIT 挂起");
|
||
}
|
||
|
||
/// 面 24:同步依赖零 diff 守卫——无 gate、无 AWAIT、模块保持同步。
|
||
#[test]
|
||
fn sync_dep_no_gate() {
|
||
let (ir, _) = emit_module(
|
||
"import { x } from \"./dep.js\"; export { x };",
|
||
"./entry.js",
|
||
&[("./dep.js", ModuleKind::Js, "export const x = 1;")],
|
||
)
|
||
.expect("含同步依赖的模块应编译成功");
|
||
assert!(!ir.is_async, "无顶层 await 且依赖同步的模块不得标记异步");
|
||
assert_eq!(native_calls_to(&ir, "__moduleWaitAll"), 0, "同步依赖不得发聚合 gate");
|
||
assert_eq!(ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(), 0, "主身不得含 AWAIT");
|
||
}
|
||
|
||
/// 面 25:`export const x = await ...`(ExportNamedDeclaration 带变量声明)是
|
||
/// 顶层 await,模块标记异步、体 await 编译为 AWAIT。
|
||
#[test]
|
||
fn export_named_var_decl_await_makes_module_async() {
|
||
let (ir, _) = emit_module("export const x = await Promise.resolve(1);", "./entry.js", &[])
|
||
.expect("export const x = await 模块应编译成功");
|
||
assert!(ir.is_async, "export const x = await ... 应标记模块异步求值");
|
||
assert_eq!(
|
||
ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(),
|
||
1,
|
||
"顶层 await 应编译为一条 AWAIT"
|
||
);
|
||
}
|
||
|
||
/// 面 26:`export default await ...`(ExportDefaultDeclaration 表达式形态)是
|
||
/// 顶层 await,模块标记异步、体 await 编译为 AWAIT。
|
||
#[test]
|
||
fn export_default_await_makes_module_async() {
|
||
let (ir, _) = emit_module("export default await Promise.resolve(1);", "./entry.js", &[])
|
||
.expect("export default await 模块应编译成功");
|
||
assert!(ir.is_async, "export default await ... 应标记模块异步求值");
|
||
assert_eq!(
|
||
ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(),
|
||
1,
|
||
"顶层 await 应编译为一条 AWAIT"
|
||
);
|
||
}
|
||
|
||
/// 面 27:`export const f = async () => {}`(导出声明初始化器为异步函数表达式)
|
||
/// 不使模块异步——函数体是独立异步语义边界,不进入游走。
|
||
#[test]
|
||
fn export_named_async_fn_expr_not_tla() {
|
||
let (ir, _) = emit_module("export const f = async () => { await Promise.resolve(1); };", "./entry.js", &[])
|
||
.expect("导出异步函数表达式模块应编译成功");
|
||
assert!(!ir.is_async, "异步函数表达式体不得使模块标记异步");
|
||
assert_eq!(ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(), 0, "主身不得含 AWAIT");
|
||
}
|
||
|
||
/// 面 28:`if (await ...)`(IfStatement 的 test 表达式)是顶层 await,
|
||
/// 模块标记异步、体 await 编译为 AWAIT。
|
||
#[test]
|
||
fn if_test_await_makes_module_async() {
|
||
let (ir, _) = emit_module("if (await Promise.resolve(1)) { globalThis.v = 1; }", "./entry.js", &[])
|
||
.expect("if (await ...) 模块应编译成功");
|
||
assert!(ir.is_async, "if (await ...) 应标记模块异步求值");
|
||
assert_eq!(
|
||
ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(),
|
||
1,
|
||
"顶层 await 应编译为一条 AWAIT"
|
||
);
|
||
}
|
||
|
||
/// 面 29:`export class A extends (await ...)`(ClassDeclaration 的 extends
|
||
/// 表达式)是顶层 await,模块标记异步、体 await 编译为 AWAIT;类体不进入游走。
|
||
#[test]
|
||
fn extends_await_makes_module_async() {
|
||
let (ir, _) = emit_module("export class A extends (await Promise.resolve(1)) {}", "./entry.js", &[])
|
||
.expect("extends (await ...) 模块应编译成功");
|
||
assert!(ir.is_async, "extends (await ...) 应标记模块异步求值");
|
||
assert_eq!(
|
||
ir.insts.iter().filter(|i| i.op == OpCode::AWAIT).count(),
|
||
1,
|
||
"顶层 await 应编译为一条 AWAIT"
|
||
);
|
||
}
|