fix: 模块自导入绑定改为源绑定活别名

This commit is contained in:
2026-09-17 22:54:06 +00:00
parent 6aa87cd40e
commit 2bd6da4b00
6 changed files with 480 additions and 70 deletions
+10
View File
@@ -142,7 +142,10 @@ pub struct CompileCtx {
/// 自导入(import from 自身)的 source 字符串集合:绑定走别名语义,不能链接期快照。
pub(crate) module_self_import_specs: HashSet<String>,
/// 自导入别名:导出名 → 本地绑定槽寄存器(export 语句执行时回写绑定值)。
/// 仅承载无法静态解析源绑定的退化占位路径(star 转发的自导入名)。
pub(crate) module_self_aliases: HashMap<String, u32>,
/// 自导入别名(本地名,基源绑定名):供别名捕获后处理合并源/别名 cell。
pub(crate) module_alias_pairs: Vec<(String, String)>,
/// 标签模板 site 计数器:本编译树内全局唯一(子 ctx 继承父值继续递增)。
/// 运行时与模块 flat_id 组成模板对象缓存键,保证同一编译树同 site 恒返回
/// 同一对象、不同编译树(eval 每次编译)互不共享。
@@ -226,6 +229,7 @@ impl CompileCtx {
module_dep_ns_regs: HashMap::new(),
module_self_import_specs: HashSet::new(),
module_self_aliases: HashMap::new(),
module_alias_pairs: Vec::new(),
next_template_site: 0,
}
}
@@ -379,6 +383,12 @@ impl CompileCtx {
self.scopes.symbols.init_var(name);
}
/// 声明本地名为源绑定活引用的别名(模块自导入):槽位复用源寄存器,
/// TDZ/提升/活值/不可变全部委托源绑定。
pub(crate) fn add_alias(&mut self, local: &str, source: &str) -> Result<(), String> {
self.scopes.symbols.add_alias(local, source)
}
pub(crate) fn next_label_id(&mut self) -> u32 {
let id = self.labels.label_counter;
self.labels.label_counter += 1;
+256 -54
View File
@@ -1,11 +1,12 @@
//! 模块编译:import/export 解析、依赖加载(递归)、快照式链接与命名空间。
//!
//! 快照式链接:依赖模块先整体求值(`__moduleEval`)并返回命名空间对象,
//! 导入绑定在 prelude 一次性初始化;`export` 语句就地注册导出值。
//! 未支持:live binding(导入绑定活态跟随源模块值变化)、defer(延迟求值)、
//! source-phase——导入绑定为链接期快照,不跟随源模块值变化;循环导入在编译期跳过。
//! 普通依赖的导入绑定在 prelude 一次性链接为快照;自导入(`import ... from
//! 自身`)的局部名解析为本模块源绑定的活别名,TDZ/提升/活值/不可变四语义全部
//! 委托源绑定。`export` 语句就地注册导出值。
//! 未支持:跨模块 live binding(导入绑定不跟随依赖模块值变化)、defer(延迟
//! 求值)、source-phase;star 转发的自导入名退化为链接期快照;循环导入在编译期跳过。
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use crate::capture::{collect_captured_bindings, collect_own_binding_names};
@@ -158,10 +159,95 @@ fn is_hoisted_function_decl(stmt: &Statement) -> bool {
Statement::ExportNamedDeclaration(exp) => {
matches!(&exp.declaration, Some(Declaration::FunctionDeclaration(_)))
}
// export default function 声明形态在实例化期绑定并预初始化(提升至模块求值前),
// 与普通函数声明同一发射次序。
Statement::ExportDefaultDeclaration(exp) => {
matches!(&exp.declaration, ExportDefaultDeclarationKind::FunctionDeclaration(_))
}
_ => false,
}
}
/// 合成默认导出的源绑定名(匿名 default 函数/类与 default 表达式共用)。
/// 含 `*`,与任何用户标识符都不可能重名。
pub(crate) const SYNTHETIC_DEFAULT_BINDING: &str = "*default*";
/// 收集模块自身导出名 → 源绑定名映射:把 self-import 的局部名别名到该映射给出的
/// 源绑定,使别名读/写/TDZ/const 全部经源绑定通道自然成立。
///
/// # 边界与前提
/// - 带声明的导出:var/fn/class 的每个绑定名 → 自身名。
/// - 无 source 再导出 `export { x as y }`:`y → x`。
/// - self source 再导出 `export { local as indirect } from './self'`:`indirect → local`。
/// - 非 self source 再导出与 star 转发不入表(跨模块面由运行期刷新承载),
/// 映射缺失时调用方回退非别名路径。
/// - `export default`:具名 fn/class → 具名 id;匿名 fn/class 与表达式 → 合成 `*default*`。
///
/// # 注意事项
/// - 须在依赖收集循环之后构建(self 再导出判定读 `module_self_import_specs`)。
fn module_export_name_map(body: &[Statement], self_specs: &HashSet<String>) -> HashMap<String, String> {
let mut map = HashMap::new();
for stmt in body {
match stmt {
Statement::ExportNamedDeclaration(exp) => {
if let Some(decl) = &exp.declaration {
match decl {
Declaration::VariableDeclaration(vd) => {
for d in &vd.declarations {
if let BindingPattern::BindingIdentifier(bi) = &d.id {
let name = bi.name.to_string();
map.insert(name.clone(), name);
}
}
}
Declaration::FunctionDeclaration(fd) => {
if let Some(id) = &fd.id {
let name = id.name.to_string();
map.insert(name.clone(), name);
}
}
Declaration::ClassDeclaration(cl) => {
if let Some(id) = &cl.id {
let name = id.name.to_string();
map.insert(name.clone(), name);
}
}
_ => {}
}
} else if let Some(src) = &exp.source {
// self 再导出:导出名解析到本模块的本地名,本模块体执行中
// 该本地名(可能是 import 名或本地声明)对别名可见。
if self_specs.contains(src.value.as_str()) {
for spec in &exp.specifiers {
map.insert(module_export_name_str(&spec.exported), module_export_name_str(&spec.local));
}
}
} else {
// 无 source 再导出:导出名直接映射到本地声明名。
for spec in &exp.specifiers {
map.insert(module_export_name_str(&spec.exported), module_export_name_str(&spec.local));
}
}
}
Statement::ExportDefaultDeclaration(exp) => {
let source = match &exp.declaration {
ExportDefaultDeclarationKind::FunctionDeclaration(fd) => {
fd.id.as_ref().map(|id| id.name.to_string())
}
ExportDefaultDeclarationKind::ClassDeclaration(cl) => cl.id.as_ref().map(|id| id.name.to_string()),
_ => None,
};
map.insert(
DEFAULT_EXPORT_NAME.to_string(),
source.unwrap_or_else(|| SYNTHETIC_DEFAULT_BINDING.to_string()),
);
}
_ => {}
}
}
map
}
impl Emitter {
/// 编译 ES module:顶层 body + 递归依赖。
/// `module_path` 为模块文件路径;入口即归一为规范绝对路径(依赖解析基准 =
@@ -274,16 +360,17 @@ impl Emitter {
}
}
// —— 导入绑定初始化(const 语义;命名空间绑定直接引用 ns 对象)——
// —— 导入绑定初始化(普通依赖为链接期快照;命名空间绑定直接引用 ns 对象;
// 自导入绑定解析为本模块源绑定的活别名)——
let own_export_names = module_own_export_names(body);
// 无命名 `export * from 'x'` 会把依赖的全部导出转发给自身;自导入名可能来自
// star 再导出,编译期无法静态判定该名是否存在于自身导出,故链接校验放宽。
let has_unnamed_star = body
.iter()
.any(|s| matches!(s, Statement::ExportAllDeclaration(e) if e.exported.is_none()));
// 自导入(import from 自身)的绑定在 body 执行中由 export 语句回写:
// prelude 先初始化为 undefined(规范:实例化后未求值前 var 源绑定为
// undefined),并登记 导出名 → 本地绑定槽 供回写。
// 导出名 → 本模块源绑定名:self-import 的局部名别名到该源绑定。star 转发的
// 自导入名不在表内(退化为下方非别名占位路径)。
let export_name_map = module_export_name_map(body, &ctx.module_self_import_specs);
for stmt in body {
if let Statement::ImportDeclaration(imp) = stmt {
let dep_spec = imp.source.value.to_string();
@@ -297,24 +384,21 @@ impl Emitter {
match sp {
ImportDeclarationSpecifier::ImportSpecifier(s) => {
let imported = module_export_name_str(&s.imported);
let alias_source = is_self.then(|| export_name_map.get(&imported)).flatten();
if is_self && !own_export_names.contains(&imported) && !has_unnamed_star {
return Err(format!("requested module export is not exported: {imported}"));
}
if let Some(source) = alias_source {
// 导出名可静态解析到本模块源绑定:局部名注册为
// 源绑定的活别名(源不存在时回退占位路径)。
if ctx.add_alias(s.local.name.as_str(), source).is_ok() {
ctx.module_alias_pairs.push((s.local.name.to_string(), source.to_string()));
continue;
}
}
if is_self {
if !own_export_names.contains(&imported) && !has_unnamed_star {
return Err(format!("requested module export is not exported: {imported}"));
}
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
self.emit_bind_target(
s.local.name.as_str(),
undef_reg,
VariableDeclarationKind::Const,
true,
false,
ctx,
)?;
if let Ok(reg) = ctx.lookup(s.local.name.as_str()) {
ctx.module_self_aliases.insert(imported, reg);
}
let alias_reg = self.emit_self_alias_placeholder(s.local.name.as_str(), ctx)?;
ctx.module_self_aliases.insert(imported, alias_reg);
} else {
let name_reg = self.load_string_const(&imported, ctx);
let val_reg =
@@ -330,24 +414,31 @@ impl Emitter {
}
}
ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
let alias_source =
is_self.then(|| export_name_map.get(DEFAULT_EXPORT_NAME)).flatten().cloned();
if is_self && !own_export_names.contains(DEFAULT_EXPORT_NAME) {
return Err("requested module export is not exported: default".into());
}
if let Some(source) = alias_source {
// 匿名 default 函数/类与 default 表达式无本地声明名,
// 源绑定须惰性建:只在此处存在 self-import default 时
// 建立,保证无 self-import 的模块零 IR 变化。
if source == SYNTHETIC_DEFAULT_BINDING
&& ctx.scopes.symbols.lookup_any(&source).is_none()
{
let reg = ctx.alloc_reg();
ctx.declare_predeclared(&source, reg, VariableDeclarationKind::Let, false)?;
// 表达式形态 TDZ 保持;函数声明形态由提升臂在求值
// 前初始化,此处不做初始化。
}
if ctx.add_alias(s.local.name.as_str(), &source).is_ok() {
ctx.module_alias_pairs.push((s.local.name.to_string(), source.clone()));
continue;
}
}
if is_self {
if !own_export_names.contains(DEFAULT_EXPORT_NAME) {
return Err("requested module export is not exported: default".into());
}
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
self.emit_bind_target(
s.local.name.as_str(),
undef_reg,
VariableDeclarationKind::Const,
true,
false,
ctx,
)?;
if let Ok(reg) = ctx.lookup(s.local.name.as_str()) {
ctx.module_self_aliases.insert(DEFAULT_EXPORT_NAME.to_string(), reg);
}
let alias_reg = self.emit_self_alias_placeholder(s.local.name.as_str(), ctx)?;
ctx.module_self_aliases.insert(DEFAULT_EXPORT_NAME.to_string(), alias_reg);
} else {
let name_reg = self.load_string_const(DEFAULT_EXPORT_NAME, ctx);
let val_reg =
@@ -380,6 +471,32 @@ impl Emitter {
}
}
// —— 别名捕获后处理:源绑定被嵌套函数捕获时,别名必须落到同一 cell,
// 否则两处各持一份值。无捕获时别名读经源寄存器天然活。此后源被捕获的
// var 名在模块入口统一 MAKE_CELL(undefined),与脚本顶层实例化同语义。——
self.reconcile_alias_captures(ctx);
{
let mut var_names: Vec<String> = crate::capture::collect_var_binding_names(body)
.into_iter()
.filter(|n| !ctx.param_names.contains(n.as_str()))
.filter(|n| ctx.captured_bindings.contains_key(n))
.collect();
var_names.sort();
if !var_names.is_empty() {
let undef_reg = self.emit_undefined(ctx);
for name in var_names {
if let Some(&cell_idx) = ctx.captured_bindings.get(&name) {
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(undef_reg),
Operand::Imm(cell_idx as u16),
Operand::None,
));
}
}
}
}
// —— re-export(export { x } from 'mod')链接检查:实例化期解析,须先于
// body 求值。$DONOTEVALUATE 是 test262 测试框架的断言工具,断言链接错误先于
// body 求值抛出,故链接检查必须先于 body 发射。——
@@ -511,15 +628,72 @@ impl Emitter {
}
}
/// 自导入绑定的非别名占位:源绑定无法静态解析(star 转发的自导入名)时,
/// prelude 把局部名绑成已初始化的 undefined 常量槽并登记回写,由 export
/// 语句执行时就地刷新。返回该本地绑定槽寄存器。
fn emit_self_alias_placeholder(&self, local: &str, ctx: &mut CompileCtx) -> Result<u32, String> {
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
self.emit_bind_target(local, undef_reg, VariableDeclarationKind::Const, true, false, ctx)?;
ctx.lookup(local)
}
fn emit_module_set(&self, ctx: &mut CompileCtx, ns_reg: u32, name: &str, value_reg: u32) -> Result<(), String> {
let name_reg = self.load_string_const(name, ctx);
self.emit_module_call(ctx, "__moduleSet", &[ns_reg, name_reg, value_reg])?;
Ok(())
}
/// 别名捕获后处理:把源/别名对合并到同一 cell。
///
/// # 边界与前提
/// - `captured_bindings` 是名字级索引;源与别名共用寄存器槽,但捕获分析按
/// 名字给出各自(或仅一方)的 entry。两侧都有时以源 cell 为准并把别名指向
/// 同一 cell;仅别名有 cell 时把源也指向该 cell(别名会被 MAKE_CELL 建 cell)。
/// - 两者都未被捕获时读路径直接读源寄存器,天然活值,无需处理。
///
/// # 副作用
/// - 修改 `captured_bindings` 映射;使 `cells_needed` 与实际使用的 cell
/// 上限一致(多余 entry 无害,缺失 entry 会导致子函数 upvalue 索引错位)。
fn reconcile_alias_captures(&self, ctx: &mut CompileCtx) {
let pairs = std::mem::take(&mut ctx.module_alias_pairs);
for (local, source) in pairs {
let local_cell = ctx.captured_bindings.get(&local).copied();
let source_cell = ctx.captured_bindings.get(&source).copied();
match (local_cell, source_cell) {
(Some(idx), _) => {
// 别名被捕获:源须共用同一 cell(源尚未捕获时补映射)。
if source_cell.is_none() {
ctx.captured_bindings.insert(source.clone(), idx);
}
}
(None, Some(idx)) => {
// 仅源被捕获:别名读经源 cell,补别名映射使子函数引用别名时
// upvalue 解析到同一 cell。
ctx.captured_bindings.insert(local.clone(), idx);
}
(None, None) => {}
}
}
}
fn load_var_reg(&self, name: &str, ctx: &mut CompileCtx) -> Result<u32, String> {
let var_reg = ctx.lookup(name)?;
let val_reg = ctx.alloc_reg();
// 被捕获绑定只更新 cell,寄存器停留在捕获前的旧值;导出注册须经 cell
// 读取当前值,否则命名空间属性固化声明时的快照。
if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
if ctx.visible_binding_reg(name).is_some() {
ctx.inst(Inst::new(
OpCode::CELL_GET,
Operand::Reg(val_reg),
Operand::Reg(var_reg),
Operand::Imm(cell_idx as u16),
));
return Ok(val_reg);
}
}
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(val_reg), Operand::Reg(var_reg), Operand::None));
Ok(val_reg)
}
@@ -593,16 +767,20 @@ impl Emitter {
let val_reg = match &exp.declaration {
ExportDefaultDeclarationKind::FunctionDeclaration(fd) => {
let reg = self.emit_function_expression(fd, ctx)?;
if let Some(id) = &fd.id {
self.emit_bind_target(
id.name.as_str(),
reg,
VariableDeclarationKind::Const,
true,
false,
ctx,
)?;
} else {
// 具名 default 函数绑定自身 id 名;匿名 default 函数绑定合成
// `*default*` 源名(仅在该绑定已由 self-import 惰性建立时)。
// 声明形态的函数绑定可变(CreateMutableBinding),提升臂已在
// 预声明期预初始化,此处是声明点的赋值。
let binding = fd.id.as_ref().map(|id| id.name.to_string()).or_else(|| {
ctx.scopes
.symbols
.lookup_any(SYNTHETIC_DEFAULT_BINDING)
.map(|_| SYNTHETIC_DEFAULT_BINDING.to_string())
});
if let Some(name) = binding {
self.emit_bind_target(&name, reg, VariableDeclarationKind::Var, false, false, ctx)?;
}
if fd.id.is_none() {
set_implicit_name_of_last_nested(ctx, DEFAULT_EXPORT_NAME);
}
reg
@@ -623,6 +801,17 @@ impl Emitter {
false,
ctx,
)?;
} else if ctx.scopes.symbols.lookup_any(SYNTHETIC_DEFAULT_BINDING).is_some() {
// 匿名 default 类按规范在求值点初始化合成 `*default*`
// 绑定(仅在存在 self-import default 时惰性建立)。
self.emit_bind_target(
SYNTHETIC_DEFAULT_BINDING,
reg,
VariableDeclarationKind::Let,
false,
false,
ctx,
)?;
}
reg
}
@@ -630,7 +819,7 @@ impl Emitter {
let expr = other
.as_expression()
.ok_or_else(|| "unsupported export default declaration".to_string())?;
if crate::is_anonymous_function_definition(expr) {
let reg = if crate::is_anonymous_function_definition(expr) {
match strip_parens(expr) {
// 匿名类:构造器落名后还会 push 方法子模块,必须经
// implicit_name 写构造器;具名类由 ctor_name 优先。
@@ -647,7 +836,20 @@ impl Emitter {
}
} else {
self.emit_expression(expr, ctx)?
};
// default 表达式/匿名类的合成源绑定只在存在 self-import default
// 时惰性建立;无 self-import 时保持零 IR 变化(不建绑定、不赋值)。
if ctx.scopes.symbols.lookup_any(SYNTHETIC_DEFAULT_BINDING).is_some() {
self.emit_bind_target(
SYNTHETIC_DEFAULT_BINDING,
reg,
VariableDeclarationKind::Let,
false,
false,
ctx,
)?;
}
reg
}
};
self.emit_module_set(ctx, ns_reg, DEFAULT_EXPORT_NAME, val_reg)?;
+15 -16
View File
@@ -15,15 +15,20 @@ impl Emitter {
Some(Declaration::FunctionDeclaration(f)) => f,
_ => continue,
},
// export default function foo(){}:foo 绑定由 lexical predeclare 以
// Const 预声明(emit 侧 emit_bind_target(Const) 消费预登记槽)。
// export default function:具名声明名 / 匿名的合成 `*default*` 名
// 按函数声明提升并预初始化(模块实例化期即绑定函数对象)。
Statement::ExportDefaultDeclaration(exp) => match &exp.declaration {
ExportDefaultDeclarationKind::FunctionDeclaration(f) => f,
_ => continue,
},
_ => continue,
};
let Some(identifier) = &function.id else {
continue;
let name = match &function.id {
Some(identifier) => identifier.name.to_string(),
None => "*default*".to_string(),
};
let reg = ctx.alloc_reg();
let _ = ctx.declare_initialized(identifier.name.as_str(), reg, VariableDeclarationKind::Var, false);
let _ = ctx.declare_initialized(name.as_str(), reg, VariableDeclarationKind::Var, false);
}
}
@@ -342,23 +347,17 @@ impl Emitter {
}
}
}
Statement::ExportDefaultDeclaration(exp) => match &exp.declaration {
ExportDefaultDeclarationKind::ClassDeclaration(cd) => {
Statement::ExportDefaultDeclaration(exp) => {
// default 函数声明(具名与匿名)由函数预声明臂按提升 var 承载,
// 此处只为具名 default 类建 lexical Let TDZ 占位。
if let ExportDefaultDeclarationKind::ClassDeclaration(cd) = &exp.declaration {
if let Some(id) = &cd.id {
check_restricted_global_lexical(id.name.as_str(), global_lexical)?;
let reg = ctx.alloc_reg();
let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Let, false);
}
}
ExportDefaultDeclarationKind::FunctionDeclaration(fd) => {
if let Some(id) = &fd.id {
let reg = ctx.alloc_reg();
let _ =
ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true);
}
}
_ => {}
},
}
_ => {}
}
}
+113
View File
@@ -30,8 +30,14 @@ pub(crate) struct Binding {
/// 作用域符号表:名字 → 寄存器号/初始化状态/const 标志。
/// 首层为全局函数作用域,后续 push 的为块作用域。
///
/// 别名(`aliases`)承载模块自导入:自导入的局部名是源导出的活引用,其绑定
/// 槽位与源绑定相同,TDZ/提升/活值/不可变四语义全部委托源绑定;表内只负责在
/// 源绑定初始化时同步解除别名 TDZ。
pub struct SymbolTable {
pub(crate) scopes: Vec<Scope>,
/// 别名本地名 → 基源绑定名(沿 re-export 链解析到最终源名)。
aliases: HashMap<String, String>,
}
impl Default for SymbolTable {
@@ -48,6 +54,7 @@ impl SymbolTable {
bindings: HashMap::new(),
kind: ScopeKind::FunctionScope,
}],
aliases: HashMap::new(),
}
}
@@ -248,7 +255,75 @@ impl SymbolTable {
}
/// 从内到外将同名绑定标记为已初始化(var 提升后初始化阶段使用)。
/// 指向该源的别名绑定同步置为已初始化,使别名读取的 TDZ 状态跟随源声明点。
pub fn init_var(&mut self, name: &str) {
for scope in self.scopes.iter_mut().rev() {
if let Some(b) = scope.bindings.get_mut(name) {
b.initialized = true;
break;
}
}
// 别名侧表与绑定分居两处,直接按基源名命中全部别名槽位,不走作用域查找。
let alias_bases: Vec<String> = self
.aliases
.iter()
.filter(|(_, base)| base.as_str() == name)
.map(|(local, _)| local.clone())
.collect();
for local in alias_bases {
self.mark_initialized(&local);
}
}
/// 声明本地名为 `source` 源绑定的别名:别名槽位复用源寄存器,`is_const`
/// 恒为真(import 绑定是不可变的间接引用),初始化状态镜像源绑定当前值。
///
/// # 边界与前提
/// - `source` 经 `aliases` 表解析到基源名(支持别名链);基源不存在于符号表
/// 时返回 `Err`,调用方回退非别名路径。
/// - 别名名在当前最内层作用域已有绑定时返回 `Err`(重复声明)。
///
/// # 副作用
/// - 在当前最内层作用域插入别名 Binding,并登记 `aliases` 侧表。
pub(crate) fn add_alias(&mut self, local: &str, source: &str) -> Result<(), String> {
let base = self.resolve_alias_base(source).to_string();
let Some((reg, initialized)) = self
.scopes
.iter()
.rev()
.find_map(|scope| scope.bindings.get(&base).map(|binding| (binding.reg, binding.initialized)))
else {
return Err(format!("alias source '{base}' is not declared"));
};
let target = self.scopes.last_mut().expect("symbol table always has a scope");
if target.bindings.contains_key(local) {
return Err(format!("Identifier '{local}' has already been declared"));
}
target.bindings.insert(
local.to_string(),
Binding {
reg,
initialized,
is_const: true,
predeclared: false,
},
);
self.aliases.insert(local.to_string(), base);
Ok(())
}
/// 沿别名侧表把名字解析到最终基源绑定名;非别名名原样返回。
pub(crate) fn resolve_alias_base<'a>(&'a self, name: &'a str) -> &'a str {
let mut base = name;
while let Some(next) = self.aliases.get(base) {
base = next.as_str();
}
base
}
/// 把名对应绑定置为已初始化(只查绑定、不问状态)。
fn mark_initialized(&mut self, name: &str) {
for scope in self.scopes.iter_mut().rev() {
if let Some(b) = scope.bindings.get_mut(name) {
b.initialized = true;
@@ -380,4 +455,42 @@ mod tests {
assert!(st.declare("y", 2, v(), false).is_err());
st.pop_scope();
}
#[test]
fn alias_shares_source_slot_and_const() {
let mut st = SymbolTable::new();
st.declare("x", 3, l(), false).unwrap();
st.add_alias("y", "x").unwrap();
// 别名复用源寄存器,且是不可变的间接绑定。
assert_eq!(st.lookup_any("y"), Some(3));
assert!(st.lookup_any_binding("y").unwrap().0.is_const);
// 源未初始化时别名读同样报 TDZ。
assert!(st.lookup("y").unwrap_err().contains("before initialization"));
}
#[test]
fn alias_tdz_follows_source_init() {
let mut st = SymbolTable::new();
st.declare("x", 3, l(), false).unwrap();
st.add_alias("y", "x").unwrap();
st.init_var("x");
// 源声明点初始化同步解除别名 TDZ。
assert_eq!(st.lookup("y").unwrap(), 3);
}
#[test]
fn alias_chain_resolves_to_base() {
let mut st = SymbolTable::new();
st.declare("x", 3, l(), false).unwrap();
st.add_alias("y", "x").unwrap();
st.add_alias("z", "y").unwrap();
assert_eq!(st.resolve_alias_base("z"), "x");
assert_eq!(st.lookup_any("z"), Some(3));
}
#[test]
fn alias_of_missing_source_is_error() {
let mut st = SymbolTable::new();
assert!(st.add_alias("y", "missing").is_err());
}
}
@@ -214,3 +214,53 @@ fn export_default_implicit_name_named_unchanged() {
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");
}
@@ -75,3 +75,39 @@ fn self_import_module_evaluated_once() {
let r = probe(&mut vm, "globalThis.__evals");
assert!(r.is_int() && r.as_int() == 1, "自导入模块应只求值一次,实际 __evals={r:?}");
}
/// 自导入绑定是源绑定的活引用:闭包经别名读到的必须是源绑定的当前值;
/// 源在导出声明后重赋时,别名读点同步可见新值(共享 cell)。
#[test]
fn self_import_alias_reads_live_source_value() {
let cwd = std::env::current_dir().expect("cwd");
let dir = cwd.join("__selfimport_live__");
let file = dir.join("live.mjs");
std::fs::create_dir_all(&dir).expect("mkdir");
std::fs::write(
&file,
"let before;\n \
function readAlias() { return y; }\n \
try { before = readAlias(); } catch (e) { before = e instanceof ReferenceError; }\n \
import { x as y } from './live.mjs';\n \
export let x = 1;\n \
x = 2;\n \
globalThis.__live = [before, readAlias()].join('|');",
)
.expect("write module");
let _cleanup = Cleanup(dir.clone());
let allocator = oxide_parser::Allocator::default();
let source = std::fs::read_to_string(&file).expect("read module");
let program = oxide_parser::parse_module(&allocator, &source).expect("parse module");
let module = Compiler::new()
.compile_module(&program, dir.join("live.mjs").to_string_lossy().as_ref(), &mut AbsLoader)
.expect("compile module");
let mut vm = Vm::new();
vm.run(&Arc::new(module)).expect("module run");
// 声明前闭包读别名按规范抛 ReferenceError;声明后重赋读到新值 2。
let r = probe(&mut vm, "globalThis.__live");
let text = vm.lookup_str(r).unwrap_or_default();
assert_eq!(text, "true|2", "自导入别名应共享源绑定活值");
}