Skip to content

runtime: honor flat depth in dynamic Array.prototype.flat dispatch #2800

Closed

Description

@andrewtdiz

Summary

Perry has a depth-aware Array.prototype.flat(depth) lowering path, but the dynamic runtime method-dispatch branch ignores the supplied depth argument and always calls the legacy depth-1 helper. Calls that reach dynamic dispatch, such as anyArray.flat(0), anyArray.flat(2), or anyArray.flat(Infinity), should preserve Node's depth coercion semantics.

Node parity probe

Run with Node v25.9.0:

function nest(depth) {
  let x = "leaf";
  for (let i = 0; i < depth; i++) x = [x];
  return x;
}
for (const depth of [0, 1, 2, 32, 33, 40, Infinity, NaN, -1]) {
  const out = nest(40).flat(depth);
  let d = 0, cur = out;
  while (Array.isArray(cur)) {
    d++;
    cur = cur[0];
    if (d > 50) break;
  }
  console.log(`${String(depth)}: residualArrayDepth=${d} leaf=${cur}`);
}

Output:

0: residualArrayDepth=40 leaf=leaf
1: residualArrayDepth=39 leaf=leaf
2: residualArrayDepth=38 leaf=leaf
32: residualArrayDepth=8 leaf=leaf
33: residualArrayDepth=7 leaf=leaf
40: residualArrayDepth=1 leaf=leaf
Infinity: residualArrayDepth=1 leaf=leaf
NaN: residualArrayDepth=40 leaf=leaf
-1: residualArrayDepth=40 leaf=leaf

The top-level result array accounts for the final residual depth of 1 in the fully flattened cases.

Current Perry behavior in source

The static/codegen path is depth-aware:

  • crates/perry-codegen/src/lower_array_method.rs:198-218 sends zero-arg flat() to js_array_flat, but sends calls with an argument to js_array_flat_depth(recv, depth).
  • crates/perry-runtime/src/array/flat_clone.rs:31-47 converts NaN / non-positive depth to 0, finite positive depth to an integer depth, and Infinity to u32::MAX.

But dynamic method dispatch ignores the argument:

  • crates/perry-runtime/src/object/native_call_method.rs:1243-1246 handles "flat" by calling crate::array::js_array_flat(arr) unconditionally and never reads args_ptr / args_len.

So any call that falls through to dynamic method dispatch gets depth 1 regardless of whether the JS call supplied 0, 2, Infinity, or another value.

Expected fix shape

The dynamic flat branch should mirror the static path:

  • Omitted depth: call js_array_flat or js_array_flat_depth(arr, 1).
  • Supplied depth: pass the first argument to js_array_flat_depth after normal JS number coercion.
  • Preserve Node behavior for 0, negative values, NaN, finite positive depths, and Infinity.

Please add regression coverage for dynamically dispatched flat(0), flat(2), flat(Infinity), and the zero-argument default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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