fix: 对象建键入口统一走 property_key_si 规范化,数字键不再分裂

Object.fromEntries/JSON.parse/new Map 对象 entry/rest 解构静态 excluded
四处直接 intern 动态字符串键绕过整数键区间映射,规范数字键分裂为
字符串键与整数键两个键。统一经 string_key_si 规范化:规范数字串映射
整数键,其余 intern 字符串键,与 property_key_si 字符串分支口径一致。
make_int_key 增加 debug 断言兜底索引越界。
This commit is contained in:
2026-08-14 22:37:46 +08:00
parent 540153030b
commit b82fe9af32
12 changed files with 145 additions and 9 deletions
@@ -113,3 +113,19 @@ fn map_new_delete() {
let r = eval(&mut vm, "var m = new Map(); m.set('x', 10); m.delete('x'); m.has('x')").unwrap();
assert!(!r.as_bool());
}
#[test]
fn map_new_object_entry_pair_reads_key_and_value() {
// 对象 entry({0:'k',1:'v'})的数字键是整数键:读取须经 ToPropertyKey 规范化。
let mut vm = Vm::new();
let r = eval(&mut vm, "var m = new Map([{0:'k',1:'v'}]); m.get('k')").unwrap();
assert_eq!(str_val(&vm, r), "v");
}
#[test]
fn map_new_mixed_array_and_object_entries() {
// 数组 entry 与对象 entry 两种形态混合可共存。
let mut vm = Vm::new();
let r = eval(&mut vm, "var m = new Map([['a',1],{0:'b',1:2}]); m.get('a') + '/' + m.get('b')").unwrap();
assert_eq!(str_val(&vm, r), "1/2");
}
@@ -185,3 +185,33 @@ fn object_values_returns_array() {
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2);
}
// -- Object.fromEntries --
#[test]
fn from_entries_numeric_key_normalized_to_int_key() {
// 数字键 entry 走 ToPropertyKey 规范化:fromEntries 产出的 "5" 键与 o[5] 同一键。
let (_vm, result) = eval("Object.fromEntries([[5,'a']])[5]").unwrap();
assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "a");
}
#[test]
fn from_entries_string_index_key_reads_via_numeric_access() {
// 字符串 "5" 键是规范数组下标,读 o[5] 命中同一整数键。
let (_vm, result) = eval("Object.fromEntries([['5','a']])[5]").unwrap();
assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "a");
}
#[test]
fn from_entries_numeric_key_write_keeps_single_key() {
// 再写 o[5] 不产生第二个 "5" 键(键唯一)。
let (_vm, result) =
eval("var o = Object.fromEntries([[5,'a']]); o[5]='x'; Object.getOwnPropertyNames(o).join(',')").unwrap();
assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "5");
}
#[test]
fn from_entries_multiple_pairs_roundtrip() {
let (_vm, result) = eval("var o = Object.fromEntries([[1,'a'],[2,'b']]); o[1] + o[2]").unwrap();
assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "ab");
}
@@ -130,3 +130,33 @@ fn standalone_nested_rest_assignment() {
let result = eval(&mut vm, "var x; [...[x]]=[1,2,3]; x").unwrap();
assert_num(result, 1.0);
}
#[test]
fn object_rest_excludes_numeric_bound_key_from_array_source() {
// 数组元素区整数键:pattern 绑定 "0" 后 rest 不得再包含 "0"。
let mut vm = Vm::new();
let result = eval(&mut vm, "const {0:x, ...r} = [1,2,3]; Object.keys(r).length").unwrap();
assert_eq!(result.as_int(), 2);
let result = eval(&mut vm, "const {0:x, ...r} = [1,2,3]; x === 1").unwrap();
assert!(result.as_bool());
// rest 从 1 开始:r[1] 是源元素 2,r[0] 已被排除为 undefined。
let result = eval(&mut vm, "const {0:x, ...r} = [1,2,3]; r[1] + r[2]").unwrap();
assert_eq!(result.as_int(), 5);
}
#[test]
fn object_rest_excludes_multiple_numeric_keys() {
let mut vm = Vm::new();
let result = eval(&mut vm, "const {0:x, 1:y, ...r} = [1,2,3,4]; Object.keys(r).join(',')").unwrap();
let s = unsafe { &*result.as_string_ptr() }.as_str().to_string();
assert_eq!(s, "2,3");
}
#[test]
fn object_rest_keeps_numeric_key_from_shape_source() {
// 对象源数字键(shape 链整数键)同样排除。
let mut vm = Vm::new();
let result = eval(&mut vm, "const {0:x, ...r} = {0:'a',1:'b',2:'c'}; Object.keys(r).join(',')").unwrap();
let s = unsafe { &*result.as_string_ptr() }.as_str().to_string();
assert_eq!(s, "1,2");
}
+27
View File
@@ -274,3 +274,30 @@ fn json_stringify_cycle_throws_type_error() {
let err = eval(&mut vm, "var a = {}; a.self = a; JSON.stringify(a)");
assert!(err.is_err());
}
// -- 数字键对象(P1-2 回归防线) --
#[test]
fn json_parse_numeric_object_key_reads_via_index() {
// JSON 键 "5" 经字符串规范化映射整数键:o[5] 与 o["5"] 命中同一键。
let mut vm = Vm::new();
let result = eval(&mut vm, "JSON.parse('{\"5\":1}')[5]").unwrap();
assert!((result.as_double() - 1.0).abs() < 0.0001);
}
#[test]
fn json_parse_numeric_keys_do_not_clash_between_keys() {
// 多个数字键各自独立:{"0":7,"2024":8} 的 0 与 2024 不互相覆盖。
let mut vm = Vm::new();
let result = eval(&mut vm, "var o = JSON.parse('{\"0\":7,\"2024\":8}'); o[0] + o[2024]").unwrap();
assert!((result.as_double() - 15.0).abs() < 0.0001);
}
#[test]
fn json_parse_numeric_keys_stringify_roundtrip() {
// 数字键对象经 stringify 后键名保持 "5"(键字符串化语义)。
let mut vm = Vm::new();
let result = eval(&mut vm, "JSON.stringify(JSON.parse('{\"5\":1}'))").unwrap();
let s = string_value(&vm, result);
assert_eq!(s, "{\"5\":1}");
}
@@ -110,3 +110,15 @@ fn keys_with_leading_zero_not_integer_index() {
let idx_1 = keys.iter().position(|k| k == "1").unwrap();
assert!(idx_1 < idx_01, "integer '1' should come before string '01'");
}
// ── 对象字面量数字键字符串化 ──
#[test]
fn numeric_literal_key_materializes_as_string() {
// `{5:1}` 的键是字符串 "5"(键经 ToPropertyKey 字符串化),读 o[5]/o["5"] 同一键。
let r = eval_str("var o={5:1}; Object.getOwnPropertyNames(o)[0]").unwrap();
assert!(r.is_string());
let s = unsafe { &*r.as_string_ptr() }.data.clone();
assert_eq!(s, "5");
let r = eval_str("var o={5:1}; o[5] + o['5']").unwrap();
assert!(r.is_int() && r.as_int() == 2);
}