Map/Set/string iterator objects answer a bound method for return and throw, but per spec those prototypes have no such properties — only generator objects do.
const it = new Set([1, 2]).values();
console.log(typeof it.return, typeof it.throw);
node v26.5.1 undefined undefined
perry function function
typeof it[Symbol.iterator] === "function" is correct and should stay.
Where
crates/perry-runtime/src/object/field_get_set/accessors.rs, map_set_iterator_property — the synthetic bind arm:
let bind_name: Option<&'static [u8]> = match key_bytes {
b"return" => Some(b"return"),
b"throw" => Some(b"throw"),
b"@@iterator" => Some(b"@@iterator"),
_ => None,
};
Noticed while auditing #9075, which moved this arm out of get_field_by_name_tail.rs verbatim. It is not introduced there — main answers "function" too, confirmed by A/B — so this is long-standing and predates that extraction.
Why it is not purely cosmetic
return is protocol-visible: an early break out of a for…of calls IteratorClose, which does GetMethod(iterator, "return") and invokes it when present. Synthesizing one means we call into a bound method on paths where the spec performs no call at all, so the observable difference is not limited to typeof. Worth checking what the synthesized return actually does on an abrupt loop exit before deciding whether to delete the arm or make it spec-shaped.
The likely fix is to drop return/throw from the bind list and let the generic scans answer undefined, keeping @@iterator. That needs a check for callers relying on the current behaviour — dispatch_set_iterator_method/dispatch_map_iterator_method may route return through this path for their own close handling.
Map/Set/string iterator objects answer a bound method forreturnandthrow, but per spec those prototypes have no such properties — only generator objects do.typeof it[Symbol.iterator] === "function"is correct and should stay.Where
crates/perry-runtime/src/object/field_get_set/accessors.rs,map_set_iterator_property— the synthetic bind arm:Noticed while auditing #9075, which moved this arm out of
get_field_by_name_tail.rsverbatim. It is not introduced there — main answers"function"too, confirmed by A/B — so this is long-standing and predates that extraction.Why it is not purely cosmetic
returnis protocol-visible: an earlybreakout of afor…ofcallsIteratorClose, which doesGetMethod(iterator, "return")and invokes it when present. Synthesizing one means we call into a bound method on paths where the spec performs no call at all, so the observable difference is not limited totypeof. Worth checking what the synthesizedreturnactually does on an abrupt loop exit before deciding whether to delete the arm or make it spec-shaped.The likely fix is to drop
return/throwfrom the bind list and let the generic scans answerundefined, keeping@@iterator. That needs a check for callers relying on the current behaviour —dispatch_set_iterator_method/dispatch_map_iterator_methodmay routereturnthrough this path for their own close handling.