Repository navigation
runtime: return real Map and Set iterator objects #2856
Description
Summary
Perry's Map/Set entries(), keys(), and values() paths materialize arrays for common Array.from(...) / for...of use, but Node returns iterator objects. The returned values should not be Arrays, should expose .next(), should be iterable via Symbol.iterator, and should be recognized by util.types.isMapIterator() / isSetIterator() because of their actual iterator shape rather than a side-table marker.
Set.prototype.entries() also appears to be missing on the typed Set method path.
Node behavior
On Node v25.9.0:
const util = require("node:util");
const m = new Map([[1, "a"], [2, "b"]]);
const s = new Set(["x", "y"]);
for (const [label, it] of [
["map keys", m.keys()],
["map entries", m.entries()],
["set values", s.values()],
["set entries", s.entries()],
]) {
console.log(label, Array.isArray(it), typeof it.next, util.types.isMapIterator(it), util.types.isSetIterator(it));
console.log(label, JSON.stringify(it.next()), JSON.stringify(it.next()));
}
console.log("map default:", m[Symbol.iterator]().next().value.join(":"));
console.log("set default:", s[Symbol.iterator]().next().value);Expected properties:
Array.isArray(it) === falsetypeof it.next === "function"- Map iterators satisfy
util.types.isMapIterator - Set iterators satisfy
util.types.isSetIterator .next()advances one entry at a time and returns{ value, done }Map.prototype[Symbol.iterator]aliases entries, andSet.prototype[Symbol.iterator]aliases values
Current Perry evidence
Current source materializes arrays instead of iterator objects:
crates/perry-hir/src/ir/expr.rsdocumentsMapEntries,MapKeys, andMapValuesas returningArray<[key, value]>,Array<key>, andArray<value>, andSetValuesas returning anArray.crates/perry-runtime/src/map.rs::js_map_entries()allocates an outer Array of[key, value]pair Arrays, then callsmark_map_iterator_array(result)before returning it.js_map_keys()andjs_map_values()likewise allocate Arrays and mark them as map iterators.crates/perry-runtime/src/set.rs::js_set_to_array()materializes a Set as an Array and callsmark_set_iterator_array(result).crates/perry-runtime/src/object/util_types.rs::js_util_types_is_map_iterator()/js_util_types_is_set_iterator()check those side-table registrations, so an Array can be reported as a Map/Set iterator.crates/perry-codegen/src/lower_call/property_get.rsexplicitly says the runtime returns a real Array for Map iterator methods, and for Set methods only routesvalues/keysthroughjs_set_to_array; there is noentriesarm in that typed Set dispatch.crates/perry-runtime/src/object/native_call_method.rshandles dynamic Mapentries/keys/valuesby returning the same array materializers. The dynamic Set branch handlesadd/has/delete/clear/sizeonly, so dynamic Set iterator methods fall through toundefined.
This is enough for array-consuming fast paths, but it is not Node-compatible when user code calls .next(), checks Array.isArray, reuses the iterator object, or reads Set.prototype.entries().
Suggested test surface
Add Node-suite tests for:
import { types } from "node:util";
const mk = new Map([[1, "a"], [2, "b"]]).keys();
console.log("map keys array:", Array.isArray(mk));
console.log("map keys next type:", typeof (mk as any).next);
console.log("map keys util:", types.isMapIterator(mk));
console.log("map keys next:", JSON.stringify((mk as any).next()), JSON.stringify((mk as any).next()));
const se = new Set(["x", "y"]).entries();
console.log("set entries array:", Array.isArray(se));
console.log("set entries next type:", typeof (se as any).next);
console.log("set entries util:", types.isSetIterator(se));
console.log("set entries next:", JSON.stringify((se as any).next()), JSON.stringify((se as any).next()));Also cover default iterators:
console.log(new Map([[1, "a"]])[Symbol.iterator]().next().value.join(":"));
console.log(new Set(["x"])[Symbol.iterator]().next().value);Scope / non-goals
This issue is scoped to the iterator object surface for Map and Set collection methods. It does not reopen constructor iterable support (#2770 / #2771), forEach callback semantics (#2830), or deletion/insertion-order preservation (#2831).
Related but not duplicate: closed #412 fixed a Map/Set bug by making these paths usable for array-consuming cases; this issue tracks the remaining iterator-protocol shape required by Node.
Activity
fix(runtime): narrow the collection-iterator latch to Map/Set receive…