diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 7148d9b2..63fe0fc3 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -1,7 +1,7 @@ use crate::native::NativeFn; use crate::vm::{native_fn_ptr_to_fn, ForInIter, FrameArgs, FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH}; use crate::vm_trace; -use oxide_runtime_api::{to_boolean, to_string_full, NativeResult, VmHost}; +use oxide_runtime_api::{push_to_string, to_boolean, to_string_full, NativeResult, VmHost}; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::private_key::{int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key}; use oxide_types::value::JsValue; @@ -149,27 +149,33 @@ impl Vm { if (seg >> 31) == 1 { let reg = (seg & 0x7FFF_FFFF) as usize; let val = self.regs[reg]; - let s = if val.is_string() { - // SAFETY: val 是字符串值。 - unsafe { (*val.as_string_ptr()).to_owned_string() } + if val.is_string() { + // SAFETY: val 是字符串值;借用仅在本次 push 内消费,不跨分配点。 + let s = unsafe { (*val.as_string_ptr()).as_str() }; + result.push_str(s); + } else if val.is_object() || val.is_symbol() { + // 对象走完整 ToString(ToPrimitive 副作用顺序);Symbol 由 + // to_string_full 抛 TypeError(push_to_string 无 symbol 分支)。 + let s = to_string_full(val, self)?; + result.push_str(&s); } else { - to_string_full(val, self)? - }; - result.push_str(&s); + // 原始值直写结果缓冲,免中间 String。 + push_to_string(val, &mut result); + } } else { let const_idx = (seg & 0x7FFF_FFFF) as usize; let imm = self.immutables(); if const_idx < imm.len() { let val = imm[const_idx]; if val.is_string() { - // SAFETY: val 是字符串值。 - let s = unsafe { (*val.as_string_ptr()).to_owned_string() }; - result.push_str(&s); + // SAFETY: val 是字符串值;借用仅在本次 push 内消费,不跨分配点。 + let s = unsafe { (*val.as_string_ptr()).as_str() }; + result.push_str(s); } } } } - self.regs[rd] = self.new_string(&result); + self.regs[rd] = self.new_string_owned(result); Ok(()) } diff --git a/crates/oxide_vm/tests/template_tests.rs b/crates/oxide_vm/tests/template_tests.rs index 4355a7cb..96a4dc06 100644 --- a/crates/oxide_vm/tests/template_tests.rs +++ b/crates/oxide_vm/tests/template_tests.rs @@ -92,6 +92,27 @@ fn template_numeric_expression() { assert_eq!(to_str(&vm, result.unwrap()), "2"); } +#[test] +fn template_with_double_bigint_and_bool() { + // 原始值段走直写路径,输出与 ToString 一致。 + let (vm, result) = eval_val("`a${1.5}b${123n}c${true}d`"); + assert_eq!(to_str(&vm, result.unwrap()), "a1.5b123ctrued"); +} + +#[test] +fn template_with_object_uses_tostring() { + // 对象段保留完整 ToString(ToPrimitive 触发用户 toString)。 + let (vm, result) = eval_val("var o = { toString: function () { return 'T'; } }; `x${o}y`"); + assert_eq!(to_str(&vm, result.unwrap()), "xTy"); +} + +#[test] +fn template_with_symbol_throws_type_error() { + // Symbol 段经 to_string_full 抛 TypeError,不静默输出空段。 + let result = eval("`${Symbol('x')}`"); + assert!(result.contains("TypeError"), "模板串中的 Symbol 应抛 TypeError,got: {result}"); +} + #[test] fn template_expression_reads_physical_register_above_127() { let mut ir = IRFunction::new();