Skip to content

fix(url): route URLSearchParams subclass instances to their native backing#6738

Merged
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/urlsearchparams-subclass
Jul 21, 2026
Merged

fix(url): route URLSearchParams subclass instances to their native backing#6738
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/urlsearchparams-subclass

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Problem

A class X extends URLSearchParams instance — most notably Next.js's
ReadonlyURLSearchParams returned by useSearchParams() — lost its entire
inherited surface:

  • r.get(k) / r.has(k) threw TypeError: value is not a function
  • r.toString() returned "[object Object]"
  • r.size read undefined
  • for (const [k, v] of r) threw next is not a function

Root cause: Perry lowers the URLSearchParams method/property surface by the
receiver's static type (Expr::UrlSearchParams*), not as real
URLSearchParams.prototype methods. A subclass-typed receiver
(ReadonlyURLSearchParams) matches none of those static arms, so every call
fell through to generic class dispatch, which has no such member.

This surfaced as a server-side exception on logout in a real Next.js 16 app.

Fix

Attach a hidden native URLSearchParams backing to the subclass instance at
super() and delegate the inherited surface to it:

  1. Runtime backingjs_url_search_params_subclass_init creates a native
    URLSearchParams and stashes it under a hidden key on this;
    url_search_params_backing_of / resolve_search_params_receiver resolve it.
    The read/write runtime methods resolve the backing internally, so the
    subclass instance stays the observable object.
  2. super() codegen — emit the init from all three derived-construction
    paths: explicit super(init), implicit super(...args), and the
    no-constructor new path.
  3. Method dispatch — the existing #5961 dynamic-URLSearchParams block in
    js_native_call_method resolves the backing when the native shape probe
    misses, reusing url_search_params_dynamic_call (correct boolean / string /
    array boxing). It runs before the generic toString → "[object Object]"
    default. A codegen carve-out routes subclass method calls into
    js_native_call_method instead of the static class tower.
  4. .size, .toString(), iteration — new codegen/runtime carve-outs route
    these to the backing (.size property arm, toString defers the universal
    arm, js_get_iterator yields [key, value] from the backing).

A user override (Next's throwing append/delete/set/sort on
ReadonlyURLSearchParams) still resolves first via the static class tower, so
read-only semantics are preserved.

Validation

Byte-identical to node --experimental-strip-types across:

  • get / getAll / has(name[, value]) / set / append / delete /
    toString / forEach / sort
  • .size, entries() / keys() / values(), and default for...of
  • explicit and implicit super, and throwing read-only overrides

No regression to: native URLSearchParams (static lowering intact), Map/Set
subclasses, plain-class toString ([object Object]), user-defined
toString, or number.toString(radix). Runtime url/iterator/search_params
lib tests pass; cargo fmt --check clean.

Summary by CodeRabbit

  • Bug Fixes
    • Restored correct runtime behavior for classes extending URLSearchParams, including super()/new construction and native-backed state for derived variants such as ReadonlyURLSearchParams.
    • Fixed subclass handling for .size, method calls (including universal .toString()), and iterator behavior so it no longer falls back to generic/incorrect results.
    • Ensured all mutating URLSearchParams methods update the underlying entries consistently for subclasses.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 74d360dc-b370-4118-9f3a-a0f41a2fdea2

📥 Commits

Reviewing files that changed from the base of the PR and between 065e37a and 369465d.

📒 Files selected for processing (14)
  • CHANGELOG.md
  • CLAUDE.md
  • Cargo.toml
  • crates/perry-codegen/src/expr/property_get.rs
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/lower_call/console_promise.rs
  • crates/perry-codegen/src/lower_call/new.rs
  • crates/perry-codegen/src/lower_call/property_get/number_string.rs
  • crates/perry-codegen/src/runtime_decls/stdlib_ffi/web.rs
  • crates/perry-codegen/src/type_analysis.rs
  • crates/perry-codegen/src/type_analysis/strings.rs
  • crates/perry-runtime/src/object/native_call_method.rs
  • crates/perry-runtime/src/symbol/iterator.rs
  • crates/perry-runtime/src/url/search_params.rs

