feat: 实现 Symbol.for 和 Symbol.keyFor

This commit is contained in:
2026-06-14 17:48:31 +08:00
parent 4ce8fa75dc
commit cf55db1b26
5 changed files with 100 additions and 0 deletions
@@ -15,6 +15,16 @@ pub fn bind_symbol(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
configure_native_constructor(ctor, crate::builtins::symbol::symbol_constructor as *const (), 1); configure_native_constructor(ctor, crate::builtins::symbol::symbol_constructor as *const (), 1);
apply_binding_table(
session.builtin_world(),
ctor,
core,
&[
("for", crate::builtins::symbol::symbol_for as *const (), 1),
("keyFor", crate::builtins::symbol::symbol_key_for as *const (), 1),
],
);
apply_binding_table( apply_binding_table(
session.builtin_world(), session.builtin_world(),
proto, proto,
+34
View File
@@ -60,3 +60,37 @@ pub fn symbol_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
let si = vm.kernel_core().string_forge().intern(&result).0; let si = vm.kernel_core().string_forge().intern(&result).0;
NativeResult::Ok(JsValue::string(si, 0)) NativeResult::Ok(JsValue::string(si, 0))
} }
pub fn symbol_for(vm: &mut Vm, args: &[u8]) -> NativeResult {
let key = if args.len() > 1 {
coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1]))
} else {
"undefined".to_string()
};
if let Some(&idx) = vm.symbol_registry.get(&key) {
return NativeResult::Ok(JsValue::symbol(idx));
}
let idx = vm.symbol_descriptions.len() as u32;
vm.symbol_descriptions.push(key.clone());
vm.symbol_registry.insert(key, idx);
NativeResult::Ok(JsValue::symbol(idx))
}
pub fn symbol_key_for(vm: &mut Vm, args: &[u8]) -> NativeResult {
let sym = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
if !sym.is_symbol() {
return NativeResult::Err(crate::builtins::error::create_type_error(vm, "is not a symbol"));
}
let idx = sym.as_symbol_index();
for (key, &registered_idx) in &vm.symbol_registry {
if registered_idx == idx {
let si = vm.kernel_core().string_forge().intern(key).0;
return NativeResult::Ok(JsValue::string(si, 0));
}
}
NativeResult::Ok(JsValue::undefined())
}
+16
View File
@@ -1,5 +1,6 @@
#![allow(clippy::arc_with_non_send_sync)] #![allow(clippy::arc_with_non_send_sync)]
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use oxide_compiler::module::CompiledModule; use oxide_compiler::module::CompiledModule;
@@ -151,6 +152,7 @@ pub struct Vm {
pub(crate) pending_error_kind: Option<&'static str>, pub(crate) pending_error_kind: Option<&'static str>,
pub(crate) symbol_counter: u32, pub(crate) symbol_counter: u32,
pub(crate) symbol_descriptions: Vec<String>, pub(crate) symbol_descriptions: Vec<String>,
pub(crate) symbol_registry: HashMap<String, u32>,
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) for_of_iters: Vec<*mut u8>, pub(crate) for_of_iters: Vec<*mut u8>,
pub(crate) root_reg_limit: u8, pub(crate) root_reg_limit: u8,
@@ -1184,6 +1186,20 @@ mod tests {
assert!(vm.constants.is_empty()); assert!(vm.constants.is_empty());
} }
#[test]
fn full_reset_clears_symbol_state() {
let mut vm = Vm::new();
vm.symbol_counter = 1;
vm.symbol_descriptions.push("shared".to_string());
vm.symbol_registry.insert("shared".to_string(), 0);
vm.full_reset();
assert_eq!(vm.symbol_counter, 0);
assert!(vm.symbol_descriptions.is_empty());
assert!(vm.symbol_registry.is_empty());
}
#[test] #[test]
fn for_of_opcodes_fail_explicitly() { fn for_of_opcodes_fail_explicitly() {
for (op, expected) in [ for (op, expected) in [
+5
View File
@@ -42,6 +42,7 @@ impl Vm {
pending_error_kind: None, pending_error_kind: None,
symbol_counter: 0, symbol_counter: 0,
symbol_descriptions: Vec::new(), symbol_descriptions: Vec::new(),
symbol_registry: std::collections::HashMap::new(),
for_of_iters: Vec::new(), for_of_iters: Vec::new(),
root_reg_limit: 0, root_reg_limit: 0,
active_reg_limit: 0, active_reg_limit: 0,
@@ -77,6 +78,7 @@ impl Vm {
pending_error_kind: None, pending_error_kind: None,
symbol_counter: 0, symbol_counter: 0,
symbol_descriptions: Vec::new(), symbol_descriptions: Vec::new(),
symbol_registry: std::collections::HashMap::new(),
for_of_iters: Vec::new(), for_of_iters: Vec::new(),
root_reg_limit: 0, root_reg_limit: 0,
active_reg_limit: 0, active_reg_limit: 0,
@@ -95,6 +97,9 @@ impl Vm {
self.constants.clear(); self.constants.clear();
self.epoch.reset(); self.epoch.reset();
self.interned_strings.clear(); self.interned_strings.clear();
self.symbol_counter = 0;
self.symbol_descriptions.clear();
self.symbol_registry.clear();
self.root_reg_limit = 0; self.root_reg_limit = 0;
self.active_reg_limit = 0; self.active_reg_limit = 0;
} }
@@ -123,3 +123,38 @@ fn symbol_iterator_exists() {
let result = eval(&mut vm, "typeof Symbol.iterator").unwrap(); let result = eval(&mut vm, "typeof Symbol.iterator").unwrap();
assert_eq!(to_str(&vm, result), "object"); assert_eq!(to_str(&vm, result), "object");
} }
#[test]
fn symbol_for_reuses_registered_symbol() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s1 = Symbol.for('shared'); var s2 = Symbol.for('shared'); s1 === s2").unwrap();
assert!(result.as_bool());
}
#[test]
fn symbol_for_is_distinct_from_symbol_constructor() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s1 = Symbol.for('shared'); var s2 = Symbol('shared'); s1 === s2").unwrap();
assert!(!result.as_bool());
}
#[test]
fn symbol_key_for_returns_registered_key() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol.keyFor(Symbol.for('shared'))").unwrap();
assert_eq!(to_str(&vm, result), "shared");
}
#[test]
fn symbol_key_for_unregistered_symbol_returns_undefined() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol.keyFor(Symbol('shared'))").unwrap();
assert!(result.is_undefined());
}
#[test]
fn symbol_key_for_non_symbol_throws_type_error() {
let mut vm = Vm::new();
let result = eval(&mut vm, "try { Symbol.keyFor(42) } catch (e) { e instanceof TypeError }").unwrap();
assert!(result.as_bool());
}