Skip to content

fix(url): String/template/concat of URL & URLSearchParams give href/query (Node parity)#5108

Merged
proggeramlug merged 3 commits into
mainfrom
fix/url-tostring-coercion
Jun 14, 2026
Merged

fix(url): String/template/concat of URL & URLSearchParams give href/query (Node parity)#5108
proggeramlug merged 3 commits into
mainfrom
fix/url-tostring-coercion

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Problem

ToString coercion of a WHATWG URL / URLSearchParams returned [object Object], diverging from explicit url.toString() (which already works):

                    # node v26.3.0          # perry (before)
url.toString()    → https://e.com/p?x=1   https://e.com/p?x=1   ✓
String(url)       → https://e.com/p?x=1   [object Object]       ✗
`${url}`          → https://e.com/p?x=1   [object Object]       ✗
"" + url          → https://e.com/p?x=1   [object Object]       ✗
String(usp)       → a=1&b=2               [object Object]       ✗
`${usp}`          → a=1&b=2               [object Object]       ✗

URL/URLSearchParams carry native toStrings (the href / the query string) that aren't discoverable as object fields, so the generic OrdinaryToPrimitive resolves the inherited Object.prototype.toString[object Object].

Fix

Two coercion entry points needed a URL/URLSearchParams arm, mirroring the existing Date special-cases:

  • js_jsvalue_to_string (backs String(x) / explicit coercion): detect URL/USP before OrdinaryToPrimitive and return the href / query string. The pointer is normalized to POINTER_TAG first since the operand can arrive as a raw heap pointer (upper-16 == 0).
  • to_primitive_default_for_add (backs + and template literals via js_dynamic_string_or_number_add): same arm before ordinary_to_primitive_number_for_add, which would otherwise return the inherited Object.prototype.toString's [object Object].

Reuses existing js_url_href_if_url / try_read_as_search_params detectors and js_url_search_params_to_string.

Verification (vs Node v26.3.0)

  • explicit / String() / template / concat of a URL → href; of a URLSearchParams → query string. ✓
  • No regression: custom toString/valueOf/plain-object/class coercion unchanged (CUSTOM/T/C/pt, and [object Object] for valueOf-only & plain). ✓
  • The full URL differential probe (href/origin/host/searchParams/relative resolution/sort/encode/etc.) is byte-identical to Node.
  • perry-runtime to_string + dynamic + concat unit tests pass. (builtin_prototype_methods_reject_dynamic_new passes in isolation; its failure under the parallel dynamic filter is pre-existing test-interference, not this change.)

