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.
Summary
Reflect.apply(target, thisArg, argumentsList)currently routes throughcall_with_args_arrayfor non-proxy targets. That helper unpacks an Array-like Perry value but does not usethisArg, does not implementCreateListFromArrayLike, and returns soft values for invalid inputs. The proxy apply path also rewrites an explicitundefinedtrap result into a fallback target call.Node-observable gaps include:
thisArgmust be used as the functionthisbinding.argumentsListaccepts array-like objects, not just Arrays.TypeError.argumentsListvalues throwTypeError.applytrap results are returned exactly, includingundefined.Node behavior
Checked with Node v25.9.0:
Perry implementation
Reflect lowering/codegen keeps the three API arguments:
crates/perry-hir/src/lower/expr_call/native_module.rs:958lowersReflect.apply(...)toExpr::ReflectApply { func, this_arg, args }.crates/perry-codegen/src/expr/proxy_reflect.rs:163passes those values tojs_reflect_apply(f, this_arg, args_array).The runtime then loses required semantics:
crates/perry-runtime/src/proxy.rs:579documentsReflect.apply(fn, thisArg, argsArray).crates/perry-runtime/src/proxy.rs:581dispatches proxy targets tojs_proxy_apply(...), but non-proxies callcall_with_args_array(f, args_array)and ignorethis_arg.crates/perry-runtime/src/proxy.rs:467implementscall_with_args_array; it treats a null/non-array pointer as length 0 and callsclosure_from(callee).crates/perry-runtime/src/proxy.rs:483/:485returnundefinedfor 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:445calls anapplytrap with(target, thisArg, argsArray).crates/perry-runtime/src/proxy.rs:447stores the trap result.crates/perry-runtime/src/proxy.rs:455calls the target directly when the trap result isundefined, but Node returns the trap result exactly.Expected fix direction
Reflect.applyshould route through a call path that:targetis callable and throwsTypeErrorotherwise,argumentsListas an object / array-like and creates the argument list from indexed properties andlength,thisArgfor direct calls,applytrap return values exactly, includingundefined,Reflect.applypath.Duplicate search
Searched existing issues/PRs for:
Reflect.applyReflect.apply thisArgReflect.apply array-likeReflect.apply non-callableproxy apply trapReflect.apply OR proxy applyNo existing issue covered this Reflect.apply behavior.