Skip to content

Commit a6303de

Browse files
Merge pull request #391 from Distributive-Network/philippe/390-fix
Support console printing of iterable proxy
2 parents fb900e8 + 5c1cd3d commit a6303de

File tree

3 files changed

+121
-12
lines changed

3 files changed

+121
-12
lines changed

src/PyIterableProxyHandler.cc

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,48 @@ static bool iterable_next(JSContext *cx, unsigned argc, JS::Value *vp) {
6666
return iter_next(cx, args, it);
6767
}
6868

69+
static bool toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp) {
70+
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
71+
72+
JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv()));
73+
if (!proxy) {
74+
return false;
75+
}
76+
77+
PyObject *self = JS::GetMaybePtrFromReservedSlot<PyObject>(proxy, PyObjectSlot);
78+
79+
_PyUnicodeWriter writer;
80+
81+
_PyUnicodeWriter_Init(&writer);
82+
83+
PyObject *s = PyObject_Repr(self);
84+
85+
if (s == nullptr) {
86+
args.rval().setString(JS_NewStringCopyZ(cx, "<cannot repr type>"));
87+
return true;
88+
}
89+
90+
int res = _PyUnicodeWriter_WriteStr(&writer, s);
91+
Py_DECREF(s);
92+
93+
if (res < 0) {
94+
args.rval().setString(JS_NewStringCopyZ(cx, "<cannot repr type>"));
95+
return true;
96+
}
97+
98+
PyObject* repr = _PyUnicodeWriter_Finish(&writer);
99+
100+
args.rval().set(jsTypeFactory(cx, repr));
101+
return true;
102+
}
103+
104+
static bool iterable_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) {
105+
return toPrimitive(cx, argc, vp);
106+
}
107+
69108
JSMethodDef PyIterableProxyHandler::iterable_methods[] = {
70109
{"next", iterable_next, 0},
110+
{"valueOf", iterable_valueOf, 0},
71111
{NULL, NULL, 0}
72112
};
73113

@@ -172,7 +212,6 @@ bool PyIterableProxyHandler::getOwnPropertyDescriptor(
172212
JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
173213
JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc
174214
) const {
175-
176215
// see if we're calling a function
177216
if (id.isString()) {
178217
for (size_t index = 0;; index++) {
@@ -196,11 +235,38 @@ bool PyIterableProxyHandler::getOwnPropertyDescriptor(
196235
}
197236
}
198237

238+
// "constructor" property
239+
bool isConstructorProperty;
240+
if (id.isString() && JS_StringEqualsLiteral(cx, id.toString(), "constructor", &isConstructorProperty) && isConstructorProperty) {
241+
JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(proxy));
242+
243+
JS::RootedObject rootedObjectPrototype(cx);
244+
if (!JS_GetClassPrototype(cx, JSProto_Object, &rootedObjectPrototype)) {
245+
return false;
246+
}
247+
248+
JS::RootedValue Object_Prototype_Constructor(cx);
249+
if (!JS_GetProperty(cx, rootedObjectPrototype, "constructor", &Object_Prototype_Constructor)) {
250+
return false;
251+
}
252+
253+
JS::RootedObject rootedObjectPrototypeConstructor(cx, Object_Prototype_Constructor.toObjectOrNull());
254+
255+
desc.set(mozilla::Some(
256+
JS::PropertyDescriptor::Data(
257+
JS::ObjectValue(*rootedObjectPrototypeConstructor),
258+
{JS::PropertyAttribute::Enumerable}
259+
)
260+
));
261+
return true;
262+
}
263+
199264
// symbol property
200265
if (id.isSymbol()) {
201266
JS::RootedSymbol rootedSymbol(cx, id.toSymbol());
267+
JS::SymbolCode symbolCode = JS::GetSymbolCode(rootedSymbol);
202268

203-
if (JS::GetSymbolCode(rootedSymbol) == JS::SymbolCode::iterator) {
269+
if (symbolCode == JS::SymbolCode::iterator) {
204270
JSFunction *newFunction = JS_NewFunction(cx, iterable_values, 0, 0, NULL);
205271
if (!newFunction) return false;
206272
JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction));
@@ -211,6 +277,18 @@ bool PyIterableProxyHandler::getOwnPropertyDescriptor(
211277
)
212278
));
213279
return true;
280+
}
281+
else if (symbolCode == JS::SymbolCode::toPrimitive) {
282+
JSFunction *newFunction = JS_NewFunction(cx, toPrimitive, 0, 0, nullptr);
283+
if (!newFunction) return false;
284+
JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction));
285+
desc.set(mozilla::Some(
286+
JS::PropertyDescriptor::Data(
287+
JS::ObjectValue(*funObj),
288+
{JS::PropertyAttribute::Enumerable}
289+
)
290+
));
291+
return true;
214292
}
215293
}
216294

