Skip to content

fix(runtime): match Node fs.readFile prototype - #8877

Closed
proggeramlug wants to merge 2 commits into
mainfrom
fix/release-r23-node-function-prototype
Closed

fix(runtime): match Node fs.readFile prototype#8877
proggeramlug wants to merge 2 commits into
mainfrom
fix/release-r23-node-function-prototype

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary:

Validation completed locally:

  • Runtime constructor metadata unit test.
  • Release-mode issue_5268 compiled fixture.
  • Focused Node/Perry issue_5268 parity test.
  • Rust formatting, diff check, and file-size check.

Summary by CodeRabbit

  • Bug Fixes

    • Callback-style native module functions such as fs.readFile now expose a real .prototype object, matching Node.js behavior.
    • Corrected native function classification for improved compatibility with JavaScript code that inspects function prototypes.
  • Documentation

    • Added a changelog entry describing the updated native module prototype behavior.

@coderabbitai

coderabbitai Bot commented Aug 27, 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: 5d411086-fa58-497f-b7c8-8a79b85b1055

📥 Commits

Reviewing files that changed from the base of the PR and between 3b45a5b and 12724f3.

📒 Files selected for processing (1)
  • changelog.d/8877-release-r23-node-function-prototype.md

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The runtime now classifies fs.readFile as a constructor. Regression tests verify that it exposes a non-null prototype object. The changelog documents the updated Node-compatible behavior.

Changes

Native Function Prototypes

Layer / File(s) Summary
Update native export classification
crates/perry-runtime/src/object/native_module/constructor_exports.rs
fs.readFile now uses constructor classification. The runtime test expects it to be a constructor.
Update prototype regression coverage
test-files/test_issue_5268_native_ctor_prototype_undefined.ts, crates/perry/tests/issue_5268_native_ctor_prototype_undefined.rs, changelog.d/8877-release-r23-node-function-prototype.md
The regression test checks for a non-null fs.readFile.prototype object. The changelog records the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 12724

