Skip to content

fix(runtime): preserve declared receiver length semantics - #7862

Merged
proggeramlug merged 2 commits into
mainfrom
fix/7853-declared-array-length
Aug 11, 2026
Merged

fix(runtime): preserve declared receiver length semantics#7862
proggeramlug merged 2 commits into
mainfrom
fix/7853-declared-array-length

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Closes #7853.

What changed

  • add a property-semantic js_value_length_property_f64 sibling for guarded source-level .length reads
  • preserve undefined and non-numeric length values instead of collapsing them to zero
  • throw the ordinary catchable property-access TypeError for nullish receivers
  • keep array-internal js_value_length_f64 behavior unchanged
  • delegate fallback lookup through the normal dynamic property path so functions, typed arrays, native handles, accessors, inherited properties, and proxies keep their existing dispatch

Coverage

The parity regression passes real arrays, numbers, strings, numeric/string-valued array-like objects, functions, typed arrays, null, and undefined through an explicitly declared string[] receiver. Its output is byte-identical to Node.

Scope and performance

The inline Array/String layout path is unchanged; only its existing guarded slow arm calls the new helper. A quiet-M1-mini A/B used two compilers against one fixed runtime, seven alternating runs per cell: the honest-array length + loop was 0.21s vs 0.21s and length * was 0.22s vs 0.22s.

I also tested broadening the declared-number proof policy for every arithmetic use of .length; that changed the hot path and regressed those microbenchmarks substantially, so it is deliberately not part of this property-read fix. #7854 can remove its declared_only_array_locals exclusion once this fallback is available.

Tests

  • direct Node/Perry parity comparison for test_gap_7853_declared_array_length_runtime_value.ts
  • RUST_TEST_THREADS=1 cargo test -p perry-runtime --lib length_handle_band_tests (3 passed)
  • cargo test -p perry-codegen --lib (877 passed)
  • cargo fmt --all -- --check
  • bash scripts/check_file_size.sh

Summary by CodeRabbit

  • Bug Fixes

    • Fixed .length property reads to follow standard JavaScript behavior.
    • Missing or non-numeric length properties now remain undefined or retain their original value instead of becoming 0.
    • Accessing .length on null or undefined now throws a catchable TypeError.
    • Preserved expected behavior for arrays, strings, functions, typed arrays, and array-like objects.
  • Tests

    • Added regression coverage for declared array and string length access across supported value types.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cc127ce7-f631-40b3-a979-2b92dbef6d19

📥 Commits

Reviewing files that changed from the base of the PR and between 06ab783 and 1c3b7fc.

📒 Files selected for processing (7)
  • changelog.d/7862-declared-array-length-fallback.md
  • crates/perry-codegen/src/expr/property_get.rs
  • crates/perry-codegen/src/gc_call_effects.rs
  • crates/perry-codegen/src/runtime_decls/arrays.rs
  • crates/perry-runtime/src/value/dynamic_object.rs
  • crates/perry-runtime/src/value/mod.rs
  • test-files/test_gap_7853_declared_array_length_runtime_value.ts

📝 Walkthrough

Walkthrough

The change adds property-semantic .length handling for declared array, string, and named receivers. Runtime code now preserves missing and non-numeric values and throws catchable TypeError exceptions for nullish receivers. Code generation and regression tests use the new helper.

Changes

Length property semantics

Layer / File(s) Summary
Runtime length property helper
crates/perry-runtime/src/value/dynamic_object.rs, crates/perry-runtime/src/value/mod.rs, crates/perry-codegen/src/runtime_decls/arrays.rs
Adds and exports js_value_length_property_f64. The helper handles nullish values, strings, and generic property lookup without numeric coercion.
Codegen fallback integration
crates/perry-codegen/src/expr/property_get.rs
Routes TypedArray, string, and guarded array/string .length fallbacks through the new helper.
Audit and regression coverage
crates/perry-codegen/src/gc_call_effects.rs, test-files/test_gap_7853_declared_array_length_runtime_value.ts, changelog.d/7862-declared-array-length-fallback.md
Classifies the helper as a conservative getter path, tests runtime values and nullish receivers, and documents the corrected semantics.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • PerryTS/perry#6817: Both changes modify runtime .length handling, but this change addresses property-semantic fallback behavior.
  • PerryTS/perry#7430: Both changes update specialized property-read fallbacks for nullish receivers.
  • PerryTS/perry#7831: Both changes address code-generation assumptions about runtime values.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the runtime fix for declared receiver length semantics.
Description check ✅ Passed The description covers the change, related issue, scope, performance impact, and test commands, although it does not use the exact template headings.
Linked Issues check ✅ Passed The changes satisfy issue [#7853] by preserving property semantics, throwing for nullish receivers, and leaving internal array length behavior unchanged.
Out of Scope Changes check ✅ Passed The changelog, runtime, codegen, audit updates, and regression tests are all directly related to the declared-receiver length fix.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/7853-declared-array-length

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.

@proggeramlug
proggeramlug marked this pull request as ready for review August 11, 2026 15:33
@proggeramlug
proggeramlug merged commit c24e770 into main Aug 11, 2026
1 of 19 checks passed
@proggeramlug
proggeramlug deleted the fix/7853-declared-array-length branch August 11, 2026 16:50
proggeramlug added a commit that referenced this pull request Aug 11, 2026
…et a declared array type reach the guarded element read and the inline .length (#7890)

* perf(codegen): interp 0.780 -> 0.675 s, iso_miss 1.061 -> 0.967 s — let a declared array type reach the guarded element read and the inline .length

Two halves of one mechanism: what a program may do with an array type that came
from an annotation rather than from an initializer that proved an array.

A. `e.vals[i]` / `p.toks[p.pos]`. #7854 recovered a receiver's declared property
   type for a LOCAL (`const names = e.names`), never for the read used directly
   as a receiver — the HIR types a PropertyGet off a UNION receiver as `Any`, so
   `index_get.rs` routed those to `js_dyn_index_get`. The tier this unlocks,
   `lower_guarded_array_index_get`, re-checks GC_TYPE_ARRAY, forwarding,
   descriptors, the prototype latch and the bounds on the receiver itself, so a
   violated claim costs a branch and returns the same answer.

B. `.length` no longer refuses a declared-only array local. #7854 refused them
   because the arm's fallback was `js_value_length_f64`, which answered 0 where
   JS answers `undefined` and did not throw on a nullish receiver (#7853).
   #7862 replaced that fallback with `js_value_length_property_f64` and left the
   refusal standing. `declared_only_array_locals` and
   `refined_array_type_is_declared_only` are deleted with it;
   `declared_only_numeric_locals` (#7773) is untouched.

Quiet M1 mini, best-of-5, exit-checked: interp 0.7796 -> 0.6748 (-13.4%),
iso_miss 1.0607 -> 0.9670 (-8.8%). 17 of the 19 corpus programs compile
byte-identically and the two that differ are exactly the two with a type-alias
over an array; noise floor from those 17 is +-1.0%.

Claude-Session: https://claude.ai/code/session_012B8z92S82sCfqCrVqrFgS2

* test(gap): cover a declared-array PropertyGet used directly as an element-read receiver

Claude-Session: https://claude.ai/code/session_012B8z92S82sCfqCrVqrFgS2

---------

Co-authored-by: Ralph Küpper <ralph3@skelpo.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.

.length on a declared-array/Named receiver whose runtime value has no length returns 0, not undefined — and does not throw on nullish

1 participant