Skip to content

runtime: make Reflect.apply use thisArg, array-like args, and TypeErrors #2767

Description

@andrewtdiz

Summary

Reflect.apply(target, thisArg, argumentsList) currently routes through call_with_args_array for non-proxy targets. That helper unpacks an Array-like Perry value but does not use thisArg, does not implement CreateListFromArrayLike, and returns soft values for invalid inputs. The proxy apply path also rewrites an explicit undefined trap result into a fallback target call.

Node-observable gaps include:

  • thisArg must be used as the function this binding.
  • argumentsList accepts array-like objects, not just Arrays.
  • non-callable targets throw TypeError.
  • non-object argumentsList values throw TypeError.
  • proxy apply trap results are returned exactly, including undefined.

Node behavior

Checked with Node v25.9.0:

function f(a, b) {
  return [this && this.marker, a, b].join(':');
}

Reflect.apply(f, { marker: 'ctx' }, ['a', 'b']);
// 'ctx:a:b'

Reflect.apply(function (a, b) { return a + b; }, null, { 0: 2, 1: 3, length: 2 });
// 5

const proxy = new Proxy(function () { return 'target'; }, {
  apply(target, thisArg, args) {
    return [thisArg.marker, args[0], args.length].join(':');
  },
});

Reflect.apply(proxy, { marker: 'pctx' }, ['x']);
// 'pctx:x:1'

const undefinedTrap = new Proxy(function () { return 'target'; }, {
  apply() { return undefined; },
});

Reflect.apply(undefinedTrap, null, []) === undefined;
// true

Reflect.apply(1, null, []);
// TypeError: Function.prototype.apply was called on 1, which is a number and not a function

Reflect.apply(function () {}, null, null);
// TypeError: CreateListFromArrayLike called on non-object

Reflect.apply(function () {}, null, 1);
// TypeError: CreateListFromArrayLike called on non-object

Perry implementation

Reflect lowering/codegen keeps the three API arguments:

  • crates/perry-hir/src/lower/expr_call/native_module.rs:958 lowers Reflect.apply(...) to Expr::ReflectApply { func, this_arg, args }.
  • crates/perry-codegen/src/expr/proxy_reflect.rs:163 passes those values to js_reflect_apply(f, this_arg, args_array).

The runtime then loses required semantics:

  • crates/perry-runtime/src/proxy.rs:579 documents Reflect.apply(fn, thisArg, argsArray).
  • crates/perry-runtime/src/proxy.rs:581 dispatches proxy targets to js_proxy_apply(...), but non-proxies call call_with_args_array(f, args_array) and ignore this_arg.
  • crates/perry-runtime/src/proxy.rs:467 implements call_with_args_array; it treats a null/non-array pointer as length 0 and calls closure_from(callee).
  • crates/perry-runtime/src/proxy.rs:483 / :485 return undefined for a null closure pointer instead of throwing for a non-callable target.

The proxy path has another observable divergence:

  • crates/perry-runtime/src/proxy.rs:445 calls an apply trap with (target, thisArg, argsArray).
  • crates/perry-runtime/src/proxy.rs:447 stores the trap result.
  • crates/perry-runtime/src/proxy.rs:455 calls the target directly when the trap result is undefined, but Node returns the trap result exactly.

Expected fix direction

Reflect.apply should route through a call path that:

  • validates that target is callable and throws TypeError otherwise,
  • validates argumentsList as an object / array-like and creates the argument list from indexed properties and length,
  • binds thisArg for direct calls,
  • preserves proxy apply trap return values exactly, including undefined,
  • keeps any pragmatic fallback behavior out of the spec-visible Reflect.apply path.

Duplicate search

Searched existing issues/PRs for:

  • Reflect.apply
  • Reflect.apply thisArg
  • Reflect.apply array-like
  • Reflect.apply non-callable
  • proxy apply trap
  • PR search for Reflect.apply OR proxy apply

No existing issue covered this Reflect.apply behavior.

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