📝 Walkthrough

Walkthrough

URLSearchParams subclasses now receive hidden native backing storage during construction. Codegen routes inherited properties and methods through URLSearchParams runtime helpers, while runtime reads, mutations, dynamic dispatch, and iteration resolve the backing object.

Changes

URLSearchParams subclass support

Layer / File(s) Summary
Subclass detection and initialization wiring
crates/perry-codegen/src/type_analysis/strings.rs, crates/perry-codegen/src/expr/..., crates/perry-codegen/src/lower_call/..., crates/perry-codegen/src/runtime_decls/stdlib_ffi/web.rs, Cargo.toml, CLAUDE.md, CHANGELOG.md
Codegen detects transitive URLSearchParams subclasses, initializes native backing during new and super() paths, handles .size, routes inherited methods away from generic and number/string dispatch, and records the version update.
Native backing storage and mutations
crates/perry-runtime/src/url/search_params.rs
URLSearchParams operations resolve a hidden backing object for entry reads, size calculation, mutation, sorting, and subclass initialization.
Inherited method and iterator dispatch
crates/perry-runtime/src/object/native_call_method.rs, crates/perry-runtime/src/symbol/iterator.rs, crates/perry-runtime/src/url/search_params.rs
Dynamic method calls and iterators recognize subclass backing objects and operate on their native URLSearchParams entries.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant URLSearchParamsSubclass
  participant js_native_call_method
  participant try_url_search_params_dynamic_dispatch
  participant URLSearchParamsRuntime
  participant HiddenBacking
  Caller->>URLSearchParamsSubclass: invoke inherited operation
  URLSearchParamsSubclass->>js_native_call_method: dispatch method
  js_native_call_method->>try_url_search_params_dynamic_dispatch: pass receiver and arguments
  try_url_search_params_dynamic_dispatch->>URLSearchParamsRuntime: resolve subclass receiver
  URLSearchParamsRuntime->>HiddenBacking: read or mutate entries
  HiddenBacking-->>Caller: return operation result
Loading

Possibly related PRs

  • PerryTS/perry#6599: Both changes modify URLSearchParams dynamic-dispatch handling in js_native_call_method.

Suggested reviewers: thehypnoo, andrewtdiz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is informative, but it does not follow the required template sections like Summary, Changes, Related issue, and Test plan. Rewrite the PR description to match the template with Summary, Changes, Related issue, Test plan, Screenshots/output, and Checklist sections.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change to URLSearchParams subclass handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 force-pushed the fix/urlsearchparams-subclass branch from 7ae88ca to c671c7a Compare July 21, 2026 11:02

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/perry-runtime/src/url/search_params.rs (1)

330-331: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consistent, correct wiring to the backing resolver — see the standalone comment on lines 35-110 for the shared allocation/GC-safety concern behind resolve_search_params_receiver itself.

Each of these call sites (entries read, set/append/delete/delete2/size/sort) is correctly updated to route through resolve_search_params_receiver, and each is internally consistent (e.g. js_url_search_params_set resolves before writing _entries, matching the read side).

Also applies to: 653-657, 685-689, 712-716, 767-771, 784-784, 869-873

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/url/search_params.rs` around lines 330 - 331, Ensure
all URLSearchParams operations—entries reads, set, append, delete, delete2,
size, and sort—resolve the receiver through resolve_search_params_receiver
before accessing or mutating the backing _entries data, matching the shown call
site and preserving consistent subclass handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-codegen/src/expr/this_super_call.rs`:
- Around line 330-360: Remove the duplicate early URLSearchParams handling block
in the None arm, including its native initialization and field-initializer
calls, while preserving the later URLSearchParams branch and its
is_builtin_parent_name entry. Ensure the remaining branch continues to handle
URLSearchParams super() calls without changing behavior.

