Skip to content

fix(codegen): String method on a nullish receiver must throw, not coerce#6453

Merged
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/string-method-nullish-receiver
Jul 16, 2026
Merged

fix(codegen): String method on a nullish receiver must throw, not coerce#6453
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/string-method-nullish-receiver

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

The inline lowering for String.prototype char-access/case/split methods (charAt/charCodeAt/codePointAt/toUpperCase/toLowerCase/split/…) applied ToString(this) to a non-string receiver via js_string_coerce without a RequireObjectCoercible guard. ToString maps undefined"undefined" and null"null", so:

(undefined as any).codePointAt(0)  // => 117  ("undefined".codePointAt(0))
(undefined as any).toUpperCase()   // => "UNDEFINED"
(null as any).charAt(0)            // => "n"

where V8/Node throw TypeError: Cannot read properties of undefined (reading 'codePointAt') — the member access x.codePointAt reads the method off x first (ECMA-262 §13.3), before the call. The general property-get path (used for e.g. slice, indexOf) already threw correctly; only the optimistically-inlined char-access/case/split path routed through lower_string_method skipped the guard.

Fix

The non-string-receiver coercion branch now calls a new js_string_coerce_method_this(value, prop_name, prop_len) runtime helper, which performs RequireObjectCoercible(this) — throwing the V8-shaped Cannot read properties of {undefined|null} (reading '<method>') via js_throw_type_error_property_access — before ToString. A statically string-typed receiver still skips the guard (fast path, unchanged).

Test

crates/perry/tests/issue_string_method_nullish_receiver.rs compiles + runs the nullish-receiver shapes (mirroring cell.value.codePointAt(0) where cell.value is undefined) and asserts the member-access TypeError message for codePointAt/charCodeAt/charAt/toUpperCase/split on both undefined and null, plus that a real string receiver still works.

https://claude.ai/code/session_01XRHKpxgDnVB3GJdsV9ud7g

Summary by CodeRabbit

  • Bug Fixes

    • Fixed String.prototype method calls on null/undefined receivers to throw the expected member-access TypeError (instead of coercing to strings).
    • Preserved the existing fast path for statically known string receivers.
  • Tests

    • Added a regression test covering multiple string methods (charAt, codePointAt, charCodeAt, toUpperCase, split) and asserting exact error message text.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

String method lowering now uses receiver-aware coercion that preserves member-access TypeErrors for nullish receivers. The runtime declaration and implementation accept the method name for diagnostics, and a regression test covers nullish and valid string receivers.

Changes

String method receiver coercion

Layer / File(s) Summary
Receiver-aware runtime coercion
crates/perry-codegen/src/runtime_decls/strings.rs, crates/perry-runtime/src/builtins/numbers.rs
Adds js_string_coerce_method_this, which throws a property-access TypeError for undefined or null and otherwise performs string coercion.
Lowering integration and regression coverage
crates/perry-codegen/src/lower_string_method.rs, crates/perry/tests/issue_string_method_nullish_receiver.rs
Passes the interned method name to the new helper for non-string receivers and tests exact errors across multiple string methods while retaining valid string behavior.

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

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly captures the main change: String methods now throw on nullish receivers instead of coercing them.
Description check ✅ Passed The description explains the bug, fix, and test coverage, but it omits the template’s Related issue and checklist sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

@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: 1

🧹 Nitpick comments (1)
crates/perry-runtime/src/builtins/numbers.rs (1)

676-702: 📐 Maintainability & Code Quality | 🔵 Trivial

Rebuild the static wrapper crates. This runtime change should also rebuild perry-runtime-static and perry-stdlib-static so the linked archives don’t go stale.

🤖 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/builtins/numbers.rs` around lines 676 - 702, Rebuild
the static wrapper crates perry-runtime-static and perry-stdlib-static after
updating js_string_coerce_method_this, ensuring their linked archives include
the runtime change and do not remain stale.
🤖 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/tests/issue_string_method_nullish_receiver.rs`:
- Around line 60-61: Move or duplicate coverage for string method access on a
nullish receiver into cargo-test-visible unit tests, such as the perry-codegen
unit test suite, rather than relying only on
string_method_on_nullish_receiver_throws_member_access_type_error in the
integration tests. Preserve assertions that the operation throws the expected
member-access type error.

---

