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();