From ba5ff279d9c8b8ffc589296838989ea970cb9d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Sun, 14 Jun 2026 17:48:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Symbol.for=20?= =?UTF-8?q?=E5=92=8C=20Symbol.keyFor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/bindings/bind_symbol.rs | 10 ++++++ crates/oxide_vm/src/builtins/symbol.rs | 34 ++++++++++++++++++ crates/oxide_vm/src/vm.rs | 16 +++++++++ crates/oxide_vm/src/vm_support.rs | 5 +++ .../oxide_vm/tests/builtins_symbol_tests.rs | 35 +++++++++++++++++++ 5 files changed, 100 insertions(+) diff --git a/crates/oxide_vm/src/bindings/bind_symbol.rs b/crates/oxide_vm/src/bindings/bind_symbol.rs index 8a9366a..abad852 100644 --- a/crates/oxide_vm/src/bindings/bind_symbol.rs +++ b/crates/oxide_vm/src/bindings/bind_symbol.rs @@ -15,6 +15,16 @@ pub fn bind_symbol(core: &Arc, session: &KernelSession, global: &mut 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( session.builtin_world(), proto, diff --git a/crates/oxide_vm/src/builtins/symbol.rs b/crates/oxide_vm/src/builtins/symbol.rs index b9abfc6..e23afa2 100644 --- a/crates/oxide_vm/src/builtins/symbol.rs +++ b/crates/oxide_vm/src/builtins/symbol.rs @@ -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; 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, ®istered_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()) +} diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index e6f9e0b..599636a 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1,5 +1,6 @@ #![allow(clippy::arc_with_non_send_sync)] +use std::collections::HashMap; use std::sync::Arc; use oxide_compiler::module::CompiledModule; @@ -151,6 +152,7 @@ pub struct Vm { pub(crate) pending_error_kind: Option<&'static str>, pub(crate) symbol_counter: u32, pub(crate) symbol_descriptions: Vec, + pub(crate) symbol_registry: HashMap, #[allow(dead_code)] pub(crate) for_of_iters: Vec<*mut u8>, pub(crate) root_reg_limit: u8, @@ -1184,6 +1186,20 @@ mod tests { 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] fn for_of_opcodes_fail_explicitly() { for (op, expected) in [ diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 820cb61..079c5b7 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -42,6 +42,7 @@ impl Vm { pending_error_kind: None, symbol_counter: 0, symbol_descriptions: Vec::new(), + symbol_registry: std::collections::HashMap::new(), for_of_iters: Vec::new(), root_reg_limit: 0, active_reg_limit: 0, @@ -77,6 +78,7 @@ impl Vm { pending_error_kind: None, symbol_counter: 0, symbol_descriptions: Vec::new(), + symbol_registry: std::collections::HashMap::new(), for_of_iters: Vec::new(), root_reg_limit: 0, active_reg_limit: 0, @@ -95,6 +97,9 @@ impl Vm { self.constants.clear(); self.epoch.reset(); self.interned_strings.clear(); + self.symbol_counter = 0; + self.symbol_descriptions.clear(); + self.symbol_registry.clear(); self.root_reg_limit = 0; self.active_reg_limit = 0; } diff --git a/crates/oxide_vm/tests/builtins_symbol_tests.rs b/crates/oxide_vm/tests/builtins_symbol_tests.rs index 2f223e8..1e7c7c4 100644 --- a/crates/oxide_vm/tests/builtins_symbol_tests.rs +++ b/crates/oxide_vm/tests/builtins_symbol_tests.rs @@ -123,3 +123,38 @@ fn symbol_iterator_exists() { let result = eval(&mut vm, "typeof Symbol.iterator").unwrap(); 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()); +}