Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions python/pythonmonkey/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ def new(ctor):
newCtor = pm.eval("""'use strict'; (
function pmNewFactory(ctor)
{
return function newCtor(args) {
args = Array.from(args || []);
return new ctor(...args);
return function newCtor() {
return new ctor(...[...arguments]);
};
}
)""", evalOpts)(ctor)
return (lambda *args: newCtor(list(args)))
return (lambda *args, **kwargs: newCtor(*args, **kwargs))


def simpleUncaughtExceptionHandler(loop, context):
Expand Down
12 changes: 12 additions & 0 deletions src/JSFunctionProxy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ PyObject *JSFunctionProxyMethodDefinitions::JSFunctionProxy_call(PyObject *self,
}
}

if (kwargs && PyDict_Size(kwargs) > 0) {
JS::Value jsValue = jsTypeFactory(cx, kwargs);
if (PyErr_Occurred()) { // Check if an exception has already been set in the flow of control
return NULL; // Fail-fast
}
if (!jsArgsVector.append(jsValue)) {
// out of memory
setSpiderMonkeyException(cx);
return NULL;
}
}

JS::HandleValueArray jsArgs(jsArgsVector);
JS::RootedValue jsReturnVal(cx);
if (!JS_CallFunctionValue(cx, thisObj, jsFunc, jsArgs, &jsReturnVal)) {
Expand Down
28 changes: 28 additions & 0 deletions tests/js/js2py/kwargs.simple
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @file js2py/kwargs.simple
* Simple test which shows that kwargs passed from python to JS end up as an additional final dict argument
*
* @author Caleb Aikens, caikens@amd.com
* @date April 2026
*/
'use strict';

function js_func() {
return [...arguments];
}

python.exec(`
def kwarg_test(js_func):
return js_func(1, 2, i=42, j=43)
`);
const kwarg_test = python.eval('kwarg_test');

const [first, second, kwargs] = kwarg_test(js_func);

if (first !== 1 ||
second !== 2 ||
kwargs.i !== 42 ||
kwargs.j !== 43) {

throw new Error('args or kwargs were not passed correctly.');
}
14 changes: 14 additions & 0 deletions tests/python/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,17 @@ def test_default_vararg_func_too_few_args():
def f(a, b, c=42, d=43, *args):
return [a, b, c, d, *args]
assert [1, None, 42, 43] == pm.eval("(f) => f(1)")(f)

def test_kwargs_in_js():
assert [1, 2, {"i": 42, "j": 43}] == pm.eval("(first, second, kwargs) => {return [first, second, kwargs]}")(1, 2, i=42, j=43)

def test_kwargs_in_js_arguments_array():
# Note: arguments object is only available in non-arrow functions
assert [1, 2, {"i": 42, "j": 43}] == pm.eval("(function() {return [...arguments]})")(1, 2, i=42, j=43)

def test_kwargs_pass_through():
def py_func(*args, **kwargs):
return [*args, kwargs];

# Note: kwargs passed py1->js->py2 end up in args for py2 rather than kwargs, since JS has no equivalent to kwargs
assert [1, 2, {"i": 42, "j": 43}, {}] == pm.eval("(py_func, ...args) => { return py_func(...args)}")(py_func, 1, 2, i=42, j=43)
Loading