feat: 星号导出歧义名从命名空间省略

This commit is contained in:
2026-09-18 00:19:29 +00:00
parent ed65b5282f
commit f53918f287
7 changed files with 608 additions and 101 deletions
+265 -79
View File
@@ -7,7 +7,7 @@
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api::{NativeResult, VmHost};
use oxide_types::object::{Cell, JsObject, PropAttributes};
use oxide_types::object::{Cell, JsObject, PropAttributes, PropMetaEntry};
use oxide_types::private_key::make_well_known_symbol_key;
use oxide_types::value::JsValue;
@@ -34,12 +34,14 @@ fn type_error<H: VmHost>(vm: &mut H, msg: &str) -> NativeResult {
/// 命名空间条目表的来源身份(provenance)。
///
/// `Local` 为本模块直接声明或本地再导出的导出;`Reexport` 预留跨模块 star 再导出
/// 的来源身份,由 star 导出任务填入,避免另立 per-namespace 表形成两套判据。
/// `Local` 为本模块直接声明的导出;`Reexport` 描述跨模块再导出的 `(模块, 绑定)`
/// 身份,`direct` 区分显式再导出(`export { x } from`)与 star 复制的间接导出;
/// `Ambiguous` 是 star 冲突后的省略哨兵,该名不再出现在命名空间且一旦歧义恒歧义。
#[derive(Clone, Copy)]
pub enum ModuleNsOrigin {
Local,
Reexport { path_id: u32, binding_id: u32, direct: bool },
Ambiguous,
}
/// 单个导出名的活值状态。
@@ -95,7 +97,7 @@ fn install_ns_table(obj: &mut JsObject) -> *mut ModuleNsTable {
table
}
/// 追加一条未初始化的 `Value` 条目。
/// 追加一条未初始化的 `Value` 条目(来源为本地导出)。
///
/// # 注意事项
/// - 调用方保证 `name_si` 尚未入表(入口均为先查后插)。
@@ -103,6 +105,7 @@ fn push_uninitialized_entry(table: *mut ModuleNsTable, name_si: u32) {
push_entry(
table,
name_si,
ModuleNsOrigin::Local,
ModuleNsState::Value {
value: JsValue::undefined(),
initialized: false,
@@ -110,24 +113,12 @@ fn push_uninitialized_entry(table: *mut ModuleNsTable, name_si: u32) {
);
}
/// 追加一条已初始化的 `Value` 条目。
///
/// # 注意事项
/// - 调用方保证 `name_si` 尚未入表。
fn push_initialized_entry(table: *mut ModuleNsTable, name_si: u32, value: JsValue) {
push_entry(table, name_si, ModuleNsState::Value { value, initialized: true });
}
/// 向条目表追加一条条目。
/// 向条目表追加一条带来源身份的条目。
///
/// # 注意事项
/// - table 归调用方持有的 ns 对象所有,在同一 VM 世代内有效。
fn push_entry(table: *mut ModuleNsTable, name_si: u32, state: ModuleNsState) {
let entry = ModuleNsEntry {
name_si,
origin: ModuleNsOrigin::Local,
state,
};
fn push_entry(table: *mut ModuleNsTable, name_si: u32, origin: ModuleNsOrigin, state: ModuleNsState) {
let entry = ModuleNsEntry { name_si, origin, state };
// SAFETY: table 归调用方持有的 ns 对象所有,在同一 VM 世代内有效。
unsafe {
(*table).entries.push(entry);
@@ -152,6 +143,10 @@ pub fn module_ns_export(obj: &JsObject, key_si: u32) -> Option<ModuleNsQuery> {
// SAFETY: table 指向本 ns 对象持有的 Box<ModuleNsTable>,随对象存活。
let table_ref = unsafe { &*table };
let entry = table_ref.entries.get(entry_index(table_ref, key_si)?)?;
// 歧义哨兵条目:该名已从命名空间省略,查询一律视为非导出(落普通路径)。
if matches!(entry.origin, ModuleNsOrigin::Ambiguous) {
return None;
}
match &entry.state {
ModuleNsState::Value { value, initialized } => {
if *initialized {
@@ -370,16 +365,109 @@ pub fn module_object<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::from_js_object(obj))
}
/// `__moduleSet(ns, name, value)`:在命名空间上定义或更新导出属性。
/// 写入命名空间导出:命中条目则更新活值、真实属性槽与来源身份;否则经瞬态可扩展
/// 窗口定义真实属性槽,并在 ns 有条目表时追加条目。
///
/// # 步骤
/// 1. live ns 命中既有条目:`Cell` 条目 no-op(活值由 cell 承载);`Value` 条目
/// 更新活值并同步真实属性槽,键集不变故跳过重排。
/// 2. 其余(无表 / 非条目 / 新键):经瞬态可扩展窗口定义或改值真实属性槽;
/// live ns 追加已初始化条目,最后按规范顺序重排导出键。
/// # 边界与前提
/// - `Cell` 条目 no-op:被捕获导出的活值由共享 cell 承载,直接写槽会与 cell 分裂。
/// - 条目存在但真实槽缺失(歧义省略后显式导出重新定义):落新键定义路径补回槽。
///
/// # 副作用
/// - 修改条目表状态与 ns 属性槽;新键会重排 `[[OwnPropertyKeys]]` 顺序。
/// - 修改条目表状态/来源身份与 ns 属性槽;新键或删槽重定义时重排
/// `[[OwnPropertyKeys]]` 顺序。
fn set_export<H: VmHost>(
vm: &mut H, obj: &mut JsObject, name_si: u32, value: JsValue, origin: ModuleNsOrigin,
) -> Result<(), String> {
let table = ns_table_ptr(obj);
let existing = if table.is_null() {
None
} else {
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
unsafe { entry_index(&*table, name_si) }
};
// Cell 条目 no-op:被捕获导出的活值由共享 cell 承载,写槽会与 cell 分裂。
if let Some(idx) = existing {
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
if matches!(unsafe { &(&(*table).entries)[idx].state }, ModuleNsState::Cell(_)) {
return Ok(());
}
}
// 条目已存在且真实槽仍在:原地更新活值/来源,键集不变无需重排。
if let Some(idx) = existing {
if let Some(pos) = vm.get_own_property_slot(obj, name_si) {
obj.set_prop_at(pos, value);
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
unsafe {
(&mut (*table).entries)[idx].origin = origin;
(&mut (*table).entries)[idx].state = ModuleNsState::Value { value, initialized: true };
}
return Ok(());
}
// 歧义省略删槽后显式导出重新定义:落下方定义路径补回真实槽。
}
// 导出定义窗口:命名空间创建起不可扩展,定义新导出须瞬态放开扩展性。窗口内
// 不运行 JS,无可观察副作用;错误路径先复位以维持 non-extensible 不变式。
obj.set_extensible(true);
let result = vm.define_data_property(obj, name_si, value, ns_attrs());
obj.set_extensible(false);
result?;
if !table.is_null() {
if let Some(idx) = existing {
// SAFETY: idx 来自刚完成的表内查找;重定义后同步条目,勿重复入表。
unsafe {
(&mut (*table).entries)[idx].origin = origin;
(&mut (*table).entries)[idx].state = ModuleNsState::Value { value, initialized: true };
}
} else {
push_entry(table, name_si, origin, ModuleNsState::Value { value, initialized: true });
}
}
// 每次键集变化后重排:自导入在 body 内即可见有序的 [[OwnPropertyKeys]]。
crate::object::sort_namespace_exports(vm, obj);
Ok(())
}
/// 从命名空间属性表移除一个导出槽(歧义省略用),保留其余字符串与 Symbol 键及其
/// 描述符。
///
/// # 注意事项
/// - 命名空间导出属性为 non-configurable,本函数不经 `[[Delete]]`,直接重建属性表
/// 以同时保住 `@@toStringTag` Symbol 键。
fn remove_export_slot<H: VmHost>(vm: &mut H, obj: &mut JsObject, name_si: u32) {
let mut retained: Vec<(u32, JsValue, Option<PropMetaEntry>)> = crate::object::walk_own_keys(vm, obj)
.into_iter()
.filter(|(si, _)| *si != name_si)
.map(|(si, pos)| (si, obj.get_prop_at(pos), obj.prop_meta_at(pos)))
.collect();
let symbols: Vec<(u32, JsValue, Option<PropMetaEntry>)> = crate::object::walk_own_symbol_keys(vm, obj)
.into_iter()
.map(|(si, pos)| (si, obj.get_prop_at(pos), obj.prop_meta_at(pos)))
.collect();
obj.set_shape_id(EMPTY_SHAPE_ID);
obj.clear_props();
for (si, value, meta) in retained.drain(..).chain(symbols) {
let shape = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), si);
obj.set_shape_id(shape);
let pos = obj.push_prop(value);
if let Some(meta) = meta {
if meta.is_accessor {
obj.set_accessor_meta(pos, meta.get, meta.set, meta.attributes);
} else {
obj.set_data_meta(pos, meta.attributes);
}
}
}
obj.bump_generation();
}
/// `__moduleSet(ns, name, value)`:在命名空间上定义或更新本地导出属性。
///
/// # 副作用
/// - 同 `set_export`;本地导出来源身份恒为 `Local`,覆盖先前 star 条目时复位来源。
pub fn module_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 4 {
return type_error(vm, "__moduleSet: 3 arguments required");
@@ -394,41 +482,56 @@ pub fn module_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let value = vm.reg(args[3]);
let name_si = vm.property_key_si(name_val);
let obj = unsafe { &mut *ns_ptr };
// live 条目路径:命中条目则更新活值并同步真实槽,键集不变无需重排。
let table = ns_table_ptr(obj);
if !table.is_null() {
// SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。
if let Some(idx) = unsafe { entry_index(&*table, name_si) } {
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
let entry_state = unsafe { &mut (&mut (*table).entries)[idx].state };
match entry_state {
ModuleNsState::Cell(_) => return NativeResult::Ok(JsValue::undefined()),
ModuleNsState::Value { value: slot, initialized } => {
*slot = value;
*initialized = true;
if let Some(pos) = vm.get_own_property_slot(obj, name_si) {
obj.set_prop_at(pos, value);
}
return NativeResult::Ok(JsValue::undefined());
}
}
}
}
// 导出定义窗口:命名空间创建起不可扩展,定义新导出须瞬态放开扩展性。窗口内
// 不运行 JS,无可观察副作用;错误路径先复位以维持 non-extensible 不变式。
obj.set_extensible(true);
let result = vm.define_data_property(obj, name_si, value, ns_attrs());
obj.set_extensible(false);
if let Err(e) = result {
if let Err(e) = set_export(vm, obj, name_si, value, ModuleNsOrigin::Local) {
return NativeResult::Err(crate::error::create_error(vm, &e));
}
if !table.is_null() {
push_initialized_entry(table, name_si, value);
NativeResult::Ok(JsValue::undefined())
}
/// `__moduleSetReexport(ns, name, value, dep_path, imported)`:注册显式再导出
/// (`export { x } from` / `export * as ns from` / 导入绑定再导出),记录跨模块
/// 来源身份 `(dep_path, imported)`,使同一绑定经不同路径转发时来源 token 相等。
///
/// # 副作用
/// - 同 `module_set`;ns 无条目表时惰性安装,来源标为 `direct` 再导出。
pub fn module_set_reexport<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 6 {
return type_error(vm, "__moduleSetReexport: 5 arguments required");
}
let ns_val = vm.reg(args[1]);
let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleSetReexport: target is not an object") {
Ok(Some(p)) => p,
Ok(None) => return type_error(vm, "__moduleSetReexport: target is not an object"),
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
};
let name_val = vm.reg(args[2]);
let value = vm.reg(args[3]);
let path_val = vm.reg(args[4]);
let imported_val = vm.reg(args[5]);
let name_si = vm.property_key_si(name_val);
let Some(dep_path) = vm.lookup_str(path_val) else {
return type_error(vm, "__moduleSetReexport: module path is not a string");
};
let Some(imported) = vm.lookup_str(imported_val) else {
return type_error(vm, "__moduleSetReexport: imported name is not a string");
};
let obj = unsafe { &mut *ns_ptr };
if !obj.is_module_namespace() {
return type_error(vm, "__moduleSetReexport: target is not a module namespace");
}
if ns_table_ptr(obj).is_null() {
install_ns_table(obj);
}
let path_id = vm.kernel_core().perm_interner().intern(&dep_path).0;
let binding_id = vm.kernel_core().perm_interner().intern(&imported).0;
let origin = ModuleNsOrigin::Reexport {
path_id,
binding_id,
direct: true,
};
if let Err(e) = set_export(vm, obj, name_si, value, origin) {
return NativeResult::Err(crate::error::create_error(vm, &e));
}
// 每次导出写入后重排:自导入在 body 内即可见有序的 [[OwnPropertyKeys]]。
crate::object::sort_namespace_exports(vm, obj);
NativeResult::Ok(JsValue::undefined())
}
@@ -481,13 +584,55 @@ pub fn module_link_get<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
}
/// `__moduleStar(dst, src)`:把 src 命名空间的全部导出(除 default)复制到 dst。
/// 取 src 命名空间某导出名的来源身份。
///
/// # 边界与前提
/// - src 条目表命中且来源为 `Reexport`:沿用其 `(模块, 绑定)`,`direct` 归一为
/// false(经 star 复制到 dst 的是间接提供)。
/// - 其余(无表 / `Local` / 条目缺失):回退 `(src_path_id, name_si)`。只声明本地
/// 导出的源不建条目表,回退 token 使不同源的同名本地导出身份不同。
fn star_source_origin(src: &JsObject, name_si: u32, src_path_id: u32) -> ModuleNsOrigin {
let table = ns_table_ptr(src);
if !table.is_null() {
// SAFETY: table 归 src 持有,生命周期见 `module_ns_export`。
if let Some(idx) = unsafe { entry_index(&*table, name_si) } {
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
if let ModuleNsOrigin::Reexport { path_id, binding_id, .. } = unsafe { (&(*table).entries)[idx].origin } {
return ModuleNsOrigin::Reexport {
path_id,
binding_id,
direct: false,
};
}
}
}
ModuleNsOrigin::Reexport {
path_id: src_path_id,
binding_id: name_si,
direct: false,
}
}
/// `__moduleStar(dst, src, src_path)`:把 src 命名空间的可枚举导出(除 default)
/// 复制到 dst,并按来源身份解析同名冲突。
///
/// # 步骤
/// 1. 每个 src 导出名取来源 `(模块, 绑定)`;`default` 跳过。
/// 2. dst 条目为 `Ambiguous`:恒跳过(一旦歧义恒省略)。
/// 3. dst 条目为显式导出(`Local` / direct `Reexport`):显式恒胜,跳过。
/// 4. dst 条目为间接 `Reexport`:`(模块, 绑定)` 相同则保留;不同则删槽并记
/// `Ambiguous`。
/// 5. dst 无条目:真实槽已存在视为显式导出跳过;否则复制槽并记间接来源。
///
/// # 副作用
/// - 在 dst 上定义/删除真实属性槽,惰性安装条目表,并按规范顺序重排导出键。
pub fn module_star<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 3 {
return type_error(vm, "__moduleStar: 2 arguments required");
if args.len() < 4 {
return type_error(vm, "__moduleStar: 3 arguments required");
}
let dst_val = vm.reg(args[1]);
let src_val = vm.reg(args[2]);
let src_path_val = vm.reg(args[3]);
let dst_ptr = match vm.checked_object_ptr(dst_val, "__moduleStar: target is not an object") {
Ok(Some(p)) => p,
Ok(None) => return type_error(vm, "__moduleStar: target is not an object"),
@@ -498,32 +643,73 @@ pub fn module_star<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
Ok(None) => return type_error(vm, "__moduleStar: source is not an object"),
Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)),
};
let Some(src_path) = vm.lookup_str(src_path_val) else {
return type_error(vm, "__moduleStar: source path is not a string");
};
let src_path_id = vm.kernel_core().perm_interner().intern(&src_path).0;
let src = unsafe { &*src_ptr };
let default_si = vm.kernel_core().perm_interner().intern("default").0;
let keys = crate::object::walk_own_keys(vm, src);
let dst = unsafe { &mut *dst_ptr };
// live dst:star 复制的新导出名须一并入表,否则 2b 的活值读/枚举看不到条目。
let dst_table = ns_table_ptr(dst);
for (name_si, pos) in keys {
if name_si == default_si {
continue;
}
// export * 冲突省略:名称已被本命名空间直接导出或先前 star 占用时跳过
// (规范 StarExportEntries 求值后,ambiguous 名称从命名空间省略)。
if vm.get_own_property_slot(dst, name_si).is_some() {
continue;
}
let value = src.get_prop_at(pos);
// star 导出同 module_set:定义新导出走瞬态可扩展窗口,错误路径先复位。
dst.set_extensible(true);
let result = vm.define_data_property(dst, name_si, value, ns_attrs());
dst.set_extensible(false);
if let Err(e) = result {
return NativeResult::Err(crate::error::create_error(vm, &e));
}
if !dst_table.is_null() {
// 2a 保持快照语义:star 名立即视为已初始化。
push_initialized_entry(dst_table, name_si, value);
let src_origin = star_source_origin(src, name_si, src_path_id);
// live dst:star 复制的新导出名须一并入表,否则活值读/枚举看不到条目。
let dst_table = ns_table_ptr(dst);
let dst_idx = if dst_table.is_null() {
None
} else {
unsafe { entry_index(&*dst_table, name_si) }
};
match dst_idx {
Some(idx) => {
// SAFETY: idx 来自刚完成的表内查找,仍在界内。
let dst_origin = unsafe { (&(*dst_table).entries)[idx].origin };
match dst_origin {
ModuleNsOrigin::Ambiguous => continue,
ModuleNsOrigin::Local | ModuleNsOrigin::Reexport { direct: true, .. } => continue,
ModuleNsOrigin::Reexport { path_id, binding_id, .. } => {
let ModuleNsOrigin::Reexport {
path_id: sp, binding_id: sb, ..
} = src_origin
else {
continue;
};
if path_id == sp && binding_id == sb {
continue;
}
// 不同绑定同名:删除导出槽并记歧义哨兵,清空可回收的活值边。
remove_export_slot(vm, dst, name_si);
// SAFETY: idx 仍指向本表条目,删除槽不改动条目表。
unsafe {
(&mut (*dst_table).entries)[idx].origin = ModuleNsOrigin::Ambiguous;
(&mut (*dst_table).entries)[idx].state = ModuleNsState::Value {
value: JsValue::undefined(),
initialized: false,
};
}
}
}
}
None => {
// 真实槽已存在但无条目:视为先前显式导出(本地导出不建表),显式恒胜。
if vm.get_own_property_slot(dst, name_si).is_some() {
continue;
}
let value = src.get_prop_at(pos);
let table = if dst_table.is_null() { install_ns_table(dst) } else { dst_table };
// star 导出同 module_set:定义新导出走瞬态可扩展窗口,错误路径先复位。
dst.set_extensible(true);
let result = vm.define_data_property(dst, name_si, value, ns_attrs());
dst.set_extensible(false);
if let Err(e) = result {
return NativeResult::Err(crate::error::create_error(vm, &e));
}
// 快照语义:star 名立即视为已初始化。
push_entry(table, name_si, src_origin, ModuleNsState::Value { value, initialized: true });
}
}
}
// star 复制后重排:直接导出与 star 导出的合并键序保持规范顺序。
+1 -1
View File
@@ -110,7 +110,7 @@ pub fn key_si_to_js_value<H: VmHost>(vm: &mut H, si: u32) -> JsValue {
/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键。返回 `(属性键 si, 绝对存储索引)`:
/// 槽位计数须含全部非空节点(含字符串键)并对数组加元素区偏移,与物理存储及
/// [`walk_own_keys`] 的口径一致,消费方(delete 重建)才能按槽位取回正确值。
fn walk_own_symbol_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
pub(crate) fn walk_own_symbol_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
let mut keys: Vec<(u32, u32)> = Vec::new();
let shape_id = obj.shape_id();
let mut shape_ids = Vec::new();
+7
View File
@@ -143,6 +143,11 @@ pub struct CompileCtx {
pub(crate) module_local_exports: HashMap<String, Vec<String>>,
/// 已求值依赖模块的命名空间对象寄存器(按 import/export source 字符串索引)。
pub(crate) module_dep_ns_regs: HashMap<String, u32>,
/// 依赖模块规范路径(按 import/export source 字符串索引):再导出来源身份用。
pub(crate) module_dep_paths: HashMap<String, String>,
/// 非自导入的导入局部名 → (依赖模块规范路径, 导入名)。命名空间导入的导入名为
/// `oxide_kernel::MODULE_NAMESPACE_BINDING`;再导出据此重分类为间接导出。
pub(crate) module_import_origins: HashMap<String, (String, String)>,
/// 自导入(import from 自身)的 source 字符串集合:绑定走别名语义,不能链接期快照。
pub(crate) module_self_import_specs: HashSet<String>,
/// 自导入别名:导出名 → 本地绑定槽寄存器(export 语句执行时回写绑定值)。
@@ -232,6 +237,8 @@ impl CompileCtx {
module_ns_reg: None,
module_local_exports: HashMap::new(),
module_dep_ns_regs: HashMap::new(),
module_dep_paths: HashMap::new(),
module_import_origins: HashMap::new(),
module_self_import_specs: HashSet::new(),
module_self_aliases: HashMap::new(),
module_alias_pairs: Vec::new(),
+81 -20
View File
@@ -18,6 +18,7 @@ use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
use oxide_ir::{IRFunction, ParamLayout};
use oxide_kernel::MODULE_NAMESPACE_BINDING;
use oxide_parser::{
BindingPattern, Declaration, ExportDefaultDeclarationKind, Expression, ImportAttributeKey,
ImportDeclarationSpecifier, ModuleExportName, Statement, VariableDeclarationKind, WithClause,
@@ -353,6 +354,7 @@ impl Emitter {
self.emit_module_call(ctx, "__moduleEval", &[fn_reg])?
};
ctx.module_dep_ns_regs.insert(spec.clone(), dep_ns_reg);
ctx.module_dep_paths.insert(spec.clone(), resolved.path.clone());
if resolved.path == module_path {
// 自导入:绑定走别名语义(源导出在 body 执行中才就绪),
// 不能像普通依赖那样链接期快照。
@@ -382,11 +384,17 @@ impl Emitter {
.get(&dep_spec)
.ok_or_else(|| format!("module dependency missing: {dep_spec}"))?;
let is_self = ctx.module_self_import_specs.contains(&dep_spec);
// 再导出重分类依据:非自导入局部名 → (依赖路径, 导入名)。
let dep_path = ctx.module_dep_paths.get(&dep_spec).cloned().unwrap_or_default();
if let Some(specifiers) = &imp.specifiers {
for sp in specifiers {
match sp {
ImportDeclarationSpecifier::ImportSpecifier(s) => {
let imported = module_export_name_str(&s.imported);
if !is_self {
ctx.module_import_origins
.insert(s.local.name.to_string(), (dep_path.clone(), imported.clone()));
}
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}"));
@@ -417,6 +425,12 @@ impl Emitter {
}
}
ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
if !is_self {
ctx.module_import_origins.insert(
s.local.name.to_string(),
(dep_path.clone(), DEFAULT_EXPORT_NAME.to_string()),
);
}
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) {
@@ -461,6 +475,11 @@ impl Emitter {
// body 执行后导出自然可见。
if is_self {
has_self_ns_import = true;
} else {
ctx.module_import_origins.insert(
s.local.name.to_string(),
(dep_path.clone(), MODULE_NAMESPACE_BINDING.to_string()),
);
}
self.emit_bind_target(
s.local.name.as_str(),
@@ -677,6 +696,17 @@ impl Emitter {
Ok(())
}
/// 发 `__moduleSetReexport`:注册带 `(dep_path, imported)` 来源身份的再导出。
fn emit_module_set_reexport(
&self, ctx: &mut CompileCtx, ns_reg: u32, export_name: &str, value_reg: u32, dep_path: &str, imported: &str,
) -> Result<(), String> {
let name_reg = self.load_string_const(export_name, ctx);
let path_reg = self.load_string_const(dep_path, ctx);
let imported_reg = self.load_string_const(imported, ctx);
self.emit_module_call(ctx, "__moduleSetReexport", &[ns_reg, name_reg, value_reg, path_reg, imported_reg])?;
Ok(())
}
/// 顶层对导出源绑定的写入同步到命名空间:按反演表把新值写入每个导出名。
///
/// # 边界与前提
@@ -803,26 +833,43 @@ impl Emitter {
_ => return Err("unsupported export declaration".into()),
}
} else {
let dep_ns_opt = exp
.source
.as_ref()
.map(|src| ctx.module_dep_ns_regs.get(src.value.as_str()).copied());
let dep_source = exp.source.as_ref().map(|src| src.value.to_string());
for spec in &exp.specifiers {
let local_name = module_export_name_str(&spec.local);
let exported_name = module_export_name_str(&spec.exported);
let val_reg = match dep_ns_opt {
Some(Some(dep_ns_reg)) => {
let name_reg = self.load_string_const(&local_name, ctx);
self.emit_module_call(ctx, "__moduleLinkGet", &[dep_ns_reg, name_reg])?
}
Some(None) => {
return Err(format!(
"export from unknown module: {}",
exp.source.as_ref().unwrap().value
))
}
None => self.load_var_reg(&local_name, ctx)?,
};
// 有 source:来源身份为 (依赖模块路径, 导入名)。
if let Some(source) = &dep_source {
let dep_ns_reg = ctx
.module_dep_ns_regs
.get(source.as_str())
.copied()
.ok_or_else(|| format!("export from unknown module: {source}"))?;
let dep_path = ctx
.module_dep_paths
.get(source.as_str())
.cloned()
.ok_or_else(|| format!("export from unknown module: {source}"))?;
let name_reg = self.load_string_const(&local_name, ctx);
let val_reg = self.emit_module_call(ctx, "__moduleLinkGet", &[dep_ns_reg, name_reg])?;
self.emit_module_set_reexport(
ctx,
ns_reg,
&exported_name,
val_reg,
&dep_path,
&local_name,
)?;
continue;
}
// 无 source 但局部名是导入绑定:按规范重分类为间接导出,来源为
// (依赖模块路径, 导入名);命名空间导入的导入名为共享哨兵。
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)?;
continue;
}
// 本地导出:来源恒为 Local。
let val_reg = self.load_var_reg(&local_name, ctx)?;
self.emit_module_set(ctx, ns_reg, &exported_name, val_reg)?;
}
}
@@ -925,12 +972,26 @@ impl Emitter {
.module_dep_ns_regs
.get(source)
.ok_or_else(|| format!("export * from unknown module: {source}"))?;
let dep_path = ctx
.module_dep_paths
.get(source)
.cloned()
.ok_or_else(|| format!("export * from unknown module: {source}"))?;
if let Some(exported) = &exp.exported {
// export * as ns from '...':命名空间再导出。
// export * as ns from '...':命名空间再导出,绑定身份用共享哨兵。
let exported_name = module_export_name_str(exported);
self.emit_module_set(ctx, ns_reg, &exported_name, dep_ns_reg)?;
self.emit_module_set_reexport(
ctx,
ns_reg,
&exported_name,
dep_ns_reg,
&dep_path,
MODULE_NAMESPACE_BINDING,
)?;
} else {
self.emit_module_call(ctx, "__moduleStar", &[ns_reg, dep_ns_reg])?;
// 来源传播: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])?;
}
}
_ => return Err("unsupported module export statement".into()),
+7
View File
@@ -22,3 +22,10 @@ pub mod string_forge;
/// kernel 对外的三个核心类型:配置、共享核心、会话。
pub use kernel::{KernelConfig, KernelCore, KernelSession};
/// 模块命名空间再导出的绑定身份哨兵。
///
/// `export * as ns from mod` 与 `import * as ns from mod; export { ns }` 两路
/// 转发的是同一个命名空间对象,来源身份须用同一绑定名才能判为同一绑定;该字面量
/// 含 NUL,不可能与任何静态导出名相同。编译器与运行时共享此常量,禁止另写字面量。
pub const MODULE_NAMESPACE_BINDING: &str = "\u{0}namespace";
+6 -1
View File
@@ -34,13 +34,18 @@ pub fn bind_global(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
2,
),
("__moduleSet", oxide_builtins::module::module_set::<crate::vm::Vm> as *const (), 3),
(
"__moduleSetReexport",
oxide_builtins::module::module_set_reexport::<crate::vm::Vm> as *const (),
5,
),
("__moduleGet", oxide_builtins::module::module_get::<crate::vm::Vm> as *const (), 2),
(
"__moduleLinkGet",
oxide_builtins::module::module_link_get::<crate::vm::Vm> as *const (),
2,
),
("__moduleStar", oxide_builtins::module::module_star::<crate::vm::Vm> as *const (), 2),
("__moduleStar", oxide_builtins::module::module_star::<crate::vm::Vm> as *const (), 3),
("__moduleSeal", oxide_builtins::module::module_seal::<crate::vm::Vm> as *const (), 1),
("__moduleEval", oxide_builtins::module::module_eval::<crate::vm::Vm> as *const (), 1),
("__moduleData", oxide_builtins::module::module_data::<crate::vm::Vm> as *const (), 2),
@@ -0,0 +1,241 @@
//! star 再导出的同名冲突省略:第三方观察者模块导入 hub 命名空间,验证歧义名
//! 在 `in` / `Object.keys` / for-in 三面一致省略,同绑定重复提供与显式导出不误删。
//!
//! 观察者形态(hub 不自导入)与自别名回写解耦:本文件只钉省/留判定本身。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_emit::module::{ModuleKind, ModuleSourceLoader, ResolvedModule};
use oxide_vm::vm::Vm;
/// 文件加载器:base_dir/specifier join 后读源(依赖模块经真实文件解析)。
struct FileLoader;
impl ModuleSourceLoader for FileLoader {
fn resolve(
&mut self, base_dir: &str, specifier: &str, _attributes: &[(&str, &str)],
) -> Result<ResolvedModule, String> {
let joined = std::path::Path::new(base_dir).join(specifier);
let full = std::fs::canonicalize(&joined).map_err(|e| format!("cannot read {specifier}: {e}"))?;
let source = std::fs::read_to_string(&full).map_err(|e| format!("cannot read {specifier}: {e}"))?;
Ok(ResolvedModule {
source,
path: full.to_string_lossy().into_owned(),
kind: ModuleKind::Js,
})
}
}
/// 测试结束删除临时目录,避免在 crate 树残留文件。
struct Cleanup(std::path::PathBuf);
impl Drop for Cleanup {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
/// 同一 VM 续跑脚本并返回字符串完成值(读 global 探针用)。
fn probe_str(vm: &mut Vm, source: &str) -> String {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse");
let module = Compiler::new().compile(&program).expect("compile");
let value = vm.run(&Arc::new(module)).expect("probe run");
vm.lookup_str(value).unwrap_or_default()
}
/// 编译并运行带依赖的模块,返回 `main.mjs` 写入 `globalThis.__r` 的探针字符串。
fn run_observer_module(dir: &std::path::Path) -> String {
let allocator = oxide_parser::Allocator::default();
let source = std::fs::read_to_string(dir.join("main.mjs")).expect("read main");
let program = oxide_parser::parse_module(&allocator, &source).expect("parse module");
let module = Compiler::new()
.compile_module(&program, dir.join("main.mjs").to_string_lossy().as_ref(), &mut FileLoader)
.expect("compile module");
let mut vm = Vm::new();
vm.run(&Arc::new(module)).expect("module run");
probe_str(&mut vm, "globalThis.__r")
}
/// 建目录并写入给定文件(文件名, 源码)。
fn fixture_dir(tag: &str, files: &[(&str, &str)]) -> std::path::PathBuf {
let cwd = std::env::current_dir().expect("cwd");
let dir = cwd.join(format!("__module_star_{tag}__"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("mkdir");
for (name, source) in files {
std::fs::write(dir.join(name), source).expect("write fixture");
}
dir
}
/// 双星源各自本地声明同名 `both`:歧义名从命名空间省略,`in` / `Object.keys` /
/// for-in 三面一致;唯一名与显式导出照常保留。
#[test]
fn star_ambiguous_local_binding_is_omitted() {
let dir = fixture_dir(
"ambiguous",
&[
("s1.mjs", "export var first = 'a';\nexport var both = 1;"),
("s2.mjs", "export var second = 'b';\nexport var both = 2;"),
("hub.mjs", "export * from './s1.mjs';\nexport * from './s2.mjs';"),
(
"main.mjs",
"import * as ns from './hub.mjs';\n \
let forks = 0;\n \
for (const k in ns) forks += (k === 'both' ? 10 : 1);\n \
globalThis.__r = [\n \
'first' in ns,\n \
'second' in ns,\n \
'both' in ns,\n \
ns.both === undefined,\n \
Object.keys(ns).join(','),\n \
Object.getOwnPropertyNames(ns).includes('both'),\n \
forks,\n \
ns.first,\n \
ns.second,\n \
].join('|');",
),
],
);
let _cleanup = Cleanup(dir.clone());
assert_eq!(
run_observer_module(&dir),
"true|true|false|true|first,second|false|2|a|b",
"歧义名应从命名空间三面一致省略,唯一名保留"
);
}
/// 双星源经 `export * as foo from` 转发同一命名空间对象:同 `(模块, 绑定)` 保留。
#[test]
fn star_same_namespace_reexport_binding_is_kept() {
let dir = fixture_dir(
"star_as",
&[
("empty.mjs", ""),
("s1.mjs", "export * as foo from './empty.mjs';"),
("s2.mjs", "export * as foo from './empty.mjs';"),
("hub.mjs", "export * from './s1.mjs';\nexport * from './s2.mjs';"),
(
"main.mjs",
"import * as ns from './hub.mjs';\n globalThis.__r = [typeof ns.foo, 'foo' in ns].join('|');",
),
],
);
let _cleanup = Cleanup(dir.clone());
assert_eq!(run_observer_module(&dir), "object|true", "同命名空间再导出不应被误删");
}
/// 双星源经 `import * as foo; export { foo }` 转发同一命名空间对象:同绑定保留。
#[test]
fn star_same_namespace_import_reexport_binding_is_kept() {
let dir = fixture_dir(
"import_star_as",
&[
("empty.mjs", ""),
("s1.mjs", "import * as foo from './empty.mjs';\nexport { foo };"),
("s2.mjs", "import * as foo from './empty.mjs';\nexport { foo };"),
("hub.mjs", "export * from './s1.mjs';\nexport * from './s2.mjs';"),
(
"main.mjs",
"import * as ns from './hub.mjs';\n globalThis.__r = [typeof ns.foo, 'foo' in ns].join('|');",
),
],
);
let _cleanup = Cleanup(dir.clone());
assert_eq!(run_observer_module(&dir), "object|true", "导入命名空间再导出不应被误删");
}
/// 双星源经 `export { foo } from base` / `import { foo } from base; export { foo }`
/// 转发同一绑定:`(模块, 绑定)` 相同,保留。
#[test]
fn star_same_named_reexport_binding_is_kept() {
let dir = fixture_dir(
"propagates",
&[
("base.mjs", "export const foo = 2;"),
("s1.mjs", "export { foo } from './base.mjs';"),
("s2.mjs", "import { foo } from './base.mjs';\nexport { foo };"),
("hub.mjs", "export * from './s1.mjs';\nexport * from './s2.mjs';"),
(
"main.mjs",
"import * as ns from './hub.mjs';\n globalThis.__r = [ns.foo, 'foo' in ns].join('|');",
),
],
);
let _cleanup = Cleanup(dir.clone());
assert_eq!(run_observer_module(&dir), "2|true", "同源绑定经不同路径转发不应被误删");
}
/// 三源:S1/S2 本地同名歧义后,S3 与 S1 同模块(同绑定 token)也不得重新加入。
#[test]
fn star_ambiguous_stays_omitted_with_third_source() {
let dir = fixture_dir(
"three_sources",
&[
("s1.mjs", "export var x = 1;\nexport var keep = 's1';"),
("s2.mjs", "export var x = 2;"),
(
"hub.mjs",
"export * from './s1.mjs';\nexport * from './s2.mjs';\nexport * from './s1.mjs';",
),
(
"main.mjs",
"import * as ns from './hub.mjs';\n globalThis.__r = ['x' in ns, ns.keep].join('|');",
),
],
);
let _cleanup = Cleanup(dir.clone());
assert_eq!(run_observer_module(&dir), "false|s1", "一旦歧义不得因后续同绑定源重新加入");
}
/// 显式本地导出在 star 歧义省略之后执行:显式导出重新定义该名并胜出。
#[test]
fn star_ambiguous_then_local_export_revives_name() {
let dir = fixture_dir(
"revive",
&[
("s1.mjs", "export var both = 1;"),
("s2.mjs", "export var both = 2;"),
("hub.mjs", "export * from './s1.mjs';\nexport * from './s2.mjs';\nexport var both = 'local';"),
(
"main.mjs",
"import * as ns from './hub.mjs';\n globalThis.__r = [ns.both, 'both' in ns, Object.keys(ns).join(',')].join('|');",
),
],
);
let _cleanup = Cleanup(dir.clone());
assert_eq!(run_observer_module(&dir), "local|true|both", "star 歧义后显式导出应重新定义并胜出");
}
/// 显式本地导出与 star 源同名:无论语句先后,显式导出恒胜。
#[test]
fn star_local_export_wins_over_star_source() {
let dir = fixture_dir(
"local_first",
&[
("dep.mjs", "export var x = 'dep';\nexport var d = 1;"),
("hub.mjs", "export var x = 'local';\nexport * from './dep.mjs';"),
(
"main.mjs",
"import * as ns from './hub.mjs';\n globalThis.__r = [ns.x, 'x' in ns, ns.d].join('|');",
),
],
);
let _cleanup = Cleanup(dir.clone());
assert_eq!(run_observer_module(&dir), "local|true|1", "本地导出先于 star 时恒胜");
let dir = fixture_dir(
"local_last",
&[
("dep.mjs", "export var x = 'dep';\nexport var d = 1;"),
("hub.mjs", "export * from './dep.mjs';\nexport var x = 'local';"),
(
"main.mjs",
"import * as ns from './hub.mjs';\n globalThis.__r = [ns.x, 'x' in ns, ns.d].join('|');",
),
],
);
let _cleanup = Cleanup(dir.clone());
assert_eq!(run_observer_module(&dir), "local|true|1", "本地导出后于 star 时覆盖 star 值");
}