Skip to content

fix(runtime): unify [[OwnPropertyKeys]] enumerators into one canonical op (#336)#442

Merged
dowdiness merged 1 commit into
mainfrom
fix/unify-ownpropertykeys-336
Jun 24, 2026
Merged

fix(runtime): unify [[OwnPropertyKeys]] enumerators into one canonical op (#336)#442
dowdiness merged 1 commit into
mainfrom
fix/unify-ownpropertykeys-336

Conversation

@dowdiness

Copy link
Copy Markdown
Owner

Closes #336.

What

Collapses the three divergent runtime own-key enumerators into a single canonical Interpreter::own_property_keys implementing ES §10.1.11 OrdinaryOwnPropertyKeys plus the Array §10.4.2.1 / String / Module / TypedArray §10.4.5 / Proxy §10.5.11 exotic variants. Every consumer now routes through it:

  • Object.getOwnPropertyNames / getOwnPropertySymbols (filter the canonical list by key type)
  • Object.getOwnPropertyDescriptors / Object.defineProperties (full canonical list)
  • Reflect.ownKeys (Object/Array arm)
  • Proxy ownKeys target-key enumeration (no-trap path + invariant validation)

Deleted object_own_property_keys_snapshot and collect_target_own_keys (now identical to the canonical op); net −1 public symbol.

Behavior changes (all spec-correctness fixes)

  1. Array holes omitted everywhere (§10.4.2.1). getOwnPropertyNames and Reflect.ownKeys previously emitted hole indices.
  2. Reflect.ownKeys / Proxy ownKeys on arrays now include named string keys and symbol keys — previously only 0..length + "length".
  3. TypedArray integer indices now surface for getOwnPropertyDescriptors / defineProperties — previously enumerated as an ordinary bag, missing the buffer-backed indices.

The stale hidden-internal-slot divergence noted in #336 no longer applies: engine [[…]] slots moved to PropertyBag.internal_slots (#337), so they are not in bag.properties; is_hidden_internal_property_key is gone.

Verification

  • moon check clean; moon info/moon fmt applied. .mbti diff = exactly the symbol changes above.
  • Unit tests: interpreter 1217 / runtime 92 / stdlib 42 — all pass (the pinned stage8 ownkeys … hole handling test updated to the spec-correct expectation; 2 new tests added).
  • test262 symmetric failing-set diff vs main (per-mode, both strict & non-strict) across built-ins/{Reflect/ownKeys, Proxy/ownKeys, Object/getOwnProperty{Names,Symbols,Descriptors}, Object/defineProperties, TypedArrayConstructors/internals/OwnPropertyKeys, Object/assign, keys, seal, freeze}:
    • +4 fixed (2× Reflect/ownKeys/return-non-enumerable-keys, 2× Object/getOwnPropertyNames/order-after-define-property — both modes each)
    • 0 regressions across every filter.

Out of scope (pre-existing, documented in the op + tracked as follow-ups)

  • Array index keys ≥ 2^31 (bag-stored) emit after "length" rather than merged into the ascending integer run (Reflect/ownKeys/return-on-corresponding-order-large-index still fails — same on main).
  • Proxy ownKeys invariant validation treats all Symbol keys as configurable.
  • Reflect.ownKeys still throws on Map/Set/Promise (its type guard, unchanged here); now trivially widenable since the canonical op handles them.

🤖 Generated with Claude Code

…l op (#336)

Collapse the three divergent runtime own-key enumerators
(object_own_property_keys_snapshot, own_string_property_names/
own_symbol_properties, and collect_target_own_keys) into a single
canonical Interpreter::own_property_keys implementing ES 10.1.11
OrdinaryOwnPropertyKeys plus the Array/String/Module/TypedArray/Proxy
exotic variants. Every consumer (getOwnPropertyNames/Symbols/
Descriptors, defineProperties, Reflect.ownKeys, Proxy ownKeys target
enumeration) now routes through it.

Behavior changes (all spec-correctness fixes, ES 10.4.2.1 / 10.1.11):
- Array holes are omitted everywhere (getOwnPropertyNames and
  Reflect.ownKeys previously emitted hole indices).
- Reflect.ownKeys / Proxy ownKeys on arrays now include named string
  keys and symbol keys (previously only 0..length + "length").
- TypedArray integer indices now surface for getOwnPropertyDescriptors
  / defineProperties (previously enumerated as an ordinary bag, missing
  the buffer-backed indices).

The stale hidden-internal-slot divergence noted in #336 no longer
applies: engine [[...]] slots moved to PropertyBag.internal_slots
(#337), so they are not in bag.properties.

Known pre-existing limitations left out of scope (documented in the op):
array index keys beyond Int range emit after "length"; proxy ownKeys
invariant validation treats all symbols as configurable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@dowdiness, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 45 minutes and 17 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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 credits.

🚦 How do rate 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 see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1ca673b9-978b-4a0b-bafc-8e8f9afa14bf

📥 Commits

Reviewing files that changed from the base of the PR and between 2bfb17f and 64e82f2.

📒 Files selected for processing (6)
  • interpreter/interpreter_test.mbt
  • interpreter/runtime/own_property_keys.mbt
  • interpreter/runtime/pkg.generated.mbti
  • interpreter/runtime/proxy_helpers.mbt
  • interpreter/stdlib/builtins_object_descriptors.mbt
  • interpreter/stdlib/builtins_reflect.mbt
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/unify-ownpropertykeys-336

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64e82f2380

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@runtime.proxy_own_property_keys(interp, proxy_data)
Object(_) | Value::Array(_) =>
@runtime.make_array(@runtime.collect_target_own_keys(target, interp))
@runtime.make_array(interp.own_property_keys(target))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include descriptor-only own keys in Reflect.ownKeys

On ordinary targets this now routes through Interpreter::own_property_keys, but that helper's ordinary-object path only walks bag.properties. The removed collect_target_own_keys also appended bag.descriptors entries that have no value entry, and ordinary_get_own_property still reports those descriptor-only entries as own properties. When runtime/native code constructs such a target, for example via make_object with a descriptor map, Reflect.ownKeys drops the key and proxy ownKeys invariant validation can miss required non-configurable keys. Please merge descriptor-only string keys into the canonical list before using it here.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Run: https://github.com/dowdiness/js_engine/actions/runs/28098923401

startup/tiny_program is the PR #153 / issue #141 guardrail for built-in realm-stamping startup cost.

Stage summary

stage benchmarks total mean slowest benchmark slowest mean noisy rows
startup 3 2.565 ms startup/tiny_program 1.294 ms 0
frontend 7 0.933 ms pipeline/parse_heavy 0.514 ms 2
execution 26 15209.669 ms exec/fibonacci_30 13753.471 ms 2

Focused bytecode base-vs-head comparison

Base-vs-head deltas are reporting-only. Negative delta and PR/base < 1.00x mean the PR is faster; interpret high-CV or noisy rows cautiously.

benchmark stage base mean PR mean delta PR/base base CV PR CV noisy
baseline/bytecode/closure_factory execution 13.556 ms 13.269 ms -2.1% 0.98x 9.5% 7.0% no
pipeline/bytecode/evaluate execution 8.942 ms 8.623 ms -3.6% 0.96x 3.0% 2.4% no
isolate/bytecode/call_frame execution 7.428 ms 7.591 ms +2.2% 1.02x 0.9% 5.0% no
isolate/bytecode/runtime_helpers execution 10.896 ms 11.102 ms +1.9% 1.02x 0.5% 1.0% no
isolate/bytecode/local_access execution 37.920 ms 38.275 ms +0.9% 1.01x 2.9% 4.4% no
isolate/bytecode/env_access execution 36.774 ms 37.688 ms +2.5% 1.02x 2.5% 1.8% no
isolate/bytecode/captured_access execution 38.257 ms 37.301 ms -2.5% 0.98x 3.4% 1.5% no
isolate/bytecode/dispatch_stack execution 22.331 ms 22.913 ms +2.6% 1.03x 0.5% 3.0% no

Base-vs-head comparison

benchmark stage base mean PR mean delta PR/base base CV PR CV noisy
startup/tiny_program startup 1.270 ms 1.294 ms +1.9% 1.02x 7.2% 3.8% no
lexer/small frontend 0.034 ms 0.035 ms +1.8% 1.02x 26.1% 24.1% base, PR
lexer/large frontend 0.298 ms 0.301 ms +1.2% 1.01x 0.7% 0.7% no
exec/fibonacci_30 execution 13707.442 ms 13753.471 ms +0.3% 1.00x 0.6% 0.7% no
exec/property_chain execution 15.497 ms 15.342 ms -1.0% 0.99x 8.5% 11.5% no
startup/phase/parse_tiny frontend 0.002 ms 0.002 ms -0.5% 0.99x 0.6% 0.4% no
startup/phase/new_interpreter startup 1.247 ms 1.271 ms +1.9% 1.02x 14.3% 12.0% no
startup/phase/execute_preparsed_tiny execution 0.001 ms 0.001 ms +3.6% 1.04x 0.8% 0.8% no
startup/phase/event_loop_drain_empty startup 0.000 ms 0.000 ms +1.9% 1.02x 0.9% 1.1% no
startup/phase/result_stringify_output execution 0.000 ms 0.000 ms -1.2% 0.99x 0.5% 0.4% no
exec/array_map_filter execution 19.236 ms 20.329 ms +5.7% 1.06x 18.2% 20.3% base, PR
exec/closure_factory execution 29.405 ms 29.419 ms +0.0% 1.00x 6.1% 5.9% no
baseline/closure_legacy/closure_factory execution 27.650 ms 27.366 ms -1.0% 0.99x 9.6% 7.6% no
baseline/bytecode/closure_factory execution 13.556 ms 13.269 ms -2.1% 0.98x 9.5% 7.0% no
isolate/bytecode/dispatch_stack execution 22.331 ms 22.913 ms +2.6% 1.03x 0.5% 3.0% no
isolate/bytecode/local_access execution 37.920 ms 38.275 ms +0.9% 1.01x 2.9% 4.4% no
isolate/bytecode/env_access execution 36.774 ms 37.688 ms +2.5% 1.02x 2.5% 1.8% no
isolate/bytecode/captured_access execution 38.257 ms 37.301 ms -2.5% 0.98x 3.4% 1.5% no
isolate/bytecode/call_frame execution 7.428 ms 7.591 ms +2.2% 1.02x 0.9% 5.0% no
isolate/bytecode/runtime_helpers execution 10.896 ms 11.102 ms +1.9% 1.02x 0.5% 1.0% no
isolate/bytecode/property_get execution 43.083 ms 44.031 ms +2.2% 1.02x 1.5% 1.4% no
isolate/bytecode/property_set execution 39.688 ms 39.914 ms +0.6% 1.01x 0.5% 0.7% no
isolate/bytecode/method_call execution 8.524 ms 8.541 ms +0.2% 1.00x 1.1% 0.7% no
isolate/bytecode/object_literal execution 13.575 ms 13.660 ms +0.6% 1.01x 1.0% 1.0% no
isolate/bytecode/array_literal execution 14.872 ms 14.745 ms -0.9% 0.99x 2.6% 1.2% no
exec/for_of execution 5.970 ms 5.955 ms -0.2% 1.00x 13.2% 11.4% no
exec/arithmetic_loop execution 1095.683 ms 1000.270 ms -8.7% 0.91x 0.3% 0.5% no
exec/object_construction execution 7.870 ms 7.419 ms -5.7% 0.94x 10.1% 7.0% no
exec/string_ops execution 2.099 ms 1.977 ms -5.8% 0.94x 21.5% 25.3% base, PR
pipeline/exec/lex frontend 0.030 ms 0.031 ms +2.7% 1.03x 0.8% 0.5% no
pipeline/exec/parse frontend 0.027 ms 0.027 ms +0.4% 1.00x 2.9% 3.9% no
pipeline/exec/evaluate execution 26.381 ms 25.479 ms -3.4% 0.97x 12.2% 1.0% no
pipeline/closure_legacy/evaluate execution 25.363 ms 24.988 ms -1.5% 0.99x 5.5% 4.4% no
pipeline/bytecode/compile frontend 0.022 ms 0.022 ms +0.1% 1.00x 22.7% 30.8% base, PR
pipeline/bytecode/evaluate execution 8.942 ms 8.623 ms -3.6% 0.96x 3.0% 2.4% no
pipeline/parse_heavy frontend 0.519 ms 0.514 ms -1.0% 0.99x 5.9% 7.0% no

Mean-time chart (log scale)

benchmark stage mean chart
startup/tiny_program startup 1.294 ms ##
lexer/small frontend 0.035 ms ⚠ #
lexer/large frontend 0.301 ms #
exec/fibonacci_30 execution 13753.471 ms ##############################
exec/property_chain execution 15.342 ms ########
startup/phase/parse_tiny frontend 0.002 ms #
startup/phase/new_interpreter startup 1.271 ms ##
startup/phase/execute_preparsed_tiny execution 0.001 ms #
startup/phase/event_loop_drain_empty startup 0.000 ms #
startup/phase/result_stringify_output execution 0.000 ms #
exec/array_map_filter execution 20.329 ms ⚠ #########
exec/closure_factory execution 29.419 ms ##########
baseline/closure_legacy/closure_factory execution 27.366 ms ##########
baseline/bytecode/closure_factory execution 13.269 ms ########
isolate/bytecode/dispatch_stack execution 22.913 ms #########
isolate/bytecode/local_access execution 38.275 ms ###########
isolate/bytecode/env_access execution 37.688 ms ###########
isolate/bytecode/captured_access execution 37.301 ms ###########
isolate/bytecode/call_frame execution 7.591 ms ######
isolate/bytecode/runtime_helpers execution 11.102 ms #######
isolate/bytecode/property_get execution 44.031 ms ###########
isolate/bytecode/property_set execution 39.914 ms ###########
isolate/bytecode/method_call execution 8.541 ms #######
isolate/bytecode/object_literal execution 13.660 ms ########
isolate/bytecode/array_literal execution 14.745 ms ########
exec/for_of execution 5.955 ms ######
exec/arithmetic_loop execution 1000.270 ms #####################
exec/object_construction execution 7.419 ms ######
exec/string_ops execution 1.977 ms ⚠ ###
pipeline/exec/lex frontend 0.031 ms #
pipeline/exec/parse frontend 0.027 ms #
pipeline/exec/evaluate execution 25.479 ms ##########
pipeline/closure_legacy/evaluate execution 24.988 ms ##########
pipeline/bytecode/compile frontend 0.022 ms ⚠ #
pipeline/bytecode/evaluate execution 8.623 ms #######
pipeline/parse_heavy frontend 0.514 ms #

Closure-conversion comparison

  • unavailable

@dowdiness dowdiness merged commit 6f65fd3 into main Jun 24, 2026
15 checks passed
@dowdiness dowdiness deleted the fix/unify-ownpropertykeys-336 branch June 24, 2026 12:56
dowdiness added a commit that referenced this pull request Jun 24, 2026
…y symbol keys in proxy [[OwnPropertyKeys]] (#445)

Two #336 follow-ups left out of scope by the canonical-enumerator
unification (PR #442):

- #444: Reflect.ownKeys (§28.1.11) threw TypeError on Map/Set/Promise.
  The canonical Interpreter::own_property_keys already handles those
  variants; widen the dispatch arm to match instead of falling through
  to the non-object TypeError.

- #443: Proxy [[OwnPropertyKeys]] (§10.5.11) step 16 must classify every
  non-configurable own key of the target so the trap cannot hide one.
  The keys-split loop assumed all symbol keys were configurable; classify
  them via bag.symbol_descriptors, mirroring target_has_non_configurable_key
  for strings. Fixes the invariant check for symbol keys
  (built-ins/Object/getOwnPropertyNames/proxy-invariant-absent-not-configurable-symbol-key).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Unify runtime [[OwnPropertyKeys]] enumerators (fix hole/hidden-key/descriptor-only divergences)

1 participant