src/PyObjectProxyHandler.cc

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,19 +123,27 @@ void PyObjectProxyHandler::finalize(JS::GCContext *gcx, JSObject *proxy) const {
123123
bool PyObjectProxyHandler::ownPropertyKeys(JSContext *cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const {
124124
PyObject *self = JS::GetMaybePtrFromReservedSlot<PyObject>(proxy, PyObjectSlot);
125125
PyObject *keys = PyObject_Dir(self);
126-
size_t keysLength = PyList_Size(keys);
127126

128-
PyObject *nonDunderKeys = PyList_New(0);
129-
for (size_t i = 0; i < keysLength; i++) {
130-
PyObject *key = PyList_GetItem(keys, i);
131-
if (PyObject_CallMethod(key, "startswith", "(s)", "__") == Py_False) { // if key starts with "__", ignore it
132-
PyList_Append(nonDunderKeys, key);
133-
}
134-
}
127+
if (keys != nullptr) {
128+
size_t keysLength = PyList_Size(keys);
135129

136-
size_t length = PyList_Size(nonDunderKeys);
130+
PyObject *nonDunderKeys = PyList_New(0);
131+
for (size_t i = 0; i < keysLength; i++) {
132+
PyObject *key = PyList_GetItem(keys, i);
133+
if (PyObject_CallMethod(key, "startswith", "(s)", "__") == Py_False) { // if key starts with "__", ignore it
134+
PyList_Append(nonDunderKeys, key);
135+
}
136+
}
137137

138-
return handleOwnPropertyKeys(cx, nonDunderKeys, length, props);
138+
return handleOwnPropertyKeys(cx, nonDunderKeys, PyList_Size(nonDunderKeys), props);
139+
}
140+
else {
141+
if (PyErr_Occurred()) {
142+
PyErr_Clear();
143+
}
144+
145+
return handleOwnPropertyKeys(cx, PyList_New(0), 0, props);
146+
}
139147
}
140148

141149
bool PyObjectProxyHandler::delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,

tests/python/test_objects.py

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

34

45
def test_eval_pyobjects():
@@ -148,3 +149,25 @@ def __init__(self):
148149

149150
o = MyClass()
150151
assert '[object Object]' == pm.eval("(obj) => { return obj.toLocaleString(); }")(o)
152+
153+
154+
def test_toPrimitive_iterable():
155+
iterable = iter([1,2])
156+
toPrimitive = pm.eval("(obj) => { return obj[Symbol.toPrimitive]; }")(iterable)
157+
assert repr(toPrimitive).__contains__("<pythonmonkey.JSFunctionProxy object at")
158+
159+
160+
def test_constructor_iterable():
161+
iterable = iter([1,2])
162+
constructor = pm.eval("(obj) => { return obj.constructor; }")(iterable)
163+
assert repr(constructor).__contains__("<pythonmonkey.JSFunctionProxy object at")
164+
165+
166+
def test_toPrimitive_stdin():
167+
toPrimitive = pm.eval("(obj) => { return obj[Symbol.toPrimitive]; }")(sys.stdin)
168+
assert repr(toPrimitive).__contains__("<pythonmonkey.JSFunctionProxy object at")
169+
170+
171+
def test_constructor_stdin():
172+
constructor = pm.eval("(obj) => { return obj.constructor; }")(sys.stdin)
173+
assert repr(constructor).__contains__("<pythonmonkey.JSFunctionProxy object at")

0 commit comments

Comments
 (0)