Skip to content

Spread method call on an imported-array receiver is wrong (arr.map(...[cb]), arr.slice(...[1,3])) #7191

Description

@proggeramlug

Summary

A spread method call whose receiver is an imported binding (arr.map(...[cb]) where arr comes from another module) produces wrong results or throws. Pre-existing on d8be5f4a2; the failure shape changes with #7188 but it is wrong in both arms.

Repro

lib.ts

export const arr = [3, 1, 2];
export const nums: number[] = [10, 20, 30];

main.ts

import { arr, nums } from "./lib.ts";
const cb = (x: number) => x * 2;
console.log(JSON.stringify(arr.map(...([cb] as [any]))));
console.log(JSON.stringify(arr.slice(...([1, 3] as [any, any]))));
console.log(JSON.stringify(nums.join(...(["-"] as [any]))));
console.log(JSON.stringify(nums.includes(...([20] as [any]))));
case node 26.5.1 perry d8be5f4a2 perry with #7188
arr.map(...[cb]) [6,2,4] TypeError: object is not a function TypeError: Cannot convert undefined or null to object
arr.slice(...[1,3]) [1,2] [3,1,2] (silently wrong) []
nums.join(...["-"]) "10-20-30" TypeError: Cannot convert undefined or null to object same
nums.includes(...[20]) true false (silently wrong) TypeError: Cannot convert undefined or null to object

4/4 wrong before, 4/4 wrong after — #7188 is not a regression here, but two cases move from a silently-wrong value to a throw, and one moves from one wrong value to another.

Where it comes from

crates/perry-hir/src/lower/expr_call/imported_array_methods.rs carries a note from #6718:

a spread method call on an IMPORTED array … is intentionally NOT declined here … The imported binding lowers to an ExternFuncRef receiver, which that [generic Expr::CallSpread] tail's member-callee arm skips (it may be an imported function), so routing there would fail to dispatch the native method.

That is the bug: the generic CallSpread tail declines member callees on ExternFuncRef receivers, so the spread call never dispatches the receiver's method. The imported-array fold was papering over it — and it papered badly (slice/includes were already returning wrong values, since the spread argument list arrives as a single array in the builtin's first slot). The test_gap_6718_spread_call_native_method repro covers inline literals, local receivers and queueMicrotask — not imported receivers — so this has never been under test.

Fix belongs in the CallSpread member-callee arm: let it dispatch on an ExternFuncRef receiver instead of skipping.

Environment

Node 26.5.1 (.node-version pin), perry @ d8be5f4a2, macOS arm64, PERRY_NO_AUTO_OPTIMIZE=1. Found while investigating #7154 / #7188.

Refs #7154, #7188, #6718.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions