Skip to content

fix(hir): operator-expression semantics test262 tail (inc/dec ToNumber, != bigint/object, instanceof) - #4850

Merged
proggeramlug merged 1 commit into
mainfrom
language-operators-parity
Jun 9, 2026
Merged

proggeramlug merged 1 commit into
mainfrom
language-operators-parity

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Operator-expression parity across language/expressions/* (the 8 assigned dirs):
617→669 pass (82.6%→89.6%), with 0 regressions on the broad built-ins language
shard 0/12 (and +6 bonus fixes in equals/ and strict-does-not-equals/ from the
BigInt equality work).

Per-dir (rt-fail before→after): does-not-equals 13→0; postfix-increment 14→4;
postfix-decrement 14→4; prefix-increment 13→4; prefix-decrement 13→4; instanceof 11→10;
compound-assignment 33→33 (deferred); logical-assignment 3→3 (deferred).

Root causes fixed

  1. inc/dec ToNumber (expr/literals_vars.rs, ~30 tests). x++/--x did a raw
    fadd on the loaded NaN-boxed bits and returned the original operand, so
    var x=true; x++ left x as true. The operand is now ToNumber-coerced
    (js_number_coerce) before the step and the postfix result is the coerced number.
    Gated to skip statically-integer loop counters so the hot path stays a single fadd.

  2. i32-bounded Update gating (collectors/i32_locals.rs, ~8 tests).
    is_strictly_i32_bounded_expr treated any Expr::Update as i32-bounded, so
    var y = x++ got an i32 slot and truncated the postfix result
    (var x=1.1; var y=x++ stored y=1). Now i32-bounded only when the updated local
    is itself a known integer.

  3. !=/== with BigInt and objects (expr/compare.rs + builtins/arithmetic.rs,
    does-not-equals 13→0). The codegen BigInt fast path fired when either side was
    BigInt and fed js_bigint_cmp a non-BigInt operand (0n != undefined dereferenced
    an undefined NaN-box as a BigIntHeader); narrowed to both-sides BigInt. Added full
    BigInt abstract equality (BigInt↔BigInt/Number/String/Boolean), Object→ToPrimitive,
    and Object-vs-Object reference equality (new Boolean(true) != new Boolean(true)) to
    js_loose_eq.

  4. instanceof non-constructor RHS (lower/lower_expr.rs). A primitive/this/call
    right-hand side was silently rewritten to instanceof Object and returned false;
    it is now lowered to a value and routed through js_instanceof_dynamic, which throws
    the spec TypeError (true instanceof true, ({}) instanceof this).

Deferred (noted in code)

  • compound-assignment A7 left-before-right single-evaluation (needs expression-level
    temp-slot machinery in the shared expr_assign.rs).
  • instanceof OrdinaryHasInstance .prototype-must-be-object + undefined-ident ReferenceError.
  • BigInt ++/-- strict-eq (runtime value is already correct BigInt, but === uses
    fcmp because the Update-result static type is inferred as Number).
  • with/eval/global compound + update compile-fails.

Validation

  • Build clean; cargo fmt --all applied; file-size gate passes.
  • Zero regressions verified by diffing failure sets of branch vs a from-scratch
    origin/main build over --dir built-ins language --shard 0/12 (2645 judged).

…r, != bigint/object, instanceof)

Operator-expression parity in language/expressions/* (8 dirs): 617→669 pass
(82.6%→89.6%), 0 regressions on the broad `built-ins language` shard 0/12
(+6 bonus fixes in equals/ and strict-does-not-equals/).

Root causes fixed:

1. inc/dec ToNumber (postfix/prefix increment/decrement, ~30 tests). `x++`/`--x`
   did a raw `fadd` on the loaded NaN-boxed bits and returned the *original*
   operand, so `var x=true; x++` left x as `true`. Now the operand is
   ToNumber-coerced (js_number_coerce: bool/null/string/object-valueOf) before
   the step, and the postfix result is the coerced number. Gated to skip
   statically-integer loop counters so the hot path stays a single `fadd`.

2. i32-bounded Update gating (A4 tests, ~8). `is_strictly_i32_bounded_expr`
   treated *any* `Expr::Update` as i32-bounded, so `var y = x++` got an i32 slot
   and truncated the postfix result (`var x=1.1; var y=x++` stored y=1). Now only
   bounded when the updated local is itself a known integer.

3. `!=` / `==` with BigInt and objects (does-not-equals 13→0). The codegen
   bigint fast path fired when *either* side was BigInt and fed `js_bigint_cmp`
   a non-BigInt operand (`0n != undefined` dereffed undefined as a BigIntHeader);
   narrowed to both-sides-BigInt. Added full BigInt abstract equality
   (BigInt↔BigInt/Number/String/Boolean) + Object→ToPrimitive + Object-vs-Object
   reference equality (`new Boolean(true) != new Boolean(true)`) to js_loose_eq.

4. instanceof non-constructor RHS (S11.8.6_A3 etc). A primitive/`this`/call RHS
   was silently rewritten to `instanceof Object` and returned false; now lowered
   to a value and routed through js_instanceof_dynamic, which throws the spec
   TypeError on a non-callable right-hand side.

Deferred (genuinely harder, noted in code): compound-assignment A7 left-before-
right single-evaluation (needs an expression-level temp-slot machinery / shared
expr_assign.rs), instanceof OrdinaryHasInstance `.prototype`-must-be-object +
undefined-ident ReferenceError, BigInt `++`/`--` strict-eq (needs Update-result
static type inferred as BigInt), and the `with`/eval/global compound + update
compile-fails.

Files: perry-codegen/src/expr/{literals_vars,compare}.rs,
collectors/i32_locals.rs, perry-hir/src/lower/lower_expr.rs,
perry-runtime/src/builtins/arithmetic.rs.
@proggeramlug
proggeramlug merged commit f9e117b into main Jun 9, 2026
13 checks passed
@proggeramlug
proggeramlug deleted the language-operators-parity branch June 9, 2026 18:42
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