Skip to content

Commit a0c7c14

Browse files
authored
Merge pull request #198 from Distributive-Network/philippecaleb/fix/191
fix(JSObjectProxy): do not allow '|' operand on dicts and JSObjectProxys if python version is lower than 3.9
2 parents 3d46e55 + 2595f76 commit a0c7c14

File tree

4 files changed

+138
-1
lines changed

4 files changed

+138
-1
lines changed

include/JSObjectProxy.hh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,25 @@ public:
129129
* @return the string representation (a PyUnicodeObject) on success, NULL on failure
130130
*/
131131
static PyObject *JSObjectProxy_repr(JSObjectProxy *self);
132+
133+
/**
134+
* @brief Set union method
135+
*
136+
* @param self - The JSObjectProxy
137+
* @param other - The other PyObject to be or'd, expected to be dict or JSObjectProxy
138+
* @return PyObject* The resulting new dict
139+
*/
140+
static PyObject *JSObjectProxy_or(JSObjectProxy *self, PyObject *other);
141+
142+
/**
143+
* @brief Set union method, in place
144+
*
145+
* @param self - The JSObjectProxy
146+
* @param other - The other PyObject to be or'd, expected to be dict or JSObjectProxy
147+
* @return PyObject* The resulting new dict, must be same object as self
148+
*/
149+
static PyObject *JSObjectProxy_ior(JSObjectProxy *self, PyObject *other);
150+
132151
};
133152

134153

@@ -146,6 +165,11 @@ static PySequenceMethods JSObjectProxy_sequence_methods = {
146165
.sq_contains = (objobjproc)JSObjectProxyMethodDefinitions::JSObjectProxy_contains
147166
};
148167

168+
static PyNumberMethods JSObjectProxy_number_methods = {
169+
.nb_or = (binaryfunc)JSObjectProxyMethodDefinitions::JSObjectProxy_or,
170+
.nb_inplace_or = (binaryfunc)JSObjectProxyMethodDefinitions::JSObjectProxy_ior
171+
};
172+
149173
/**
150174
* @brief Struct for the JSObjectProxyType, used by all JSObjectProxy objects
151175
*/

src/JSObjectProxy.cc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
#include <Python.h>
2323

24+
#include <object.h>
25+
2426
JSContext *GLOBAL_CX; /**< pointer to PythonMonkey's JSContext */
2527

2628
bool keyToId(PyObject *key, JS::MutableHandleId idp) {
@@ -258,3 +260,61 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_repr(JSObjectProxy *self
258260
PyDict_DelItem(tsDict, cyclicKey);
259261
return str;
260262
}
263+
264+
PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_or(JSObjectProxy *self, PyObject *other) {
265+
#if PY_VERSION_HEX < 0x03090000
266+
// | is not supported on dicts in python3.8 or less, so only allow if both
267+
// operands are JSObjectProxy
268+
if (!PyObject_TypeCheck(self, &JSObjectProxyType) || !PyObject_TypeCheck(other, &JSObjectProxyType)) {
269+
Py_RETURN_NOTIMPLEMENTED;
270+
}
271+
#endif
272+
if (!PyDict_Check(self) || !PyDict_Check(other)) {
273+
Py_RETURN_NOTIMPLEMENTED;
274+
}
275+
276+
if (!PyObject_TypeCheck(self, &JSObjectProxyType) && PyObject_TypeCheck(other, &JSObjectProxyType)) {
277+
return PyDict_Type.tp_as_number->nb_or((PyObject *)&(self->dict), other);
278+
} else {
279+
JS::Rooted<JS::ValueArray<3>> args(GLOBAL_CX);
280+
args[0].setObjectOrNull(JS_NewPlainObject(GLOBAL_CX));
281+
args[1].setObjectOrNull(self->jsObject); // this is null is left operand is real dict
282+
JS::RootedValue jValueOther(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, other));
283+
args[2].setObject(jValueOther.toObject());
284+
285+
JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject));
286+
287+
// call Object.assign
288+
JS::RootedValue Object(GLOBAL_CX);
289+
JS_GetProperty(GLOBAL_CX, *global, "Object", &Object);
290+
291+
JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull());
292+
JS::RootedValue ret(GLOBAL_CX);
293+
294+
if (!JS_CallFunctionName(GLOBAL_CX, rootedObject, "assign", args, &ret)) return NULL;
295+
return pyTypeFactory(GLOBAL_CX, global, &ret)->getPyObject();
296+
}
297+
}
298+
299+
PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_ior(JSObjectProxy *self, PyObject *other) {
300+
if (!PyDict_Check(self) || !PyDict_Check(other)) {
301+
Py_RETURN_NOTIMPLEMENTED;
302+
}
303+
304+
JS::Rooted<JS::ValueArray<2>> args(GLOBAL_CX);
305+
args[0].setObjectOrNull(self->jsObject);
306+
JS::RootedValue jValueOther(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, other));
307+
args[1].setObject(jValueOther.toObject());
308+
309+
JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject));
310+
311+
// call Object.assign
312+
JS::RootedValue Object(GLOBAL_CX);
313+
JS_GetProperty(GLOBAL_CX, *global, "Object", &Object);
314+
315+
JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull());
316+
JS::RootedValue ret(GLOBAL_CX);
317+
if (!JS_CallFunctionName(GLOBAL_CX, rootedObject, "assign", args, &ret)) return NULL;
318+
Py_INCREF(self);
319+
return (PyObject *)self;
320+
}

