From 2643e500d477576c4d1dcbc8d19b1b93a612171a Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 25 Sep 2026 17:11:14 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20for-in=20=E5=8F=B3=E5=80=BC=E5=8E=9F?= =?UTF-8?q?=E5=A7=8B=E5=80=BC=E8=A3=85=E7=AE=B1=E5=90=8E=E6=9E=9A=E4=B8=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/vm_dispatch_misc.rs | 6 ++-- .../tests/boxed_string_exotic_tests.rs | 29 +++++++++++++++++-- crates/oxide_vm/tests/control_flow_tests.rs | 6 ++-- .../tests/for_in_null_undefined_tests.rs | 12 ++++---- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index eb7c071a..f57f92bc 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -461,7 +461,7 @@ impl Vm { pub(crate) fn dispatch_for_in_init(&mut self, a: usize) -> Result<(), String> { vm_trace!("FOR_IN_INIT r{}={:?}", a, self.regs[a]); - let obj_val = self.regs[a]; + let mut obj_val = self.regs[a]; if obj_val.is_null() || obj_val.is_undefined() { // null/undefined 枚举不到任何键——是空 for-in,而非 TypeError。 let keys_vec: bumpalo::collections::Vec<(JsValue, u32)> = @@ -471,8 +471,8 @@ impl Vm { return Ok(()); } if !obj_val.is_object() { - // 未支持:其它基本类型的 ToObject 强转尚未实现,在此之前抛 TypeError 是正确行为。 - return self.raise_type_error("for-in right-hand side is not an object"); + // ToObject:字符串盒构造期已物化索引属性面,其余原始值盒无自身可枚举属性。 + obj_val = oxide_runtime_api::to_object(obj_val, self)?; } let mut keys_vec: Vec<(JsValue, u32)> = Vec::new(); diff --git a/crates/oxide_vm/tests/boxed_string_exotic_tests.rs b/crates/oxide_vm/tests/boxed_string_exotic_tests.rs index b81fbb49..4f47435c 100644 --- a/crates/oxide_vm/tests/boxed_string_exotic_tests.rs +++ b/crates/oxide_vm/tests/boxed_string_exotic_tests.rs @@ -1,8 +1,9 @@ //! 字符串盒(String exotic)构造期物化钉测:`boxed_value` 专属载荷与字符索引/ //! length 固有属性面。覆盖 test262 盒面红转绿的语义面——索引/length 读、in/ -//! 枚举、描述符、seal/freeze、defineProperty 同值改写放行/异值与属性面变更 -//! 拒绝、写保护(strict/sloppy)、delete 返 false、Object.assign/解构消费面、 -//! %StringPrototype% 自身盒面、迭代器/Array.from 载荷读、@@toStringTag 覆盖。 +//! 枚举、for-in 原始值右值装箱、描述符、seal/freeze、defineProperty 同值改写 +//! 放行/异值与属性面变更拒绝、写保护(strict/sloppy)、delete 返 false、 +//! Object.assign/解构消费面、%StringPrototype% 自身盒面、迭代器/Array.from 载荷读、 +//! @@toStringTag 覆盖。 //! //! 语义对齐规范 String exotic:索引属性 writable:false / enumerable:true / //! configurable:false,length writable:false / enumerable:false / @@ -67,6 +68,28 @@ fn forin_enumerates_indexes_only() { assert!(r.as_bool()); } +#[test] +fn forin_primitive_string() { + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "(function(){ const keys = []; for (const k in 'ab') keys.push(k); return keys.length === 2 && keys[0] === '0' && keys[1] === '1'; })()", + ) + .unwrap(); + assert!(r.as_bool()); +} + +#[test] +fn forin_primitive_no_keys() { + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "(function(){ const a = []; for (const k in 5) a.push(k); const b = []; for (const k in true) b.push(k); const c = []; for (const k in '') c.push(k); return a.length === 0 && b.length === 0 && c.length === 0; })()", + ) + .unwrap(); + assert!(r.as_bool()); +} + #[test] fn keys_and_own_keys() { let mut vm = Vm::new(); diff --git a/crates/oxide_vm/tests/control_flow_tests.rs b/crates/oxide_vm/tests/control_flow_tests.rs index 59ebebe1..3a04ef99 100644 --- a/crates/oxide_vm/tests/control_flow_tests.rs +++ b/crates/oxide_vm/tests/control_flow_tests.rs @@ -177,9 +177,9 @@ fn eval_for_in_empty_object() { } #[test] -fn eval_for_in_non_object_throws() { - let result = eval("for (k in 42) {}"); - assert!(result.contains("TypeError"), "expected TypeError, got: {result}"); +fn eval_for_in_primitive_number() { + // 右值经 ToObject 装箱后枚举:数字盒无自身可枚举属性,空枚举。 + assert_eq!(eval("var n=0; for (k in 42) { n=n+1; } n"), "0"); } #[test] diff --git a/crates/oxide_vm/tests/for_in_null_undefined_tests.rs b/crates/oxide_vm/tests/for_in_null_undefined_tests.rs index 2e8085bf..06df57c2 100644 --- a/crates/oxide_vm/tests/for_in_null_undefined_tests.rs +++ b/crates/oxide_vm/tests/for_in_null_undefined_tests.rs @@ -51,9 +51,11 @@ fn for_in_null_does_not_throw() { } #[test] -fn for_in_number_primitive_currently_throws() { - // 过渡行为:非 null/undefined 基本类型的 ToObject 强转尚未实现, - // 因此数字右侧仍抛 TypeError。ToObject 落地后它应变成空循环,届时本断言需变更。 - let out = eval("for(var k in 42){}"); - assert!(out.contains("TypeError"), "expected TypeError for number primitive, got: {out}"); +fn for_in_number_primitive_empty_loop() { + // 右值经 ToObject 装箱:数字盒无自身可枚举属性,空循环。 + assert_eq!( + eval("var r=0;for(var k in 42){r=r+1;}r"), + "0", + "for-in number primitive iterates zero times" + ); }