Skip to content

fix: built-in prototype methods on all built-in prototypes resolve as property values (#2142)#2176

Merged
proggeramlug merged 1 commit into
mainfrom
fix-2142-builtin-proto-values
May 28, 2026
Merged

fix: built-in prototype methods on all built-in prototypes resolve as property values (#2142)#2176
proggeramlug merged 1 commit into
mainfrom
fix-2142-builtin-proto-values

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Closes #2142.

Problem

After #2058/#2059/#2060/#2063 landed, the value-form gap for built-in prototype methods narrowed but didn't close. Reading a method off Array.prototype / Date.prototype / RegExp.prototype / String.prototype / Promise.prototype / %TypedArray%.prototype etc. as a property value still returned undefined — even for Object.prototype / Number.prototype / Function.prototype (the typeof fold from #2058 only catches typeof <Ctor>.prototype.<m> at AST level; once the value lands in a local or array literal, the indirect typeof goes through the same broken member-access path).

// Issue's repro
for (var [n, v] of [
  ["Array.prototype.map",        Array.prototype.map],         // was: undefined,  now: function
  ["Date.prototype.toISOString", Date.prototype.toISOString],  // was: undefined,  now: function
  ["RegExp.prototype.test",      RegExp.prototype.test],       // was: undefined,  now: function
  ["String.prototype.slice",     String.prototype.slice],      // was: undefined,  now: function
  ["Promise.prototype.then",     Promise.prototype.then],      // was: undefined,  now: function
  ["Int8Array.prototype.copyWithin", Int8Array.prototype.copyWithin], // was: undefined, now: function
]) console.log(n, typeof v);

Per #2142, this is the dominant cascade in Test262's built-ins/{Object,Array,TypedArray,Date,RegExp,Function} re-harvest after #2058/#2059/#2060/#2063 — driving the bulk of the (no output) / reading 'name' / reading 'constructor' failures.

Fix

Two coordinated changes:

  1. HIR (expr_member.rs) — broaden the bug: built-in function .name own-property returns undefined (function foo(){}; foo.name === undefined) #2060 typed-array-only collapse exemption. The feat(runtime): .constructor on Date/Array/Object instances #973 builtin-ident reroute turned Array.prototype into globalThis.prototype (which doesn't exist; the prototype object lives on the constructor closure's dynamic-prop side table). Typed arrays already exempted themselves so the per-kind proto with accessor descriptors stayed reachable; the same logic applies to every built-in proto, so the exemption now fires unconditionally when the outer member is .prototype.

  2. Runtime (global_this.rs) — extend populate_builtin_prototype_methods to install named callable closures for every well-known method on Array/Object/Function/String/Number/Boolean/Date/RegExp/Promise/Map/Set/WeakMap/WeakSet/Error*/typed arrays. Each closure is backed by the shared noop_thunk so typeof === "function", .name reads the method name, and .length reads the spec arity — covering Test262's verifyProperty / assert.throws introspection paths.

    Array.prototype.slice and Object.prototype.toString keep their dedicated thunks (ramda's curry/variadic helpers depend on Array.prototype.slice.call(args, ...) returning a real sliced array even via indirection, and ramda's _isArguments.js IIFE calls Object.prototype.toString.call(arguments) at module-init time).

    The hot-path forms are unaffected: arr.map(fn) (codegen NativeMethodCall) and Array.prototype.map.call(arr, fn) (HIR rewrite via try_builtin_prototype_method_apply_call) bypass the closure entirely. The reified noop closures only get invoked through indirect calls (const m = Array.prototype.map; m.call(arr, fn)), which is rare in real code but now returns undefined instead of throwing TypeError: undefined is not a function.

Validation

  • New test-files/test_issue_2142_builtin_proto_method_values.ts is byte-identical to node --experimental-strip-types.
  • Regression sweep clean on test_gap_array_methods / test_gap_date_methods / test_gap_string_methods / test_gap_object_methods / test_gap_typed_arrays / test_gap_arguments_in_object_literal_method / test_gap_async_advanced / test_gap_closures.
  • cargo test -p perry-hir green (111/111).
  • cargo fmt --all -- --check clean.

Test plan

  • Issue repro byte-identical to Node
  • Existing gap-suite passes (Array / Date / String / Object / TypedArray)
  • Array.prototype.slice.call(arr, ...) still returns a real sliced array (no regression on ramda path)
  • Object.prototype.toString.call(arr) still returns "[object Array]" (no regression)

… property values (#2142)

Extends #2058/#2077 to the remaining built-in prototypes
(`Array`/`Date`/`RegExp`/`String`/`Promise`/`Map`/`Set`/`Error`/typed
arrays/etc.). Before this change, the typeof-fold made `typeof
Array.prototype.map === "function"` pass at AST level, but any
INDIRECT read — storing into a local, an array literal, or otherwise
hiding the value behind a variable — returned `undefined`. The issue's
repro (`for ([n,v] of [[ ... , Array.prototype.map], ...])`) hit
exactly that gap.

Two coordinated changes:

1. HIR (`expr_member.rs`) — broaden the #2060 typed-array-only
   collapse exemption. The #973 builtin-ident reroute collapsed
   `<Ctor>.prototype` to `globalThis.prototype` (which is undefined,
   not the constructor's real prototype). Typed arrays already
   exempted themselves so the per-kind proto with the `length` /
   `byteLength` / `byteOffset` / `buffer` accessor descriptors stayed
   reachable; the same logic applies to every built-in proto, so the
   exemption now fires unconditionally when the outer member is
   `.prototype`.

2. Runtime (`global_this.rs`) — extend `populate_builtin_prototype_methods`
   to install named callable closures for every well-known method on
   `Array`/`Object`/`Function`/`String`/`Number`/`Boolean`/`Date`/
   `RegExp`/`Promise`/`Map`/`Set`/`WeakMap`/`WeakSet`/`Error*`/typed
   arrays. Each closure is backed by the shared `noop_thunk` so
   `typeof === "function"`, `.name` reads the method name, and
   `.length` reads the spec arity — covering Test262's `verifyProperty`
   / `assert.throws` introspection paths.

   `Array.prototype.slice` and `Object.prototype.toString` keep their
   dedicated thunks (ramda's curry/variadic helpers depend on
   `Array.prototype.slice.call(args,…)` returning a real sliced array
   even via indirection, and ramda's `_isArguments.js` IIFE calls
   `Object.prototype.toString.call(arguments)` at module-init time).
   The hot-path calls — `arr.map(fn)` (codegen NativeMethodCall) and
   `Array.prototype.map.call(arr, fn)` (HIR rewrite via
   `try_builtin_prototype_method_apply_call`) — are unaffected.

Adds `test-files/test_issue_2142_builtin_proto_method_values.ts`,
byte-identical to `node --experimental-strip-types`.

Per #2142, this is the single biggest remaining cascade in Test262's
`built-ins/{Object,Array,TypedArray,Date,RegExp,Function}` categories
after #2058/#2059/#2060/#2063 — the `(no output)` / `reading 'name'` /
`reading 'constructor'` cascades all collapse on the same prototype-
method-value gap.
@proggeramlug proggeramlug merged commit 6256176 into main May 28, 2026
10 checks passed
@proggeramlug proggeramlug deleted the fix-2142-builtin-proto-values branch May 28, 2026 10:34
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.

bug: built-in prototype methods on Array/Date/RegExp/String/Promise/TypedArray undefined as property values (extend #2058 to all built-in prototypes)

1 participant