src/modules/pythonmonkey/pythonmonkey.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ PyTypeObject JSObjectProxyType = {
7474
.tp_basicsize = sizeof(JSObjectProxy),
7575
.tp_dealloc = (destructor)JSObjectProxyMethodDefinitions::JSObjectProxy_dealloc,
7676
.tp_repr = (reprfunc)JSObjectProxyMethodDefinitions::JSObjectProxy_repr,
77+
.tp_as_number = &JSObjectProxy_number_methods,
7778
.tp_as_sequence = &JSObjectProxy_sequence_methods,
7879
.tp_as_mapping = &JSObjectProxy_mapping_methods,
7980
.tp_getattro = (getattrofunc)JSObjectProxyMethodDefinitions::JSObjectProxy_get,

tests/python/test_dicts_lists.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pythonmonkey as pm
2+
import sys
23

34
def test_eval_dicts():
45
d = {"a":1}
@@ -137,4 +138,55 @@ def test_eval_objects_jsproxy_does_not_contain():
137138

138139
def test_eval_objects_jsproxy_does_not_contain_value():
139140
a = pm.eval("({'c':5})")
140-
assert not(5 in a)
141+
assert not(5 in a)
142+
143+
def test_eval_objects_jsproxy_or():
144+
if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less
145+
a = pm.eval("({'c':5})")
146+
b = pm.eval("({'d':6})")
147+
c = a | b
148+
assert a == {'c': 5.0}
149+
assert c == {'c': 5.0, 'd': 6.0}
150+
assert b == {'d': 6.0}
151+
152+
def test_eval_objects_jsproxy_or_true_dict_right():
153+
if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less
154+
a = pm.eval("({'c':5})")
155+
b = {'d': 6.0}
156+
c = a | b
157+
assert a == {'c': 5.0}
158+
assert c == {'c': 5.0, 'd': 6.0}
159+
assert b == {'d': 6.0}
160+
161+
def test_eval_objects_jsproxy_or_true_dict_left():
162+
if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less
163+
a = {'c':5}
164+
b = pm.eval("({'d':6})")
165+
c = a | b
166+
assert a == {'c': 5.0}
167+
assert c == {'c': 5.0, 'd': 6.0}
168+
assert b == {'d': 6.0}
169+
170+
def test_eval_objects_jsproxy_inplace_or():
171+
if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less
172+
a = pm.eval("({'c':5})")
173+
b = pm.eval("({'d':6})")
174+
a |= b
175+
assert a == {'c': 5.0, 'd': 6.0}
176+
assert b == {'d': 6.0}
177+
178+
def test_eval_objects_jsproxy_inplace_or_true_dict_right():
179+
if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less
180+
a = pm.eval("({'c':5})")
181+
b = {'d':6.0}
182+
a |= b
183+
assert a == {'c': 5.0, 'd': 6.0}
184+
assert b == {'d': 6.0}
185+
186+
def test_eval_objects_jsproxy_inplace_or_true_dict_left():
187+
if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less
188+
a = {'c':5.0}
189+
b = pm.eval("({'d':6})")
190+
a |= b
191+
assert a == {'c': 5.0, 'd': 6.0}
192+
assert b == {'d': 6.0}

0 commit comments

Comments
 (0)