diff --git a/docs/project/changelog.md b/docs/project/changelog.md index 83d2c6d69b2..926768c6932 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -19,6 +19,10 @@ myst: - {{ Enhancement }} Added experimental support for stack switching. {pr}`3957`, {pr}`3964`, {pr}`3987`, {pr}`3990`, {pr}`3210` +- {{ Fix }} `jsarray.pop` now works correctly. It previously returned the wrong + value and leaked memory. + {pr}`4236` + ### Packages - Added `river` version 0.19.0 {pr}`4197` diff --git a/src/core/error_handling.h b/src/core/error_handling.h index 3cda22a5198..c4b4a9ff35e 100644 --- a/src/core/error_handling.h +++ b/src/core/error_handling.h @@ -199,7 +199,7 @@ console_error_obj(JsRef obj); #define FAIL_IF_JS_NULL(ref) \ do { \ - if (unlikely(Jsv_is_null(ref))) { \ + if (unlikely(JsvNull_Check(ref))) { \ FAIL(); \ } \ } while (0) diff --git a/src/core/hiwire.c b/src/core/hiwire.c index 2bdff7058ae..95ea2432c45 100644 --- a/src/core/hiwire.c +++ b/src/core/hiwire.c @@ -235,38 +235,12 @@ EM_JS(void _Py_NO_RETURN, hiwire_throw_error, (JsRef iderr), { throw Hiwire.pop_value(iderr); }); -static JsRef -convert_va_args(va_list args) -{ - JsRef idargs = JsArray_New(); - while (true) { - JsRef idarg = va_arg(args, JsRef); - if (idarg == NULL) { - break; - } - JsArray_Push_unchecked(idargs, idarg); - } - va_end(args); - return idargs; -} - EM_JS_REF(JsRef, hiwire_call, (JsRef idfunc, JsRef idargs), { let jsfunc = Hiwire.get_value(idfunc); let jsargs = Hiwire.get_value(idargs); return Hiwire.new_value(jsfunc(... jsargs)); }); -JsRef -hiwire_call_va(JsRef idobj, ...) -{ - va_list args; - va_start(args, idobj); - JsRef idargs = convert_va_args(args); - JsRef idresult = hiwire_call(idobj, idargs); - hiwire_decref(idargs); - return idresult; -} - EM_JS_REF(JsRef, hiwire_call_OneArg, (JsRef idfunc, JsRef idarg), { let jsfunc = Hiwire.get_value(idfunc); let jsarg = Hiwire.get_value(idarg); @@ -377,28 +351,6 @@ hiwire_CallMethodId(JsRef idobj, Js_Identifier* name_id, JsRef idargs) return hiwire_CallMethod(idobj, name_ref, idargs); } -JsRef -hiwire_CallMethodString_va(JsRef idobj, const char* ptrname, ...) -{ - va_list args; - va_start(args, ptrname); - JsRef idargs = convert_va_args(args); - JsRef idresult = hiwire_CallMethodString(idobj, ptrname, idargs); - hiwire_decref(idargs); - return idresult; -} - -JsRef -hiwire_CallMethodId_va(JsRef idobj, Js_Identifier* name, ...) -{ - va_list args; - va_start(args, name); - JsRef idargs = convert_va_args(args); - JsRef idresult = hiwire_CallMethodId(idobj, name, idargs); - hiwire_decref(idargs); - return idresult; -} - JsRef hiwire_CallMethodId_NoArgs(JsRef obj, Js_Identifier* name) { @@ -511,32 +463,6 @@ hiwire_get_length(JsRef idobj) return -1; } -EM_JS_BOOL(bool, hiwire_get_bool, (JsRef idobj), { - let val = Hiwire.get_value(idobj); - // clang-format off - if (!val) { - return false; - } - // We want to return false on container types with size 0. - if (val.size === 0) { - if(/HTML[A-Za-z]*Element/.test(getTypeTag(val))){ - // HTMLSelectElement and HTMLInputElement can have size 0 but we still - // want to return true. - return true; - } - // I think other things with a size are container types. - return false; - } - if (val.length === 0 && JsArray_Check(idobj)) { - return false; - } - if (val.byteLength === 0) { - return false; - } - return true; - // clang-format on -}); - EM_JS_BOOL(bool, hiwire_is_generator, (JsRef idobj), { // clang-format off return getTypeTag(Hiwire.get_value(idobj)) === "[object Generator]"; @@ -694,139 +620,6 @@ EM_JS_REF(JsRef, hiwire_subarray, (JsRef idarr, int start, int end), { return Hiwire.new_value(jssub); }); -// ==================== JsArray API ==================== - -EM_JS_BOOL(bool, JsArray_Check, (JsRef idobj), { - let obj = Hiwire.get_value(idobj); - if (Array.isArray(obj)) { - return true; - } - let typeTag = getTypeTag(obj); - // We want to treat some standard array-like objects as Array. - // clang-format off - if(typeTag === "[object HTMLCollection]" || typeTag === "[object NodeList]"){ - // clang-format on - return true; - } - // What if it's a TypedArray? - // clang-format off - if (ArrayBuffer.isView(obj) && obj.constructor.name !== "DataView") { - // clang-format on - return true; - } - return false; -}); - -// clang-format off -EM_JS_REF(JsRef, JsArray_New, (), { - return Hiwire.new_value([]); -}); -// clang-format on - -EM_JS_NUM(errcode, JsArray_Push, (JsRef idarr, JsRef idval), { - Hiwire.get_value(idarr).push(Hiwire.get_value(idval)); -}); - -EM_JS(int, JsArray_Push_unchecked, (JsRef idarr, JsRef idval), { - const arr = Hiwire.get_value(idarr); - arr.push(Hiwire.get_value(idval)); - return arr.length - 1; -}); - -EM_JS_NUM(errcode, JsArray_Extend, (JsRef idarr, JsRef idvals), { - Hiwire.get_value(idarr).push(... Hiwire.get_value(idvals)); -}); - -EM_JS_REF(JsRef, JsArray_Get, (JsRef idobj, int idx), { - let obj = Hiwire.get_value(idobj); - let result = obj[idx]; - // clang-format off - if (result === undefined && !(idx in obj)) { - // clang-format on - return ERROR_REF; - } - return Hiwire.new_value(result); -}); - -EM_JS_NUM(errcode, JsArray_Set, (JsRef idobj, int idx, JsRef idval), { - Hiwire.get_value(idobj)[idx] = Hiwire.get_value(idval); -}); - -EM_JS_NUM(errcode, JsArray_Delete, (JsRef idobj, int idx), { - let obj = Hiwire.get_value(idobj); - // Weird edge case: allow deleting an empty entry, but we raise a key error if - // access is attempted. - if (idx < 0 || idx >= obj.length) { - return ERROR_NUM; - } - obj.splice(idx, 1); -}); - -EM_JS_REF(JsRef, JsArray_Splice, (JsRef idobj, int idx), { - let obj = Hiwire.get_value(idobj); - // Weird edge case: allow deleting an empty entry, but we raise a key error if - // access is attempted. - if (idx < 0 || idx >= obj.length) { - return 0; - } - return Hiwire.new_value(obj.splice(idx, 1)); -}); - -// clang-format off -EM_JS_REF(JsRef, -JsArray_slice, -(JsRef idobj, int length, int start, int stop, int step), -{ - let obj = Hiwire.get_value(idobj); - let result; - if (step === 1) { - result = obj.slice(start, stop); - } else { - result = Array.from({ length }, (_, i) => obj[start + i * step]); - } - return Hiwire.new_value(result); -}); - -EM_JS_NUM(errcode, -JsArray_slice_assign, -(JsRef idobj, int slicelength, int start, int stop, int step, int values_length, PyObject **values), -{ - let obj = Hiwire.get_value(idobj); - let jsvalues = []; - for(let i = 0; i < values_length; i++){ - let ref = _python2js(DEREF_U32(values, i)); - if(ref === 0){ - return -1; - } - jsvalues.push(Hiwire.pop_value(ref)); - } - if (step === 1) { - obj.splice(start, slicelength, ...jsvalues); - } else { - if(values !== 0) { - for(let i = 0; i < slicelength; i ++){ - obj.splice(start + i * step, 1, jsvalues[i]); - } - } else { - for(let i = slicelength - 1; i >= 0; i --){ - obj.splice(start + i * step, 1); - } - } - } -}); -// clang-format on - -EM_JS_NUM(errcode, JsArray_Clear, (JsRef idobj), { - let obj = Hiwire.get_value(idobj); - obj.splice(0, obj.length); -}) - -EM_JS_NUM(JsRef, JsArray_ShallowCopy, (JsRef idobj), { - const obj = Hiwire.get_value(idobj); - const res = ("slice" in obj) ? obj.slice() : Array.from(obj); - return Hiwire.new_value(res); -}) - // ==================== JsObject API ==================== // clang-format off diff --git a/src/core/hiwire.h b/src/core/hiwire.h index 748dcdda93b..39fe76c3755 100644 --- a/src/core/hiwire.h +++ b/src/core/hiwire.h @@ -147,16 +147,6 @@ hiwire_call(JsRef idobj, JsRef idargs); JsRef hiwire_call_OneArg(JsRef idobj, JsRef idarg); -/** - * Call a function - * - * Arguments are specified as a NULL-terminated variable arguments list of - * JsRefs. - * - */ -JsRef -hiwire_call_va(JsRef idobj, ...); - JsRef hiwire_call_bound(JsRef idfunc, JsRef idthis, JsRef idargs); @@ -187,23 +177,12 @@ hiwire_CallMethodString(JsRef obj, const char* name, JsRef args); JsRef hiwire_CallMethodString_OneArg(JsRef obj, const char* name, JsRef arg); -/** - * name is the method name, as null-terminated UTF8. - * Arguments are specified as a NULL-terminated variable arguments list of - * JsRefs. - */ -JsRef -hiwire_CallMethodString_va(JsRef obj, const char* name, ...); - JsRef hiwire_CallMethod(JsRef obj, JsRef name, JsRef args); JsRef hiwire_CallMethod_OneArg(JsRef obj, JsRef name, JsRef arg); -JsRef -hiwire_CallMethod_va(JsRef obj, JsRef name, ...); - /** * name is the method name, as a Js_Identifier * args is a hiwire Array containing the arguments. @@ -211,14 +190,6 @@ hiwire_CallMethod_va(JsRef obj, JsRef name, ...); JsRef hiwire_CallMethodId(JsRef obj, Js_Identifier* name, JsRef args); -/** - * name is the method name, as a Js_Identifier - * Arguments are specified as a NULL-terminated variable arguments list of - * JsRefs. - */ -JsRef -hiwire_CallMethodId_va(JsRef obj, Js_Identifier* name, ...); - JsRef hiwire_CallMethodId_NoArgs(JsRef obj, Js_Identifier* name); @@ -403,72 +374,6 @@ hiwire_get_buffer_info(JsRef idobj, JsRef hiwire_subarray(JsRef idarr, int start, int end); -// ==================== JsArray API ==================== - -bool -JsArray_Check(JsRef idobj); - -/** - * Create a new JavaScript Array. - * - * Returns: New reference - */ -JsRef -JsArray_New(); - -/** - * Push a value to the end of a JavaScript array. - */ -errcode WARN_UNUSED -JsArray_Push(JsRef idobj, JsRef idval); - -/** - * Same as JsArray_Push but panics on failure - */ -int -JsArray_Push_unchecked(JsRef idobj, JsRef idval); - -errcode -JsArray_Extend(JsRef idobj, JsRef idvals); - -JsRef -JsArray_ShallowCopy(JsRef idobj); - -/** - * Get an object member by integer. - * - * Returns: New reference - */ -JsRef -JsArray_Get(JsRef idobj, int idx); - -/** - * Set an object member by integer. - */ -errcode WARN_UNUSED -JsArray_Set(JsRef idobj, int idx, JsRef idval); - -errcode WARN_UNUSED -JsArray_Delete(JsRef idobj, int idx); - -JsRef -JsArray_Splice(JsRef idobj, int idx); - -JsRef -JsArray_slice(JsRef idobj, int slicelength, int start, int stop, int step); - -errcode -JsArray_slice_assign(JsRef idobj, - int slicelength, - int start, - int stop, - int step, - int values_length, - PyObject** values); - -errcode -JsArray_Clear(JsRef idobj); - // ==================== JsObject API ==================== /** diff --git a/src/core/jslib.c b/src/core/jslib.c index cd6598b4b68..25ed55ffb2f 100644 --- a/src/core/jslib.c +++ b/src/core/jslib.c @@ -1,8 +1,11 @@ #include "jslib.h" #include "error_handling.h" +#include "jsmemops.h" + +// ==================== Conversions between JsRef and JsVal ==================== JsVal -Jsv_pop_ref(JsRef ref) +JsRef_pop(JsRef ref) { if (ref == NULL) { return JS_NULL; @@ -11,7 +14,7 @@ Jsv_pop_ref(JsRef ref) } JsVal -Jsv_from_ref(JsRef ref) +JsRef_toVal(JsRef ref) { if (ref == NULL) { return JS_NULL; @@ -19,29 +22,135 @@ Jsv_from_ref(JsRef ref) return hiwire_get(ref); } +JsRef +JsRef_new(JsVal v) +{ + if (JsvNull_Check(v)) { + return NULL; + } + return hiwire_new(v); +} + +// ==================== Primitive Conversions ==================== + +EM_JS(JsVal, JsvInt, (int x), { return x; }) + +EM_JS(bool, Jsv_to_bool, (JsVal x), { return !!x; }) + // clang-format off EM_JS(JsVal, JsvUTF8ToString, (const char* ptr), { return UTF8ToString(ptr); }) + +// ==================== JsvArray API ==================== + EM_JS(JsVal, JsvArray_New, (), { return []; }); -EM_JS(JsVal, JsvArray_Get_js, (JsVal array, int idx), { - return nullToUndefined(array[idx]); +EM_JS_BOOL(bool, JsvArray_Check, (JsVal obj), { + if (Array.isArray(obj)) { + return true; + } + let typeTag = getTypeTag(obj); + // We want to treat some standard array-like objects as Array. + // clang-format off + if(typeTag === "[object HTMLCollection]" || typeTag === "[object NodeList]"){ + // clang-format on + return true; + } + // What if it's a TypedArray? + // clang-format off + if (ArrayBuffer.isView(obj) && obj.constructor.name !== "DataView") { + // clang-format on + return true; + } + return false; +}); + +EM_JS_VAL(JsVal, JsvArray_Get, (JsVal arr, int idx), { + const result = arr[idx]; + // clang-format off + if (result === undefined && !(idx in arr)) { + // clang-format on + return null; + } + return nullToUndefined(result); +}); + +EM_JS_NUM(errcode, JsvArray_Set, (JsVal arr, int idx, JsVal val), { + arr[idx] = val; +}); + +EM_JS_VAL(JsVal, JsvArray_Delete, (JsVal arr, int idx), { + // Weird edge case: allow deleting an empty entry, but we raise a key error if + // access is attempted. + if (idx < 0 || idx >= arr.length) { + return null; + } + return arr.splice(idx, 1)[0]; +}); + +// clang-format off +EM_JS(int, JsvArray_Push, (JsVal arr, JsVal obj), { + return arr.push(obj); +}); + +EM_JS(void, JsvArray_Extend, (JsVal arr, JsVal vals), { + arr.push(...vals); +}); +// clang-format on + +EM_JS_NUM(JsVal, JsvArray_ShallowCopy, (JsVal arr), { + return ("slice" in arr) ? arr.slice() : Array.from(arr); }) -JsVal -JsvArray_Get(JsVal array, int idx) +// clang-format off +EM_JS_REF(JsVal, +JsvArray_slice, +(JsVal obj, int length, int start, int stop, int step), { - return JsvArray_Get_js(array, idx); -} + let result; + if (step === 1) { + result = obj.slice(start, stop); + } else { + result = Array.from({ length }, (_, i) => obj[start + i * step]); + } + return result; +}); + -EM_JS(void, JsvArray_Push, (JsVal array, JsVal obj), { - array.push(obj); +EM_JS_NUM(errcode, +JsvArray_slice_assign, +(JsVal obj, int slicelength, int start, int stop, int step, int values_length, PyObject **values), +{ + let jsvalues = []; + for(let i = 0; i < values_length; i++){ + const ref = _python2js_val(DEREF_U32(values, i)); + if(ref === null){ + return -1; + } + jsvalues.push(ref); + } + if (step === 1) { + obj.splice(start, slicelength, ...jsvalues); + } else { + if(values !== 0) { + for(let i = 0; i < slicelength; i ++){ + obj.splice(start + i * step, 1, jsvalues[i]); + } + } else { + for(let i = slicelength - 1; i >= 0; i --){ + obj.splice(start + i * step, 1); + } + } + } }); +// ==================== JsvObject API ==================== + + EM_JS(JsVal, JsvObject_New, (), { return {}; }); @@ -65,6 +174,28 @@ JsvObject_CallMethodId(JsVal obj, Js_Identifier* name_id, JsVal args) return JsvObject_CallMethod(obj, hiwire_get(name_ref), args); } +JsVal +JsvObject_CallMethodId_OneArg(JsVal obj, Js_Identifier* name_id, JsVal arg) +{ + JsVal args = JsvArray_New(); + JsvArray_Push(args, arg); + return JsvObject_CallMethodId(obj, name_id, args); +} + +JsVal +JsvObject_CallMethodId_TwoArgs(JsVal obj, + Js_Identifier* name_id, + JsVal arg1, + JsVal arg2) +{ + JsVal args = JsvArray_New(); + JsvArray_Push(args, arg1); + JsvArray_Push(args, arg2); + return JsvObject_CallMethodId(obj, name_id, args); +} + +// ==================== JsvFunction API ==================== + EM_JS_BOOL(bool, JsvFunction_Check, (JsVal obj), { // clang-format off return typeof obj === 'function'; @@ -84,6 +215,8 @@ JsvFunction_Construct, }); // clang-format on +// ==================== JsvPromise API ==================== + EM_JS_BOOL(bool, JsvPromise_Check, (JsVal obj), { // clang-format off return Hiwire.isPromise(obj); @@ -96,6 +229,8 @@ EM_JS_VAL(JsVal, JsvPromise_Resolve, (JsVal obj), { // clang-format on }); +// ==================== Miscellaneous ==================== + EM_JS_BOOL(bool, JsvGenerator_Check, (JsVal obj), { // clang-format off return getTypeTag(obj) === "[object Generator]"; diff --git a/src/core/jslib.h b/src/core/jslib.h index 1160af09944..a68122f7e0d 100644 --- a/src/core/jslib.h +++ b/src/core/jslib.h @@ -2,26 +2,71 @@ #define JSLIB_H #include "hiwire.h" +// ==================== JS_NULL ==================== + #define JS_NULL __builtin_wasm_ref_null_extern() -int Jsv_is_null(JsVal); +int JsvNull_Check(JsVal); + +// ==================== Conversions between JsRef and JsVal ==================== + +JsRef +JsRef_new(JsVal v); JsVal -Jsv_pop_ref(JsRef ref); +JsRef_pop(JsRef ref); JsVal -Jsv_from_ref(JsRef ref); +JsRef_toVal(JsRef ref); + +// ==================== Primitive Conversions ==================== + +JsVal +JsvInt(int x); + +bool +Jsv_to_bool(JsVal x); JsVal JsvUTF8ToString(const char*); +// ==================== JsvArray API ==================== + JsVal JsvArray_New(); +bool +JsvArray_Check(JsVal obj); + JsVal JsvArray_Get(JsVal, int); -void JsvArray_Push(JsVal, JsVal); +errcode +JsvArray_Set(JsVal, int, JsVal); + +JsVal +JsvArray_Delete(JsVal, int); + +int JsvArray_Push(JsVal, JsVal); + +void JsvArray_Extend(JsVal, JsVal); + +JsVal +JsvArray_ShallowCopy(JsVal obj); + +JsVal +JsvArray_slice(JsVal obj, int length, int start, int stop, int step); + +errcode +JsvArray_slice_assign(JsVal idobj, + int slicelength, + int start, + int stop, + int step, + int values_length, + PyObject** values); + +// ==================== JsvObject API ==================== JsVal JsvObject_New(); @@ -35,6 +80,17 @@ JsvObject_CallMethod(JsVal obj, JsVal meth, JsVal args); JsVal JsvObject_CallMethodId(JsVal obj, Js_Identifier* meth_id, JsVal args); +JsVal +JsvObject_CallMethodId_OneArg(JsVal obj, Js_Identifier* meth_id, JsVal arg); + +JsVal +JsvObject_CallMethodId_TwoArgs(JsVal obj, + Js_Identifier* meth_id, + JsVal arg1, + JsVal arg2); + +// ==================== JsvFunction API ==================== + bool JsvFunction_Check(JsVal obj); @@ -44,6 +100,8 @@ JsvFunction_CallBound(JsVal func, JsVal this, JsVal args); JsVal JsvFunction_Construct(JsVal func, JsVal args); +// ==================== Miscellaneous ==================== + bool JsvPromise_Check(JsVal obj); diff --git a/src/core/jslib_asm.s b/src/core/jslib_asm.s index b16e3ac496b..b7ee51d556e 100644 --- a/src/core/jslib_asm.s +++ b/src/core/jslib_asm.s @@ -1,6 +1,6 @@ -.globl Jsv_is_null -Jsv_is_null: - .functype Jsv_is_null (externref) -> (i32) +.globl JsvNull_Check +JsvNull_Check: + .functype JsvNull_Check (externref) -> (i32) local.get 0 ref.is_null end_function diff --git a/src/core/jsproxy.c b/src/core/jsproxy.c index a83b7aa04eb..504bd937a44 100644 --- a/src/core/jsproxy.c +++ b/src/core/jsproxy.c @@ -315,18 +315,6 @@ JsProxy_js_id_private(PyObject* mod, PyObject* obj) return PyLong_FromLong((int)idval); } -void -setReservedError(char* action, char* word) -{ - PyErr_Format(PyExc_AttributeError, - "The string '%s' is a Python reserved word. To %s an attribute " - "on a JS object called '%s' use '%s_'.", - word, - action, - word, - word); -} - EM_JS(bool, isReservedWord, (int word), { if (!Module.pythonReservedWords) { Module.pythonReservedWords = new Set([ @@ -394,7 +382,7 @@ JsProxy_GetAttr(PyObject* self, PyObject* attr) const char* key = PyUnicode_AsUTF8(attr); FAIL_IF_NULL(key); - if (strcmp(key, "keys") == 0 && JsArray_Check(JsProxy_REF(self))) { + if (strcmp(key, "keys") == 0 && JsvArray_Check(JsProxy_VAL(self))) { // Sometimes Python APIs test for the existence of a "keys" function // to decide whether something should be treated like a dict. // This mixes badly with the javascript Array.keys API, so pretend that it @@ -405,7 +393,7 @@ JsProxy_GetAttr(PyObject* self, PyObject* attr) } jsresult = JsProxy_GetAttr_js(JsProxy_VAL(self), key); - if (Jsv_is_null(jsresult)) { + if (JsvNull_Check(jsresult)) { PyErr_SetString(PyExc_AttributeError, key); FAIL(); } @@ -613,15 +601,14 @@ handle_next_result(JsRef next_res, PyObject** result, bool obj_map_hereditary){ PySendResult JsProxy_am_send(PyObject* self, PyObject* arg, PyObject** result) { - JsRef proxies = NULL; JsRef jsarg = Js_undefined; JsRef next_res = NULL; *result = NULL; PySendResult ret = PYGEN_ERROR; + JsVal proxies; if (arg) { - proxies = JsArray_New(); - FAIL_IF_NULL(proxies); + proxies = JsvArray_New(); jsarg = python2js_track_proxies(arg, proxies, true); FAIL_IF_NULL(jsarg); } @@ -629,10 +616,9 @@ JsProxy_am_send(PyObject* self, PyObject* arg, PyObject** result) FAIL_IF_NULL(next_res); ret = handle_next_result(next_res, result, JsObjMap_HEREDITARY(self)); finally: - if (proxies) { + if (arg) { destroy_proxies(proxies, &PYPROXY_DESTROYED_AT_END_OF_FUNCTION_CALL); } - hiwire_CLEAR(proxies); hiwire_CLEAR(jsarg); hiwire_CLEAR(next_res); return ret; @@ -1120,13 +1106,13 @@ _agen_handle_result(JsRef promise, bool closing) static PyObject* JsGenerator_asend(PyObject* self, PyObject* arg) { - JsRef proxies = NULL; JsRef jsarg = Js_undefined; JsRef next_res = NULL; PyObject* result = NULL; + + JsVal proxies; if (arg != NULL) { - proxies = JsArray_New(); - FAIL_IF_NULL(proxies); + proxies = JsvArray_New(); jsarg = python2js_track_proxies(arg, proxies, true); FAIL_IF_NULL(jsarg); } @@ -1135,10 +1121,9 @@ JsGenerator_asend(PyObject* self, PyObject* arg) result = _agen_handle_result(next_res, false); finally: - if (proxies) { + if (arg) { destroy_proxies(proxies, &PYPROXY_DESTROYED_AT_END_OF_FUNCTION_CALL); } - hiwire_CLEAR(proxies); hiwire_CLEAR(jsarg); hiwire_CLEAR(next_res); return result; @@ -1291,11 +1276,10 @@ JsProxy_item_array(PyObject* o, Py_ssize_t i) { PyObject* pyresult = NULL; JsProxy* self = (JsProxy*)o; - JsRef jsresult = JsArray_Get(self->js, i); - FAIL_IF_NULL(jsresult); - pyresult = js2python(jsresult); + JsVal jsresult = JsvArray_Get(JsProxy_VAL(self), i); + FAIL_IF_JS_NULL(jsresult); + pyresult = js2python_val(jsresult); finally: - hiwire_CLEAR(jsresult); return pyresult; } @@ -1306,7 +1290,6 @@ static PyObject* JsArray_subscript(PyObject* o, PyObject* item) { JsProxy* self = (JsProxy*)o; - JsRef jsresult = NULL; PyObject* pyresult = NULL; if (PyIndex_Check(item)) { @@ -1318,14 +1301,14 @@ JsArray_subscript(PyObject* o, PyObject* item) FAIL_IF_MINUS_ONE(length); i += length; } - jsresult = JsArray_Get(self->js, i); - if (jsresult == NULL) { + JsVal jsresult = JsvArray_Get(JsProxy_VAL(self), i); + if (JsvNull_Check(jsresult)) { if (!PyErr_Occurred()) { PyErr_SetObject(PyExc_IndexError, item); } FAIL(); } - pyresult = js2python(jsresult); + pyresult = js2python_val(jsresult); goto success; } if (PySlice_Check(item)) { @@ -1335,13 +1318,15 @@ JsArray_subscript(PyObject* o, PyObject* item) FAIL_IF_MINUS_ONE(length); // PySlice_AdjustIndices is "Always successful" per the docs. Py_ssize_t slicelength = PySlice_AdjustIndices(length, &start, &stop, step); + JsVal jsresult; if (slicelength <= 0) { - jsresult = JsArray_New(); + jsresult = JsvArray_New(); } else { - jsresult = JsArray_slice(self->js, slicelength, start, stop, step); + jsresult = + JsvArray_slice(JsProxy_VAL(self), slicelength, start, stop, step); } - FAIL_IF_NULL(jsresult); - pyresult = js2python(jsresult); + FAIL_IF_JS_NULL(jsresult); + pyresult = js2python_val(jsresult); goto success; } PyErr_Format(PyExc_TypeError, @@ -1349,21 +1334,18 @@ JsArray_subscript(PyObject* o, PyObject* item) Py_TYPE(item)->tp_name); success: finally: - hiwire_CLEAR(jsresult); return pyresult; } PyObject* JsArray_sq_item(PyObject* o, Py_ssize_t i) { - JsRef jsresult = NULL; PyObject* pyresult = NULL; - jsresult = JsArray_Get(JsProxy_REF(o), i); - FAIL_IF_NULL(jsresult); - pyresult = js2python(jsresult); + JsVal jsresult = JsvArray_Get(JsProxy_VAL(o), i); + FAIL_IF_JS_NULL(jsresult); + pyresult = js2python_val(jsresult); finally: - hiwire_CLEAR(jsresult); return pyresult; } @@ -1371,23 +1353,21 @@ Py_ssize_t JsArray_sq_ass_item(PyObject* o, Py_ssize_t i, PyObject* pyval) { bool success = false; - JsRef jsval = NULL; if (pyval == NULL) { // Delete - jsval = JsArray_Splice(JsProxy_REF(o), i); - FAIL_IF_NULL(jsval); + JsVal jsval = JsvArray_Delete(JsProxy_VAL(o), i); + FAIL_IF_JS_NULL(jsval); success = true; goto finally; } - jsval = python2js(pyval); - FAIL_IF_NULL(jsval); - FAIL_IF_MINUS_ONE(JsArray_Set(JsProxy_REF(o), i, jsval)); + JsVal jsval = python2js_val(pyval); + FAIL_IF_JS_NULL(jsval); + FAIL_IF_MINUS_ONE(JsvArray_Set(JsProxy_VAL(o), i, jsval)); success = true; finally: - hiwire_CLEAR(jsval); return success ? 0 : -1; } @@ -1439,7 +1419,6 @@ JsArray_ass_subscript(PyObject* o, PyObject* item, PyObject* pyvalue) { JsProxy* self = (JsProxy*)o; bool success = false; - JsRef idvalue = NULL; PyObject* seq = NULL; Py_ssize_t i; if (PySlice_Check(item)) { @@ -1475,7 +1454,8 @@ JsArray_ass_subscript(PyObject* o, PyObject* item, PyObject* pyvalue) start = stop + step * (slicelength - 1) - 1; step = -step; } - JsArray_slice_assign(self->js, slicelength, start, stop, step, 0, NULL); + JsvArray_slice_assign( + JsProxy_VAL(self), slicelength, start, stop, step, 0, NULL); } else { if (step != 1 && !slicelength) { // At this point, assigning to an extended slice of length 0 must be a @@ -1483,13 +1463,13 @@ JsArray_ass_subscript(PyObject* o, PyObject* item, PyObject* pyvalue) success = true; goto finally; } - JsArray_slice_assign(self->js, - slicelength, - start, - stop, - step, - PySequence_Fast_GET_SIZE(seq), - PySequence_Fast_ITEMS(seq)); + JsvArray_slice_assign(JsProxy_VAL(self), + slicelength, + start, + stop, + step, + PySequence_Fast_GET_SIZE(seq), + PySequence_Fast_ITEMS(seq)); } success = true; goto finally; @@ -1510,21 +1490,20 @@ JsArray_ass_subscript(PyObject* o, PyObject* item, PyObject* pyvalue) } if (pyvalue == NULL) { - if (JsArray_Delete(self->js, i)) { + if (JsvNull_Check(JsvArray_Delete(JsProxy_VAL(self), i))) { if (!PyErr_Occurred()) { PyErr_SetObject(PyExc_IndexError, item); } FAIL(); } } else { - idvalue = python2js(pyvalue); - FAIL_IF_NULL(idvalue); - FAIL_IF_MINUS_ONE(JsArray_Set(self->js, i, idvalue)); + JsVal jsvalue = python2js_val(pyvalue); + FAIL_IF_JS_NULL(jsvalue); + FAIL_IF_MINUS_ONE(JsvArray_Set(JsProxy_VAL(self), i, jsvalue)); } success = true; finally: Py_CLEAR(seq); - hiwire_CLEAR(idvalue); return success ? 0 : -1; } @@ -1548,10 +1527,9 @@ JsTypedArray_ass_subscript(PyObject* o, PyObject* item, PyObject* pyvalue) } static int -JsArray_extend_by_python_iterable(JsRef jsarray, PyObject* iterable) +JsArray_extend_by_python_iterable(JsVal jsarray, PyObject* iterable) { PyObject* it = NULL; - JsRef jsval = NULL; bool success = false; if (PyList_CheckExact(iterable) || PyTuple_CheckExact(iterable)) { @@ -1572,10 +1550,9 @@ JsArray_extend_by_python_iterable(JsRef jsarray, PyObject* iterable) /* populate the end of self with iterable's items */ PyObject** src = PySequence_Fast_ITEMS(iterable); for (int i = 0; i < n; i++) { - jsval = python2js(src[i]); - FAIL_IF_NULL(jsval); - FAIL_IF_MINUS_ONE(JsArray_Push(jsarray, jsval)); - hiwire_CLEAR(jsval); + JsVal jsval = python2js_val(src[i]); + FAIL_IF_JS_NULL(jsval); + JsvArray_Push(jsarray, jsval); } } else { Py_INCREF(iterable); @@ -1596,21 +1573,19 @@ JsArray_extend_by_python_iterable(JsRef jsarray, PyObject* iterable) } break; } - JsRef jsval = python2js(item); - FAIL_IF_NULL(jsval); - FAIL_IF_MINUS_ONE(JsArray_Push(jsarray, jsval)); - hiwire_CLEAR(jsval); + JsVal jsval = python2js_val(item); + FAIL_IF_JS_NULL(jsval); + JsvArray_Push(jsarray, jsval); } } success = true; finally: - hiwire_CLEAR(jsval); Py_CLEAR(it); return success ? 0 : -1; } -EM_JS(void, destroy_jsarray_entries, (JsRef idarray), { - for (let v of Hiwire.get_value(idarray)) { +EM_JS(void, destroy_jsarray_entries, (JsVal array), { + for (let v of array) { // clang-format off try { if(typeof v.destroy === "function"){ @@ -1626,20 +1601,17 @@ EM_JS(void, destroy_jsarray_entries, (JsRef idarray), { static PyObject* JsArray_extend_meth(PyObject* o, PyObject* iterable) { - JsRef temp = NULL; bool success = false; - temp = JsArray_New(); - FAIL_IF_NULL(temp); + JsVal temp = JsvArray_New(); // Make sure that if anything goes wrong the original array stays unmodified FAIL_IF_MINUS_ONE(JsArray_extend_by_python_iterable(temp, iterable)); - FAIL_IF_MINUS_ONE(JsArray_Extend(JsProxy_REF(o), temp)); + JsvArray_Extend(JsProxy_VAL(o), temp); success = true; finally: if (!success) { destroy_jsarray_entries(temp); } - hiwire_CLEAR(temp); if (success) { Py_RETURN_NONE; } else { @@ -1656,21 +1628,19 @@ static PyMethodDef JsArray_extend_MethodDef = { static PyObject* JsArray_sq_concat(PyObject* self, PyObject* other) { - JsRef jsresult = NULL; PyObject* pyresult = NULL; bool success = true; - jsresult = JsArray_ShallowCopy(JsProxy_REF(self)); - FAIL_IF_NULL(jsresult); - pyresult = js2python(jsresult); + JsVal jsresult = JsvArray_ShallowCopy(JsProxy_VAL(self)); + FAIL_IF_JS_NULL(jsresult); + pyresult = js2python_val(jsresult); FAIL_IF_NULL(pyresult); FAIL_IF_MINUS_ONE( - JsArray_extend_by_python_iterable(JsProxy_REF(pyresult), other)); + JsArray_extend_by_python_iterable(JsProxy_VAL(pyresult), other)); finally: if (!success) { Py_CLEAR(pyresult); } - hiwire_CLEAR(jsresult); return pyresult; } @@ -1725,17 +1695,16 @@ JsArray_sq_inplace_repeat(PyObject* o, Py_ssize_t count) } static PyObject* -JsArray_append(PyObject* o, PyObject* arg) +JsArray_append(PyObject* self, PyObject* arg) { - JsProxy* self = (JsProxy*)o; bool success = false; - JsRef jsarg = NULL; - jsarg = python2js(arg); - FAIL_IF_NULL(jsarg); - FAIL_IF_MINUS_ONE(JsArray_Push(self->js, jsarg)); + + JsVal jsarg = python2js_val(arg); + FAIL_IF_JS_NULL(jsarg); + JsvArray_Push(JsProxy_VAL(self), jsarg); + success = true; finally: - hiwire_CLEAR(jsarg); if (success) { Py_RETURN_NONE; } else { @@ -1767,7 +1736,6 @@ static PyObject* JsArray_pop(PyObject* o, PyObject* const* args, Py_ssize_t nargs) { JsProxy* self = (JsProxy*)o; - JsRef jsresult = NULL; PyObject* pyresult = NULL; PyObject* iobj = NULL; Py_ssize_t index = -1; @@ -1799,9 +1767,9 @@ JsArray_pop(PyObject* o, PyObject* const* args, Py_ssize_t nargs) FAIL(); } - jsresult = JsArray_Splice(self->js, index); - FAIL_IF_NULL(jsresult); - pyresult = js2python(jsresult); + JsVal jsresult = JsvArray_Delete(JsProxy_VAL(self), index); + FAIL_IF_JS_NULL(jsresult); + pyresult = js2python_val(jsresult); finally: Py_CLEAR(iobj); @@ -1879,17 +1847,16 @@ JsArray_index(PyObject* o, PyObject* args) stop = length; } - JsRef jsvalue = python2js_track_proxies(value, NULL, true); + JsRef jsvalue = python2js_track_proxies(value, JS_NULL, true); if (jsvalue == NULL) { PyErr_Clear(); for (int i = start; i < stop; i++) { - JsRef jsobj = JsArray_Get(self->js, i); + JsVal jsobj = JsvArray_Get(JsProxy_VAL(self), i); // We know `value` is not a `JsProxy`: if it were we would have taken the // other branch. Thus, if `jsobj` is not a `PyProxy`, // `PyObject_RichCompareBool` is guaranteed to return false. As a speed // up, only perform the check if the object is a `PyProxy`. PyObject* pyobj = pyproxy_AsPyObject(jsobj); /* borrowed! */ - hiwire_decref(jsobj); if (pyobj == NULL) { continue; } @@ -1939,7 +1906,7 @@ static PyObject* JsArray_count(PyObject* o, PyObject* value) { JsProxy* self = (JsProxy*)o; - JsRef jsvalue = python2js_track_proxies(value, NULL, true); + JsRef jsvalue = python2js_track_proxies(value, JS_NULL, true); if (jsvalue == NULL) { PyErr_Clear(); int result = 0; @@ -1948,13 +1915,12 @@ JsArray_count(PyObject* o, PyObject* value) return NULL; } for (int i = 0; i < stop; i++) { - JsRef jsobj = JsArray_Get(self->js, i); + JsVal jsobj = JsvArray_Get(JsProxy_VAL(self), i); // We know `value` is not a `JsProxy`: if it were we would have taken the // other branch. Thus, if `jsobj` is not a `PyProxy`, // `PyObject_RichCompareBool` is guaranteed to return false. As a speed // up, only perform the check if the object is a `PyProxy`. PyObject* pyobj = pyproxy_AsPyObject(jsobj); /* borrowed! */ - hiwire_decref(jsobj); if (pyobj == NULL) { continue; } @@ -2058,35 +2024,30 @@ JsProxy_subscript(PyObject* o, PyObject* pyidx) * Controlled by HAS_SET. */ static int -JsProxy_ass_subscript(PyObject* o, PyObject* pyidx, PyObject* pyvalue) +JsProxy_ass_subscript(PyObject* self, PyObject* pyidx, PyObject* pyvalue) { - JsProxy* self = (JsProxy*)o; bool success = false; - JsRef ididx = NULL; - JsRef idvalue = NULL; - JsRef jsresult = NULL; - ididx = python2js(pyidx); + + JsVal idx = python2js_val(pyidx); + FAIL_IF_JS_NULL(idx); if (pyvalue == NULL) { - jsresult = hiwire_CallMethodId_OneArg(self->js, &JsId_delete, ididx); - FAIL_IF_NULL(jsresult); - if (!hiwire_to_bool(jsresult)) { + JsVal result = + JsvObject_CallMethodId_OneArg(JsProxy_VAL(self), &JsId_delete, idx); + FAIL_IF_JS_NULL(result); + if (!Jsv_to_bool(result)) { if (!PyErr_Occurred()) { PyErr_SetObject(PyExc_KeyError, pyidx); } FAIL(); } } else { - idvalue = python2js(pyvalue); - FAIL_IF_NULL(idvalue); - jsresult = - hiwire_CallMethodId_va(self->js, &JsId_set, ididx, idvalue, NULL); - FAIL_IF_NULL(jsresult); + JsVal value = python2js_val(pyvalue); + FAIL_IF_JS_NULL(value); + FAIL_IF_JS_NULL( + JsvObject_CallMethodId_TwoArgs(JsProxy_VAL(self), &JsId_set, idx, value)); } success = true; finally: - hiwire_CLEAR(jsresult); - hiwire_CLEAR(idvalue); - hiwire_CLEAR(ididx); return success ? 0 : -1; } @@ -2442,7 +2403,7 @@ JsProxy_Dir(PyObject* self, PyObject* _args) FAIL_IF_NULL(pydir); // Merge and sort FAIL_IF_MINUS_ONE(_PySet_Update(result_set, pydir)); - if (JsArray_Check(JsProxy_REF(self))) { + if (JsvArray_Check(JsProxy_VAL(self))) { // See comment about Array.keys in GetAttr keys_str = PyUnicode_FromString("keys"); FAIL_IF_NULL(keys_str); @@ -2498,8 +2459,8 @@ JsProxy_toPy(PyObject* self, } PyObject* result = js2python_convert(JsProxy_REF(self), depth, default_converter_js); - if (pyproxy_Check(Jsv_from_ref(default_converter_js))) { - destroy_proxy(Jsv_from_ref(default_converter_js), NULL); + if (pyproxy_Check(JsRef_toVal(default_converter_js))) { + destroy_proxy(JsRef_toVal(default_converter_js), NULL); } hiwire_decref(default_converter_js); return result; @@ -2511,6 +2472,31 @@ static PyMethodDef JsProxy_toPy_MethodDef = { METH_FASTCALL | METH_KEYWORDS, }; +EM_JS_BOOL(bool, JsProxy_Bool_js, (JsVal val), { + // clang-format off + if (!val) { + return false; + } + // We want to return false on container types with size 0. + if (val.size === 0) { + if(/HTML[A-Za-z]*Element/.test(getTypeTag(val))){ + // HTMLSelectElement and HTMLInputElement can have size 0 but we still + // want to return true. + return true; + } + // I think other things with a size are container types. + return false; + } + if (val.length === 0 && JsvArray_Check(val)) { + return false; + } + if (val.byteLength === 0) { + return false; + } + return true; + // clang-format on +}); + /** * Overload for bool(proxy), implemented for every JsProxy. Return `False` if * the object is falsey in JavaScript, or if it has a `size` field equal to 0, @@ -2521,10 +2507,9 @@ static PyMethodDef JsProxy_toPy_MethodDef = { * be falsey. */ static int -JsProxy_Bool(PyObject* o) +JsProxy_Bool(PyObject* self) { - JsProxy* self = (JsProxy*)o; - return hiwire_get_bool(self->js) ? 1 : 0; + return JsProxy_Bool_js(JsProxy_VAL(self)); } /** @@ -2634,7 +2619,7 @@ JsProxy_then(JsProxy* self, PyObject* args, PyObject* kwds) FAIL_IF_JS_NULL(promise_handles); JsVal result_promise = JsvObject_CallMethodId(promise, &JsId_then, promise_handles); - if (Jsv_is_null(result_promise)) { + if (JsvNull_Check(result_promise)) { Py_CLEAR(onfulfilled); Py_CLEAR(onrejected); FAIL(); @@ -2668,7 +2653,7 @@ JsProxy_catch(JsProxy* self, PyObject* onrejected) FAIL_IF_JS_NULL(promise_handles); JsVal result_promise = JsvObject_CallMethodId(promise, &JsId_then, promise_handles); - if (Jsv_is_null(result_promise)) { + if (JsvNull_Check(result_promise)) { Py_DECREF(onrejected); FAIL(); } @@ -2694,29 +2679,26 @@ static PyMethodDef JsProxy_catch_MethodDef = { static PyObject* JsProxy_finally(JsProxy* self, PyObject* onfinally) { - JsRef proxy = NULL; - JsRef promise_id = NULL; - JsRef result_promise = NULL; PyObject* result = NULL; - promise_id = hiwire_resolve_promise(self->js); - FAIL_IF_NULL(promise_id); + JsVal promise = JsvPromise_Resolve(JsProxy_VAL(self)); + FAIL_IF_JS_NULL(promise); // Finally method is called no matter what so we can use // `create_once_callable`. - proxy = create_once_callable(onfinally); - FAIL_IF_NULL(proxy); - result_promise = - hiwire_CallMethodId_va(promise_id, &JsId_finally, proxy, NULL); - if (result_promise == NULL) { + JsVal proxy = create_once_callable(onfinally); + FAIL_IF_JS_NULL(proxy); + JsVal result_promise = + JsvObject_CallMethodId_OneArg(promise, &JsId_finally, proxy); + if (JsvNull_Check(result_promise)) { Py_DECREF(onfinally); FAIL(); } - result = JsProxy_create(result_promise); + + JsRef result_promise_ref = hiwire_new(result_promise); + result = JsProxy_create(result_promise_ref); + hiwire_CLEAR(result_promise_ref); finally: - hiwire_CLEAR(promise_id); - hiwire_CLEAR(proxy); - hiwire_CLEAR(result_promise); return result; } @@ -3004,7 +2986,7 @@ JsVal JsMethod_ConvertArgs(PyObject* const* pyargs, Py_ssize_t nargs, PyObject* kwnames, - JsRef proxies) + JsVal proxies) { JsVal jsargs = JS_NULL; JsRef idarg = NULL; @@ -3169,19 +3151,18 @@ JsMethod_Vectorcall(PyObject* self, PyObject* kwnames) { bool success = false; - JsRef proxies = NULL; JsVal jsresult = JS_NULL; bool destroy_args = true; PyObject* pyresult = NULL; + JsVal proxies = JsvArray_New(); // Recursion error? FAIL_IF_NONZERO(Py_EnterRecursiveCall(" while calling a JavaScript object")); - proxies = JsArray_New(); JsVal jsargs = JsMethod_ConvertArgs(pyargs, PyVectorcall_NARGS(nargsf), kwnames, proxies); FAIL_IF_JS_NULL(jsargs); jsresult = JsvFunction_CallBound( - JsProxy_VAL(self), Jsv_from_ref(JsMethod_THIS(self)), jsargs); + JsProxy_VAL(self), JsRef_toVal(JsMethod_THIS(self)), jsargs); FAIL_IF_JS_NULL(jsresult); // various cases where we want to extend the lifetime of the arguments: // 1. if the return value is a promise we extend arguments lifetime until the @@ -3194,9 +3175,9 @@ JsMethod_Vectorcall(PyObject* self, !is_promise && !is_generator && JsvAsyncGenerator_Check(jsresult); destroy_args = (!is_promise) && (!is_generator) && (!is_async_generator); if (is_generator) { - jsresult = wrap_generator(jsresult, hiwire_get(proxies)); + jsresult = wrap_generator(jsresult, proxies); } else if (is_async_generator) { - jsresult = wrap_async_generator(jsresult, hiwire_get(proxies)); + jsresult = wrap_async_generator(jsresult, proxies); } FAIL_IF_JS_NULL(jsresult); if (is_promise) { @@ -3205,8 +3186,7 @@ JsMethod_Vectorcall(PyObject* self, // Instead we return a Future. When the promise is ready, we resolve the // Future with the result from the Promise and destroy the arguments and // result. - pyresult = wrap_promise( - jsresult, get_async_js_call_done_callback(hiwire_get(proxies))); + pyresult = wrap_promise(jsresult, get_async_js_call_done_callback(proxies)); } else { pyresult = js2python_val(jsresult); } @@ -3219,15 +3199,14 @@ JsMethod_Vectorcall(PyObject* self, // If we succeeded and the result was a promise then we destroy the // arguments in async_done_callback instead of here. Otherwise, destroy the // arguments and return value now. - if (!Jsv_is_null(jsresult) && pyproxy_Check(jsresult)) { + if (!JsvNull_Check(jsresult) && pyproxy_Check(jsresult)) { // TODO: don't destroy proxies with roundtrip = true? - JsvArray_Push(hiwire_get(proxies), jsresult); + JsvArray_Push(proxies, jsresult); } destroy_proxies(proxies, &PYPROXY_DESTROYED_AT_END_OF_FUNCTION_CALL); } else { gc_register_proxies(proxies); } - hiwire_CLEAR(proxies); if (!success) { Py_CLEAR(pyresult); } @@ -3248,13 +3227,12 @@ JsMethod_Construct(PyObject* self, PyObject* kwnames) { bool success = false; - JsRef proxies = NULL; PyObject* pyresult = NULL; + JsVal proxies = JsvArray_New(); // Recursion error? FAIL_IF_NONZERO(Py_EnterRecursiveCall(" in JsMethod_Construct")); - proxies = JsArray_New(); JsVal jsargs = JsMethod_ConvertArgs(pyargs, nargs, kwnames, proxies); FAIL_IF_JS_NULL(jsargs); JsVal jsresult = JsvFunction_Construct(JsProxy_VAL(self), jsargs); @@ -3269,7 +3247,6 @@ JsMethod_Construct(PyObject* self, "This borrowed proxy was automatically destroyed. Try using " "create_proxy or create_once_callable."); destroy_proxies(proxies, &msg); - hiwire_CLEAR(proxies); if (!success) { Py_CLEAR(pyresult); } @@ -4237,8 +4214,7 @@ JsProxy_get_subtype(int flags) #define SET_FLAG_IF_HAS_METHOD(flag, meth) \ SET_FLAG_IF(flag, hasMethod(obj, meth)) -EM_JS_NUM(int, JsProxy_compute_typeflags, (JsRef idobj), { - let obj = Hiwire.get_value(idobj); +EM_JS_NUM(int, JsProxy_compute_typeflags, (JsVal obj), { let type_flags = 0; // clang-format off if (API.isPyProxy(obj) && !pyproxyIsAlive(obj)) { @@ -4359,7 +4335,7 @@ JsProxy_create_with_type(int type_flags, JsRef object, JsRef this) PyObject* JsProxy_create_objmap(JsRef object, bool objmap) { - int typeflags = JsProxy_compute_typeflags(object); + int typeflags = JsProxy_compute_typeflags(hiwire_get(object)); if (typeflags == 0 && objmap) { typeflags |= IS_OBJECT_MAP; } @@ -4380,7 +4356,7 @@ JsProxy_create_with_this(JsRef object, JsRef this) // Comlink proxies are weird and break our feature detection pretty badly. type_flags = IS_CALLABLE | IS_AWAITABLE | IS_ARRAY; } else { - type_flags = JsProxy_compute_typeflags(object); + type_flags = JsProxy_compute_typeflags(hiwire_get(object)); if (type_flags == -1) { fail_test(); PyErr_SetString(internal_error, @@ -4419,6 +4395,12 @@ JsProxy_AsJs(PyObject* x) return hiwire_incref(js_proxy->js); } +JsVal +JsProxy_Val(PyObject* x) +{ + return JsProxy_VAL(x); +} + static PyMethodDef methods[] = { { "hiwire_id", diff --git a/src/core/jsproxy.h b/src/core/jsproxy.h index 5561a01ac30..2a454fc8193 100644 --- a/src/core/jsproxy.h +++ b/src/core/jsproxy.h @@ -14,7 +14,7 @@ * */ int -JsProxy_compute_typeflags(JsRef obj); +JsProxy_compute_typeflags(JsVal obj); PyObject* JsProxy_create_with_type(int type_flags, JsRef object, JsRef this); @@ -59,6 +59,9 @@ JsProxy_Check(PyObject* x); JsRef JsProxy_AsJs(PyObject* x); +JsVal +JsProxy_Val(PyObject* x); + /** Initialize global state for JsProxy functionality. */ int JsProxy_init(PyObject* core_module); diff --git a/src/core/pyproxy.c b/src/core/pyproxy.c index 83636a9430e..70963b288fc 100644 --- a/src/core/pyproxy.c +++ b/src/core/pyproxy.c @@ -50,30 +50,24 @@ _Py_IDENTIFIER(athrow); EM_JS(int, pyproxy_Check, (JsVal val), { return API.isPyProxy(val); }); -EM_JS(PyObject*, pyproxy_AsPyObject, (JsRef x), { - if (x == 0) { - return 0; - } - let val = Hiwire.get_value(x); +EM_JS(PyObject*, pyproxy_AsPyObject, (JsVal val), { if (!API.isPyProxy(val)) { return 0; } return Module.PyProxy_getPtr(val); }); -EM_JS(void, destroy_proxies, (JsRef proxies_id, Js_Identifier* msg_ptr), { +EM_JS(void, destroy_proxies, (JsVal proxies, Js_Identifier* msg_ptr), { let msg = undefined; if (msg_ptr) { msg = Hiwire.get_value(_JsString_FromId(msg_ptr)); } - let proxies = Hiwire.get_value(proxies_id); for (let px of proxies) { Module.pyproxy_destroy(px, msg, false); } }); -EM_JS(void, gc_register_proxies, (JsRef proxies_id), { - let proxies = Hiwire.get_value(proxies_id); +EM_JS(void, gc_register_proxies, (JsVal proxies), { for (let px of proxies) { Module.gc_register_proxy(Module.PyProxy_getAttrs(px).shared); } @@ -413,7 +407,7 @@ _pyproxy_getattr(PyObject* pyobj, JsVal key, JsVal proxyCache) int is_method = _PyObject_GetMethod(pyobj, pykey, &pydescr); FAIL_IF_NULL(pydescr); JsVal cached_proxy = proxy_cache_get(proxyCache, pydescr); /* borrowed */ - if (!Jsv_is_null(cached_proxy)) { + if (!JsvNull_Check(cached_proxy)) { result = cached_proxy; goto success; } @@ -560,7 +554,6 @@ _pyproxy_slice_assign(PyObject* pyobj, PyObject* pyval = NULL; PyObject* pyresult = NULL; JsVal jsresult = JS_NULL; - JsRef proxies = NULL; pyval = js2python_val(val); @@ -571,14 +564,12 @@ _pyproxy_slice_assign(PyObject* pyobj, pyresult = PySequence_GetSlice(pyobj, start, stop); FAIL_IF_NULL(pyresult); FAIL_IF_MINUS_ONE(PySequence_SetSlice(pyobj, start, stop, pyval)); - proxies = JsArray_New(); - FAIL_IF_NULL(proxies); - jsresult = Jsv_pop_ref(python2js_with_depth(pyresult, 1, proxies)); + JsVal proxies = JsvArray_New(); + jsresult = JsRef_pop(python2js_with_depth(pyresult, 1, proxies)); finally: Py_CLEAR(pyresult); Py_CLEAR(pyval); - hiwire_CLEAR(proxies); return jsresult; } @@ -1244,8 +1235,8 @@ _pyproxy_get_buffer(buffer_struct* target, PyObject* ptrobj) // case, shape, strides and suboffsets MUST be NULL." // https://docs.python.org/3/c-api/buffer.html#c.Py_buffer.ndim result.largest_ptr += view.itemsize; - result.shape = JsArray_New(); - result.strides = JsArray_New(); + result.shape = hiwire_new(JsvArray_New()); + result.strides = hiwire_new(JsvArray_New()); result.c_contiguous = true; result.f_contiguous = true; goto success; @@ -1294,21 +1285,19 @@ _pyproxy_get_buffer(buffer_struct* target, PyObject* ptrobj) } // clang-format off -EM_JS_REF(JsRef, +EM_JS_REF(JsVal, pyproxy_new_ex, (PyObject * ptrobj, bool capture_this, bool roundtrip, bool gcRegister), { - return Hiwire.new_value( - Module.pyproxy_new(ptrobj, { + return Module.pyproxy_new(ptrobj, { props: { captureThis: !!capture_this, roundtrip: !!roundtrip }, gcRegister, - }) - ); + }); }); // clang-format on -EM_JS_REF(JsRef, pyproxy_new, (PyObject * ptrobj), { - return Hiwire.new_value(Module.pyproxy_new(ptrobj)); +EM_JS_REF(JsVal, pyproxy_new, (PyObject * ptrobj), { + return Module.pyproxy_new(ptrobj); }); /** @@ -1317,7 +1306,7 @@ EM_JS_REF(JsRef, pyproxy_new, (PyObject * ptrobj), { * releases it. Useful for the "finally" wrapper on a JsProxy of a promise, and * also exposed in the pyodide Python module. */ -EM_JS_REF(JsRef, create_once_callable, (PyObject * obj), { +EM_JS_REF(JsVal, create_once_callable, (PyObject * obj), { _Py_IncRef(obj); let alreadyCalled = false; function wrapper(... args) @@ -1341,13 +1330,13 @@ EM_JS_REF(JsRef, create_once_callable, (PyObject * obj), { _Py_DecRef(obj); }; Module.finalizationRegistry.register(wrapper, [ obj, undefined ], wrapper); - return Hiwire.new_value(wrapper); + return wrapper; }); static PyObject* create_once_callable_py(PyObject* _mod, PyObject* obj) { - JsRef ref = create_once_callable(obj); + JsRef ref = hiwire_new(create_once_callable(obj)); PyObject* result = JsProxy_create(ref); hiwire_decref(ref); return result; @@ -1455,11 +1444,7 @@ create_proxy(PyObject* self, args, nargs, kwnames, &_parser, &obj, &capture_this, &roundtrip)) { return NULL; } - - JsRef ref = pyproxy_new_ex(obj, capture_this, roundtrip, true); - PyObject* result = JsProxy_create(ref); - hiwire_decref(ref); - return result; + return JsProxy_create_val(pyproxy_new_ex(obj, capture_this, roundtrip, true)); } static PyMethodDef methods[] = { diff --git a/src/core/pyproxy.h b/src/core/pyproxy.h index 580578abbf4..fc8f0bebb18 100644 --- a/src/core/pyproxy.h +++ b/src/core/pyproxy.h @@ -10,10 +10,10 @@ // This implements the JavaScript Proxy handler interface as defined here: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy -JsRef +JsVal pyproxy_new_ex(PyObject* obj, bool capture_this, bool roundtrip, bool register); -JsRef +JsVal pyproxy_new(PyObject* obj); /** @@ -30,16 +30,16 @@ pyproxy_Check(JsVal x); * is not NULL or a valid JsRef. */ PyObject* -pyproxy_AsPyObject(JsRef x); +pyproxy_AsPyObject(JsVal x); /** * Destroy a list of PyProxies. */ void -destroy_proxies(JsRef proxies_id, Js_Identifier* msg); +destroy_proxies(JsVal proxies, Js_Identifier* msg); void -gc_register_proxies(JsRef proxies_id); +gc_register_proxies(JsVal proxies); /** * Destroy a PyProxy. @@ -53,7 +53,7 @@ destroy_proxy(JsVal proxy, Js_Identifier* msg); * decremented. The Proxy also has a "destroy" API that can decrement the * reference count without calling the function. */ -JsRef +JsVal create_once_callable(PyObject* obj); /** diff --git a/src/core/pyproxy.ts b/src/core/pyproxy.ts index 34f92db8c48..fd1b313c3fc 100644 --- a/src/core/pyproxy.ts +++ b/src/core/pyproxy.ts @@ -727,15 +727,15 @@ export class PyProxy { } = {}): any { let ptrobj = _getPtr(this); let idresult; - let proxies_id; + let proxies; let dict_converter_id = 0; let default_converter_id = 0; if (!create_pyproxies) { - proxies_id = 0; + proxies = null; } else if (pyproxies) { - proxies_id = Hiwire.new_value(pyproxies); + proxies = pyproxies; } else { - proxies_id = Hiwire.new_value([]); + proxies = []; } if (dict_converter) { dict_converter_id = Hiwire.new_value(dict_converter); @@ -748,7 +748,7 @@ export class PyProxy { idresult = _python2js_custom( ptrobj, depth, - proxies_id, + proxies, dict_converter_id, default_converter_id, ); @@ -756,7 +756,6 @@ export class PyProxy { } catch (e) { API.fatal_error(e); } finally { - Hiwire.decref(proxies_id); Hiwire.decref(dict_converter_id); Hiwire.decref(default_converter_id); } diff --git a/src/core/python2js.c b/src/core/python2js.c index 8058bb3aca9..825ba76accd 100644 --- a/src/core/python2js.c +++ b/src/core/python2js.c @@ -43,15 +43,13 @@ typedef struct ConversionContext_s JsRef _python2js(ConversionContext context, PyObject* x); +// clang-format off EM_JS(void, - _python2js_addto_postprocess_list, - (JsRef idlist, JsRef idparent, JsRef idkey, PyObject* value), - { - const list = Hiwire.get_value(idlist); - const parent = Hiwire.get_value(idparent); - const key = Hiwire.get_value(idkey); - list.push([ parent, key, value ]); - }); +_python2js_addto_postprocess_list, +(JsVal list, JsVal parent, JsVal key, PyObject* value), { + list.push([ parent, key, value ]); +}); +// clang-format on EM_JS(void, _python2js_handle_postprocess_list, (JsRef idlist, JsRef idcache), { const list = Hiwire.get_value(idlist); @@ -206,11 +204,10 @@ _python2js_sequence(ConversionContext context, PyObject* x) bool success = false; PyObject* pyitem = NULL; JsRef jsitem = NULL; - // result: - JsRef jsarray = NULL; - jsarray = JsArray_New(); - FAIL_IF_MINUS_ONE(_python2js_add_to_cache(context.cache, x, jsarray)); + JsVal jsarray = JsvArray_New(); + JsRef jsarray_ref = hiwire_new(jsarray); + FAIL_IF_MINUS_ONE(_python2js_add_to_cache(context.cache, x, jsarray_ref)); Py_ssize_t length = PySequence_Size(x); FAIL_IF_MINUS_ONE(length); for (Py_ssize_t i = 0; i < length; ++i) { @@ -219,12 +216,11 @@ _python2js_sequence(ConversionContext context, PyObject* x) jsitem = _python2js(context, pyitem); FAIL_IF_NULL(jsitem); if (jsitem == Js_novalue) { - JsRef index = hiwire_int(JsArray_Push_unchecked(jsarray, Js_null)); + JsVal index = JsvInt(JsvArray_Push(jsarray, JS_NULL)); _python2js_addto_postprocess_list( - context.jspostprocess_list, jsarray, index, pyitem); - hiwire_CLEAR(index); + hiwire_get(context.jspostprocess_list), jsarray, index, pyitem); } else { - JsArray_Push_unchecked(jsarray, jsitem); + JsvArray_Push(jsarray, hiwire_get(jsitem)); } Py_CLEAR(pyitem); hiwire_CLEAR(jsitem); @@ -232,11 +228,9 @@ _python2js_sequence(ConversionContext context, PyObject* x) success = true; finally: Py_CLEAR(pyitem); + hiwire_CLEAR(jsarray_ref); hiwire_CLEAR(jsitem); - if (!success) { - hiwire_CLEAR(jsarray); - } - return jsarray; + return success ? hiwire_new(jsarray) : NULL; } /** @@ -268,8 +262,10 @@ _python2js_dict(ConversionContext context, PyObject* x) jsval = _python2js(context, pyval); FAIL_IF_NULL(jsval); if (jsval == Js_novalue) { - _python2js_addto_postprocess_list( - context.jspostprocess_list, jsdict, jskey, pyval); + _python2js_addto_postprocess_list(hiwire_get(context.jspostprocess_list), + hiwire_get(jsdict), + hiwire_get(jskey), + pyval); } else { FAIL_IF_MINUS_ONE( context.dict_add_keyvalue(context, jsdict, jskey, jsval)); @@ -425,9 +421,9 @@ _python2js_deep(ConversionContext context, PyObject* x) return python2js__default_converter(context.jscontext, x); } if (context.proxies) { - JsRef proxy = pyproxy_new(x); - JsArray_Push_unchecked(context.proxies, proxy); - return proxy; + JsVal proxy = pyproxy_new(x); + JsvArray_Push(hiwire_get(context.proxies), proxy); + return hiwire_new(proxy); } PyErr_SetString(conversion_error, "No conversion known for x."); finally: @@ -501,7 +497,7 @@ _python2js(ConversionContext context, PyObject* x) if (context.default_converter) { return python2js__default_converter(context.jscontext, x); } - return python2js_track_proxies(x, context.proxies, true); + return python2js_track_proxies(x, hiwire_get(context.proxies), true); } else { context.depth--; return _python2js_deep(context, x); @@ -516,20 +512,20 @@ _python2js(ConversionContext context, PyObject* x) * */ JsRef -python2js_inner(PyObject* x, JsRef proxies, bool track_proxies, bool gc_register) +python2js_inner(PyObject* x, JsVal proxies, bool track_proxies, bool gc_register) { RETURN_IF_HAS_VALUE(_python2js_immutable(x)); RETURN_IF_HAS_VALUE(_python2js_proxy(x)); - if (track_proxies && proxies == NULL) { + if (track_proxies && JsvNull_Check(proxies)) { PyErr_SetString(conversion_error, "No conversion known for x."); FAIL(); } - JsRef proxy = pyproxy_new_ex(x, false, false, gc_register); - FAIL_IF_NULL(proxy); + JsVal proxy = pyproxy_new_ex(x, false, false, gc_register); + FAIL_IF_JS_NULL(proxy); if (track_proxies) { - JsArray_Push_unchecked(proxies, proxy); + JsvArray_Push(proxies, proxy); } - return proxy; + return hiwire_new(proxy); finally: if (PyErr_Occurred()) { if (!PyErr_ExceptionMatches(conversion_error)) { @@ -552,7 +548,7 @@ python2js_inner(PyObject* x, JsRef proxies, bool track_proxies, bool gc_register * of creating a proxy. */ JsRef -python2js_track_proxies(PyObject* x, JsRef proxies, bool gc_register) +python2js_track_proxies(PyObject* x, JsVal proxies, bool gc_register) { return python2js_inner(x, proxies, true, gc_register); } @@ -564,7 +560,7 @@ python2js_track_proxies(PyObject* x, JsRef proxies, bool gc_register) EMSCRIPTEN_KEEPALIVE JsRef python2js(PyObject* x) { - return python2js_inner(x, NULL, false, true); + return python2js_inner(x, JS_NULL, false, true); } EMSCRIPTEN_KEEPALIVE JsVal @@ -595,7 +591,7 @@ _JsMap_Set(ConversionContext context, JsRef map, JsRef key, JsRef value) * down to depth "depth". */ EMSCRIPTEN_KEEPALIVE JsRef -python2js_with_depth(PyObject* x, int depth, JsRef proxies) +python2js_with_depth(PyObject* x, int depth, JsVal proxies) { return python2js_custom(x, depth, proxies, NULL, NULL); } @@ -603,7 +599,7 @@ python2js_with_depth(PyObject* x, int depth, JsRef proxies) static JsRef _JsArray_New(ConversionContext context) { - return JsArray_New(); + return hiwire_new(JsvArray_New()); } EM_JS_NUM(int, @@ -704,7 +700,7 @@ python2js_custom__create_jscontext, EMSCRIPTEN_KEEPALIVE JsRef python2js_custom(PyObject* x, int depth, - JsRef proxies, + JsVal proxies, JsRef dict_converter, JsRef default_converter) { @@ -712,14 +708,10 @@ python2js_custom(PyObject* x, if (cache == NULL) { return NULL; } - JsRef postprocess_list = JsArray_New(); - if (postprocess_list == NULL) { - hiwire_CLEAR(cache); - return NULL; - } + JsRef postprocess_list = hiwire_new(JsvArray_New()); ConversionContext context = { .cache = cache, .depth = depth, - .proxies = proxies, + .proxies = JsRef_new(proxies), .jscontext = NULL, .default_converter = false, .jspostprocess_list = postprocess_list }; @@ -743,9 +735,8 @@ python2js_custom(PyObject* x, JsRef result = _python2js(context, x); _python2js_handle_postprocess_list(context.jspostprocess_list, context.cache); hiwire_CLEAR(context.jspostprocess_list); - if (context.jscontext) { - hiwire_CLEAR(context.jscontext); - } + hiwire_CLEAR(context.jscontext); + hiwire_CLEAR(context.proxies); // Destroy the cache. Because the cache has raw JsRefs inside, we need to // manually dealloc them. _python2js_destroy_cache(cache); @@ -818,28 +809,28 @@ to_js(PyObject* self, Py_INCREF(obj); return obj; } - JsRef proxies = NULL; JsRef js_dict_converter = NULL; JsRef js_default_converter = NULL; JsRef js_result = NULL; PyObject* py_result = NULL; + JsVal proxies; if (!create_proxies) { - proxies = NULL; + proxies = JS_NULL; } else if (pyproxies) { if (!JsProxy_Check(pyproxies)) { PyErr_SetString(PyExc_TypeError, - "Expected a JsProxy for the pyproxies argument"); + "Expected a JsArray for the pyproxies argument"); FAIL(); } - proxies = JsProxy_AsJs(pyproxies); - if (!JsArray_Check(proxies)) { + proxies = JsProxy_Val(pyproxies); + if (!JsvArray_Check(proxies)) { PyErr_SetString(PyExc_TypeError, - "Expected a Js Array for the pyproxies argument"); + "Expected a JsArray for the pyproxies argument"); FAIL(); } } else { - proxies = JsArray_New(); + proxies = JsvArray_New(); } if (py_dict_converter) { js_dict_converter = python2js(py_dict_converter); @@ -857,13 +848,12 @@ to_js(PyObject* self, py_result = js2python(js_result); } finally: - if (pyproxy_Check(Jsv_from_ref(js_dict_converter))) { + if (pyproxy_Check(JsRef_toVal(js_dict_converter))) { destroy_proxy(hiwire_get(js_dict_converter), NULL); } - if (pyproxy_Check(Jsv_from_ref(js_default_converter))) { + if (pyproxy_Check(JsRef_toVal(js_default_converter))) { destroy_proxy(hiwire_get(js_default_converter), NULL); } - hiwire_CLEAR(proxies); hiwire_CLEAR(js_dict_converter); hiwire_CLEAR(js_default_converter); hiwire_CLEAR(js_result); @@ -877,8 +867,8 @@ to_js(PyObject* self, // method, that will get called (is this a good thing??) // 3. destroy_proxies won't destroy proxies with roundtrip set to true, this // will. -EM_JS_NUM(errcode, destroy_proxies_js, (JsRef proxies_id), { - for (let proxy of Hiwire.get_value(proxies_id)) { +EM_JS_NUM(errcode, destroy_proxies_js, (JsVal proxies_id), { + for (const proxy of proxies_id) { proxy.destroy(); } }) @@ -892,10 +882,9 @@ destroy_proxies_(PyObject* self, PyObject* arg) return NULL; } bool success = false; - JsRef proxies = NULL; - proxies = JsProxy_AsJs(arg); - if (!JsArray_Check(proxies)) { + JsVal proxies = JsProxy_Val(arg); + if (!JsvArray_Check(proxies)) { PyErr_SetString(PyExc_TypeError, "Expected a Js Array for the pyproxies argument"); FAIL(); @@ -904,7 +893,6 @@ destroy_proxies_(PyObject* self, PyObject* arg) success = true; finally: - hiwire_CLEAR(proxies); if (success) { Py_RETURN_NONE; } else { diff --git a/src/core/python2js.h b/src/core/python2js.h index debd5c393b6..3970da9e3ae 100644 --- a/src/core/python2js.h +++ b/src/core/python2js.h @@ -27,7 +27,7 @@ python2js_val(PyObject* x); * the proxy to the array if one is created. */ JsRef -python2js_track_proxies(PyObject* x, JsRef proxies, bool gc_register); +python2js_track_proxies(PyObject* x, JsVal proxies, bool gc_register); /** * Convert a Python object to a JavaScript object, copying standard collections @@ -41,7 +41,7 @@ python2js_track_proxies(PyObject* x, JsRef proxies, bool gc_register); * exception. */ JsRef -python2js_with_depth(PyObject* x, int depth, JsRef proxies); +python2js_with_depth(PyObject* x, int depth, JsVal proxies); /** * dict_converter should be a JavaScript function that converts an Iterable of @@ -51,7 +51,7 @@ python2js_with_depth(PyObject* x, int depth, JsRef proxies); JsRef python2js_custom(PyObject* x, int depth, - JsRef proxies, + JsVal proxies, JsRef dict_converter, JsRef default_converter); diff --git a/src/js/types.ts b/src/js/types.ts index 9c74fb82d99..2d0759dd1e5 100644 --- a/src/js/types.ts +++ b/src/js/types.ts @@ -82,7 +82,7 @@ declare global { export const _python2js_custom: ( obj: number, depth: number, - proxies: number, + proxies: PyProxy[] | null, dict_converter: number, default_converter: number, ) => number; diff --git a/src/tests/test_jsproxy.py b/src/tests/test_jsproxy.py index c19bae1503d..ee08045970d 100644 --- a/src/tests/test_jsproxy.py +++ b/src/tests/test_jsproxy.py @@ -105,7 +105,7 @@ def test_jsproxy_document(selenium): @pytest.mark.parametrize( "js,result", [ - ("{}", False), + ("{}", True), ("{a:1}", True), ("[]", False), ("[1]", True), @@ -113,8 +113,8 @@ def test_jsproxy_document(selenium): ("new Map([[0, 0]])", True), ("new Set()", False), ("new Set([0])", True), - ("class T {}; T", True), - ("class T {}; new T()", True), + ("class T {}", True), + ("new (class T {})", True), ("new Uint8Array(0)", False), ("new Uint8Array(1)", True), ("new ArrayBuffer(0)", False), @@ -125,7 +125,7 @@ def test_jsproxy_document(selenium): def test_jsproxy_bool(selenium, js, result): from pyodide.code import run_js - assert bool(run_js(js)) == result + assert bool(run_js(f"({js})")) == result @pytest.mark.xfail_browsers(node="No document in node") @@ -252,7 +252,7 @@ def test_jsproxy_implicit_iter(selenium): ) == [1, 2, 3] -def test_jsproxy_call(selenium): +def test_jsproxy_call1(selenium): assert ( selenium.run_js( """ @@ -272,6 +272,14 @@ def test_jsproxy_call(selenium): ) +@run_in_pyodide +def test_jsproxy_call2(selenium): + from pyodide.code import run_js + + f = run_js("(function(){ return arguments.length; })") + assert [f(*range(n)) for n in range(10)] == list(range(10)) + + def test_jsproxy_call_kwargs(selenium): assert ( selenium.run_js( @@ -908,7 +916,7 @@ def test_mixins_errors_1(selenium): set(){ return false; }, delete(){ return false; }, }; - await pyodide.runPythonAsync(` + pyodide.runPython(` from unittest import TestCase raises = TestCase().assertRaises from js import a, b @@ -1374,6 +1382,30 @@ def test_jsarray_reverse(selenium): assert b.to_bytes() == bytes(l) +@run_in_pyodide +def test_array_empty_slot(selenium): + import pytest + + from pyodide.code import run_js + + a = run_js("[1,,2]") + with pytest.raises(IndexError): + a[1] + + assert a.to_py() == [1, None, 2] + del a[1] + assert a.to_py() == [1, 2] + + +@run_in_pyodide +def test_array_pop(selenium): + from pyodide.code import run_js + + a = run_js("[1, 2, 3]") + assert a.pop() == 3 + assert a.pop(0) == 1 + + @std_hypothesis_settings @given(l=st.lists(st.integers()), slice=st.slices(50)) @example(l=[0, 1], slice=slice(None, None, -1)) diff --git a/src/tests/test_typeconversions.py b/src/tests/test_typeconversions.py index 0d884a8db57..bc87cb7db2f 100644 --- a/src/tests/test_typeconversions.py +++ b/src/tests/test_typeconversions.py @@ -1081,20 +1081,15 @@ def test_memoryview_conversion(selenium): def test_python2js_with_depth(selenium): selenium.run_js( """ - let x = pyodide.runPython(` + const x = pyodide.runPython(` class Test: pass [Test(), [Test(), [Test(), [Test()]]]] `); - let Module = pyodide._module; - let proxies = []; - let proxies_id = Module.hiwire.new_value(proxies); - - let result = Module.hiwire.pop_value(Module._python2js_with_depth(Module.PyProxy_getPtr(x), -1, proxies_id)); - Module.hiwire.decref(proxies_id); - + const Module = pyodide._module; + const proxies = []; + const result = Module.hiwire.pop_value(Module._python2js_with_depth(Module.PyProxy_getPtr(x), -1, proxies)); assert(() => proxies.length === 4); - - let result_proxies = [result[0], result[1][0], result[1][1][0], result[1][1][1][0]]; + const result_proxies = [result[0], result[1][0], result[1][1][0], result[1][1][1][0]]; const sortFunc = (x, y) => Module.PyProxy_getPtr(x) < Module.PyProxy_getPtr(y); proxies.sort(sortFunc); result_proxies.sort(sortFunc); @@ -1102,7 +1097,7 @@ class Test: pass assert(() => proxies[i] == result_proxies[i]); } x.destroy(); - for(let px of proxies){ + for(const px of proxies){ px.destroy(); } """