From 4016cfbf9b3ded42386e123c17e0cd65012f13a9 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 22 Sep 2026 09:13:10 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Map=20getOrInsert?= =?UTF-8?q?=20=E5=91=BD=E4=B8=AD=E8=BF=94=E7=8E=B0=E5=80=BC=E7=BC=BA?= =?UTF-8?q?=E5=A4=B1=E8=BF=BD=E5=8A=A0=E6=9C=AB=E5=B0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/map.rs | 21 ++++++++ crates/oxide_vm/src/bindings/bind_map.rs | 1 + crates/oxide_vm/tests/builtins_map_tests.rs | 58 +++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/crates/oxide_builtins/src/map.rs b/crates/oxide_builtins/src/map.rs index 1c6edc13..cd10090f 100644 --- a/crates/oxide_builtins/src/map.rs +++ b/crates/oxide_builtins/src/map.rs @@ -268,6 +268,27 @@ pub fn map_get(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(found.unwrap_or(JsValue::undefined())) } +/// `Map.prototype.getOrInsert(key, value)`:键命中返回已有存储值(不插入、 +/// size 不变);键缺失时末尾追加新条目并返回实参 value。 +/// +/// # 边界与前提 +/// - 命中判定不区分存储值 undefined:命中即返回现值,不得视为缺失。 +/// - 键按 SameValueZero 语义归一(±0 同键、NaN 自相等),与 set/get 同域。 +pub fn map_get_or_insert(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let inner = native_try!(get_map_inner(vm, this_val)); + let key = vm.reg(if args.len() > 1 { args[1] } else { 0 }); + let val = vm.reg(if args.len() > 2 { args[2] } else { 0 }); + let found = unsafe { (*inner).get(&SetKey(key)).copied() }; + if let Some(existing) = found { + return NativeResult::Ok(existing); + } + unsafe { + (*inner).insert(SetKey(key), val); + } + NativeResult::Ok(val) +} + /// `Map.prototype.has(key)`:key 是否存在。 pub fn map_has(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); diff --git a/crates/oxide_vm/src/bindings/bind_map.rs b/crates/oxide_vm/src/bindings/bind_map.rs index 7549f118..55bf02b1 100644 --- a/crates/oxide_vm/src/bindings/bind_map.rs +++ b/crates/oxide_vm/src/bindings/bind_map.rs @@ -24,6 +24,7 @@ pub fn bind_map(core: &Arc, session: &KernelSession, global: &mut Js &[ ("set", oxide_builtins::map::map_set:: as *const (), 2), ("get", oxide_builtins::map::map_get:: as *const (), 1), + ("getOrInsert", oxide_builtins::map::map_get_or_insert:: as *const (), 2), ("has", oxide_builtins::map::map_has:: as *const (), 1), ("delete", oxide_builtins::map::map_delete:: as *const (), 1), ("clear", oxide_builtins::map::map_clear:: as *const (), 0), diff --git a/crates/oxide_vm/tests/builtins_map_tests.rs b/crates/oxide_vm/tests/builtins_map_tests.rs index 379c0592..777f0933 100644 --- a/crates/oxide_vm/tests/builtins_map_tests.rs +++ b/crates/oxide_vm/tests/builtins_map_tests.rs @@ -210,3 +210,61 @@ fn map_many_runtime_string_keys() { .unwrap(); assert_eq!(str_val(&vm, r), "ok"); } + +// ── getOrInsert:命中返回现值不插入,缺失末尾追加并返回实参 ── + +#[test] +fn map_get_or_insert_missing_appends_and_returns_arg() { + // 键缺失:末尾追加新条目、返回实参值,size 加一。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "var m = new Map(); m.set('a', 1); var v = m.getOrInsert('b', 2); v + '/' + m.size", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "2/2"); +} + +#[test] +fn map_get_or_insert_present_returns_stored_value() { + // 键命中:返回现值、不插入,size 不变。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "var m = new Map(); m.set('a', 1); var v = m.getOrInsert('a', 99); v + '/' + m.size", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "1/1"); +} + +#[test] +fn map_get_or_insert_present_undefined_value_returns_undefined() { + // 存储值为 undefined 也计命中:直接返回 undefined,不插入、size 不变。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "var m = new Map(); m.set('u', undefined); var hit = m.getOrInsert('u', 7); hit === undefined && m.size === 1", + ) + .unwrap(); + assert!(r.as_bool()); +} + +#[test] +fn map_get_or_insert_zero_key_normalized() { + // ±0 归一同键:存 -0 后以 +0 查,命中返回现值。 + let mut vm = Vm::new(); + let r = eval(&mut vm, "var m = new Map(); m.set(-0, 1); m.getOrInsert(0, 9) + '/' + m.size").unwrap(); + assert_eq!(str_val(&vm, r), "1/1"); +} + +#[test] +fn map_get_or_insert_length_and_name() { + // 函数描述符:length 2、name 'getOrInsert'。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "Map.prototype.getOrInsert.length === 2 && Map.prototype.getOrInsert.name === 'getOrInsert'", + ) + .unwrap(); + assert!(r.as_bool()); +}