Skip to content

fix(hir): iteration-statement protocol test262 parity#4750

Merged
proggeramlug merged 1 commit into
mainfrom
iteration-parity
Jun 7, 2026
Merged

fix(hir): iteration-statement protocol test262 parity#4750
proggeramlug merged 1 commit into
mainfrom
iteration-parity

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Improves language/statements/for-in, for-of, and for-await-of test262 parity by making the iterator/enumeration protocol spec-compliant in the runtime fallback paths. +10 tests fixed, 0 regressions across all five iteration-statement directories.

Root causes fixed

1. for (key in …) enumeration was Object.keys, not the spec EnumerateObjectProperties (for-in).
The for-in desugar emitted Expr::ObjectKeysjs_object_keys_value, which (a) throws TypeError on null/undefined and (b) returns only own enumerable keys. Per ECMA-262 §14.7.5, for (k in null/undefined) {} is a no-op (no throw) and inherited enumerable string keys on the prototype chain must be visited (deduplicated). Added a dedicated Expr::ForInKeys HIR node lowering to a new runtime js_for_in_keys_value, which: returns [] for nullish; reuses js_object_keys_value per level for the enumerable subset (so arrays→indices, strings→indices, typed arrays, proxies, class instances all keep working); and walks the [[Prototype]] chain. Shadowing follows the spec exactly — a name that is an own property at a closer level (even a non-enumerable one) hides the same name farther up, so each level marks all own names (js_object_get_own_property_names) as seen after emitting its enumerable subset.

2. Non-iterable primitives silently no-op'd instead of throwing (for-of).
js_for_of_to_array returned an empty array for null/undefined/number/boolean, so for (x of null) / for (x of 37) ran zero times instead of throwing TypeError (GetIterator → ToObject/GetMethod). Now throws throw_not_iterable.

3. IteratorNext accepted non-object results (for-of).
js_iterator_to_array treated a non-object next() result as "done" and broke. Per §7.4.2 step 3, Type(result) not Object must throw TypeError. Now throws when the result isn't a POINTER_TAG heap object (excluding registered symbols, which are pointer-tagged but not objects).

Before → after (parity)

dir before after fixed
for-in 61.0% 68.8% +6 (S12.6.4_A1, A2, A6, A6.1, let-block-with-newline, let-identifier-with-newline)
for-of 59.0% 60.7% +3 (head-expr-to-obj, head-expr-primitive-iterator-method, iterator-next-result-type)
for-await-of 56.5% 60.9% +1 (let-block-with-newline)
for 88.5% 88.5%
while 73.0% 73.0%

Zero regressions: every test that passed before still passes (verified by diffing the per-dir failure sets across all five directories). The one transient for-in regression introduced by a first-cut prototype walk (12.6.4-2, non-enumerable own key must still shadow) was fixed before finalizing. Spread / Math.max(...) / [...map] / [...set] / [...string] / generator spread all verified byte-identical to Node.

Files

New IR node ForInKeys wired through perry-hir (ir/expr.rs, lower/stmt_loops.rs, lower_decl/body_stmt.rs, analysis, js_transform, monomorph, stable_hash, walkers) and perry-codegen (logical_collections, expr/mod, type_analysis, boxed_vars, 4 collectors, runtime_decls/strings, codegen-js). Runtime: new js_for_in_keys_value (object/field_get_set.rs) and the two for-of throws (array/iterator.rs).

@proggeramlug proggeramlug merged commit 5b18213 into main Jun 7, 2026
13 checks passed
@proggeramlug proggeramlug deleted the iteration-parity branch June 7, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant