Skip to content

[JSC] Implement Promise.allKeyed and Promise.allSettledKeyed behind usePromiseAllKeyed - #359

Open
robobun wants to merge 2 commits into
mainfrom
farm/bb8c6554/promise-allKeyed
Open

[JSC] Implement Promise.allKeyed and Promise.allSettledKeyed behind usePromiseAllKeyed#359
robobun wants to merge 2 commits into
mainfrom
farm/bb8c6554/promise-allKeyed

Conversation

@robobun

@robobun robobun commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Implements the Stage 3 await-dictionary proposal (Promise.allKeyed / Promise.allSettledKeyed) as JS builtins on the Promise constructor, gated by a new usePromiseAllKeyed option (default off).

Semantics

Both methods:

  1. NewPromiseCapability(this) then GetPromiseResolve(this) (rejecting on failure).
  2. Reject with TypeError if the argument is not an Object.
  3. Enumerate own enumerable keys via a single [[OwnPropertyKeys]] call (strings then symbols), skipping non-enumerable and inherited properties.
  4. For each key, Call(promiseResolve, C, value) then Invoke(nextPromise, "then", ...).
  5. When the counter reaches zero, resolve with a null-prototype object whose own data properties mirror the enumerated keys in order (CreateKeyedPromiseCombinatorResultObject).

allSettledKeyed wraps each settlement as {status: "fulfilled", value} / {status: "rejected", reason} (Object.prototype) and never rejects for individual entries.

Implementation

JS builtins in PromiseConstructor.js, following the Promise.try pattern. Registered conditionally in JSPromiseConstructor::finishCreation so the methods are absent when the flag is off.

To get spec-correct [[OwnPropertyKeys]] behaviour (one Proxy ownKeys trap call rather than two), a new @Object.@ownKeys private slot is added that wraps ownPropertyKeys(..., StringsAndSymbols, Include), the same helper Reflect.ownKeys uses.

Resolve/reject element closures use the (0, arrow) form so their .name is "" per CreateBuiltinFunction(..., 1, "", ...).

