From cf3f355306e138175e5c02b08a2cf188b7988919 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 22 Sep 2022 15:36:38 -0700 Subject: [PATCH] Add descr_get handler to JsMethod (#3130) --- docs/project/changelog.md | 6 ++++++ src/core/jsproxy.c | 25 +++++++++++++++++++++++++ src/tests/test_jsproxy.py | 17 +++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/docs/project/changelog.md b/docs/project/changelog.md index 563b4c939..22a74ed6a 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -54,6 +54,12 @@ substitutions: first argument using the {any}`captureThis` method. {pr}`3103` +- {{ Enhancement }} A `JsProxy` of a function now has a `__get__` descriptor + method, so it's possible to use a JavaScript function as a Python method. When + the method is called, `this` will be a `PyProxy` pointing to the Python object + the method is called on. + {pr}`3130` + ### Build System / Package Loading - New packages: pycryptodomex {pr}`2966`, pycryptodome {pr}`2965`, diff --git a/src/core/jsproxy.c b/src/core/jsproxy.c index 027499138..6d7c2ceaa 100644 --- a/src/core/jsproxy.c +++ b/src/core/jsproxy.c @@ -103,6 +103,9 @@ typedef struct static void JsProxy_dealloc(JsProxy* self) { + if (pyproxy_Check(self->this_)) { + destroy_proxy(self->this_, NULL); + } #ifdef DEBUG_F extern bool tracerefs; if (tracerefs) { @@ -1897,6 +1900,26 @@ static PyMethodDef JsMethod_Construct_MethodDef = { }; // clang-format on +static PyObject* +JsMethod_descr_get(PyObject* self, PyObject* obj, PyObject* type) +{ + JsRef jsobj = NULL; + PyObject* result = NULL; + + if (obj == Py_None || obj == NULL) { + Py_INCREF(self); + return self; + } + + jsobj = python2js(obj); + FAIL_IF_NULL(jsobj); + result = JsProxy_create_with_this(JsProxy_REF(self), jsobj); + +finally: + hiwire_CLEAR(jsobj); + return result; +} + static int JsMethod_cinit(PyObject* obj, JsRef this_) { @@ -2445,6 +2468,8 @@ JsProxy_create_subtype(int flags) tp_flags |= _Py_TPFLAGS_HAVE_VECTORCALL; slots[cur_slot++] = (PyType_Slot){ .slot = Py_tp_call, .pfunc = (void*)PyVectorcall_Call }; + slots[cur_slot++] = (PyType_Slot){ .slot = Py_tp_descr_get, + .pfunc = (void*)JsMethod_descr_get }; // We could test separately for whether a function is constructable, // but it generates a lot of false positives. methods[cur_method++] = JsMethod_Construct_MethodDef; diff --git a/src/tests/test_jsproxy.py b/src/tests/test_jsproxy.py index 075ace323..2b7a246df 100644 --- a/src/tests/test_jsproxy.py +++ b/src/tests/test_jsproxy.py @@ -1276,3 +1276,20 @@ def test_jsarray_reverse(selenium): assert a.to_py() == l assert b.to_bytes() == bytes(l) + + +@run_in_pyodide +def test_jsproxy_descr_get(selenium): + from pyodide.code import run_js + + class T: + a: int + b: int + f = run_js("function f(x) {return this[x]; }; f") + + t = T() + t.a = 7 + t.b = 66 + assert t.f("a") == 7 + assert t.f("b") == 66 + assert t.f("c") is None