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.
Summary
Array.prototype.indexOf,Array.prototype.includes, and the matching%TypedArray%methods should honor the optionalfromIndexargument. Perry's staticindexOf/includescodegen 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:
Output:
Current Perry behavior in source
lastIndexOfalready has an optional-from path, but forward search does not:crates/perry-codegen/src/lower_array_method.rs:420-450requiresArray.includesto have exactly one argument and callsjs_array_includes_jsvalue(arr, value).crates/perry-codegen/src/lower_array_method.rs:451-472requiresArray.indexOfto have exactly one argument and callsjs_array_indexOf_jsvalue(arr, value).crates/perry-codegen/src/lower_array_method.rs:474-492accepts one or two args forlastIndexOfand passesfromIndexplus ahas_fromflag.The runtime helpers also search from the beginning only:
crates/perry-runtime/src/array/search.rs:41-65implementsjs_array_indexOf_jsvalue(arr, value)and loops from0for Arrays and TypedArrays.crates/perry-runtime/src/array/search.rs:175-206implementsjs_array_includes_jsvalue(arr, value)and loops from0for Arrays and TypedArrays while using SameValueZero.crates/perry-runtime/src/object/native_call_method.rs:1262-1271dynamic dispatch forindexOf/includesreads only the first argument and calls those two-argument helpers.So
indexOf(value, fromIndex)andincludes(value, fromIndex)either fail the static codegen arity check or silently ignorefromIndexin 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:
fromIndex: start at0.fromIndex: start at that index, returning not-found when it is>= length.fromIndex: start atmax(length + fromIndex, 0).NaN: treat as0.Infinity: return not-found for forward search.indexOfstrict-equality behavior andincludesSameValueZero behavior, includingincludes(NaN) === true.Please cover Array and TypedArray receivers, static and dynamic dispatch, and
fromIndexvaluesundefined, positive, negative,NaN, andInfinity.Related but separate: #2457 / PR #2501 covered TypedArray
lastIndexOf; this issue is for forwardindexOf/includesoptionalfromIndexhandling.