diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index aee998ca..83e185ea 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -142,7 +142,10 @@ pub struct CompileCtx { /// 自导入(import from 自身)的 source 字符串集合:绑定走别名语义,不能链接期快照。 pub(crate) module_self_import_specs: HashSet, /// 自导入别名:导出名 → 本地绑定槽寄存器(export 语句执行时回写绑定值)。 + /// 仅承载无法静态解析源绑定的退化占位路径(star 转发的自导入名)。 pub(crate) module_self_aliases: HashMap, + /// 自导入别名(本地名,基源绑定名):供别名捕获后处理合并源/别名 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; diff --git a/crates/oxide_emit/src/module.rs b/crates/oxide_emit/src/module.rs index 82d90fa6..c7521054 100644 --- a/crates/oxide_emit/src/module.rs +++ b/crates/oxide_emit/src/module.rs @@ -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) -> HashMap { + 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 = 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 { + 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 { 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)?; diff --git a/crates/oxide_emit/src/prepass/declare.rs b/crates/oxide_emit/src/prepass/declare.rs index fd748b33..00b8aac8 100644 --- a/crates/oxide_emit/src/prepass/declare.rs +++ b/crates/oxide_emit/src/prepass/declare.rs @@ -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); - } - } - _ => {} - }, + } _ => {} } } diff --git a/crates/oxide_emit/src/symbol_table.rs b/crates/oxide_emit/src/symbol_table.rs index ea9fbd6d..859e4a50 100644 --- a/crates/oxide_emit/src/symbol_table.rs +++ b/crates/oxide_emit/src/symbol_table.rs @@ -30,8 +30,14 @@ pub(crate) struct Binding { /// 作用域符号表:名字 → 寄存器号/初始化状态/const 标志。 /// 首层为全局函数作用域,后续 push 的为块作用域。 +/// +/// 别名(`aliases`)承载模块自导入:自导入的局部名是源导出的活引用,其绑定 +/// 槽位与源绑定相同,TDZ/提升/活值/不可变四语义全部委托源绑定;表内只负责在 +/// 源绑定初始化时同步解除别名 TDZ。 pub struct SymbolTable { pub(crate) scopes: Vec, + /// 别名本地名 → 基源绑定名(沿 re-export 链解析到最终源名)。 + aliases: HashMap, } 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 = 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()); + } } diff --git a/crates/oxide_emit/tests/module_link_tests.rs b/crates/oxide_emit/tests/module_link_tests.rs index a4573055..45267d08 100644 --- a/crates/oxide_emit/tests/module_link_tests.rs +++ b/crates/oxide_emit/tests/module_link_tests.rs @@ -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"); +} diff --git a/crates/oxide_vm/tests/self_import_path_tests.rs b/crates/oxide_vm/tests/self_import_path_tests.rs index ca512778..f688e896 100644 --- a/crates/oxide_vm/tests/self_import_path_tests.rs +++ b/crates/oxide_vm/tests/self_import_path_tests.rs @@ -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", "自导入别名应共享源绑定活值"); +}