Skip to content

runtime: honor fromIndex for Array and TypedArray indexOf/includes #2804

Description

@andrewtdiz

Summary

Array.prototype.indexOf, Array.prototype.includes, and the matching %TypedArray% methods should honor the optional fromIndex argument. Perry's static indexOf / includes codegen currently accepts exactly one argument, and the runtime helpers / dynamic dispatch paths only search from index 0.

Node parity probe

Run with Node v25.9.0:

const arr = [1, 2, 1, NaN];
const ta = new Float64Array([1, 2, 1, NaN]);
for (const [label, recv] of [["Array", arr], ["TypedArray", ta]]) {
  for (const [caseLabel, fn] of [
    ["indexOf from 1", () => recv.indexOf(1, 1)],
    ["indexOf from -2", () => recv.indexOf(1, -2)],
    ["indexOf from Infinity", () => recv.indexOf(1, Infinity)],
    ["includes NaN", () => recv.includes(NaN)],
    ["includes from 4", () => recv.includes(1, 4)],
    ["includes from -2", () => recv.includes(1, -2)],
    ["includes from Infinity", () => recv.includes(1, Infinity)],
  ]) {
    try {
      console.log(`${label}.${caseLabel}: ${JSON.stringify(fn())}`);
    } catch (e) {
      console.log(`${label}.${caseLabel}: throws ${e.name}: ${e.message}`);
    }
  }
}

Output:

Array.indexOf from 1: 2
Array.indexOf from -2: 2
Array.indexOf from Infinity: -1
Array.includes NaN: true
Array.includes from 4: false
Array.includes from -2: true
Array.includes from Infinity: false
TypedArray.indexOf from 1: 2
TypedArray.indexOf from -2: 2
TypedArray.indexOf from Infinity: -1
TypedArray.includes NaN: true
TypedArray.includes from 4: false
TypedArray.includes from -2: true
TypedArray.includes from Infinity: false

Current Perry behavior in source

lastIndexOf already has an optional-from path, but forward search does not:

  • crates/perry-codegen/src/lower_array_method.rs:420-450 requires Array.includes to have exactly one argument and calls js_array_includes_jsvalue(arr, value).
  • crates/perry-codegen/src/lower_array_method.rs:451-472 requires Array.indexOf to have exactly one argument and calls js_array_indexOf_jsvalue(arr, value).
  • By contrast, crates/perry-codegen/src/lower_array_method.rs:474-492 accepts one or two args for lastIndexOf and passes fromIndex plus a has_from flag.

The runtime helpers also search from the beginning only:

  • crates/perry-runtime/src/array/search.rs:41-65 implements js_array_indexOf_jsvalue(arr, value) and loops from 0 for Arrays and TypedArrays.
  • crates/perry-runtime/src/array/search.rs:175-206 implements js_array_includes_jsvalue(arr, value) and loops from 0 for Arrays and TypedArrays while using SameValueZero.
  • crates/perry-runtime/src/object/native_call_method.rs:1262-1271 dynamic dispatch for indexOf / includes reads only the first argument and calls those two-argument helpers.

So indexOf(value, fromIndex) and includes(value, fromIndex) either fail the static codegen arity check or silently ignore fromIndex in dynamic dispatch.

Expected fix shape

Add from-index-aware helpers or extend the existing helpers so both static and dynamic paths can pass the optional second argument:

  • Omitted fromIndex: start at 0.
  • Positive finite fromIndex: start at that index, returning not-found when it is >= length.
  • Negative fromIndex: start at max(length + fromIndex, 0).
  • NaN: treat as 0.
  • Infinity: return not-found for forward search.
  • Preserve indexOf strict-equality behavior and includes SameValueZero behavior, including includes(NaN) === true.

Please cover Array and TypedArray receivers, static and dynamic dispatch, and fromIndex values undefined, positive, negative, NaN, and Infinity.

Related but separate: #2457 / PR #2501 covered TypedArray lastIndexOf; this issue is for forward indexOf / includes optional fromIndex handling.

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