Nitpick comments:
In `@crates/perry-runtime/src/builtins/numbers.rs`:
- Around line 676-702: Rebuild the static wrapper crates perry-runtime-static
and perry-stdlib-static after updating js_string_coerce_method_this, ensuring
their linked archives include the runtime change and do not remain stale.
🪄 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: 68e6c49f-36eb-4223-be4a-7228ed0fa875

📥 Commits

Reviewing files that changed from the base of the PR and between fb04be0 and e54704e.

📒 Files selected for processing (4)
  • crates/perry-codegen/src/lower_string_method.rs
  • crates/perry-codegen/src/runtime_decls/strings.rs
  • crates/perry-runtime/src/builtins/numbers.rs
  • crates/perry/tests/issue_string_method_nullish_receiver.rs

Comment thread crates/perry/tests/issue_string_method_nullish_receiver.rs
The inline lowering for `String.prototype` char-access/case/split methods
(`charAt`/`charCodeAt`/`codePointAt`/`toUpperCase`/`toLowerCase`/`split`/…)
applied `ToString(this)` to a non-string receiver via `js_string_coerce`
without a `RequireObjectCoercible` guard. `ToString` maps `undefined`→
`"undefined"` and `null`→`"null"`, so:

    (undefined as any).codePointAt(0)  // => 117  ("undefined".codePointAt(0))
    (undefined as any).toUpperCase()   // => "UNDEFINED"
    (null as any).charAt(0)            // => "n"

where V8/Node throw `TypeError: Cannot read properties of undefined
(reading 'codePointAt')` — the member access `x.codePointAt` reads the
method off `x` first (ECMA-262 §13.3), before the call. The general
property-get path (used for e.g. `slice`, `indexOf`) already threw
correctly; only the optimistically-inlined char-access/case/split path
routed through `lower_string_method` skipped the guard.

Fix: the non-string-receiver coercion branch now calls a new
`js_string_coerce_method_this(value, prop_name, prop_len)` runtime helper,
which performs `RequireObjectCoercible(this)` — throwing the V8-shaped
`Cannot read properties of {undefined|null} (reading '<method>')` via
`js_throw_type_error_property_access` — before `ToString`. A statically
string-typed receiver still skips the guard (fast path, unchanged).

Regression test compiles + runs the nullish-receiver shapes and asserts the
member-access TypeError message (and that a real string receiver still
works).

Claude-Session: https://claude.ai/code/session_01XRHKpxgDnVB3GJdsV9ud7g
@proggeramlug
proggeramlug force-pushed the fix/string-method-nullish-receiver branch from e54704e to 44e4186 Compare July 16, 2026 02:10

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

🧹 Nitpick comments (1)
crates/perry-runtime/src/builtins/numbers.rs (1)

686-686: 📐 Maintainability & Code Quality | 🔵 Trivial

Rebuild static wrapper crates.

As per path instructions, when changing runtime or stdlib code, remember to rebuild the corresponding static wrapper crates (perry-runtime-static and perry-stdlib-static). The runtime and stdlib crates themselves emit only rlibs and otherwise may leave stale archives linked during local testing or builds.

🤖 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/builtins/numbers.rs` at line 686, After modifying
the runtime or stdlib, rebuild the corresponding static wrapper crates per the
repository instructions: perry-runtime-static and perry-stdlib-static. Ensure
their generated archives are refreshed so local tests and builds do not link
stale artifacts.

Source: Path instructions

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

Nitpick comments:
In `@crates/perry-runtime/src/builtins/numbers.rs`:
- Line 686: After modifying the runtime or stdlib, rebuild the corresponding
static wrapper crates per the repository instructions: perry-runtime-static and
perry-stdlib-static. Ensure their generated archives are refreshed so local
tests and builds do not link stale artifacts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 35b9ab43-a324-4abf-bcfc-9ea2063387d3

📥 Commits

Reviewing files that changed from the base of the PR and between e54704e and 44e4186.

📒 Files selected for processing (4)
  • crates/perry-codegen/src/lower_string_method.rs
  • crates/perry-codegen/src/runtime_decls/strings.rs
  • crates/perry-runtime/src/builtins/numbers.rs
  • crates/perry/tests/issue_string_method_nullish_receiver.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/perry-codegen/src/lower_string_method.rs
  • crates/perry/tests/issue_string_method_nullish_receiver.rs

@proggeramlug
proggeramlug merged commit 23a9f43 into PerryTS:main Jul 16, 2026
26 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