Skip to content

runtime: add dynamic Array.prototype.copyWithin dispatch #2802

Description

@andrewtdiz

Summary

Array.prototype.copyWithin(target, start?, end?) mutates and returns the receiver for all valid argument counts with a supplied target. Perry has a static/codegen path for this method, but dynamic runtime method dispatch still has no copyWithin branch, so calls that fall through to dynamic dispatch do not reach js_array_copy_within.

Node parity probe

Run with Node v25.9.0:

for (const [label, fn] of [
  ["copyWithin target only", () => [1, 2, 3, 4].copyWithin(1)],
  ["copyWithin target start", () => [1, 2, 3, 4].copyWithin(1, 2)],
  ["copyWithin target start end", () => [1, 2, 3, 4].copyWithin(1, 0, 2)],
  ["copyWithin neg", () => [1, 2, 3, 4].copyWithin(-2, 0, -1)],
]) {
  try {
    console.log(label + ": " + JSON.stringify(fn()));
  } catch (e) {
    console.log(label + ": throws " + e.name + ": " + e.message);
  }
}

Output:

copyWithin target only: [1,1,2,3]
copyWithin target start: [1,3,4,4]
copyWithin target start end: [1,1,2,4]
copyWithin neg: [1,2,1,2]

Current Perry behavior in source

The static/codegen path exists:

  • crates/perry-codegen/src/lower_array_method.rs:160-195 handles copyWithin, including the one-argument form by defaulting start to 0 and the omitted end form by passing has_end = 0.
  • crates/perry-runtime/src/array/immutable.rs:198-257 implements js_array_copy_within(target, start, has_end, end) with negative-index normalization, end clamping, and memmove semantics.
  • crates/perry-hir/src/lower/expr_call/local_array_methods.rs:508-520 has an HIR fast path for local array receivers with at least two arguments.

But dynamic runtime method dispatch does not include a copyWithin branch:

  • crates/perry-runtime/src/object/native_call_method.rs has dynamic branches for nearby mutating array methods such as sort, reverse, reduce, flat, flatMap, fill, and others, but source search finds no "copyWithin" branch.
  • The static codegen comment at crates/perry-codegen/src/lower_array_method.rs:161-167 explicitly notes that the general method-dispatch fallback does not know about copyWithin and silently no-ops.

So any copyWithin call that reaches dynamic dispatch, including calls on any/unproven receivers, misses the runtime helper even though the helper already exists.

Expected fix shape

Add a dynamic dispatch branch that mirrors the static path:

  • Require at least the target argument.
  • Default omitted start to 0.
  • Preserve omitted end with has_end = 0; otherwise pass the supplied end.
  • Call js_array_copy_within and return the original receiver pointer.

Please add regression coverage for dynamically dispatched copyWithin(target), copyWithin(target, start), copyWithin(target, start, end), negative indexes, and overlapping copy regions.

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