Files
OxideJS-New/crates/oxide_builtins/tests/array_hole_semantics_tests.rs
T

1443 lines
56 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! splice/reverse/copyWithin/at 四方法洞语义与原型链读语义引擎钉:
//! 源洞保洞/换洞/删洞、重叠反向复制、元素读走 Get。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval_value(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new();
let result = vm.run(&Arc::new(module))?;
Ok((vm, result))
}
fn eval_str(source: &str) -> Result<String, String> {
let (vm, result) = eval_value(source)?;
vm.lookup_str(result)
.ok_or_else(|| "completion value is not a string".to_string())
}
// splice 被删收集:源洞位在 removed 保洞,present 值原样落入。
#[test]
fn test_splice_removed_keeps_holes() {
let out = eval_str(
"(() => { const a = new Array(3); a[1] = 1; a[2] = 3; \
const r = a.splice(0, 3); \
return r.length + '|' + (0 in r) + '|' + r[1]; })()",
)
.unwrap();
assert_eq!(out, "3|false|1");
}
// splice 被删收集走 Get:洞位落原型链,访问器值落入 removed 且该位为 present。
#[test]
fn test_splice_removed_get_through_proto() {
let out = eval_str(
"(() => { Object.defineProperty(Array.prototype, 0, { get: () => 'G0' }); \
try { const a = new Array(2); a[1] = 1; const r = a.splice(0, 2); \
return r[0] + '|' + (0 in r) + '|' + r.length; } \
finally { delete Array.prototype[0]; } })()",
)
.unwrap();
assert_eq!(out, "G0|true|2");
}
// splice 扩容:洞洞源 removed 全洞;数组体洞位随搬移保留、插入位 present。
#[test]
fn test_splice_grow_keeps_array_holes() {
let out = eval_str(
"(() => { const a = new Array(3); const r = a.splice(0, 2, 'a'); \
return (0 in r) + '|' + (1 in r) + '|' + r.length + '|' + \
(0 in a) + '|' + (1 in a) + '|' + a.length; })()",
)
.unwrap();
assert_eq!(out, "false|false|2|true|false|2");
}
// splice 中插:插入点之后的既有洞位随搬移落位,`3 in` 为 false。
#[test]
fn test_splice_insert_mid_hole_moves() {
let out = eval_str(
"(() => { const a = new Array(4); a[0] = 1; a[1] = 2; a[3] = 4; \
a.splice(2, 0, 'x'); \
return (3 in a) + '|' + (2 in a) + '|' + a[2] + '|' + a.length; })()",
)
.unwrap();
assert_eq!(out, "false|true|x|5");
}
// reverse 洞对 present:值位互换、洞位互换,洞不物化。
#[test]
fn test_reverse_hole_and_value_swap() {
let out = eval_str(
"(() => { const a = new Array(3); a[0] = 1; a[2] = 3; a.reverse(); \
return (1 in a) + '|' + a[0] + '|' + a[2]; })()",
)
.unwrap();
assert_eq!(out, "false|3|1");
}
// reverse 洞对洞:双缺失端不动,洞位互换后各归其位。
#[test]
fn test_reverse_hole_to_hole() {
let out = eval_str(
"(() => { const a = new Array(3); a[1] = 2; a[2] = 3; a.reverse(); \
return (2 in a) + '|' + a[0] + '|' + a[1]; })()",
)
.unwrap();
assert_eq!(out, "false|3|2");
}
// reverse 原型链存在性:自身洞 + 原型 present 视同双存在,两端互写为 present。
#[test]
fn test_reverse_proto_present_both_sides() {
let out = eval_str(
"(() => { Array.prototype[1] = 1; \
try { const x = [0]; x.length = 2; x.reverse(); \
return (0 in x) + '|' + (1 in x) + '|' + x[0] + '|' + x[1]; } \
finally { delete Array.prototype[1]; } })()",
)
.unwrap();
assert_eq!(out, "true|true|1|0");
}
// copyWithin 自拷贝:源洞位 DeleteProperty 自身,洞保洞。
#[test]
fn test_copy_within_self_copy_hole_stays_hole() {
let out = eval_str(
"(() => { const a = new Array(3); a[0] = 1; a[2] = 3; a.copyWithin(0); \
return (1 in a) + '|' + a[0] + '|' + a[2]; })()",
)
.unwrap();
assert_eq!(out, "false|1|3");
}
// copyWithin 重叠正向陷阱:目标领先且重叠须反向迭代,源洞目标位删洞。
#[test]
fn test_copy_within_overlap_reverse_direction() {
let out = eval_str(
"(() => { const a = new Array(4); a[1] = 1; a[2] = 3; a[3] = 4; \
a.copyWithin(1, 0, 2); \
return (0 in a) + '|' + (1 in a) + '|' + (2 in a) + '|' + a[2]; })()",
)
.unwrap();
assert_eq!(out, "false|false|true|1");
}
// copyWithin 重叠方向判别:密集数组反向复制结果与正向不同。
#[test]
fn test_copy_within_dense_overlap() {
let out = eval_str("(() => { const a = [0, 1, 2, 3]; a.copyWithin(1, 0, 2); return a.join(','); })()").unwrap();
assert_eq!(out, "0,0,1,3");
}
// at 元素读走 Get:洞位落原型链,索引访问器触发。
#[test]
fn test_at_get_through_proto() {
let out = eval_str(
"(() => { Object.defineProperty(Array.prototype, 0, { get: () => 'AG' }); \
try { const a = new Array(2); a[1] = 5; \
return a.at(0) + '|' + a.at(-2) + '|' + a.at(1); } \
finally { delete Array.prototype[0]; } })()",
)
.unwrap();
assert_eq!(out, "AG|AG|5");
}
// reverse 读值期 getter 截断元素区:上端写越界扩展不得物化被删端,
// 终态被删端保洞、值端 present。
#[test]
fn test_reverse_getter_truncation_pair() {
let out = eval_str(
"(() => { const a = ['first', 'second']; \
Object.defineProperty(a, 0, { get: () => { a.length = 0; return 'first'; } }); \
a.reverse(); \
return (0 in a) + '|' + (1 in a) + '|' + a[1] + '|' + a.length; })()",
)
.unwrap();
assert_eq!(out, "false|true|first|2");
}
// reverse 读值期 getter 中间截断:上端写越界扩展的整段间隙 [old_count, upper)
// 补置洞,不只补被删端一洞。
#[test]
fn test_reverse_getter_mid_truncation_gaps_holes() {
let out = eval_str(
"(() => { const a = new Array(5); a[0] = 1; \
Object.defineProperty(a, 0, { get: () => { a.length = 3; return 1; } }); \
a.reverse(); \
return (1 in a) + '|' + (3 in a) + '|' + a[4] + '|' + a.length; })()",
)
.unwrap();
assert_eq!(out, "false|false|1|5");
}
// 防过度修护栏:fill 对洞位无条件 Set,洞位变 present-0,不改。
#[test]
fn test_fill_materializes_hole_unchanged() {
let out = eval_str(
"(() => { const a = new Array(3); a[0] = 1; a[2] = 3; a.fill(0); \
return (1 in a) + '|' + a[1]; })()",
)
.unwrap();
assert_eq!(out, "true|0");
}
// at 越界返回 undefined(无原型链参与,防误改读路径的回归护栏)。
#[test]
fn test_at_out_of_bounds_undefined() {
let (_vm, result) = eval_value("[1, 2].at(9)").unwrap();
assert!(result.is_undefined());
}
// splice species:Ctor 返回体直接作结果,构造器恰收单参 actualDeleteCount,O 已搬移。
#[test]
fn test_splice_species_ctor_returned_instance() {
let out = eval_str(
"(() => { let seen = null; const inst = { mark: 1 }; \
function Ctor(n) { seen = n; inst.len = n; return inst; } \
const a = [0,1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
const r = a.splice(2); \
return seen + '|' + (r === inst) + '|' + r.len + '|' + a.join(','); })()",
)
.unwrap();
assert_eq!(out, "2|true|2|0,1");
}
// splice species 普通 Ctor 无返回体:结果非数组、原型为 Ctor.prototype,
// Set(A,"length") 扩长至删除数且值逐位填入。
#[test]
fn test_splice_species_plain_ctor_extends_length() {
let out = eval_str(
"(() => { function Ctor() {} \
const a = [0,1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
const r = a.splice(0); \
return Array.isArray(r) + '|' + (Object.getPrototypeOf(r) === Ctor.prototype) + '|' + \
r.length + '|' + r[0] + ',' + r[1] + ',' + r[2] + ',' + r[3]; })()",
)
.unwrap();
assert_eq!(out, "false|true|4|0,1,2,3");
}
// splice species 为 null / 缺省:回退真数组(原型为 Array.prototype),值与长度正确。
#[test]
fn test_splice_species_null_undef_plain_array() {
let out = eval_str(
"(() => { const a = [0,1,2]; a.constructor = {}; a.constructor[Symbol.species] = null; \
const r1 = a.splice(1); \
const b = [0,1,2]; b.constructor = {}; \
const r2 = b.splice(1); \
return Array.isArray(r1) + '|' + (Object.getPrototypeOf(r1) === Array.prototype) + '|' + \
r1.length + '|' + r1[0] + '|' + Array.isArray(r2) + '|' + \
(Object.getPrototypeOf(r2) === Array.prototype) + '|' + r2.length; })()",
)
.unwrap();
assert_eq!(out, "true|true|2|1|true|true|2");
}
// splice species 构造器收 -0 折算的删除数时收到 +0(非 -0)。
#[test]
fn test_splice_species_neg_zero() {
let out = eval_str(
"(() => { let isNeg = null; \
function Ctor(n) { isNeg = Object.is(n, -0); } \
const a = []; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
const r = a.splice(0, -0); \
return 'neg=' + isNeg + '|len=' + r.length; })()",
)
.unwrap();
assert_eq!(out, "neg=false|len=0");
}
// splice species 结果 "0" 不可写:CreateDataPropertyOrThrow 覆盖为 present,
// 描述符恢复可写可配置。
#[test]
fn test_splice_species_writable_false_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,2]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
const r = a.splice(1); \
const d = Object.getOwnPropertyDescriptor(r, '0'); \
return r[0] + '|' + d.writable + '|' + d.configurable + '|' + r.length; })()",
)
.unwrap();
assert_eq!(out, "1|true|true|2");
}
// splice species 结果 "0" 不可配置:CreateDataPropertyOrThrow 抛 TypeError。
#[test]
fn test_splice_species_non_configurable_throws() {
let out = eval_str(
"(() => { function Ctor(n) { const o = {}; o.length = n; \
Object.defineProperty(o, '0', { writable: true, configurable: false, enumerable: true, value: 99 }); \
return o; } \
const a = [0,1,2]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
try { a.splice(1); return 'no-throw'; } catch (e) { return e.constructor.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// splice species 结果不可扩展:CreateDataPropertyOrThrow 抛 TypeError。
#[test]
fn test_splice_species_non_extensible_throws() {
let out = eval_str(
"(() => { function Ctor(n) { const o = {}; o.length = n; Object.preventExtensions(o); return o; } \
const a = [0,1,2]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
try { a.splice(1); return 'no-throw'; } catch (e) { return e.constructor.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// splice constructor 为非对象非 undefined(null/数字/字符串/布尔四值)均抛 TypeError。
#[test]
fn test_splice_ctor_non_object_throws() {
let out = eval_str(
"(() => { let ok = 0; \
for (const cv of [null, 1, 'string', true]) { \
const a = [0,1,2]; a.constructor = cv; \
try { a.splice(1); } catch (e) { if (e instanceof TypeError) ok++; } } \
return String(ok); })()",
)
.unwrap();
assert_eq!(out, "4");
}
// 防过度修:无 species 时结果仍为真数组、原型为 Array.prototype,自定义 Ctor 不被调用。
#[test]
fn test_splice_no_species_plain_array() {
let out = eval_str(
"(() => { let calls = 0; function Ctor() { calls++; } \
const a = [0,1,2]; \
const r = a.splice(1); \
return Array.isArray(r) + '|' + (Object.getPrototypeOf(r) === Array.prototype) + '|' + r.length + '|' + calls; })()",
)
.unwrap();
assert_eq!(out, "true|true|2|0");
}
// 步骤序:构造器在收集与搬移之前执行,读到未改动的 O。
#[test]
fn test_splice_ctor_reads_o_before_mutation() {
let out = eval_str(
"(() => { let seenLen = null, seen0 = null; \
function Ctor(n) { seenLen = a.length; seen0 = a[0]; } \
const a = [0,1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
a.splice(0, 2, 9); \
return seenLen + '|' + seen0 + '|' + a.join(','); })()",
)
.unwrap();
assert_eq!(out, "4|0|9,2,3");
}
// @@species getter 抛错原样透传(非构造器 constructor 上的 species 读取异常上抛)。
#[test]
fn test_splice_species_poisoned_propagates() {
let out = eval_str(
"(() => { const a = [0,1,2]; a.constructor = {}; \
Object.defineProperty(a.constructor, Symbol.species, { get() { throw new RangeError('species-get'); } }); \
try { a.splice(1); return 'no-throw'; } catch (e) { return e.constructor.name + ':' + e.message; } })()",
)
.unwrap();
assert_eq!(out, "RangeError:species-get");
}
// length 不可写(语料同形 defineProperty 仅收窄 writable 位):新旧长度相同的
// 空 splice 与收缩/插入 splice 均抛 TypeError。
#[test]
fn test_splice_length_nonwritable_throws() {
let out = eval_str(
"(() => { const a = [0,1,2]; Object.defineProperty(a, 'length', { writable: false }); \
let k1; try { a.splice(1, 2, 4); return 'shrink-no-throw'; } catch (e) { k1 = e.constructor.name; } \
const b = [0,1,2]; Object.defineProperty(b, 'length', { writable: false }); \
try { b.splice(0, 0); return 'noop-no-throw'; } catch (e) { return k1 + '|' + e.constructor.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError|TypeError");
}
// 冻结数组空删除也走 length 写:抛 TypeError(防空 splice 被特例放行)。
#[test]
fn test_splice_frozen_empty_delete_throws_length() {
let out = eval_str(
"(() => { try { Object.freeze([0,1,2]).splice(0, 0); return 'no-throw'; } \
catch (e) { return e.constructor.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// 冻结数组删除命中元素:元素严格 Set 面抛 TypeError(回归护栏)。
#[test]
fn test_splice_frozen_element_set_throws() {
let out = eval_str(
"(() => { try { Object.freeze([0,1,2]).splice(1, 1); return 'no-throw'; } \
catch (e) { return e.constructor.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// 引擎钉:splice 长度源按 ToLength 读取,2^53 及以上钳位到 2^53-1。
#[test]
fn test_splice_length_clamped_to_integer_limit() {
let out = eval_str(
"(() => { const o = {}; o.length = 9007199254740992; \
Array.prototype.splice.call(o); return String(o.length); })()",
)
.unwrap();
assert_eq!(out, "9007199254740991");
}
// 引擎钉:splice 新长度(len + insertCount - actualDeleteCount)超 2^53-1 抛 TypeError。
#[test]
fn test_splice_new_length_over_integer_limit_throws() {
let out = eval_str(
"(() => { const o = {}; o.length = 9007199254740991; \
try { Array.prototype.splice.call(o, 0, 0, null); return 'no-throw'; } \
catch (e) { return e.constructor.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// 引擎钉:splice 极限扩容对——arraylike 上元素右移一位、插入位落值、长度扩到 2^53-1。
#[test]
fn test_splice_integer_limit_grow_pair() {
let out = eval_str(
"(() => { const o = { '9007199254740985': '9007199254740985', \
'9007199254740986': '9007199254740986', '9007199254740987': '9007199254740987', \
'9007199254740989': '9007199254740989', '9007199254740991': '9007199254740991', \
length: 9007199254740990 }; \
const r = Array.prototype.splice.call(o, 9007199254740986, 0, 'new-value'); \
return r.length + '|' + o.length + '|' + o['9007199254740986'] + '|' + \
o['9007199254740987'] + '|' + o['9007199254740988'] + '|' + \
('9007199254740989' in o) + '|' + o['9007199254740990'] + '|' + o['9007199254740991']; })()",
)
.unwrap();
assert_eq!(
out,
"0|9007199254740991|new-value|9007199254740986|9007199254740987|false|9007199254740989|9007199254740991"
);
}
// 引擎钉:splice 极限收缩对——搬移 + 尾部删位 + 长度缩到 2^53-2。
#[test]
fn test_splice_integer_limit_shrink_pair() {
let out = eval_str(
"(() => { const o = { '9007199254740986': '9007199254740986', \
'9007199254740987': '9007199254740987', '9007199254740988': '9007199254740988', \
'9007199254740990': '9007199254740990', '9007199254740991': '9007199254740991', \
length: 9007199254740992 }; \
const r = Array.prototype.splice.call(o, 9007199254740987, 1); \
return r.length + '|' + r[0] + '|' + o.length + '|' + o['9007199254740986'] + '|' + \
o['9007199254740987'] + '|' + ('9007199254740988' in o) + '|' + \
o['9007199254740989'] + '|' + ('9007199254740990' in o) + '|' + o['9007199254740991']; })()",
)
.unwrap();
assert_eq!(out, "1|9007199254740987|9007199254740990|9007199254740986|9007199254740988|false|9007199254740990|false|9007199254740991");
}
// 引擎钉:splice 无参——length getter 恰读一次、setter 恰写一次且值为 0。
#[test]
fn test_splice_no_args_length_get_set_once() {
let out = eval_str(
"(() => { let g = 0, s = 0, v = null; \
const o = { get length() { g += 1; return '0'; }, \
set length(x) { s += 1; v = x; } }; \
Array.prototype.splice.call(o); return g + '|' + s + '|' + v; })()",
)
.unwrap();
assert_eq!(out, "1|1|0");
}
// 引擎钉:reverse 在超大 arraylike 上首对即触发上端 getter(StopReverse 上抛)。
#[test]
fn test_reverse_integer_limit_object_throws_on_first_pair() {
let out = eval_str(
"(() => { function StopReverse() {} \
const o = { get '9007199254740990'() { throw new StopReverse(); }, \
length: 9007199254740994 }; \
try { Array.prototype.reverse.call(o); return 'no-throw'; } \
catch (e) { return String(e instanceof StopReverse); } })()",
)
.unwrap();
assert_eq!(out, "true");
}
// 引擎钉:copyWithin 极限区间——源缺失位目标删位、present 位跨极大下标拷贝。
#[test]
fn test_copy_within_integer_limit_range() {
let out = eval_str(
"(() => { const si = 9007199254740988; \
const o = { 0: 0, 1: 1, 2: 2, length: 9007199254740992 }; \
o[si] = -3; o[si + 2] = -1; \
Array.prototype.copyWithin.call(o, 0, si, si + 3); \
return o[0] + '|' + (1 in o) + '|' + o[2]; })()",
)
.unwrap();
assert_eq!(out, "-3|false|-1");
}
// 引擎钉:fill 极限区间三格全落值;setter 抛错原样上抛(严格 Set 面)。
#[test]
fn test_fill_integer_limit_range_and_setter_throws() {
let out = eval_str(
"(() => { const si = 9007199254740988; const v = { m: 1 }; \
const o = { length: 9007199254740992 }; \
Array.prototype.fill.call(o, v, si, si + 3); \
const a2 = { length: 1 }; \
Object.defineProperty(a2, '0', { set: function () { throw new RangeError('set'); } }); \
let th = null; try { Array.prototype.fill.call(a2); } catch (e) { th = e.constructor.name; } \
return (o[si] === v) + '|' + (o[si + 1] === v) + '|' + (o[si + 2] === v) + '|' + th; })()",
)
.unwrap();
assert_eq!(out, "true|true|true|RangeError");
}
// 引擎钉:at 负索引自逻辑长度(稀疏覆盖值)折算,越界返回 undefined。
#[test]
fn test_at_negative_index_uses_logical_length() {
let out = eval_str(
"(() => { const a = new Array(1); a.length = 2147483653; a[0] = 'x'; \
const b = new Array(1); b.length = 2147483649; \
return a.at(-2147483653) + '|' + String(b.at(2147483648) === undefined); })()",
)
.unwrap();
assert_eq!(out, "x|true");
}
// 引擎钉:at 极负索引 k = len + relative < 0 返 undefined(不钳零回首元素);
// at(-len) 为首元素、at(-len-1) 为 undefined。
#[test]
fn test_at_extreme_negative_index_undefined() {
let out = eval_str(
"(() => { const a = [7, 8]; \
return String(a.at(-1e20) === undefined) + '|' + a.at(-2) + '|' + String(a.at(-3) === undefined); })()",
)
.unwrap();
assert_eq!(out, "true|7|true");
}
// 引擎钉:基元 this 经 ToObject 装箱——reverse 返回 Boolean 包装体、splice 返回真数组。
#[test]
fn test_splice_reverse_primitive_this_boxed() {
let out = eval_str(
"(() => { const r1 = Array.prototype.reverse.call(true); \
const r2 = Array.prototype.splice.call(false); \
return (r1 instanceof Boolean) + '|' + Array.isArray(r2) + '|' + r2.length; })()",
)
.unwrap();
assert_eq!(out, "true|true|0");
}
// 引擎钉:concat 空数组实参按 ConcatSteps 展开为零元素——不贡献元素也不占位。
#[test]
fn test_concat_empty_array_arg_expands_zero_elements() {
let out = eval_str(
"(() => { return [1].concat([]).length + '|' + [1].concat([], []).length + '|' + \
[].concat([]).length + '|' + [1, 2].concat(new Array(0)).length; })()",
)
.unwrap();
assert_eq!(out, "1|1|0|2");
}
// 引擎钉:concat 多实参混合 present/空数组/非数组实参展开序与规范一致,
// 空数组实参中间穿插不占位(length 观察面钉死);洞实参位保洞。
#[test]
fn test_concat_mixed_args_order_and_holes() {
let out = eval_str(
"(() => { const mixed = [].concat([], 5, []); \
const ordered = [1].concat([2, 3], 0, []); \
const r = [].concat(new Array(1)); \
return mixed.join(',') + '|' + mixed.length + '|' + ordered.join(',') + '|' + \
ordered.length + '|' + r.length + '|' + (0 in r); })()",
)
.unwrap();
assert_eq!(out, "5|1|1,2,3,0|4|1|false");
}
// ── concat species 面收口钉(node v20.19.2 实测值钉死) ───────────────────
// 引擎钉:concat 基元 this 经 ToObject 装箱——数字/布尔接收者展开为单元素
// 真数组,包装体身份保留。
#[test]
fn test_concat_primitive_this_boxed() {
let out = eval_str(
"(() => { const r1 = Array.prototype.concat.call(101); \
const r2 = Array.prototype.concat.call(true); \
return (r1[0] instanceof Number) + '|' + r1.length + '|' + \
(r2[0] instanceof Boolean) + '|' + r2.length; })()",
)
.unwrap();
assert_eq!(out, "true|1|true|1");
}
// 引擎钉:spreadable 对象源按 HasProperty 门控展开——洞位保洞、present 位
// 按 Get 取值,length 决定展开宽度。
#[test]
fn test_concat_spreadable_object_holes() {
let out = eval_str(
"(() => { const o = { length: 6 }; o[Symbol.isConcatSpreadable] = true; \
o[0] = 'A'; o[2] = 'C'; \
const r = [].concat(o); \
return r.length + '|' + (0 in r) + '|' + (1 in r) + '|' + r[0] + '|' + r[2]; })()",
)
.unwrap();
assert_eq!(out, "6|true|false|A|C");
}
// 引擎钉:spreadable 源 length 为负数 / 经 toString 折算 NaN 均 ToLength 归 0,
// 不展开任何元素。
#[test]
fn test_concat_spreadable_length_to_zero() {
let out = eval_str(
"(() => { const o = { length: -5, 0: 'A' }; o[Symbol.isConcatSpreadable] = true; \
const r1 = [].concat(o); \
const o2 = { 0: 'A' }; o2[Symbol.isConcatSpreadable] = true; \
o2.length = { toString() { return 'SIX'; }, valueOf: null }; \
return r1.length + '|' + [].concat(o2).length; })()",
)
.unwrap();
assert_eq!(out, "0|0");
}
// 引擎钉:spreadable 源 length getter 抛错 / 索引访问器抛错均原样透传。
#[test]
fn test_concat_poisoned_length_and_index_propagate() {
let out = eval_str(
"(() => { const o = {}; o[Symbol.isConcatSpreadable] = true; \
Object.defineProperty(o, 'length', { get() { throw new Error('lc'); } }); \
let t1 = ''; try { [].concat(o); } catch (e) { t1 = e.message; } \
const a = [1, 2, 3]; \
Object.defineProperty(a, 1, { configurable: true, get() { throw new Error('ie'); } }); \
let t2 = ''; try { [].concat(a); } catch (e) { t2 = e.message; } \
return t1 + '|' + t2; })()",
)
.unwrap();
assert_eq!(out, "lc|ie");
}
// 引擎钉:isConcatSpreadable getter 抛错透传(读序上先于一切展开)。
#[test]
fn test_concat_is_concat_spreadable_getter_throws() {
let out = eval_str(
"(() => { const o = {}; \
Object.defineProperty(o, Symbol.isConcatSpreadable, { get() { throw new Error('sg'); } }); \
let t = ''; try { [].concat(o); } catch (e) { t = e.message; } \
return t; })()",
)
.unwrap();
assert_eq!(out, "sg");
}
// 引擎钉:读序 constructor → isConcatSpreadable(this) → isConcatSpreadable(实参),
// getter 返回值 undefined 时实参整值追加。
#[test]
fn test_concat_read_order_constructor_then_spreadable() {
let out = eval_str(
"(() => { const calls = []; \
const arr = []; \
Object.defineProperty(arr, 'constructor', { configurable: true, get() { calls.push('c'); return Array; } }); \
Object.defineProperty(arr, Symbol.isConcatSpreadable, { configurable: true, get() { calls.push('s'); } }); \
const arg = {}; \
Object.defineProperty(arg, Symbol.isConcatSpreadable, { configurable: true, get() { calls.push('as'); } }); \
const r = arr.concat(arg); \
return calls.join(',') + '|' + (r[0] === arg) + '|' + r.length; })()",
)
.unwrap();
assert_eq!(out, "c,s,as|true|1");
}
// 引擎钉:isConcatSpreadable 值非 undefined 取 ToBoolean——truthy 展开(零元素
// length)、falsey 整值追加。
#[test]
fn test_concat_spreadable_value_boolean_coercion() {
let out = eval_str(
"(() => { const o = {}; o[Symbol.isConcatSpreadable] = 86; \
const r1 = [].concat(o); \
o[Symbol.isConcatSpreadable] = false; \
const r2 = [].concat(o); \
return r1.length + '|' + (r2[0] === o) + '|' + r2.length; })()",
)
.unwrap();
assert_eq!(out, "0|true|1");
}
// 引擎钉:函数 / 正则 / 装箱串默认整值,显式 spreadable 时按索引展开(含
// 原型链命中),实参侧真数组参数自有索引访问器按 Get 触发。
#[test]
fn test_concat_spreadable_function_and_proto_chain() {
let out = eval_str(
"(() => { const fn = function (a, b, c) {}; \
const r0 = [].concat(fn); \
fn[Symbol.isConcatSpreadable] = true; fn[0] = 1; fn[1] = 2; fn[2] = 3; \
const r1 = [].concat(fn); \
const a = [1, 2]; \
Object.defineProperty(a, 1, { configurable: true, get() { return 99; } }); \
const r2 = [0].concat(a); \
return (r0[0] === fn) + '|' + r1.join(',') + '|' + r1.length + '|' + r2[2] + '|' + r2.length; })()",
)
.unwrap();
assert_eq!(out, "true|1,2,3|3|99|3");
}
// 引擎钉:arguments 源 spreadable + 删位——删位保洞、余位按值展开。
#[test]
fn test_concat_arguments_deleted_slot_hole() {
let out = eval_str(
"(() => { const args = (function (a, b, c) { return arguments; })(1, 2, 3); \
delete args[1]; args[Symbol.isConcatSpreadable] = true; \
const r = [].concat(args); \
return r.length + '|' + (1 in r) + '|' + r[0] + '|' + r[2]; })()",
)
.unwrap();
assert_eq!(out, "3|false|1|3");
}
// 引擎钉:装箱串源 spreadable 时按 UTF-16 单元展开(单元未物化,直读串数据)。
#[test]
fn test_concat_string_wrapper_spreadable_units() {
let out = eval_str(
"(() => { const s = new String('ab'); s[Symbol.isConcatSpreadable] = true; \
const r = [].concat(s); \
return r.join(',') + '|' + r.length + '|' + (0 in r); })()",
)
.unwrap();
assert_eq!(out, "a,b|2|true");
}
// 引擎钉:非数组对象 this 整值追加且实参保留(结果真数组、proto 为
// Array.prototype,不读 constructor)。
#[test]
fn test_concat_non_array_this_whole_value_args_kept() {
let out = eval_str(
"(() => { let ctorReads = 0; \
const o = { length: 2, 0: 'x' }; \
Object.defineProperty(o, 'constructor', { configurable: true, get() { ctorReads++; } }); \
const r = Array.prototype.concat.call(o, 4, 5, 6); \
return r.length + '|' + (r[0] === o) + '|' + r[1] + '|' + r[3] + '|' + \
Array.isArray(r) + '|' + ctorReads; })()",
)
.unwrap();
assert_eq!(out, "4|true|4|6|true|0");
}
// 引擎钉:species 目标自有不可写 length——收尾 Set 抛 TypeError。
#[test]
fn test_concat_species_non_writable_length_throws() {
let out = eval_str(
"(() => { const A = function (_l) { Object.defineProperty(this, 'length', { value: 0, writable: false }); }; \
const arr = []; arr.constructor = {}; arr.constructor[Symbol.species] = A; \
let t = ''; try { arr.concat(1); } catch (e) { t = e.name; } \
return t; })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// 引擎钉:Array.prototype 只读 "0" 下 CDO 写自有 writable:true 位。
#[test]
fn test_concat_prototype_readonly_index_own_writable() {
let out = eval_str(
"(() => { Object.defineProperty(Array.prototype, '0', { value: 100, writable: false, configurable: true }); \
try { const r = [101].concat(); \
const d = Object.getOwnPropertyDescriptor(r, '0'); \
return d.value + '|' + d.writable + '|' + r.hasOwnProperty('0'); } \
finally { delete Array.prototype['0']; } })()",
)
.unwrap();
assert_eq!(out, "101|true|true");
}
// ── 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");
}
// 引擎钉:flatMap ToObject 通用入口——装箱基元 this(旧无装箱形态对基元抛
// TypeError)长度为 0 返空真数组;arraylike 接收者形态同入口,回调结果
// 真数组展开一层。
#[test]
fn test_flat_map_to_object_entry() {
let out = eval_str(
"(() => { const r1 = Array.prototype.flatMap.call(5, (v) => v); \
const r2 = Array.prototype.flatMap.call({ length: 1, 0: 5 }, (v) => [v, v + 1]); \
return r1.length + '|' + Array.isArray(r1) + '|' + r2.join(',') + '|' + r2.length; })()",
)
.unwrap();
assert_eq!(out, "0|true|5,6|2");
}
// 引擎钉:flatMap species——ctor 传播结果构造(旧无物种裸构造形态不走 ctor,
// tag 缺位、ctor 日志空)。
#[test]
fn test_flat_map_species_ctor_propagates() {
let out = eval_str(
"(() => { const log = []; function Ctor(n) { log.push('c' + n); const a = []; a.tag = 'X'; return a; } \
const a = [1, 2]; a.constructor = {}; a.constructor[Symbol.species] = Ctor; \
const r = a.flatMap((v) => [v]); \
return r.join(',') + '|' + r.length + '|' + r.tag + '|' + log.join(','); })()",
)
.unwrap();
assert_eq!(out, "1,2|2|X|c0");
}
// 引擎钉:flatMap species frozen 目标——CreateDataPropertyOrThrow 抛 TypeError
// (旧裸写不抛形态静默落空不抛)。
#[test]
fn test_flat_map_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.flatMap((v) => [v]); } catch (e) { t = e.name; } return t; })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// 引擎钉:flatMap 回调结果洞源——展开长度源为 LengthOfArrayLike,洞位不入
// 目标(旧 prop_count 长度源形态少读洞后存在位,结果短一元素)。
#[test]
fn test_flat_map_hole_source_length_of_arraylike() {
let out = eval_str(
"(() => { const r = [1, 2].flatMap((v) => { const x = new Array(2); x[1] = v; return x; }); \
return r.join(',') + '|' + r.length + '|' + (0 in r) + '|' + (1 in r); })()",
)
.unwrap();
assert_eq!(out, "1,2|2|true|true");
}
// 引擎钉:species 构造器为 undefined 时不查 species,四挂载点(slice/splice/
// flat/flatMap)均回退内置 ArrayCreate——纯数组、原型 Array.prototype、元素逐值。
#[test]
fn test_species_ctor_undefined_plain_array_four_mounts() {
let out = eval_str(
"(() => { const mk = () => { const a = [1, 2, 3]; a.constructor = undefined; return a; }; \
const s = mk().slice(); const sp = mk().splice(1, 1); \
const f = mk().flat(); const fm = mk().flatMap((x) => x); \
return Array.isArray(s) + '|' + (Object.getPrototypeOf(s) === Array.prototype) + '|' + \
s.join(',') + '|' + Array.isArray(sp) + '|' + sp.join(',') + '|' + \
Array.isArray(f) + '|' + f.join(',') + '|' + Array.isArray(fm) + '|' + fm.join(','); })()",
)
.unwrap();
assert_eq!(out, "true|true|1,2,3|true|2|true|1,2,3|true|1,2,3");
}
// 防过度修护栏:species 构造器为 null/数字/字符串/布尔四值时,四挂载点均保持
// 抛 TypeError(仅 undefined 判定前移,null/基元臂不得回退)。
#[test]
fn test_species_ctor_null_primitive_throws_four_mounts() {
let out = eval_str(
"(() => { let ok = 0; \
for (const cv of [null, 1, 'string', true]) { \
const a = [1, 2, 3]; a.constructor = cv; \
try { a.slice(); } catch (e) { if (e instanceof TypeError) ok++; } \
const b = [1, 2, 3]; b.constructor = cv; \
try { b.splice(1); } catch (e) { if (e instanceof TypeError) ok++; } \
const c = [1, 2, 3]; c.constructor = cv; \
try { c.flat(); } catch (e) { if (e instanceof TypeError) ok++; } \
const d = [1, 2, 3]; d.constructor = cv; \
try { d.flatMap((x) => x); } catch (e) { if (e instanceof TypeError) ok++; } } \
return String(ok); })()",
)
.unwrap();
assert_eq!(out, "16");
}
// 引擎钉:reverse arraylike 单端缺失——(N,S) 臂 Delete 先于 Set,缺端不物化。
#[test]
fn test_reverse_arraylike_single_side_missing() {
let out = eval_str(
"(() => { const o = {0:'a', length:2}; Array.prototype.reverse.call(o); \
return (0 in o) + '|' + (1 in o) + '|' + o[1] + '|' + o.length; })()",
)
.unwrap();
assert_eq!(out, "false|true|a|2");
}
// 引擎钉:reverse arraylike 洞位保持——洞不物化、length 不变。
#[test]
fn test_reverse_arraylike_hole_kept() {
let out = eval_str(
"(() => { const o = {0:'a', 2:'b', length:3}; Array.prototype.reverse.call(o); \
return (0 in o) + '|' + (1 in o) + '|' + (2 in o) + '|' + o[0] + '|' + o[2] + '|' + o.length; })()",
)
.unwrap();
assert_eq!(out, "true|false|true|b|a|3");
}
// 引擎钉:reverse arraylike accessor length——getter 触发一次,抛错原值传播。
#[test]
fn test_reverse_arraylike_length_accessor_throws() {
let result = eval_value(
"(() => { const o = {0:'x', 1:'y', length:2}; \
Object.defineProperty(o,'length',{get(){throw 99;},set(v){},enumerable:true,configurable:true}); \
Array.prototype.reverse.call(o); return 'no-throw'; })()",
);
match result {
Err(e) => assert!(e.contains("99"), "error should carry thrown 99, got: {}", e),
Ok(_) => panic!("expected accessor length throw to propagate"),
}
}
// 引擎钉:reverse arraylike length 值形态——ToLength 取整定界、原值不写回。
#[test]
fn test_reverse_arraylike_length_not_written_back() {
let out = eval_str(
"(() => { const o = {0:'x', 1:'y', length:'2'}; Array.prototype.reverse.call(o); \
return o.length + '|' + o[0] + '|' + o[1]; })()",
)
.unwrap();
assert_eq!(out, "2|y|x");
let out = eval_str(
"(() => { const o = {0:'x', 1:'y', 2:'z', length:2.7}; Array.prototype.reverse.call(o); \
return o.length + '|' + o[0] + '|' + o[1]; })()",
)
.unwrap();
assert_eq!(out, "2.7|y|x");
}
// map species 实例型 Ctor:构造恰一次且收单参 n=5,返回体直接作结果,
// 回调值经 CreateDataPropertyOrThrow 逐位写入。
#[test]
fn test_map_species_instance_ctor() {
let out = eval_str(
"(() => { let cc = 0, seen = null; \
function C1(n) { cc++; seen = n; return { tag: 'inst' }; } \
const a = [1,2,3,4,5]; a.constructor = {}; a.constructor[Symbol.species] = C1; \
const r = a.map(x => x * 2); \
return cc + '|' + seen + '|' + r.tag + '|' + r[0] + '|' + r[4]; })()",
)
.unwrap();
assert_eq!(out, "1|5|inst|2|10");
}
// map species 普通 Ctor 无返回体:结果非数组、原型为 Ctor.prototype,且无收尾
// Set(length)(length 为 undefined,仅元素位 present)。
#[test]
fn test_map_species_plain_ctor_no_length() {
let out = eval_str(
"(() => { let cc = 0; \
function C2(n) { cc++; } \
const a = [1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = C2; \
const r = a.map(x => x + 10); \
return cc + '|' + Array.isArray(r) + '|' + (Object.getPrototypeOf(r) === C2.prototype) + '|' + \
r.length + '|' + r[0] + '|' + r[2]; })()",
)
.unwrap();
assert_eq!(out, "1|false|true|undefined|11|13");
}
// map constructor 访问器抛错:Get 异常原样传播。
#[test]
fn test_map_ctor_accessor_throws() {
let out = eval_str(
"(() => { const a = [1]; \
Object.defineProperty(a, 'constructor', { get() { throw new Error('ctor-poison'); } }); \
try { a.map(x => x); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()",
)
.unwrap();
assert_eq!(out, "Error:ctor-poison");
}
// map constructor 为非对象非 undefined(null/数字/字符串/布尔四值)均抛 TypeError。
#[test]
fn test_map_ctor_non_object_throws() {
let out = eval_str(
"(() => { let ok = 0; \
for (const cv of [null, 1, 'string', true]) { \
const a = [1,2]; a.constructor = cv; \
try { a.map(x => x); } catch (e) { if (e instanceof TypeError) ok++; } } \
return String(ok); })()",
)
.unwrap();
assert_eq!(out, "4");
}
// map @@species 访问器抛错原样透传。
#[test]
fn test_map_species_accessor_throws() {
let out = eval_str(
"(() => { const a = [1]; a.constructor = { get [Symbol.species]() { throw new Error('species-poison'); } }; \
try { a.map(x => x); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()",
)
.unwrap();
assert_eq!(out, "Error:species-poison");
}
// map species 为非构造器对象(箭头函数)抛 TypeError。
#[test]
fn test_map_species_non_ctor_throws() {
let out = eval_str(
"(() => { const a = [1]; a.constructor = {}; a.constructor[Symbol.species] = () => {}; \
try { a.map(x => x); return 'no-throw'; } catch (e) { return e.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// map 目标不可扩展:CreateDataPropertyOrThrow 抛 TypeError。
#[test]
fn test_map_species_non_extensible_throws() {
let out = eval_str(
"(() => { function C3() { return Object.preventExtensions({}); } \
const a = [1]; a.constructor = {}; a.constructor[Symbol.species] = C3; \
try { a.map(x => x); return 'no-throw'; } catch (e) { return e.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// map 洞位输入:洞不写属性(结果该位缺失),length 与 present 位值正确。
#[test]
fn test_map_hole_skipped_not_written() {
let out = eval_str(
"(() => { const r = [1,,3].map(x => x * 10); \
return (1 in r) + '|' + r.length + '|' + r[0] + '|' + r[2]; })()",
)
.unwrap();
assert_eq!(out, "false|3|10|30");
}
// map species 为 null / undefined:回退真数组(原型 Array.prototype),值正确。
#[test]
fn test_map_species_null_undef_plain_array() {
let out = eval_str(
"(() => { const a = [1,2]; a.constructor = {}; a.constructor[Symbol.species] = null; \
const r1 = a.map(x => x); \
const b = [1,2]; b.constructor = {}; b.constructor[Symbol.species] = undefined; \
const r2 = b.map(x => x); \
return Array.isArray(r1) + '|' + (Object.getPrototypeOf(r1) === Array.prototype) + '|' + r1.length + '|' + \
Array.isArray(r2) + '|' + r2.length; })()",
)
.unwrap();
assert_eq!(out, "true|true|2|true|2");
}
// filter species 实例型 Ctor:构造恰一次且收单参 count=0,返回体直接作结果,
// 保留元素逐位写入。
#[test]
fn test_filter_species_instance_ctor() {
let out = eval_str(
"(() => { let cc = 0, seen = null; \
function F1(n) { cc++; seen = n; return { tag: 'inst' }; } \
const a = [1,2,3,4,5,6]; a.constructor = {}; a.constructor[Symbol.species] = F1; \
const r = a.filter(x => x % 2 === 0); \
return cc + '|' + seen + '|' + r.tag + '|' + r[0] + '|' + r[1]; })()",
)
.unwrap();
assert_eq!(out, "1|0|inst|2|4");
}
// filter species Ctor 预设 length=99:无收尾 Set(length),预设值原样保留,
// 保留元素逐位写入。
#[test]
fn test_filter_species_pre_length_kept() {
let out = eval_str(
"(() => { let cc = 0; \
function F2(n) { cc++; this.length = 99; } \
const a = [1,2,3,4]; a.constructor = {}; a.constructor[Symbol.species] = F2; \
const r = a.filter(x => x > 1); \
return cc + '|' + Object.prototype.hasOwnProperty.call(r, 'length') + '|' + r.length + '|' + \
r[0] + '|' + r[1] + '|' + Object.keys(r).join(','); })()",
)
.unwrap();
assert_eq!(out, "1|true|99|2|3|0,1,2,length");
}
// filter constructor 访问器抛错:Get 异常原样传播。
#[test]
fn test_filter_ctor_accessor_throws() {
let out = eval_str(
"(() => { const a = [1]; \
Object.defineProperty(a, 'constructor', { get() { throw new Error('ctor-poison'); } }); \
try { a.filter(x => true); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()",
)
.unwrap();
assert_eq!(out, "Error:ctor-poison");
}
// filter constructor 为非对象非 undefined(null/数字/字符串/布尔四值)均抛 TypeError。
#[test]
fn test_filter_ctor_non_object_throws() {
let out = eval_str(
"(() => { let ok = 0; \
for (const cv of [null, 1, 'string', true]) { \
const a = [1]; a.constructor = cv; \
try { a.filter(x => true); } catch (e) { if (e instanceof TypeError) ok++; } } \
return String(ok); })()",
)
.unwrap();
assert_eq!(out, "4");
}
// filter @@species 访问器抛错原样透传。
#[test]
fn test_filter_species_accessor_throws() {
let out = eval_str(
"(() => { const a = [1]; a.constructor = { get [Symbol.species]() { throw new Error('species-poison'); } }; \
try { a.filter(x => true); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()",
)
.unwrap();
assert_eq!(out, "Error:species-poison");
}
// filter species 为非构造器对象(箭头函数)抛 TypeError。
#[test]
fn test_filter_species_non_ctor_throws() {
let out = eval_str(
"(() => { const a = [1]; a.constructor = {}; a.constructor[Symbol.species] = () => {}; \
try { a.filter(x => true); return 'no-throw'; } catch (e) { return e.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// filter 目标不可扩展:CreateDataPropertyOrThrow 抛 TypeError。
#[test]
fn test_filter_species_non_extensible_throws() {
let out = eval_str(
"(() => { function C3() { return Object.preventExtensions({}); } \
const a = [1,2,3,4]; a.constructor = {}; a.constructor[Symbol.species] = C3; \
try { a.filter(x => x > 0); return 'no-throw'; } catch (e) { return e.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// filter 非数组接收者(arraylike):结果恒为真数组,值正确。
#[test]
fn test_filter_arraylike_receiver_plain() {
let out = eval_str(
"(() => { const r = Array.prototype.filter.call({ 0: 1, 1: 2, length: 2 }, x => x > 0); \
return Array.isArray(r) + '|' + r.length + '|' + r.join(','); })()",
)
.unwrap();
assert_eq!(out, "true|2|1,2");
}
// filter 普通数组无 species 覆写:结果真数组、值与长度正确(防过度修)。
#[test]
fn test_filter_plain_array_plain_result() {
let out = eval_str(
"(() => { const r = [1,2,3,4].filter(x => x % 2 === 0); \
return Array.isArray(r) + '|' + r.length + '|' + r.join(','); })()",
)
.unwrap();
assert_eq!(out, "true|2|2,4");
}
// 可构造 C 覆写 @@species:结果经 species 构造(Other 实例),而非 C 本身。
#[test]
fn test_slice_species_ctor_override_returns_other() {
let out = eval_str(
"(() => { function Base(n) { this.tag = 'base'; } \
function Other(n) { this.tag = 'other'; } \
Base[Symbol.species] = Other; \
const a = [1,2,3]; a.constructor = Base; \
const r = a.slice(0); \
return r.tag + '|' + Array.isArray(r); })()",
)
.unwrap();
assert_eq!(out, "other|false");
}
// 可构造 C 无 @@species:Get 查得 undefined,回退真数组(原型 Array.prototype)。
#[test]
fn test_slice_ctor_plain_no_species_plain_array() {
let out = eval_str(
"(() => { function C(n) { this.tag = 'C'; } \
const a = [1,2,3]; a.constructor = C; \
const r = a.slice(0); \
return Array.isArray(r) + '|' + (Object.getPrototypeOf(r) === Array.prototype); })()",
)
.unwrap();
assert_eq!(out, "true|true");
}
// 可构造 C 的 @@species 为非构造器对象抛 TypeError。
#[test]
fn test_slice_species_non_ctor_throws() {
let out = eval_str(
"(() => { function C3(n) { this.tag = 'C3'; } C3[Symbol.species] = () => {}; \
const a = [1,2,3]; a.constructor = C3; \
try { a.slice(0); return 'no-throw'; } catch (e) { return e.name; } })()",
)
.unwrap();
assert_eq!(out, "TypeError");
}
// 可构造 C 的 @@species 访问器抛错原样透传。
#[test]
fn test_slice_species_accessor_throws() {
let out = eval_str(
"(() => { function C4(n) { this.tag = 'C4'; } \
Object.defineProperty(C4, Symbol.species, { get() { throw new Error('species-poison'); } }); \
const a = [1,2,3]; a.constructor = C4; \
try { a.slice(0); return 'no-throw'; } catch (e) { return e.name + ':' + e.message; } })()",
)
.unwrap();
assert_eq!(out, "Error:species-poison");
}
// 守卫:c={} 不可构造形态经 @@species 构造(136 系列保真,防 228 修点漂移)。
#[test]
fn test_slice_ctor_braces_species_plain() {
let out = eval_str(
"(() => { function G(n) { this.tag = 'G'; } \
const a = [1,2,3]; a.constructor = {}; a.constructor[Symbol.species] = G; \
const r = a.slice(0); \
return r.tag; })()",
)
.unwrap();
assert_eq!(out, "G");
}
// 守卫:普通数组 slice 无 species 覆写,结果真数组、值正确。
#[test]
fn test_slice_plain_array_guard() {
let out = eval_str(
"(() => { const r = [1,2,3].slice(0); \
return Array.isArray(r) + '|' + r.join(','); })()",
)
.unwrap();
assert_eq!(out, "true|1,2,3");
}