Found via a node --experimental-strip-types differential sweep; relates to the parity roadmap (#4315). No version bump / changelog per maintainer instruction.

Summary by CodeRabbit

  • Bug Fixes
    • Improved coercion behavior so String(url) now stringifies URL objects to their href value.
    • Improved coercion behavior so URLSearchParams objects now stringify to their serialized query-string form.
    • Enhanced numeric/arithmetic conversions for URL/URLSearchParams to better match native URL/URLSearchParams behavior.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: cf62b5ed-9144-4d8b-a531-5451052417fa

📥 Commits

Reviewing files that changed from the base of the PR and between 4dd77d2 and a474806.

📒 Files selected for processing (2)
  • crates/perry-runtime/src/value/dynamic_arith.rs
  • crates/perry-runtime/src/value/to_string.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/perry-runtime/src/value/to_string.rs
  • crates/perry-runtime/src/value/dynamic_arith.rs

📝 Walkthrough

Walkthrough

Two coercion functions gain early URL/URLSearchParams detection: to_primitive_default_for_add in dynamic_arith.rs and js_jsvalue_to_string in to_string.rs. Both box the raw heap pointer, check for URL via js_url_href_if_url, fall back to try_read_as_search_params for URLSearchParams, and return the appropriate string before reaching the existing OrdinaryToPrimitive path.

Changes

URL/URLSearchParams Coercion Fix

Layer / File(s) Summary
URL/URLSearchParams early-exit in + and toString coercion
crates/perry-runtime/src/value/dynamic_arith.rs, crates/perry-runtime/src/value/to_string.rs
Both to_primitive_default_for_add and js_jsvalue_to_string now box the heap pointer, call js_url_href_if_url for URL objects and try_read_as_search_params for URLSearchParams, and return the serialized string before falling through to OrdinaryToPrimitive.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 A URL hops into a string,
No [object Object] — what a thing!
The href returns, the query streams,
Coercion now does what it seems.
Warren rejoices, specs align!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: enabling proper string coercion for URL and URLSearchParams objects to return href/query instead of [object Object], achieving Node.js parity.
Description check ✅ Passed The description provides comprehensive context including the problem statement, root cause analysis, the specific fix implemented in two functions, verification results, and test coverage information.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/url-tostring-coercion

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

proggeramlug pushed a commit that referenced this pull request Jun 14, 2026
proggeramlug pushed a commit that referenced this pull request Jun 14, 2026
…ral) for URL coercion guard (CodeRabbit #5108 / addr-class audit)
proggeramlug pushed a commit that referenced this pull request Jun 14, 2026
proggeramlug pushed a commit that referenced this pull request Jun 14, 2026
…ral) for URL coercion guard (CodeRabbit #5108 / addr-class audit)
@proggeramlug proggeramlug force-pushed the fix/url-tostring-coercion branch from 76c8017 to 4dd77d2 Compare June 14, 2026 06:06
Ralph Küpper added 3 commits June 14, 2026 09:33
…uery (Node parity)

ToString *coercion* of a WHATWG `URL` / `URLSearchParams` returned
`[object Object]` — `String(url)`, `` `${url}` ``, and `"" + url` all
diverged from explicit `url.toString()` (which already returns the href).
Their native `toString`s (href / the query string) aren't discoverable as
object fields, so the generic OrdinaryToPrimitive resolves the inherited
`Object.prototype.toString` and yields `[object Object]`.

Two coercion entry points needed the URL/URLSearchParams arm, mirroring
the existing Date special-cases:

- `js_jsvalue_to_string` (backs `String(x)` / explicit coercion): detect
  before OrdinaryToPrimitive and return the href / query string. The
  pointer is normalized to `POINTER_TAG` first because the operand can
  arrive as a raw heap pointer (upper-16 == 0).
- `to_primitive_default_for_add` (backs `+` and template literals, via
  `js_dynamic_string_or_number_add`): same arm before
  `ordinary_to_primitive_number_for_add`, which would otherwise return the
  inherited `Object.prototype.toString`'s `[object Object]`.

Reuses the existing `js_url_href_if_url` / `try_read_as_search_params`
detectors and `js_url_search_params_to_string`.

Verified vs Node v26.3.0: explicit / `String()` / template / concat of a
`URL` all yield the href, and of a `URLSearchParams` the query string;
custom `toString`/`valueOf`/plain-object/class coercion unchanged
(`[object Object]` where Node gives it); the full URL probe is
byte-identical. value/to_string + dynamic + concat unit tests pass
(the lone `builtin_prototype_methods_reject_dynamic_new` flake passes in
isolation — pre-existing parallel-test interference, not this change).
…ral) for URL coercion guard (CodeRabbit #5108 / addr-class audit)
@proggeramlug proggeramlug force-pushed the fix/url-tostring-coercion branch from 4dd77d2 to a474806 Compare June 14, 2026 07:33
@proggeramlug proggeramlug merged commit ee5ca34 into main Jun 14, 2026
15 checks passed
@proggeramlug proggeramlug deleted the fix/url-tostring-coercion branch June 14, 2026 08:57
proggeramlug pushed a commit that referenced this pull request Jun 14, 2026
Rolls up the issue-fix batch merged on top of 0.5.1165 (#5102, #5103,
#5105, #5106, #5107, #5108, #5109, #5110, #5112, #5117). See CHANGELOG
for the per-PR breakdown.
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