From 8cd3cf1440d04c3d5e15fb7e1e1a99e319935b19 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 19 Sep 2026 18:46:46 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20flat/flatMap=20=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E9=80=9A=E7=94=A8=E5=8C=96=E3=80=81=E6=B7=B1=E5=BA=A6=2064=20?= =?UTF-8?q?=E4=BD=8D=E5=9F=9F=E3=80=81=E7=89=A9=E7=A7=8D=E6=97=8F=E4=B8=8E?= =?UTF-8?q?=20CDO=20=E6=94=B6=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array/element.rs | 155 +++++++----- crates/oxide_builtins/src/array/iterate.rs | 117 ++++++--- .../tests/array_hole_semantics_tests.rs | 233 ++++++++++++++++++ 3 files changed, 410 insertions(+), 95 deletions(-) diff --git a/crates/oxide_builtins/src/array/element.rs b/crates/oxide_builtins/src/array/element.rs index 301e6412..980bd5a5 100644 --- a/crates/oxide_builtins/src/array/element.rs +++ b/crates/oxide_builtins/src/array/element.rs @@ -678,76 +678,107 @@ pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(this_val) } -/// `Array.prototype.flat(depth)`:按深度递归展开嵌套数组(含循环引用保护)返回新数组。 +/// `Array.prototype.flat([depth])`:按深度递归展开嵌套数组返回新数组(循环引用原样保留)。 +/// +/// # 步骤 +/// 1. `ToObject` 装箱基元 this(null/undefined 抛 TypeError),装箱体钉入返回 +/// 寄存器跨用户窗口保 GC 根(结果数组钉入后让位,装箱体按接收者值传递保活); +/// 长度按规范 `ToLength(Get(O, "length"))` 读取(上限 2^53-1)。 +/// 2. depth:无实参/undefined 实参缺省 1;否则 ToIntegerOrInfinity(转换异常 +/// 传播),负数归 0 不展开,+∞ 保留无限深度臂。 +/// 3. 结果 `A` 按 `ArraySpeciesCreate(O, 0)` 构建(真数组按 constructor/@@species +/// 构造,非可构造值抛 TypeError)并钉入返回寄存器;写入一律 +/// CreateDataPropertyOrThrow(frozen 等受限目标抛 TypeError),真数组结果的 +/// length 随元素写入逐位扩展。 +/// 4. 流式 DFS:显式栈帧 = (源对象, 源长度, 剩余深度, 下一索引);每个源索引 +/// HasProperty 门控、命中才 Get;深度内的数组元素压栈递归(长度按 +/// LengthOfArrayLike 读取),已在栈上的数组(循环引用)原样写入结果;洞位 +/// 不写目标(结果恒紧凑)。 +/// +/// # 副作用 +/// - 接收者/元素的 Get 可经原型链触发用户代码;species 构造与目标属性写入 +/// 可按规范抛 TypeError。 pub fn array_flat(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.flat called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &*arr_ptr }; - let n = arr.prop_count() as usize; - let depth = if args.len() > 1 { - let n = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN); - if !n.is_finite() { - vm.kernel_core().config.max_call_depth - } else { - (n as i32).max(1) as usize - } - } else { - 1 + // ToObject:基元 this 装箱(null/undefined 抛 TypeError)。 + let this_val = match oxide_runtime_api::to_object(vm.reg(args[0]), vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)), + }; + // 装箱体钉入返回寄存器:它只存于 Rust 局部,跨 length getter / depth 折算 + // 用户窗口须保 GC 根。call 转发形态的实参寄存器集可占该槽,占位时跳过钉位 + // (装箱体按接收者值传递保活)。结果 A 钉入后此槽让位。 + if !args.contains(&0) { + vm.set_reg(0, this_val); } - .min(vm.kernel_core().config.max_call_depth); + let (arr_ptr, n, is_array) = match get_this_arraylike(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + // depth:缺省 1;ToIntegerOrInfinity 后负数归 0(NaN 折算为 0 同不展开), + // +∞ 保留无限深度。 + let mut depth = 1.0f64; + if args.len() > 1 && !vm.reg(args[1]).is_undefined() { + depth = match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + if depth < 0.0 { + depth = 0.0; + } + } + // ArraySpeciesCreate(O, 0):真数组按 constructor/@@species 构造目标。 + let a_ptr = match array_species_create(vm, this_val, is_array, 0) { + Ok(p) => p, + Err(err) => return NativeResult::Err(err), + }; + // 结果钉入返回寄存器:其后的元素 Get / 嵌套长度 Get 各带用户窗口,结果 + // 指针须保 GC 根,返回时从钉位读回。 + vm.set_reg(0, JsValue::from_js_object(a_ptr)); - fn flatten(items: &[JsValue], remaining_depth: usize, seen: &mut Vec<*mut JsObject>) -> Vec { - let mut out = Vec::new(); - for &v in items { - if remaining_depth > 0 && v.is_object() { - let ptr = v.as_js_object_ptr(); - if !ptr.is_null() { - if seen.iter().any(|seen_ptr| std::ptr::eq(*seen_ptr, ptr)) { - out.push(v); - continue; - } - let o = unsafe { &*ptr }; - if o.is_array() { - seen.push(ptr); - // 嵌套展开丢弃洞位:flat 结果恒为紧凑数组。 - let on = o.prop_count() as usize; - let dense = o.array_elements_meta_vec().is_none(); - let mut sub: Vec = Vec::with_capacity(on); - for i in 0..on { - if dense || !o.prop_meta_at(i).is_some_and(|m| m.is_hole()) { - sub.push(o.get_prop_at(i)); - } - } - let flat = flatten(&sub, remaining_depth - 1, seen); - seen.pop(); - out.extend(flat); - continue; - } - } - } - out.push(v); + // 流式 DFS:帧 = (源对象, 源长度, 剩余深度, 下一源索引)。 + let mut stack: Vec<(*mut JsObject, usize, f64, usize)> = vec![(arr_ptr, n, depth, 0)]; + let mut target_index = 0usize; + while let Some(&top) = stack.last() { + let (src, src_len, d, i) = top; + if i >= src_len { + stack.pop(); + continue; } - out - } - - // 顶层同样丢弃洞位(接收者恒为真数组,元数据表判洞免字符串键)。 - let dense = arr.array_elements_meta_vec().is_none(); - let mut all: Vec = Vec::with_capacity(n); - for i in 0..n { - if dense || !arr.prop_meta_at(i).is_some_and(|m| m.is_hole()) { - all.push(arr.get_prop_at(i)); + // HasProperty 门控:洞位不写目标(结果恒紧凑)。 + if !arraylike_index_present(vm, src, i) { + stack.last_mut().unwrap().3 = i + 1; + continue; } - } - let mut seen = vec![arr_ptr]; - let flat = flatten(&all, depth, &mut seen); - let new_arr = create_new_array(vm, flat.len()); - unsafe { - for (i, val) in flat.iter().enumerate() { - (*new_arr).set_prop_at(i, *val); + let elem = arraylike_get_or_err!(vm, src, i); + // 深度内的数组元素压栈递归;已在栈上的数组(循环引用)原样写入。 + let e_ptr = if elem.is_object() { elem.as_js_object_ptr() } else { std::ptr::null_mut() }; + let descend = d > 0.0 + && !e_ptr.is_null() + && unsafe { &*e_ptr }.is_array() + && !stack.iter().any(|f| std::ptr::eq(f.0, e_ptr)); + stack.last_mut().unwrap().3 = i + 1; + if descend { + // elementLen = LengthOfArrayLike(element)。 + let (_, elen, _) = match get_this_arraylike(vm, elem) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + stack.push((e_ptr, elen, d - 1.0, 0)); + continue; } - (*new_arr).set_prop_count(flat.len()); + // 目标索引上限 2^53-1(规范 FlattenIntoArray 步 6.vi.i)。 + if target_index >= 9_007_199_254_740_991 { + return NativeResult::Err(array_type_error(vm, "Invalid array length")); + } + let key = vm.new_string(&target_index.to_string()); + let key_si = vm.property_key_si(key); + if let Err(err) = create_data_property_or_throw(vm, unsafe { &mut *a_ptr }, key_si, elem) { + return NativeResult::Err(err); + } + target_index += 1; } - NativeResult::Ok(JsValue::from_js_object(new_arr)) + NativeResult::Ok(JsValue::from_js_object(a_ptr)) } /// `Array.prototype.shift()`:移除并返回首元素,其余元素前移;空数组返回 undefined。 diff --git a/crates/oxide_builtins/src/array/iterate.rs b/crates/oxide_builtins/src/array/iterate.rs index 8e849699..c854df03 100644 --- a/crates/oxide_builtins/src/array/iterate.rs +++ b/crates/oxide_builtins/src/array/iterate.rs @@ -2,7 +2,7 @@ use oxide_types::value::JsValue; -use oxide_runtime_api::{NativeResult, VmHost}; +use oxide_runtime_api::{to_object, NativeResult, VmHost}; use crate::builtins_debug; use crate::builtins_error; @@ -12,6 +12,7 @@ use super::common::{ check_array_create_len, create_new_array, get_this_arraylike, invoke_native_callback, js_array_index, require_callback, unexpected_tail_call_error, }; +use super::from::{array_species_create, create_data_property_or_throw, from_engine_error}; /// `Array.prototype.forEach(callback, thisArg)`:对每个元素调用 callback,返回 undefined。 pub fn array_for_each(vm: &mut H, args: &[u8]) -> NativeResult { @@ -315,10 +316,39 @@ pub fn array_every(vm: &mut H, args: &[u8]) -> NativeResult { } /// `Array.prototype.flatMap(callback, thisArg)`:map 后把返回的数组展开一层。 +/// +/// # 步骤 +/// 1. `ToObject` 装箱基元 this(null/undefined 抛 TypeError),装箱体钉入返回 +/// 寄存器跨用户窗口保 GC 根(结果数组钉入后让位,按接收者值传递保活)。 +/// 2. 回调可调用校验;逐索引 HasProperty 门控 + Get,回调以 (elem, 索引, O) 调用。 +/// 3. 结果 `A` 按 `ArraySpeciesCreate(O, 0)` 构建(真数组按 constructor/@@species +/// 构造,非可构造值抛 TypeError)并钉入返回寄存器。 +/// 4. 回调结果为真数组时展开一层(长度按 LengthOfArrayLike 读取,洞位不入目标); +/// 其余结果原样写入;写入一律 CreateDataPropertyOrThrow(受限目标抛 +/// TypeError),真数组结果的 length 随元素写入逐位扩展。 +/// +/// # 副作用 +/// - 回调为用户代码;Get 可经原型链触发 getter;species 构造与目标属性写入 +/// 可按规范抛 TypeError。 pub fn array_flat_map(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.flatMap called with {} args", args.len()); - let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args); - let o_val = vm.reg(args[0]); + // ToObject:基元 this 装箱(null/undefined 抛 TypeError)。 + let this_val = match to_object(vm.reg(args[0]), vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)), + }; + // 装箱体钉入返回寄存器:它只存于 Rust 局部,跨 length getter / species + // 用户窗口须保 GC 根。call 转发形态的实参寄存器集可占该槽,占位时跳过钉位 + // (装箱体按接收者值传递保活)。结果 A 钉入后此槽让位,循环期装箱体按 + // 接收者值传递保活。 + if !args.contains(&0) { + vm.set_reg(0, this_val); + } + let (arr_ptr, n, is_array) = match get_this_arraylike(vm, this_val) { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), + }; + let o_val = this_val; if args.len() < 2 { builtins_error!("Array.prototype.flatMap: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); @@ -330,38 +360,27 @@ pub fn array_flat_map(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Err(err); } }; - let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; - let mut flat: Vec = Vec::new(); + let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; if let Err(err) = check_array_create_len(vm, n) { return NativeResult::Err(err); } + // ArraySpeciesCreate(O, 0):真数组按 constructor/@@species 构造目标。 + let a_ptr = match array_species_create(vm, o_val, is_array, 0) { + Ok(p) => p, + Err(err) => return NativeResult::Err(err), + }; + // 结果钉入返回寄存器:其后的回调 / 元素 Get 各带用户窗口,结果指针须保 + // GC 根,返回时从钉位读回。 + vm.set_reg(0, JsValue::from_js_object(a_ptr)); + let mut target_index = 0usize; for i in 0..n { // 洞位跳过:回调不得在缺失索引上触发。 if !arraylike_index_present(vm, arr_ptr, i) { continue; } let elem = arraylike_get_or_err!(vm, arr_ptr, i); - match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) { - NativeResult::Ok(result) => { - if result.is_object() { - let r_ptr = result.as_js_object_ptr(); - if !r_ptr.is_null() { - let r = unsafe { &*r_ptr }; - if r.is_array() { - // 嵌套数组展开丢弃洞位:结果为紧凑数组。 - let rn = r.prop_count() as usize; - let dense = r.array_elements_meta_vec().is_none(); - for j in 0..rn { - if dense || !r.prop_meta_at(j).is_some_and(|m| m.is_hole()) { - flat.push(r.get_prop_at(j)); - } - } - continue; - } - } - } - flat.push(result); - } + let result = match invoke_native_callback(vm, callback_val, this_arg, &[elem, js_array_index(i), o_val]) { + NativeResult::Ok(result_val) => result_val, NativeResult::Err(err) => { builtins_error!("Array.prototype.flatMap: invalid receiver"); return NativeResult::Err(err); @@ -370,16 +389,48 @@ pub fn array_flat_map(vm: &mut H, args: &[u8]) -> NativeResult { builtins_error!("Array.prototype.flatMap: invalid receiver"); return unexpected_tail_call_error(vm); } + }; + // 回调结果为真数组时展开一层,其余结果原样写入目标。 + let r_ptr = if result.is_object() { + result.as_js_object_ptr() + } else { + std::ptr::null_mut() + }; + if !r_ptr.is_null() && unsafe { &*r_ptr }.is_array() { + // elementLen = LengthOfArrayLike(result):洞位不入目标。 + let (_, rn, _) = match get_this_arraylike(vm, result) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + for j in 0..rn { + if !arraylike_index_present(vm, r_ptr, j) { + continue; + } + let v = arraylike_get_or_err!(vm, r_ptr, j); + // 目标索引上限 2^53-1(规范 FlattenIntoArray 步 6.vi.i)。 + if target_index >= 9_007_199_254_740_991 { + return NativeResult::Err(array_type_error(vm, "Invalid array length")); + } + let key = vm.new_string(&target_index.to_string()); + let key_si = vm.property_key_si(key); + if let Err(err) = create_data_property_or_throw(vm, unsafe { &mut *a_ptr }, key_si, v) { + return NativeResult::Err(err); + } + target_index += 1; + } + continue; } - } - let new_arr = create_new_array(vm, flat.len()); - unsafe { - for (i, val) in flat.iter().enumerate() { - (*new_arr).set_prop_at(i, *val); + if target_index >= 9_007_199_254_740_991 { + return NativeResult::Err(array_type_error(vm, "Invalid array length")); } - (*new_arr).set_prop_count(flat.len()); + let key = vm.new_string(&target_index.to_string()); + let key_si = vm.property_key_si(key); + if let Err(err) = create_data_property_or_throw(vm, unsafe { &mut *a_ptr }, key_si, result) { + return NativeResult::Err(err); + } + target_index += 1; } - NativeResult::Ok(JsValue::from_js_object(new_arr)) + NativeResult::Ok(JsValue::from_js_object(a_ptr)) } /// `Array.prototype.findIndex(callback, thisArg)`:返回首个 callback 为真的索引,否则 -1。 diff --git a/crates/oxide_builtins/tests/array_hole_semantics_tests.rs b/crates/oxide_builtins/tests/array_hole_semantics_tests.rs index 85449a06..4948bc7e 100644 --- a/crates/oxide_builtins/tests/array_hole_semantics_tests.rs +++ b/crates/oxide_builtins/tests/array_hole_semantics_tests.rs @@ -564,3 +564,236 @@ fn test_concat_mixed_args_order_and_holes() { .unwrap(); assert_eq!(out, "5|1|1,2,3,0|4|1|false"); } + +// ── flat / flatMap 收口钉(node v20.19.2 实测值钉死) ────────────────────── + +// 引擎钉:reverse (S,N) 臂非可配置 lower——Delete 先于 Set,抛时零改动。 +#[test] +fn test_reverse_sn_non_configurable_lower_throws_untouched() { + let out = eval_str( + "(() => { const a = [1, 2]; delete a[1]; \ + Object.defineProperty(a, '0', { value: 1, writable: true, enumerable: true, configurable: false }); \ + let threw = false; try { a.reverse(); } catch (e) { threw = e.name === 'TypeError'; } \ + return threw + '|' + (1 in a) + '|' + a[0] + '|' + a[1]; })()", + ) + .unwrap(); + assert_eq!(out, "true|false|1|undefined"); +} + +// 引擎钉:reverse (N,S) 臂非可配置 upper——Set(lower) 先成功、Delete(upper) 抛, +// 部分改动形态保留(判别力核心)。 +#[test] +fn test_reverse_ns_non_configurable_upper_partial_mutation() { + let out = eval_str( + "(() => { const a = [undefined, 2, 3]; delete a[0]; \ + Object.defineProperty(a, '2', { value: 3, writable: true, enumerable: true, configurable: false }); \ + let threw = false; try { a.reverse(); } catch (e) { threw = e.name === 'TypeError'; } \ + return threw + '|' + a[0] + '|' + a[1] + '|' + a[2]; })()", + ) + .unwrap(); + assert_eq!(out, "true|3|2|3"); +} + +// 引擎钉:reverse 读序 Hf,Gf,Ht,Gt 逐对交错 + (S,S) 臂写序 S0,S3,S1,S2; +// getter 还原原值,终读经 getter 呈现原序列(读序钉位核心)。 +// 访问器逐条显式定义(不走 for 循环闭包捕获,规避既有作用域面)。 +#[test] +fn test_reverse_get_set_order_and_final_values() { + let out = eval_str( + "(() => { const a = [10, 11, 22, 30]; const log = []; \ + Object.defineProperty(a, 0, { configurable: true, get() { log.push('G0'); return 10; }, set(x) { log.push('S0=' + x); } }); \ + Object.defineProperty(a, 1, { configurable: true, get() { log.push('G1'); return 11; }, set(x) { log.push('S1=' + x); } }); \ + Object.defineProperty(a, 2, { configurable: true, get() { log.push('G2'); return 22; }, set(x) { log.push('S2=' + x); } }); \ + Object.defineProperty(a, 3, { configurable: true, get() { log.push('G3'); return 30; }, set(x) { log.push('S3=' + x); } }); \ + a.reverse(); return log.join(',') + '|' + a.join(','); })()", + ) + .unwrap(); + assert_eq!(out, "G0,G3,S0=30,S3=10,G1,G2,S1=22,S2=11|10,11,22,30"); +} + +// 引擎钉:reverse getter 内 length=0 截断(GID 形态)——(S,N) 臂 Delete(lower) +// 后 upper 写越界扩长,`0 in` 假、`1 in` 真、a[1] 为下值。 +#[test] +fn test_reverse_getter_length_zero_in_check() { + let out = eval_str( + "(() => { const array = ['first', 'second']; \ + Object.defineProperty(array, 0, { get() { array.length = 0; return 'first'; } }); \ + array.reverse(); return (0 in array) + '|' + (1 in array) + '|' + array[1]; })()", + ) + .unwrap(); + assert_eq!(out, "false|true|first"); +} + +// 引擎钉:flat arraylike 接收者——ToObject 通用入口 + LengthOfArrayLike 长度源, +// 嵌套数组元素展开一层。 +#[test] +fn test_flat_arraylike_receiver_nested() { + let out = eval_str( + "(() => { const r = Array.prototype.flat.call({ length: 2, 0: [1], 1: [2] }); \ + return r.join(',') + '|' + r.length; })()", + ) + .unwrap(); + assert_eq!(out, "1,2|2"); +} + +// 引擎钉:flat arraylike 分数长度——ToLength 折算 2.9 → 2,第三元素不入。 +#[test] +fn test_flat_arraylike_fractional_length() { + let out = eval_str( + "(() => { const r = Array.prototype.flat.call({ length: 2.9, 0: 1, 1: 2, 2: 3 }); \ + return r.join(',') + '|' + r.length; })()", + ) + .unwrap(); + assert_eq!(out, "1,2|2"); +} + +// 引擎钉:flat arraylike NaN 长度——ToLength(NaN) = 0,空结果。 +#[test] +fn test_flat_arraylike_nan_length() { + let out = eval_str( + "(() => { const r = Array.prototype.flat.call({ length: NaN, 0: 1 }); \ + return r.join(',') + '|' + r.length; })()", + ) + .unwrap(); + assert_eq!(out, "|0"); +} + +// 引擎钉:flat arraylike undefined 长度——Get length 缺省 undefined,ToLength 0。 +#[test] +fn test_flat_arraylike_undefined_length() { + let out = eval_str( + "(() => { const r = Array.prototype.flat.call({ length: undefined, 0: [1] }); \ + return r.join(',') + '|' + r.length; })()", + ) + .unwrap(); + assert_eq!(out, "|0"); +} + +// 引擎钉:flat 装箱基元 this——ToObject 后长度为 0,结果空真数组。 +#[test] +fn test_flat_boxed_primitive_this() { + let out = eval_str( + "(() => { const r = Array.prototype.flat.call(true); \ + return r.length + '|' + Array.isArray(r); })()", + ) + .unwrap(); + assert_eq!(out, "0|true"); +} + +// 引擎钉:flat 负 depth——ToIntegerOrInfinity 后归 0,不展开。 +#[test] +fn test_flat_negative_depth_no_flatten() { + assert_eq!(eval_str("(() => { return JSON.stringify([[1]].flat(-1)); })()").unwrap(), "[[1]]"); +} + +// 引擎钉:flat NaN depth——ToIntegerOrInfinity(NaN) = 0,不展开(非默认深 1)。 +#[test] +fn test_flat_nan_depth_no_flatten() { + assert_eq!(eval_str("(() => { return JSON.stringify([[1]].flat(NaN)); })()").unwrap(), "[[1]]"); +} + +// 引擎钉:flat 0 depth——0 不展开(旧 (n as i32).max(1) 形态会误展开一层)。 +#[test] +fn test_flat_zero_depth_no_flatten() { + assert_eq!(eval_str("(() => { return JSON.stringify([[1]].flat(0)); })()").unwrap(), "[[1]]"); +} + +// 引擎钉:flat +∞ depth——无限深度臂,全嵌套展开。 +#[test] +fn test_flat_infinity_depth() { + let out = eval_str("(() => { return [1, [2, [3, [4]]]].flat(Infinity).join(','); })()").unwrap(); + assert_eq!(out, "1,2,3,4"); +} + +// 引擎钉:flat 超大 depth(i32 回绕域)——f64 域无回绕,浅数组上等效深 1。 +#[test] +fn test_flat_huge_depth_no_wrap() { + assert_eq!(eval_str("(() => { return JSON.stringify([[1]].flat(3000000000)); })()").unwrap(), "[1]",); +} + +// 引擎钉:flat Symbol / null 原型对象 depth——ToPrimitive 转换异常按规范传播 +// TypeError(旧 unwrap_or 吞错形态会静默返空)。 +#[test] +fn test_flat_symbol_null_proto_depth_throws() { + let out = eval_str( + "(() => { let t1 = '?'; let t2 = '?'; \ + try { [1].flat(Symbol()); } catch (e) { t1 = e.name; } \ + try { [1].flat(Object.create(null)); } catch (e) { t2 = e.name; } \ + return t1 + '|' + t2; })()", + ) + .unwrap(); + assert_eq!(out, "TypeError|TypeError"); +} + +// 引擎钉:flat species frozen 目标——CreateDataPropertyOrThrow 抛 TypeError。 +#[test] +fn test_flat_species_frozen_target_throws() { + let out = eval_str( + "(() => { const a = [1, 2]; a.constructor = {}; \ + a.constructor[Symbol.species] = (n) => Object.freeze([]); \ + let t = '?'; try { a.flat(); } catch (e) { t = e.name; } return t; })()", + ) + .unwrap(); + assert_eq!(out, "TypeError"); +} + +// 引擎钉:flat species 构造器为 null——ArraySpeciesCreate 步 9 抛 TypeError。 +#[test] +fn test_flat_species_null_ctor_throws() { + let out = eval_str( + "(() => { const a = [1, 2]; a.constructor = null; \ + let t = '?'; try { a.flat(); } catch (e) { t = e.name; } return t; })()", + ) + .unwrap(); + assert_eq!(out, "TypeError"); +} + +// 引擎钉:flat species 非可写 '0' 槽(configurable)——CDO 覆写并恢复可写描述符; +// 普通对象目标 length 为普通属性,不随 CDO 扩长。 +#[test] +fn test_flat_species_non_writable_slot_overwritten() { + let out = eval_str( + "(() => { function Ctor(n) { const o = {}; o.length = n; \ + Object.defineProperty(o, '0', { writable: false, configurable: true, enumerable: true, value: 99 }); \ + return o; } \ + const a = [0, 1]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \ + const r = a.flat(); const d = Object.getOwnPropertyDescriptor(r, '0'); \ + return r[0] + '|' + d.writable + '|' + d.configurable + '|' + r.length; })()", + ) + .unwrap(); + assert_eq!(out, "0|true|true|0"); +} + +// 引擎钉:flat 洞语义——HasProperty 门控丢洞,结果恒紧凑(防误修回归)。 +#[test] +fn test_flat_holes_dropped_compact() { + let out = eval_str( + "(() => { const r1 = [1, , 2].flat(); const r2 = [[1, , 2], [3]].flat(); \ + return r1.join(',') + '|' + r1.length + '|' + r2.join(',') + '|' + r2.length; })()", + ) + .unwrap(); + assert_eq!(out, "1,2|2|1,2,3|3"); +} + +// 引擎钉:flat 真数组 length 增/减后——长度源为 ToLength(Get length),洞位丢洞。 +#[test] +fn test_flat_logical_length_grow_shrink() { + let out = eval_str( + "(() => { const a = [1, 2, 3]; a.length = 5; const r1 = a.flat(); \ + const b = [1, 2, 3]; b.length = 1; const r2 = b.flat(); \ + return r1.join(',') + '|' + r1.length + '|' + r2.join(',') + '|' + r2.length; })()", + ) + .unwrap(); + assert_eq!(out, "1,2,3|3|1|1"); +} + +// 引擎钉:flat 非数组嵌套元素原样保留(防过度展开:仅真数组递归)。 +#[test] +fn test_flat_non_array_elements_kept() { + let out = eval_str( + "(() => { const o = { a: 1 }; const r = [[o], 2].flat(); \ + return r.length + '|' + (r[0] === o) + '|' + r[1]; })()", + ) + .unwrap(); + assert_eq!(out, "2|true|2"); +}