fix: flat/flatMap 入口通用化、深度 64 位域、物种族与 CDO 收口

This commit is contained in:
2026-09-19 18:46:46 +00:00
parent 43c859f35d
commit 8cd3cf1440
3 changed files with 410 additions and 95 deletions
+93 -62
View File
@@ -678,76 +678,107 @@ pub fn array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
NativeResult::Ok(this_val) 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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn array_flat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("Array.prototype.flat called with {} args", args.len()); builtins_debug!("Array.prototype.flat called with {} args", args.len());
let arr_ptr = array_ptr!(vm, args); // ToObject:基元 this 装箱(null/undefined 抛 TypeError)。
let arr = unsafe { &*arr_ptr }; let this_val = match oxide_runtime_api::to_object(vm.reg(args[0]), vm) {
let n = arr.prop_count() as usize; Ok(v) => v,
let depth = if args.len() > 1 { Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
let n = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN); };
if !n.is_finite() { // 装箱体钉入返回寄存器:它只存于 Rust 局部,跨 length getter / depth 折算
vm.kernel_core().config.max_call_depth // 用户窗口须保 GC 根。call 转发形态的实参寄存器集可占该槽,占位时跳过钉位
} else { // (装箱体按接收者值传递保活)。结果 A 钉入后此槽让位。
(n as i32).max(1) as usize if !args.contains(&0) {
} vm.set_reg(0, this_val);
} else {
1
} }
.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<JsValue> { // 流式 DFS:帧 = (源对象, 源长度, 剩余深度, 下一源索引)。
let mut out = Vec::new(); let mut stack: Vec<(*mut JsObject, usize, f64, usize)> = vec![(arr_ptr, n, depth, 0)];
for &v in items { let mut target_index = 0usize;
if remaining_depth > 0 && v.is_object() { while let Some(&top) = stack.last() {
let ptr = v.as_js_object_ptr(); let (src, src_len, d, i) = top;
if !ptr.is_null() { if i >= src_len {
if seen.iter().any(|seen_ptr| std::ptr::eq(*seen_ptr, ptr)) { stack.pop();
out.push(v); continue;
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<JsValue> = 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);
} }
out // HasProperty 门控:洞位不写目标(结果恒紧凑)。
} if !arraylike_index_present(vm, src, i) {
stack.last_mut().unwrap().3 = i + 1;
// 顶层同样丢弃洞位(接收者恒为真数组,元数据表判洞免字符串键)。 continue;
let dense = arr.array_elements_meta_vec().is_none();
let mut all: Vec<JsValue> = 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));
} }
} let elem = arraylike_get_or_err!(vm, src, i);
let mut seen = vec![arr_ptr]; // 深度内的数组元素压栈递归;已在栈上的数组(循环引用)原样写入。
let flat = flatten(&all, depth, &mut seen); let e_ptr = if elem.is_object() { elem.as_js_object_ptr() } else { std::ptr::null_mut() };
let new_arr = create_new_array(vm, flat.len()); let descend = d > 0.0
unsafe { && !e_ptr.is_null()
for (i, val) in flat.iter().enumerate() { && unsafe { &*e_ptr }.is_array()
(*new_arr).set_prop_at(i, *val); && !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。 /// `Array.prototype.shift()`:移除并返回首元素,其余元素前移;空数组返回 undefined。
+84 -33
View File
@@ -2,7 +2,7 @@
use oxide_types::value::JsValue; 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_debug;
use crate::builtins_error; 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, check_array_create_len, create_new_array, get_this_arraylike, invoke_native_callback, js_array_index,
require_callback, unexpected_tail_call_error, 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。 /// `Array.prototype.forEach(callback, thisArg)`:对每个元素调用 callback,返回 undefined。
pub fn array_for_each<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn array_for_each<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
@@ -315,10 +316,39 @@ pub fn array_every<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} }
/// `Array.prototype.flatMap(callback, thisArg)`:map 后把返回的数组展开一层。 /// `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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn array_flat_map<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("Array.prototype.flatMap called with {} args", args.len()); builtins_debug!("Array.prototype.flatMap called with {} args", args.len());
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args); // ToObject:基元 this 装箱(null/undefined 抛 TypeError)。
let o_val = vm.reg(args[0]); 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 { if args.len() < 2 {
builtins_error!("Array.prototype.flatMap: invalid receiver"); builtins_error!("Array.prototype.flatMap: invalid receiver");
return NativeResult::Err(array_type_error(vm, "callback is not a function")); return NativeResult::Err(array_type_error(vm, "callback is not a function"));
@@ -330,38 +360,27 @@ pub fn array_flat_map<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Err(err); return NativeResult::Err(err);
} }
}; };
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let mut flat: Vec<JsValue> = Vec::new();
if let Err(err) = check_array_create_len(vm, n) { if let Err(err) = check_array_create_len(vm, n) {
return NativeResult::Err(err); 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 { for i in 0..n {
// 洞位跳过:回调不得在缺失索引上触发。 // 洞位跳过:回调不得在缺失索引上触发。
if !arraylike_index_present(vm, arr_ptr, i) { if !arraylike_index_present(vm, arr_ptr, i) {
continue; continue;
} }
let elem = arraylike_get_or_err!(vm, arr_ptr, i); 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]) { let result = match invoke_native_callback(vm, callback_val, this_arg, &[elem, js_array_index(i), o_val]) {
NativeResult::Ok(result) => { NativeResult::Ok(result_val) => result_val,
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);
}
NativeResult::Err(err) => { NativeResult::Err(err) => {
builtins_error!("Array.prototype.flatMap: invalid receiver"); builtins_error!("Array.prototype.flatMap: invalid receiver");
return NativeResult::Err(err); return NativeResult::Err(err);
@@ -370,16 +389,48 @@ pub fn array_flat_map<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_error!("Array.prototype.flatMap: invalid receiver"); builtins_error!("Array.prototype.flatMap: invalid receiver");
return unexpected_tail_call_error(vm); 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;
} }
} if target_index >= 9_007_199_254_740_991 {
let new_arr = create_new_array(vm, flat.len()); return NativeResult::Err(array_type_error(vm, "Invalid array length"));
unsafe {
for (i, val) in flat.iter().enumerate() {
(*new_arr).set_prop_at(i, *val);
} }
(*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。 /// `Array.prototype.findIndex(callback, thisArg)`:返回首个 callback 为真的索引,否则 -1。
@@ -564,3 +564,236 @@ fn test_concat_mixed_args_order_and_holes() {
.unwrap(); .unwrap();
assert_eq!(out, "5|1|1,2,3,0|4|1|false"); 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");
}