fix: Iterator.from 鸭子回退包装器创建时缓存 next 闭包——getter 恰触发一次,消费期零重读,getter 抛错透传

This commit is contained in:
2026-09-11 16:29:35 +00:00
parent ee32b2c682
commit f3533167b5
2 changed files with 128 additions and 17 deletions
@@ -53,6 +53,71 @@ fn iterator_from_iterator_forwards_next() {
assert_eq!(result.as_int(), 7);
}
#[test]
fn iterator_from_duck_fallback_next_getter_read_only_once() {
// 鸭子回退的 from() 恰读一次 next getter(规范单读语义):
// 包装器缓存 from() 时读到的闭包,消费期直用缓存闭包——getter 不被重触发,
// 底层新产的值经同一闭包透出。
let mut vm = Vm::new();
let bool_cases = [
(
"var nextGets = 0; \
var nextCalls = 0; \
var obj = { \
get next() { \
++nextGets; \
var i = 0; \
return function () { \
++nextCalls; \
return i < 4 ? { value: i++, done: false } : { value: undefined, done: true }; \
}; \
}, \
}; \
var it = Iterator.from(obj); \
nextGets === 1 && nextCalls === 0",
true,
),
(
"var nextGets = 0; \
var nextCalls = 0; \
var obj = { \
get next() { \
++nextGets; \
var i = 0; \
return function () { \
++nextCalls; \
return i < 4 ? { value: i++, done: false } : { value: undefined, done: true }; \
}; \
}, \
}; \
var it = Iterator.from(obj); \
var vals = []; \
for (var k = 0; k < 5; ++k) { var r = it.next(); if (!r.done) { vals.push(r.value); } } \
nextGets === 1 && nextCalls === 5 && vals.join(',') === '0,1,2,3'",
true,
),
];
for (src, expected) in bool_cases {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
}
#[test]
fn iterator_from_throwing_next_getter_propagates() {
// next getter 抛错经 from() 透传原异常,不降级为 "not iterable" TypeError。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var ok = false; \
try { Iterator.from({ get next() { throw new RangeError('read'); } }); } \
catch (e) { ok = e instanceof RangeError; } \
ok",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn new_iterator_throws_type_error() {
let mut vm = Vm::new();