This localized change restores Node-compatible fs.readFile prototype behavior and updates the matching tests and release note; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides a useful summary and lists validation steps, but it omits the required template sections for Changes, Related issue, Test plan details, and Checklist confirmations. Rewrite the description using the repository template. Add the Changes section, state the related issue or use "n/a", provide explicit test commands and checkbox results, and complete the Checklist.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: making the fs.readFile prototype match Node behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 3 files. (1 skipped: 1 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 3 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/release-r23-node-function-prototype

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 added a commit that referenced this pull request Aug 27, 2026
* perf(transform): inline safe cross-module function graphs

* perf(array): reuse resolved headers across indexed stores

* perf(array): split dynamic canonical read keys

* perf(method): guard synthetic-arguments direct calls

* perf(method): scalarize length-only arguments bundles

* perf(array): call captureless some callbacks directly

* perf(method): inline bounded tiny allocation kernels

* perf(inline): optimize functions inside candidate methods

* perf(for-of): preserve Map entry types in function bodies

* perf(array): trust validated rooted iterator headers

* perf(array): establish element shape proofs on demand

* perf(array): reuse dynamic all-pointer append proofs

* perf(property): inline dynamic collection size reads

* perf(compare): inline exact three-byte literal equality

* perf(descriptors): index descriptors by owner instead of scanning every entry

Three hot paths answered "what does THIS owner have?" by walking every
descriptor in the process and filtering on the owner address:

  * js_object_keys' array branch, twice (enumeration.rs) — a full
    property_descriptors walk per enumeration, just to decide whether a
    per-index enumerable check was needed;
  * accessor_descriptor_keys_for_obj, on the own-keys path;
  * transfer_descriptor_owner, on every ArrayHeader growth;
  * scan_descriptor_roots_mut, on EVERY GC cycle — so since the moving
    young-gen scavenge became default (#7019) this was a per-collection
    tax proportional to the whole program's descriptor count rather than
    to what actually moved.

Profiling `claude -p` put 46.6% of main-thread samples in
shapes/descriptors, with a HashMap Keys iteration the single hottest
self-time entry by 4x over anything else.

DescriptorTables now carries attr_keys_by_owner / accessor_keys_by_owner
mirroring the two (owner, key) maps, so each of those becomes a lookup.
The maps stay authoritative; the index is a mirror, and the tests assert
that invariant directly (index == what a full scan would return) across
install, redefine, delete, bulk-clear and owner transfer, because the
failure mode of a mirror is silent drift, not a crash.

Also fixes a pre-existing correctness bug the new tests caught:
transfer_descriptor_owner moved descriptors to the new address but never
carried the per-object Bloom summary. A freshly grown array has a null
meta, for which owner_may_have_descriptor_entries answers false
AUTHORITATIVELY — so after an array grew, Object.keys and
getOwnPropertyDescriptor silently lost every accessor it had. That was
equally true before this change: the gate sat in front of the old scan,
so the scan never ran for the new owner.

* changelog: add fragment for #8875

* ci: clear the lint gates for #8872

The `lint` job failed on four gates that the PR's own changes tripped:

- changelog: add the `changelog.d/8872-*` fragment for the crates/ changes.
- file size: `array/indexing.rs` reached 2,024 lines after the resolved-store
  work; move the transactional `js_array_numeric_range_add*` kernel (a block
  with no raw-handle or address-classification debt, so no per-module ratchet
  ceiling moves) into `array/numeric_range.rs`.
- local-binding-type audit: classify the synthetic `arguments.length` marker
  read in `property_get.rs::lower` (runtime-validated: the marker type exists
  only in direct-call-only clones whose caller materialized the count).
- GC store-site inventory: register `store_array_slot_resolved` as a
  chain-verified discharge helper for the three BARRIERED markers that now
  lean on it, mark its own resolved slot write, and pin the second `apush`
  codegen marker (the unconditional element store inside
  `emit_dynamic_pointer_push_store`, barriered by the same stem) with the
  self-test tree updated to match.

Every step of the lint job was replayed locally, including the raw-handle and
unrooted-local ratchets against the merge base d354443.

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

* style: cargo fmt (rustfmt import wrapping after the new re-export)

* fix(runtime): match Node fs readFile prototype

* chore: name r23 changelog for PR

---------

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
Co-authored-by: Ralph Küpper <ralph3@skelpo.com>
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Landed on main via the #8878 batch.

proggeramlug pushed a commit to proggeramlug/perry that referenced this pull request Aug 27, 2026
…-mutation

Second merge round after the PerryTS#8872/PerryTS#8875/PerryTS#8877 batch landed. Conflicts and
their resolutions:

- codegen/method.rs: keep this branch's guarded-falsy/index/pshape-arg clone
  handling and add main's `!arguments_length_clone` exclusions.
- expr/property_get.rs: keep both the Symbol-then-named-field IC dispatch
  (ours) and main's synthetic `arguments.length` fast path.
- property_get/generic_dispatch.rs: main's native Map/Set `size` split ahead
  of the object PIC, with this branch's `is_object_kind` naming.
- lower_call/method_override.rs: `direct_call_fn` (main, argument-length
  clone) is consulted first, then the pshape+index clone (ours); the two are
  mutually exclusive by construction.
- array/element_shape.rs: adopt main's demand-driven proofs (no eager
  `establish` on the first store) inside this branch's
  `note_element_store_with_bit` / `_resolved_flags` split; the now-unused
  `element_identity_of_bits` goes with it, and the renamed
  `pushes_do_not_create_an_unrequested_element_shape_proof` test replaces the
  eager-establishment one.
- array/header_gc_slots.rs + mod.rs: keep both resolved-head store helpers
  (`note_array_slot_resolved_flags` ours, `store_array_slot_resolved` main).
- array/push_pop.rs: `js_array_push_f64_resolved` now stores through main's
  `store_array_slot_resolved`.
- array/indexing.rs: the strict setter keeps this branch's dense fast path
  first, then main's resolved-head strict path; main moved the numeric-range
  helpers into `array/numeric_range.rs` (byte-identical bodies), so the
  in-file copies and their keepalive anchors are dropped; main's fused
  strict store in `js_array_set_index_or_string_strict` is ported into
  `indexing_keyed.rs`.
- expr/index_get_claim_tests.rs: union of imports/constants and both test
  sets (main's canonical-i32 split tier and this branch's `Any`-key tier
  are complementary arms).
- lower_call/property_get/dynamic_dispatch.rs grew past the 2,000-line gate;
  the tower-of-pshape routing moved to `dynamic_dispatch_tower.rs`.

Verified locally: fmt; perry-codegen and perry-runtime lib + test targets
build warning-free; both suites green; file-size, GC store-site, addr-class,
raw-handle, shape-descriptor census, binding and architecture audits pass.

Claude-Session: https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ
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.

1 participant