Skip to content

Two ES5 correctness bugs: Object(null)→{} and function-declaration reassignment #3149

@proggeramlug

Description

@proggeramlug

Summary

Two small, independent ES5/core-semantics correctness bugs found during the Test262/parity dive. Both are self-contained; an agent can fix both in one PR (clearly separate root causes/files).

Bug 1 — Object(null) / Object() / Object(undefined) returns undefined instead of a fresh {}

typeof Object(null)       // Perry "undefined"   Node "object"
typeof Object()           // Perry "undefined"   Node "object"
typeof Object(undefined)  // Perry "undefined"   Node "object"
// (new Object(null) also affected)

Per spec, the Object coercion function called with null/undefined/no args returns a new ordinary object. Perry returns undefined.

Where: the Object(...) call path (not new). The new path in crates/perry-runtime/src/object/class_registry.rs::js_new_function_construct already has an "Object" => js_object_alloc(0,0) arm under identify_global_builtin_constructor; the plain-call path (codegen call dispatch for the Object global / global_this.rs Object thunk) needs the same nullish→{} handling. Verify both Object(null) and new Object(null).

Bug 2 — reassigning a function-declaration binding is ignored

function G(m){ this.v = "OLD"; }
G = function(m){ this.v = "NEW"; };
new G(1).v;   // Perry "OLD"   Node "NEW"
// also: var f = G; G = otherFn; f and G should reflect the reassignment per binding semantics

A top-level function declaration's binding is treated as an immutable FuncRef at use sites, so a later G = ... reassignment doesn't take effect — new G() / G() still calls the original. Function-declaration bindings are mutable var-like bindings in JS.

Where: HIR lowering resolves an identifier that names a function to Expr::FuncRef(func_id) (see crates/perry-hir/src/lower/lookup_func resolution in lower_expr.rs / call + new lowering). When the name has been reassigned, the binding must read the current value (a mutable slot), not the static FuncRef. Likely needs the function name to also have a mutable local/global slot that assignment writes and reads go through once reassigned.

Acceptance criteria

  • typeof Object(null) === "object", Object()/Object(undefined) return {}, new Object(null) too.
  • After G = fn2, both new G() and G() invoke fn2; unre-assigned function declarations keep working (no perf/behavior regression for the common case).
  • Add gap tests; differential vs node --experimental-strip-types.

Refs: Test262 radar #799, Node+TS roadmap #793.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparityNode.js compatibility / parity gaps

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions