Skip to content

runtime/object-model: fix Object(...) boxing and Object.assign ToObject/copy semantics #3986

Description

@andrewtdiz

Summary

The current c262 sample shows Object(...), new Object(...), and Object.assign(...) still diverging on core ToObject behavior. Perry often returns or reports primitive-ish values where Node exposes wrapper objects, loses constructor/prototype identity, and misses descriptor/getter/proxy behavior during assignment.

Evidence

Run from origin/main at 1f6d20e19 on 2026-06-01, Node v25.9.0, Test262 4249661388e5d3f92a85186213da140a6481490f:

scripts/test262_subset.py --root /tmp/perry-test262-4249661 --dir built-ins/Object --max 180 --timeout 10 --sample-cap 50 --report /tmp/perry-c262-builtins-object.json

Observed: 97 pass, 83 runtime-fail, 0 compile-fail, 53.9% sample parity.

Representative failures:

  • built-ins/Object/S9.9_A3.js: Object(true).valueOf() expected true, Perry returns an object-ish value.
  • built-ins/Object/S9.9_A4.js: Object(0).valueOf() expected 0, Perry returns an object-ish value.
  • built-ins/Object/S9.9_A5.js: Object("some string").valueOf() expected the original string, Perry returns an object-ish value.
  • built-ins/Object/assign/Target-String.js: Object.assign should box a string target and return an object; Perry reports typeof as string.
  • built-ins/Object/assign/source-get-attr-error.js: getter abrupt completion should propagate; Perry reports no expected Test262Error.
  • built-ins/Object/assign/source-own-prop-desc-missing.js: proxy/property descriptor path is not observed as Node does.

Product triage

User outcome: library code that uses object coercion, option merging, primitive wrappers, and descriptor-aware assignment behaves like Node instead of silently dropping values.

Shortest path: implement the shared ToObject/wrapper path and reuse it from Object(...), new Object(...), and Object.assign(...) before expanding into less-used Object static helpers.

Manual/narrow assumptions to test: boxed primitive probes for boolean/number/string/symbol plus a focused Object.assign fixture with primitive targets, non-enumerable source props, throwing getters, read-only target props, and proxies.

Core vs nice: core is ToObject, valueOf, constructor/prototype identity, enumerable own-property copy, and abrupt propagation. Nice is full Object static method descriptor polish already partially covered by #3655 and validation work in #3146/#3662.

Keep flexible: centralize ToObject so String wrappers, primitive receiver boxing (#3577), and future exotic wrappers can share it.

Suggested scope

Acceptance criteria

  • The representative rows above pass under the Test262 differential.
  • built-ins/Object --max 180 improves materially without moving cases to skip.
  • A small local probe covering Object(true/0/"x").valueOf() and primitive-target Object.assign matches Node output.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions