diff --git a/crates/oxide_builtins/src/symbol.rs b/crates/oxide_builtins/src/symbol.rs index 410a1e5..1896818 100644 --- a/crates/oxide_builtins/src/symbol.rs +++ b/crates/oxide_builtins/src/symbol.rs @@ -24,23 +24,30 @@ pub fn symbol_constructor(vm: &mut H, args: &[u8]) -> NativeResult { } } - // 描述符按规范 ToString(description):对象经 ToPrimitive(string hint) 强制转换, - // Symbol 抛 TypeError,对象方法抛出的原始异常原样传播。 + // 描述符按规范 ToString(description):缺省参数与 undefined 对应无描述 + // ([[Description]] 为 undefined,`symbol.description` 返回 undefined); + // 对象经 ToPrimitive(string hint) 强制转换,Symbol 抛 TypeError, + // 对象方法抛出的原始异常原样传播。 let description = if args.len() > 1 { - match oxide_runtime_api::to_string_full(vm.reg(args[1]), vm) { - Ok(s) => s, - Err(_) => { - if let Some(exc) = vm.take_uncaught_value() { - return NativeResult::Err(exc); + let desc_val = vm.reg(args[1]); + if desc_val.is_undefined() { + None + } else { + match oxide_runtime_api::to_string_full(desc_val, vm) { + Ok(s) => Some(s), + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); } - return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); } } } else { - String::new() + None }; - let idx = vm.symbol_intern(Some(description)); + let idx = vm.symbol_intern(description); NativeResult::Ok(JsValue::symbol(idx)) } @@ -96,6 +103,22 @@ pub fn symbol_value_of(vm: &mut H, args: &[u8]) -> NativeResult { } } +/// `Symbol.prototype.description` getter:返回该 Symbol 的 description 字符串; +/// 无 description 的 Symbol 返回 undefined。this 必须是 Symbol 或 Symbol 包装对象, +/// 否则抛 TypeError(规范 `SymbolDescriptiveString` 的 thisSymbolValue 校验)。 +pub fn symbol_description_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let sym = match this_symbol_value(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + let idx = sym.as_symbol_index(); + match vm.symbol_description(idx) { + Some(desc) => NativeResult::Ok(vm.new_string_owned(desc.to_string())), + None => NativeResult::Ok(JsValue::undefined()), + } +} + /// `Symbol.for(key)`:在全局 symbol 注册表中查找并返回同 key 的 Symbol; /// 不存在则新建并登记。同 key 的 Symbol 在全局唯一。 pub fn symbol_for(vm: &mut H, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index 6962722..6417d70 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -729,7 +729,6 @@ fn classify_vm_error(e: &str, neg: Option<&Negative>, no_skip: bool) -> TestOutc || e.contains("unsupported") || e.contains("step limit") || e.contains("NEW_EXPRESSION") - || e.contains("IC_GET_PROP on non-object") || e.contains("GET_PROP_DYNAMIC on non-object") || e.contains("SET_PROP_DYNAMIC on non-object") || e.contains("private field brand check") @@ -1634,6 +1633,7 @@ mod tests { "TypeError: Cannot convert object to primitive value", "TypeError: Cannot create property on non-object", "TypeError: Property description must be an object", + "IC_GET_PROP on non-object", ] { assert_outcome(e, None, false, &TestOutcome::Fail("".into())); } @@ -1647,7 +1647,6 @@ mod tests { "feature is not supported", "unsupported syntax", "vm error: NEW_EXPRESSION not supported", - "IC_GET_PROP on non-object", "GET_PROP_DYNAMIC on non-object", "SET_PROP_DYNAMIC on non-object", "private field brand check", diff --git a/crates/oxide_vm/src/bindings/bind_symbol.rs b/crates/oxide_vm/src/bindings/bind_symbol.rs index 46c0359..455c32d 100644 --- a/crates/oxide_vm/src/bindings/bind_symbol.rs +++ b/crates/oxide_vm/src/bindings/bind_symbol.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::bindings::{apply_binding_table, configure_native_constructor}; +use crate::bindings::{apply_binding_table, bind_accessor_getter, configure_native_constructor}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::JsObject; use oxide_types::value::JsValue; @@ -36,6 +36,15 @@ pub fn bind_symbol(core: &Arc, session: &KernelSession, global: &mut ], ); + // description 是访问器 getter(Symbol 原始值成员访问经 Symbol.prototype 链命中)。 + bind_accessor_getter( + core, + session, + proto, + "description", + oxide_builtins::symbol::symbol_description_getter:: as *const (), + ); + for (name, val) in [ ( "match", diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 25ede69..486a46f 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -70,6 +70,11 @@ impl Vm { let proto = unsafe { &*proto_ptr }; return self.ordinary_get(proto, prop_name_si, val).map(Some); } + if val.is_symbol() { + let proto_ptr = self.session.builtin_world().symbol_proto.as_ptr() as *mut JsObject; + let proto = unsafe { &*proto_ptr }; + return self.ordinary_get(proto, prop_name_si, val).map(Some); + } Ok(None) } diff --git a/crates/oxide_vm/tests/builtins_symbol_tests.rs b/crates/oxide_vm/tests/builtins_symbol_tests.rs index 0080b01..af058bd 100644 --- a/crates/oxide_vm/tests/builtins_symbol_tests.rs +++ b/crates/oxide_vm/tests/builtins_symbol_tests.rs @@ -283,3 +283,69 @@ fn symbol_key_read_via_get_own_property_symbols_index() { .unwrap(); assert!(result.as_bool()); } + +// --- Symbol 原始值成员访问走 Symbol.prototype 链 --- + +#[test] +fn symbol_primitive_to_string_via_symbol_proto() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Symbol('66').toString()").unwrap(); + assert_eq!(to_str(&vm, result), "Symbol(66)"); +} + +#[test] +fn symbol_primitive_value_of_via_symbol_proto() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var s = Symbol('x'); s.valueOf() === s").unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn symbol_primitive_chain_reaches_object_proto_method() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Symbol('x').hasOwnProperty('description')").unwrap(); + assert!(!result.as_bool()); +} + +#[test] +fn symbol_primitive_description_getter() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Symbol('desc').description").unwrap(); + assert_eq!(to_str(&vm, result), "desc"); +} + +#[test] +fn symbol_without_description_returns_undefined() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Symbol().description").unwrap(); + assert!(result.is_undefined()); +} + +#[test] +fn symbol_wrapper_object_description_unboxes() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Object(Symbol('w')).description").unwrap(); + assert_eq!(to_str(&vm, result), "w"); +} + +#[test] +fn symbol_description_non_symbol_receiver_throws_type_error() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "try { Object.getOwnPropertyDescriptor(Symbol.prototype, 'description').get.call(42); false } catch (e) { e instanceof TypeError }", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn symbol_proto_direct_description_access_throws_type_error() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "try { Symbol.prototype.description; false } catch (e) { e instanceof TypeError }", + ) + .unwrap(); + assert!(result.as_bool()); +} diff --git a/crates/oxide_vm/tests/ic_polymorphic_tests.rs b/crates/oxide_vm/tests/ic_polymorphic_tests.rs index 63894e3..c398b4a 100644 --- a/crates/oxide_vm/tests/ic_polymorphic_tests.rs +++ b/crates/oxide_vm/tests/ic_polymorphic_tests.rs @@ -181,3 +181,18 @@ fn primitive_receiver_skips_ic_ext_words() { ); assert_eq!(r, "303", "字符串 length 经 primitive 分支返回 3,扩展字跳越无错位"); } + +/// 非对象 receiver(symbol)经 IC primitive 分支走 Symbol.prototype 链: +/// toString/description 均经原型解析,扩展字跳越无错位。 +#[test] +fn symbol_primitive_receiver_walks_symbol_proto() { + let (r, _hits, _misses) = run_once( + r#"var s = Symbol('x'); + var ok = 0; + for (var i = 0; i < 100; i++) { + ok += (s.toString() === 'Symbol(x)') ? 1 : 0; + } + ok + ((s.description === 'x') ? 1 : 0)"#, + ); + assert_eq!(r, "101", "symbol 原始值 toString/description 经 Symbol.prototype 解析"); +}