Skip to content

fix(hir): non-class statement-semantics test262 remnant#4920

Merged
proggeramlug merged 1 commit into
mainfrom
language-statements-remnant-parity
Jun 10, 2026
Merged

fix(hir): non-class statement-semantics test262 remnant#4920
proggeramlug merged 1 commit into
mainfrom
language-statements-remnant-parity

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Cleans up the non-class statement tail of the test262 language/statements suite. Three root-cause fixes, +7 tests, zero regressions.

Results

Verified against main (bea4f5c) byte-for-byte vs node v26:

  • All 16 language/statements dirs (non-class): 1286 → 1293 pass (+7), parity 93.9% → 94.4%, 0 regressions.
  • Broad built-ins language --shard 0/12 cross-check: 0 regressions (+2 within the shard slice).
  • cargo test -p perry-hir: all green.

Fixed: do-while/S12.6.1_A10, while/S12.6.2_A10, for-of/iterator-close-via-throw, for-of/generator-close-via-throw, function/S13.2_A4_T1, function/S13.2_A4_T2, function/13.2-17-1.

Root causes

1. typeof(x) with a parenthesized operand

The typeof AST-level folds matched a bare Ident/Member, so typeof(zzz) (parens) bypassed them and fell through to a normal operand lowering — emitting a ReferenceError-throwing get instead of folding an unresolved identifier to "undefined" (spec GetValue-is-skipped-under-typeof). Peel transparent Paren wrappers before the folds run.

2. for-of IteratorClose on a throw completion

The lazy iterator-protocol loop ran IteratorClose for break/return/labeled abrupts (insert_iterator_close_on_abrupt) but not when the body completed abruptly via a throw — an explicit throw, a throwing LHS setter, a destructuring error, an assertion failure, or a generator .throw(). Wrap only the user body in a try/catch that runs an unvalidated, exception-swallowing IteratorClose and re-throws the caught error. The element-.value read and binding statements stay outside the wrapper, because per spec IteratorValue throwing sets the iterator done without closing it (iterator-next-result-value-attr-error). The close itself — including the .return getter read — is inside a swallowing try so a throwing return/getter does not replace the original error (iterator-close-throw-get-method-abrupt). Applied to both for-of lowering paths (module-init stmt_loops.rs and body/block body_stmt.rs).

3. f.prototype.constructor returned undefined

The inline nested read folds to HIR GetFunctionPrototypeMethod, whose runtime entry (js_get_function_prototype_method) only served registered prototype methods plus a small builtin-method allowlist — it returned undefined for "constructor" and never materialized the prototype object (so the synthetic class id was 0). Add a constructor arm that routes through js_function_prototype_value_for_read (which respects a replaced f.prototype = X and otherwise materializes the auto-created prototype whose constructor back-pointer is the function) and reads its constructor field.

Files

  • crates/perry-hir/src/lower/lower_expr.rs — typeof paren-peel
  • crates/perry-hir/src/lower/stmt_loops.rs, lower_decl/body_stmt.rs, lower/mod.rs — for-of IteratorClose-on-throw
  • crates/perry-runtime/src/object/class_registry.rsf.prototype.constructor

No version/CHANGELOG bump (maintainer folds those in at merge).

Three root-cause fixes across the language/statements tail (function / for-of /
do-while / while), +7 tests, zero regressions (verified vs main on all 16
statement dirs and the built-ins+language 0/12 shard).

1. typeof(x) with a parenthesized operand. The typeof AST folds matched a bare
   Ident/Member, so `typeof(zzz)` fell through to a normal operand lowering and
   emitted a ReferenceError-throwing get instead of folding an unresolved
   identifier to "undefined" (spec GetValue-skips-on-typeof). Peel transparent
   Paren wrappers before the folds.
   Fixes do-while/S12.6.1_A10, while/S12.6.2_A10.

2. for-of IteratorClose on a throw completion. The lazy iterator-protocol loop
   ran IteratorClose for break/return/labeled abrupts but not when the body
   threw (explicit `throw` or a runtime exception). Wrap only the user body in a
   try/catch that runs an unvalidated, exception-swallowing IteratorClose and
   re-throws; the element-.value read and binding stay outside, since spec
   IteratorValue throwing sets the iterator done WITHOUT closing. Applied to both
   for-of lowering paths (module-init and body/block).
   Fixes for-of/iterator-close-via-throw, for-of/generator-close-via-throw.

3. f.prototype.constructor read returned undefined. The inline nested read folds
   to GetFunctionPrototypeMethod, whose runtime entry only served registered
   methods + a builtin allowlist and returned undefined for "constructor" (and
   never materialized the prototype, so cid was 0). Add a constructor arm that
   routes through js_function_prototype_value_for_read (respecting a replaced
   f.prototype = X) and reads its constructor field.
   Fixes function/S13.2_A4_T1, function/S13.2_A4_T2, function/13.2-17-1.
@proggeramlug proggeramlug merged commit 846bd96 into main Jun 10, 2026
12 of 13 checks passed
@proggeramlug proggeramlug deleted the language-statements-remnant-parity branch June 10, 2026 13:26
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