Prior art

  • SpiderMonkey: implemented in C++ (js/src/builtin/Promise.cpp, bugs 2003970 / 2003971), Nightly-only behind a runtime pref, off by default.
  • V8: not implemented; test262.status skips every allKeyed / allSettledKeyed test.
  • Upstream WebKit: test262 tests imported (WebKit/WebKit#63980) with the await-dictionary feature skipped; no implementation.
  • core-js: polyfills shipped at esnext.promise.all-keyed / esnext.promise.all-settled-keyed via proposals/await-dictionary.

Testing

--usePromiseAllKeyed=1
  test262 built-ins/Promise/allKeyed/**          45/45 pass
  test262 built-ins/Promise/allSettledKeyed/**   44/44 pass
  JSTests/stress/promise-allKeyed.js             pass

default (flag off)
  typeof Promise.allKeyed === "undefined"
  typeof Promise.allSettledKeyed === "undefined"

JSTests/test262/config.yaml now maps the await-dictionary feature flag to usePromiseAllKeyed instead of skipping it.

JSTests/test262/test/built-ins/Promise/allSettledKeyed/result-property-descriptors.js is synced to the current upstream version, which saves the entry objects before verifyProperty deletes them (tc39/test262#5084).

Files

  • Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml: new PromiseAllKeyedEnabled preference with jscOptionName: usePromiseAllKeyed.
  • Source/JavaScriptCore/builtins/PromiseConstructor.js: allKeyed / allSettledKeyed builtins.
  • Source/JavaScriptCore/runtime/JSPromiseConstructor.cpp: conditional registration.
  • Source/JavaScriptCore/builtins/BuiltinNames.h, Source/JavaScriptCore/runtime/ObjectConstructor.cpp: @Object.@ownKeys private slot.
  • JSTests/stress/promise-allKeyed.js: stress coverage.
  • JSTests/test262/config.yaml: feature mapping.

…sePromiseAllKeyed

Adds the Stage 3 await-dictionary proposal (tc39/proposal-await-dictionary)
as JS builtins on the Promise constructor, gated by the new
usePromiseAllKeyed option (default off).

Both methods take an object, enumerate its own enumerable keys via
[[OwnPropertyKeys]] (strings then symbols, one trap call), resolve each
value through C.resolve, and resolve the capability with a null-prototype
object keyed in enumeration order. allSettledKeyed wraps each settlement
as {status, value}/{status, reason}.

To get spec-correct [[OwnPropertyKeys]] semantics from a JS builtin, a
new @object.@OwnKeys private slot is added that calls
ownPropertyKeys(StringsAndSymbols, Include), matching Reflect.ownKeys.

test262: maps the await-dictionary feature to usePromiseAllKeyed. One
vendored test (allSettledKeyed/result-property-descriptors.js) is synced
to the upstream fix for tc39/test262#5084. 89/89 tests pass.

JSTests/stress/promise-allKeyed.js covers the basics plus symbol keys,
non-enumerable and inherited skipping, ordering, rejections, and
C.resolve validation.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 5 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 122eced1-fcbf-4c7f-92c1-c1ac93f77254

📥 Commits

Reviewing files that changed from the base of the PR and between 9839239 and 0bea00d.

📒 Files selected for processing (8)
  • JSTests/stress/promise-allKeyed.js
  • JSTests/test262/config.yaml
  • JSTests/test262/test/built-ins/Promise/allSettledKeyed/result-property-descriptors.js
  • Source/JavaScriptCore/builtins/BuiltinNames.h
  • Source/JavaScriptCore/builtins/PromiseConstructor.js
  • Source/JavaScriptCore/runtime/JSPromiseConstructor.cpp
  • Source/JavaScriptCore/runtime/ObjectConstructor.cpp
  • Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any bugs, but this is a full implementation of a new Stage 3 TC39 proposal touching JSC builtins, the Promise/Object constructors, and a new private intrinsic slot — worth a human look even though it's flag-gated.

What was reviewed:

  • allKeyed/allSettledKeyed builtins: remaining-elements counter, alreadyCalled guards, null-prototype result construction, error paths route to promiseCapability.reject.
  • @Object.@ownKeys private slot wiring mirrors the existing @getOwnPropertyNames/@getOwnPropertySymbols pattern and reuses ownPropertyKeys(..., StringsAndSymbols, Include).
  • usePromiseAllKeyed option is generated from UnifiedWebPreferences.yaml the same way usePromiseIsPromise is; methods are absent when the flag is off.
Extended reasoning...

Overview

This PR implements the Stage 3 await-dictionary proposal (Promise.allKeyed / Promise.allSettledKeyed) as JS builtins in PromiseConstructor.js, registered conditionally in JSPromiseConstructor::finishCreation behind a new usePromiseAllKeyed option (default off). Supporting changes: a new @Object.@ownKeys private slot in ObjectConstructor.cpp + BuiltinNames.h (so the builtin can do a single [[OwnPropertyKeys]] call), a new PromiseAllKeyedEnabled preference in UnifiedWebPreferences.yaml that generates the JSC option, a test262/config.yaml mapping, a test262 test sync, and a new stress test.

Security risks

None apparent. The new @ownKeys is a private-name slot (unreachable from user JS), and objectConstructorOwnKeys is a thin wrapper around the same ownPropertyKeys helper Reflect.ownKeys already uses. The builtins use @-prefixed intrinsics for internal operations and only call user-observable .resolve/.then where the spec requires it. Everything is off by default.

Level of scrutiny

Moderate-to-high. While flag-gated and default-off, this is ~160 lines of new spec-implementing builtin logic in the JSC runtime — the first engine implementation of this proposal per the PR description. Spec conformance, Proxy trap ordering, and the remainingElementsCount bookkeeping are the kind of details a JSC-familiar reviewer should sign off on. It also adds a new private intrinsic surface (@ownKeys) that will presumably be maintained going forward.

Other factors

  • The implementation closely follows existing patterns (Promise.try for registration, the old JS-builtin Promise.all/allSettled shape for the counter/closure logic), which lowers risk.
  • 89/89 test262 tests reportedly pass and the stress test covers ordering, symbols, non-enumerables, inherited props, non-object args, and non-constructor this.
  • I confirmed Options::usePromiseAllKeyed() is generated from UnifiedWebPreferences.yaml via JSCWebPreferenceOptions.h.erb, matching the adjacent usePromiseIsPromise.
  • No prior human or bot review comments to address.

Given the scope (new TC39 feature, core runtime files, first-of-kind implementation), this exceeds the bar for auto-approval even with no bugs found.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Preview Builds

Commit Release Date
0bea00d0 autobuild-preview-pr-359-0bea00d0 2026-07-27 13:36:18 UTC
a9d2bf1b autobuild-preview-pr-359-a9d2bf1b 2026-07-27 12:01:25 UTC

…Keyed

Asserts that [[OwnPropertyKeys]] is called exactly once on a Proxy input
and that per-key [[GetOwnProperty]]/[[Get]] fire in the spec order (all
own keys for gOPD, only enumerable keys for Get). A refactor to
@getOwnPropertyNames + @getOwnPropertySymbols would fail the
ownKeysCalls === 1 check. Mirrors JSTests/stress/object-spread.js.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any bugs, but this is a new Stage 3 feature implementation touching core JSC runtime/builtins, so it warrants a human look.

What was reviewed:

  • Builtin structure (remainingElementsCount bookkeeping, try/catch → reject, null-prototype result via @Object.@create(null)) mirrors the existing promiseAllSlow/promiseAllSettledSlow shape.
  • @Object.@ownKeys private slot wraps the same ownPropertyKeys(..., StringsAndSymbols, Include) path as Reflect.ownKeys, and is only reachable via the private name.
  • Flag gating follows the usePromiseIsPromise pattern; Options::usePromiseAllKeyed() is generated from UnifiedWebPreferences.yaml via jscOptionName, default off.
Extended reasoning...

Overview

This PR implements the Stage 3 await-dictionary proposal (Promise.allKeyed / Promise.allSettledKeyed) as JS builtins in PromiseConstructor.js, gated behind a new usePromiseAllKeyed option (default off). Supporting changes: a new @Object.@ownKeys private slot in ObjectConstructor.cpp + BuiltinNames.h (so the builtin can perform a single [[OwnPropertyKeys]] call), conditional registration in JSPromiseConstructor::finishCreation, a new PromiseAllKeyedEnabled preference in UnifiedWebPreferences.yaml, a stress test, and a test262 config/test sync. Roughly 400 lines of diff across 8 files.

Security risks

Low. The new objectConstructorOwnKeys host function is registered only under a private name (not user-reachable) and delegates to the same ownPropertyKeys helper already used by Reflect.ownKeys / Object.getOwnPropertyNames. The builtins use @-prefixed intrinsics (@Object.@create, @putByValDirect, @isObject, etc.) so they aren't affected by prototype pollution. The feature is off by default.

Level of scrutiny

Medium-high. Even though it's flag-gated, this is a fresh spec implementation in the engine runtime — the kind of change where spec-step ordering, observable Proxy trap counts, resolve-element function .name/.length, and subclassing edge cases matter and are worth a human eye. The author reports 89/89 test262 passes and includes a Proxy-trap-count stress test, which is a good signal, but this isn't a mechanical or config-only change.

Other factors

  • The implementation approach (JS builtins with closures vs. the C++ JSPromiseCombinatorsContext fast path used for Promise.all/allSettled) is a design choice a maintainer may want to weigh in on.
  • The allKeyed resolve element uses a plain arrow (name "newResolveElement" vs. "") while allSettledKeyed uses the (0, arrow) form; the PR description notes test262 passes, so this may be fine, but it's the sort of nit a human reviewer might catch.
  • No CODEOWNERS file in the repo; no prior human review comments to address.

Given the scope and that it touches core JSC builtins/runtime, deferring to a human reviewer.

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.

2 participants