In `@crates/perry-runtime/src/url/search_params.rs`:
- Around line 35-51: Update resolve_search_params_receiver,
url_search_params_backing_of, and js_url_search_params_subclass_init to avoid
keeping raw ObjectHeader pointers live across allocating calls: use a long-lived
interned representation for URL_SEARCH_PARAMS_BACKING_KEY, and root the
receiver/backing values before js_string_from_bytes or
js_url_search_params_new_any, reloading them afterward before field access or
return.

---

Nitpick comments:
In `@crates/perry-runtime/src/url/search_params.rs`:
- Around line 330-331: Ensure all URLSearchParams operations—entries reads, set,
append, delete, delete2, size, and sort—resolve the receiver through
resolve_search_params_receiver before accessing or mutating the backing _entries
data, matching the shown call site and preserving consistent subclass handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c988f434-b5ad-410c-a340-4f31817a199d

📥 Commits

Reviewing files that changed from the base of the PR and between e5d819b and 7ae88ca.

📒 Files selected for processing (11)
  • crates/perry-codegen/src/expr/property_get.rs
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/lower_call/console_promise.rs
  • crates/perry-codegen/src/lower_call/new.rs
  • crates/perry-codegen/src/lower_call/property_get/number_string.rs
  • crates/perry-codegen/src/runtime_decls/stdlib_ffi/web.rs
  • crates/perry-codegen/src/type_analysis.rs
  • crates/perry-codegen/src/type_analysis/strings.rs
  • crates/perry-runtime/src/object/native_call_method.rs
  • crates/perry-runtime/src/symbol/iterator.rs
  • crates/perry-runtime/src/url/search_params.rs

Comment thread crates/perry-codegen/src/expr/this_super_call.rs
Comment thread crates/perry-runtime/src/url/search_params.rs
@proggeramlug
proggeramlug force-pushed the fix/urlsearchparams-subclass branch from c671c7a to 2a935b1 Compare July 21, 2026 11:24
@proggeramlug proggeramlug reopened this Jul 21, 2026
@proggeramlug
proggeramlug force-pushed the fix/urlsearchparams-subclass branch from 2a935b1 to 065e37a Compare July 21, 2026 11:38
…cking

`class X extends URLSearchParams` (Next.js's ReadonlyURLSearchParams) instances
lost their inherited surface: `.get()`/`.has()`/`.toString()`/iteration/`.size`
threw "value is not a function", returned "[object Object]", or read undefined,
because the URLSearchParams surface is type-directed lowering rather than real
prototype methods — a subclass-typed receiver matches no static arm.

Attach a hidden native backing at `super()` and delegate the inherited surface:

- runtime: `js_url_search_params_subclass_init` stashes a native URLSearchParams
  under a hidden key on the instance; `url_search_params_backing_of` +
  `resolve_search_params_receiver` resolve it, and the read/write runtime
  methods resolve the backing internally.
- super() codegen: emit the init from all three derived-construction paths
  (explicit `super(init)`, implicit `super(...args)`, no-constructor `new`).
- method dispatch: the existing PerryTS#5961 dynamic URLSearchParams block resolves the
  backing when the native shape probe misses, reusing
  `url_search_params_dynamic_call` (correct boolean/string/array boxing). This
  runs before the generic `toString` -> "[object Object]" default.
- `.size`, `.toString()`, and default `for...of` (Symbol.iterator) route to the
  backing via new codegen/runtime carve-outs. A user override (Next's throwing
  `append`/`delete`/`set`/`sort`) still resolves first via the static tower.

Validated byte-identical to `node --experimental-strip-types` across
get/getAll/has(name[,value])/set/append/delete/toString/forEach/sort, `.size`,
entries()/keys()/values()/for-of, explicit+implicit super, and throwing readonly
overrides; no regression to native URLSearchParams, Map/Set subclasses,
plain-class or user toString, or number.toString(radix).
@proggeramlug
proggeramlug force-pushed the fix/urlsearchparams-subclass branch from 065e37a to 369465d Compare July 21, 2026 13:10
@proggeramlug
proggeramlug merged commit 11865ab into PerryTS:main Jul 21, 2026
23 of 25 checks passed
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