diff --git a/crates/oxide_builtins/src/module.rs b/crates/oxide_builtins/src/module.rs index 72abb1d5..1a7bca97 100644 --- a/crates/oxide_builtins/src/module.rs +++ b/crates/oxide_builtins/src/module.rs @@ -561,6 +561,40 @@ pub fn module_set_reexport(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } +/// `__moduleAlias(ns, name, fallback)`:自别名收尾刷新用。命名空间存在该导出时 +/// 返回其值,否则返回传入的 `fallback`(不抛错):star 未提供该名时占位值原样 +/// 保留,与现行「星号未提供该名时静默 undefined」行为一致。 +/// +/// # 边界与前提 +/// - 命名空间无该导出时不抛错(区别于 `__moduleGet` 的 TypeError 与 +/// `__moduleLinkGet` 的 SyntaxError),缺失臂返回 `fallback` 原值。 +/// +/// # 副作用 +/// - 无:只读命名空间属性,不修改条目表。 +pub fn module_alias(vm: &mut H, args: &[u8]) -> NativeResult { + if args.len() < 4 { + return type_error(vm, "__moduleAlias: 3 arguments required"); + } + let ns_val = vm.reg(args[1]); + let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleAlias: target is not an object") { + Ok(Some(p)) => p, + Ok(None) => return type_error(vm, "__moduleAlias: target is not an object"), + Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)), + }; + let name_val = vm.reg(args[2]); + let name_si = vm.property_key_si(name_val); + let fallback = vm.reg(args[3]); + let obj = unsafe { &*ns_ptr }; + if vm.get_own_property_slot(obj, name_si).is_some() { + match vm.ordinary_get(obj, name_si, ns_val) { + Ok(v) => NativeResult::Ok(v), + Err(e) => NativeResult::Err(crate::error::create_error(vm, &e)), + } + } else { + NativeResult::Ok(fallback) + } +} + /// `__moduleGet(ns, name)`:读取命名空间导出属性。 pub fn module_get(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 3 { diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index 884e6b5a..82606112 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -175,8 +175,9 @@ pub struct CompileCtx { /// 自导入(import from 自身)的 source 字符串集合:绑定走别名语义,不能链接期快照。 pub(crate) module_self_import_specs: HashSet, /// 自导入别名:导出名 → 本地绑定槽寄存器(export 语句执行时回写绑定值)。 - /// 仅承载无法静态解析源绑定的退化占位路径(star 转发的自导入名)。 - pub(crate) module_self_aliases: HashMap, + /// 仅承载无法静态解析源绑定的退化占位路径(star 转发的自导入名);同名多绑定 + /// (如 default 双绑定)经 Vec 并列回写。 + pub(crate) module_self_aliases: HashMap>, /// 自导入别名(本地名,基源绑定名):供别名捕获后处理合并源/别名 cell。 pub(crate) module_alias_pairs: Vec<(String, String)>, /// 标签模板 site 计数器:本编译树内全局唯一(子 ctx 继承父值继续递增)。 diff --git a/crates/oxide_emit/src/module.rs b/crates/oxide_emit/src/module.rs index e175f85f..41e005e5 100644 --- a/crates/oxide_emit/src/module.rs +++ b/crates/oxide_emit/src/module.rs @@ -567,7 +567,7 @@ impl Emitter { } if is_self { let alias_reg = self.emit_self_alias_placeholder(s.local.name.as_str(), ctx)?; - ctx.module_self_aliases.insert(imported, alias_reg); + ctx.module_self_aliases.entry(imported).or_default().push(alias_reg); } else { let name_reg = self.load_string_const(&imported, ctx); let val_reg = @@ -619,7 +619,10 @@ impl Emitter { } if is_self { 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); + ctx.module_self_aliases + .entry(DEFAULT_EXPORT_NAME.to_string()) + .or_default() + .push(alias_reg); } else { let name_reg = self.load_string_const(DEFAULT_EXPORT_NAME, ctx); let val_reg = @@ -799,6 +802,10 @@ impl Emitter { self.emit_statement(stmt, ctx)?; } + // —— 自别名收尾刷新:star 复制等跨模块注册路径后占位槽可能仍为旧值, + // 封冻命名空间前统一从命名空间非抛出回读兜底。 —— + self.emit_self_alias_refresh(ctx, ns_reg)?; + // —— 收尾:封冻命名空间并返回(顶层 RETURN 亦终止 run)—— self.emit_module_call(ctx, "__moduleSeal", &[ns_reg])?; if top_level { @@ -888,9 +895,11 @@ impl Emitter { } /// 自导入别名回写:导出名若被本模块 self-import 绑定别名引用, - /// export 语句执行时同步把值写回本地绑定槽。 + /// export 语句执行时同步把值写回本地绑定槽。同名多槽(default 双绑定) + /// 逐槽并列回写。 fn emit_self_alias_write(&self, ctx: &mut CompileCtx, export_name: &str, value_reg: u32) { - if let Some(&alias_reg) = ctx.module_self_aliases.get(export_name) { + let regs = ctx.module_self_aliases.get(export_name).cloned().unwrap_or_default(); + for &alias_reg in ®s { ctx.inst(Inst::new( OpCode::STORE_VAR, Operand::Reg(alias_reg), @@ -900,9 +909,33 @@ impl Emitter { } } + /// 自导入别名统一刷新:对每个登记的自导入占位名,从命名空间非抛出读回写 + /// 占位槽(命名空间无该名时保留占位值,不抛错)。star 转发的自导入名在 + /// 导出语句处静态不可知,故每次 star 复制后与 body 收尾各刷新一次; + /// 名集按字典序排序后发射,保证发射序确定性。 + fn emit_self_alias_refresh(&self, ctx: &mut CompileCtx, ns_reg: u32) -> Result<(), String> { + if ctx.module_self_aliases.is_empty() { + return Ok(()); + } + let mut names: Vec = ctx.module_self_aliases.keys().cloned().collect(); + names.sort(); + for name in names { + let regs = ctx.module_self_aliases.get(&name).cloned().unwrap_or_default(); + for &alias_reg in ®s { + let name_reg = self.load_string_const(&name, ctx); + let cur_reg = ctx.alloc_reg(); + ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(cur_reg), Operand::Reg(alias_reg), Operand::None)); + let r = self.emit_module_call(ctx, "__moduleAlias", &[ns_reg, name_reg, cur_reg])?; + ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(alias_reg), Operand::Reg(r), Operand::None)); + } + } + Ok(()) + } + /// 自导入绑定的非别名占位:源绑定无法静态解析(star 转发的自导入名)时, /// prelude 把局部名绑成已初始化的 undefined 常量槽并登记回写,由 export - /// 语句执行时就地刷新。返回该本地绑定槽寄存器。 + /// 语句执行时就地刷新,body 收尾再统一从命名空间回读兜底。 + /// 返回该本地绑定槽寄存器。 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(); @@ -1132,6 +1165,9 @@ impl Emitter { &dep_path, &local_name, )?; + // 自导入名可能来自 star 转发(占位路径):导出语句执行时 + // 就地刷新本地槽,覆盖同模块 body 内导出后读取的形态。 + self.emit_self_alias_write(ctx, &exported_name, val_reg); continue; } // 无 source 但局部名是导入绑定:按规范重分类为间接导出,来源为 @@ -1139,6 +1175,8 @@ impl Emitter { if let Some((dep_path, imported)) = ctx.module_import_origins.get(&local_name).cloned() { let val_reg = self.load_var_reg(&local_name, ctx)?; self.emit_module_set_reexport(ctx, ns_reg, &exported_name, val_reg, &dep_path, &imported)?; + // 自导入名重分类为间接导出时同样就地刷新占位槽。 + self.emit_self_alias_write(ctx, &exported_name, val_reg); continue; } // 本地导出:来源恒为 Local。 @@ -1265,10 +1303,15 @@ impl Emitter { &dep_path, MODULE_NAMESPACE_BINDING, )?; + // 自导入的 star-as 名走占位路径:就地刷新为命名空间对象。 + self.emit_self_alias_write(ctx, &exported_name, dep_ns_reg); } else { // 来源传播:star 复制按 (dep_path, 绑定) 判同名冲突。 let path_reg = self.load_string_const(&dep_path, ctx); self.emit_module_call(ctx, "__moduleStar", &[ns_reg, dep_ns_reg, path_reg])?; + // star 复制后 star 转发的自导入名已就位:就地刷新占位槽, + // 覆盖 body 内 star 语句之后立即读取的形态。 + self.emit_self_alias_refresh(ctx, ns_reg)?; } } _ => return Err("unsupported module export statement".into()), diff --git a/crates/oxide_vm/src/bindings/bind_global.rs b/crates/oxide_vm/src/bindings/bind_global.rs index 81010654..fc2a85d0 100644 --- a/crates/oxide_vm/src/bindings/bind_global.rs +++ b/crates/oxide_vm/src/bindings/bind_global.rs @@ -44,6 +44,7 @@ pub fn bind_global(core: &Arc, session: &KernelSession, global: &mut oxide_builtins::module::module_set_reexport:: as *const (), 5, ), + ("__moduleAlias", oxide_builtins::module::module_alias:: as *const (), 3), ("__moduleGet", oxide_builtins::module::module_get:: as *const (), 2), ( "__moduleLinkGet",