diff options
| author | Damien George <damien@micropython.org> | 2024-06-24 13:03:44 +1000 |
|---|---|---|
| committer | Damien George <damien@micropython.org> | 2024-06-28 11:40:24 +1000 |
| commit | 95c19e05ffd204f8f375b6e04e4ae45770ec0dbc (patch) | |
| tree | d95ec6d7fa695d85527015699b3c7dff2b0ae260 | |
| parent | 5dff78f38edc0354e854e6c73af61c5064afe9d3 (diff) | |
webassembly/objjsproxy: Lookup attributes without testing they exist.
In JavaScript when accessing an attribute such as `obj.attr` a value of
`undefined` is returned if the attribute does not exist. This is unlike
Python semantics where an `AttributeError` is raised. Furthermore, in some
cases in JavaScript (eg a Proxy instance) `attr in obj` can return false
yet `obj.attr` is still valid and returns something other than `undefined`.
So the source of truth for whether a JavaScript attribute exists is to just
right away attempt `obj.attr`.
To more closely match these JavaScript semantics when proxying a JavaScript
object through to Python, change the attribute lookup logic on a `JsProxy`
so that it immediately attempts `obj.attr` instead of first testing if the
attribute exists via `attr in obj`.
This allows JavaScript objects which dynamically create attributes to work
correctly on the Python side, with both `obj.attr` and `obj["attr"]`. Note
that `obj["attr"]` already works in all cases because it immediately does
the subscript access without first testing if the attribute exists.
As a benefit, this new behaviour matches the Pyodide behaviour.
Signed-off-by: Damien George <damien@micropython.org>
| -rw-r--r-- | ports/webassembly/objjsproxy.c | 10 | ||||
| -rw-r--r-- | tests/ports/webassembly/js_proxy_attribute.mjs | 34 | ||||
| -rw-r--r-- | tests/ports/webassembly/js_proxy_attribute.mjs.exp | 9 |
3 files changed, 51 insertions, 2 deletions
diff --git a/ports/webassembly/objjsproxy.c b/ports/webassembly/objjsproxy.c index cbfe8be49..167d4382b 100644 --- a/ports/webassembly/objjsproxy.c +++ b/ports/webassembly/objjsproxy.c @@ -46,8 +46,14 @@ EM_JS(bool, has_attr, (int jsref, const char *str), { EM_JS(bool, lookup_attr, (int jsref, const char *str, uint32_t * out), { const base = proxy_js_ref[jsref]; const attr = UTF8ToString(str); - if (attr in base) { - let value = base[attr]; + + // Attempt to lookup the requested attribute from the base object: + // - If the value is not `undefined` then the attribute exists with that value. + // - Otherwise if the value is `undefined` and the `in` operator returns true, then + // that attribute does exist and is intended to have a value of `undefined`. + // - Otherwise, the attribute does not exist. + let value = base[attr]; + if (value !== undefined || attr in base) { if (typeof value === "function") { if (base !== globalThis) { if ("_ref" in value) { diff --git a/tests/ports/webassembly/js_proxy_attribute.mjs b/tests/ports/webassembly/js_proxy_attribute.mjs new file mode 100644 index 000000000..5d9501227 --- /dev/null +++ b/tests/ports/webassembly/js_proxy_attribute.mjs @@ -0,0 +1,34 @@ +// Test lookup of attributes on JsProxy objects. + +const mp = await (await import(process.argv[2])).loadMicroPython(); + +// Simple attribute names and values. +globalThis.obj1 = { a: 1, b: 2 }; + +// Unconventional attribute names and values. +globalThis.obj2 = { undefined: "undefined", undef: undefined }; + +// Dynamically created attribute names and values. +globalThis.obj3 = new Proxy(new Map(), { + get(map, name) { + if (!map.has(name)) { + console.log("creating attribute", name); + map.set(name, name); + } + return map.get(name); + }, +}); + +mp.runPython(` +import js + +print(js.obj1.a, js.obj1.b) +print(js.obj1["a"], js.obj1["b"]) + +print(js.obj2.undefined, js.obj2.undef) + +print(js.obj3.c) +print(js.obj3["c"]) +print(hasattr(js.obj3, "d")) +print(js.obj3.d) +`); diff --git a/tests/ports/webassembly/js_proxy_attribute.mjs.exp b/tests/ports/webassembly/js_proxy_attribute.mjs.exp new file mode 100644 index 000000000..7de85a566 --- /dev/null +++ b/tests/ports/webassembly/js_proxy_attribute.mjs.exp @@ -0,0 +1,9 @@ +1 2 +1 2 +undefined <undefined> +creating attribute c +c +c +creating attribute d +True +d |
