fix(hir): #5195 — inline property-access .indexOf/.includes miscompiled as array search#5454
Conversation
…o ArrayIndexOf
`lower/expr_call/array_only_methods.rs` folded a `PropertyGet` receiver of
`.indexOf` / `.includes` to `Expr::ArrayIndexOf` / `Expr::ArrayIncludes` (an
element-equality search) unless the property name was in a hardcoded
`stack|message|name|sourceSQL|expandedSQL` allowlist. A genuine string
property (`interface P { id: string }`, `arr[i].id.indexOf("PRO")`) fell
through that allowlist and was searched as an array, silently returning -1
(and `.filter(p => p.id.indexOf("PRO") >= 0)`, the same predicate shape,
returned 0 matches) with no error.
Fix: only fold receivers that are *provably* array-producing
(`.map`/`.filter`/`.sort`/`.slice`/`.split`/`Object.keys`/`Object.values`/
array literals/`Array.from`). Every property access now falls through to the
generic `js_native_call_method` dispatch, which checks the runtime value type
and routes string- vs. array-`indexOf`/`includes` correctly — the same way
the sibling `slice` arm already behaved, and the same path a known-string
local (`const pid = arr[i].id; pid.indexOf(...)`) already took. Genuine
array-typed properties keep correct semantics through that dynamic dispatch.
Removes the unsustainable property-name allowlist band-aid in both arms.
Regression test: crates/perry/tests/issue_5195_property_method_chain.rs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe Fix
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/perry-hir/src/lower/expr_call/array_only_methods.rs (1)
925-937: 💤 Low valueConsider unifying the provably-array whitelist with
slice/joinarms for consistency.The whitelist here is narrower than the
slicearm (lines 815-841) andjoinarm (lines 858-881), which also include:ArraySpread,ArrayFromMapped,ArrayFlat,ArrayToReversed,ArrayToSorted,ArrayToSpliced,ArrayWith,ArrayEntries,ArrayKeys,ArrayValues,ObjectEntries,ProcessArgv.Missing shapes correctly fall through to generic dispatch (no correctness issue), but unified whitelists would reduce maintenance burden when adding new array-producing variants.
Also applies to: 961-973
🤖 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-hir/src/lower/expr_call/array_only_methods.rs` around lines 925 - 937, The matches! check for provably-array expressions at lines 925-937 is narrower than similar checks in the slice arm (lines 815-841) and join arm (lines 858-881), missing variants like ArraySpread, ArrayFromMapped, ArrayFlat, ArrayToReversed, ArrayToSorted, ArrayToSpliced, ArrayWith, ArrayEntries, ArrayKeys, ArrayValues, ObjectEntries, and ProcessArgv. To fix this, expand the matches! pattern at lines 925-937 to include all the array-producing variants present in the slice and join arms, and apply the same unification to the other location mentioned (lines 961-973). This will ensure consistency across all three whitelists and reduce future maintenance burden when new array-producing expression types are added.
🤖 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-hir/src/lower/expr_call/array_only_methods.rs`:
- Around line 925-937: The matches! check for provably-array expressions at
lines 925-937 is narrower than similar checks in the slice arm (lines 815-841)
and join arm (lines 858-881), missing variants like ArraySpread,
ArrayFromMapped, ArrayFlat, ArrayToReversed, ArrayToSorted, ArrayToSpliced,
ArrayWith, ArrayEntries, ArrayKeys, ArrayValues, ObjectEntries, and ProcessArgv.
To fix this, expand the matches! pattern at lines 925-937 to include all the
array-producing variants present in the slice and join arms, and apply the same
unification to the other location mentioned (lines 961-973). This will ensure
consistency across all three whitelists and reduce future maintenance burden
when new array-producing expression types are added.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ba54605b-4959-4c6f-87f9-d27a42234127
📒 Files selected for processing (2)
crates/perry-hir/src/lower/expr_call/array_only_methods.rscrates/perry/tests/issue_5195_property_method_chain.rs
Fixes #5195.
Problem
Calling
.indexOf/.includesinline on a property access —arr[i].id.indexOf("PRO"),p.id.includes("PRO")— returned the wrong resultwith no error. Extracting the property to a local (
const pid = arr[i].id; pid.indexOf(...)) worked.Reproduced on the native host (
v0.5.1191, currentmain):Downstream this silently made a StoreKit product lookup
(
products.filter(p => p.id.indexOf("PRO") >= 0)) return empty, so anin-app-purchase button reported the plan as unavailable.
Root cause
crates/perry-hir/src/lower/expr_call/array_only_methods.rsfolded aPropertyGetreceiver of.indexOf/.includestoExpr::ArrayIndexOf/Expr::ArrayIncludes(an element-equality search), gated only by a hardcodedproperty-name allowlist (
stack|message|name|sourceSQL|expandedSQL). A stringproperty like
idisn't on that list, soarr[i].id(typedstring) wassearched as an array →
-1. HIR confirmed it:Fix
Only fold receivers that are provably array-producing (
.map/.filter/.sort/.slice/.split/Object.keys/Object.values/array literals/Array.from). Every property access now falls through to the genericjs_native_call_methoddispatch, which checks the runtime value type androutes string- vs. array-
indexOf/includescorrectly — the same way thesibling
slicearm already behaves and the same path a known-string localalready took. This removes the unsustainable property-name allowlist band-aid
in both arms.
Genuine array-typed properties keep correct semantics via that dynamic
dispatch (verified below).
Verification
New regression test
crates/perry/tests/issue_5195_property_method_chain.rs(2 cases, both green):string_property_indexof_includes_inline_match— the issue shape now returns 2 for the loop,.filter, and.includesforms, andarr[0].id.toLowerCase().indexOf("pro") === 4.array_typed_property_indexof_includes_still_search_array—box.tags.indexOf("green") === 1,.includes("blue") === true, miss=== -1, numeric array property=== 1.Also ran a broader behavioral sweep (bare-local / literal /
.map/.filter/.split/Object.keyschains, string locals, string & array properties) — all correct.perry-hirunit tests and the relatedissue_5271_string_method_nonstring_receiver/issue_5139_object_arraylike_method_dispatchintegration tests pass.🤖 Generated with Claude Code
Summary by CodeRabbit
indexOfandincludesmethods when called on array properties accessed within method chains. Previously, these methods could incorrectly apply string semantics. Now the implementation properly distinguishes between array and string operations, ensuring these search methods return accurate results based on the actual data type being operated upon.