Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't throw when calling str on a proxy without a toString method #4574

Merged
merged 7 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/core/jslib.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,10 @@ EM_JS_VAL(JsVal, JsvObject_Values, (JsVal obj), {

EM_JS_VAL(JsVal,
JsvObject_toString, (JsVal obj), {
return obj.toString();
if (hasMethod(obj, "toString")) {
return obj.toString();
}
return Object.prototype.toString.call(obj);
});


Expand Down
3 changes: 0 additions & 3 deletions src/core/jsproxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,6 @@ JsProxy_Repr(PyObject* self)
{
JsVal repr = JsvObject_toString(JsProxy_VAL(self));
if (JsvNull_Check(repr)) {
PyErr_Format(PyExc_TypeError,
"Pyodide cannot generate a repr for this Javascript object "
"because it has no 'toString' method");
return NULL;
}
return js2python(repr);
Expand Down
67 changes: 63 additions & 4 deletions src/tests/test_jsproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1286,13 +1286,10 @@ def test_js_id(selenium):

@run_in_pyodide
def test_object_with_null_constructor(selenium):
from unittest import TestCase

from pyodide.code import run_js

o = run_js("Object.create(null)")
with TestCase().assertRaises(TypeError):
repr(o)
assert repr(o) == "[object Object]"


@pytest.mark.parametrize("n", [1 << 31, 1 << 32, 1 << 33, 1 << 63, 1 << 64, 1 << 65])
Expand Down Expand Up @@ -2557,3 +2554,65 @@ def test_js_proxy_attribute(selenium):
assert x.c is None
with pytest.raises(AttributeError):
x.d # noqa: B018


@run_in_pyodide
async def test_js_proxy_str(selenium):
import pytest

from js import Array
from pyodide.code import run_js
from pyodide.ffi import JsException

assert str(Array) == "function Array() { [native code] }"
assert str(run_js("[1,2,3]")) == "1,2,3"
assert str(run_js("Object.create(null)")) == "[object Object]"
mod = await run_js("import('data:text/javascript,')")
assert str(mod) == "[object Module]"
# accessing toString fails, should fall back to Object.prototype.toString.call
x = run_js(
"""
({
get toString() {
throw new Error();
},
[Symbol.toStringTag] : "SomeTag"
})
"""
)
assert str(x) == "[object SomeTag]"
# accessing toString succeeds but toString call throws, let exception propagate
x = run_js(
"""
({
toString() {
throw new Error("hi!");
},
})
"""
)
with pytest.raises(JsException, match="hi!"):
str(x)

# No toString method, so we fall back to Object.prototype.toString.call
# which throws, let error propagate
x = run_js(
"""
({
get [Symbol.toStringTag]() {
throw new Error("hi!");
},
});
"""
)
with pytest.raises(JsException, match="hi!"):
str(x)

# accessing toString fails, so fall back to Object.prototype.toString.call
# which also throws, let error propagate
px = run_js("(p = Proxy.revocable({}, {})); p.revoke(); p.proxy")
with pytest.raises(
JsException,
match="Cannot perform 'Object.prototype.toString' on a proxy that has been revoked",
):
str(px)