Repository: micropython/micropython (2025-07-15T08:45:08Z)

Issue:
webassembly: getting JS symbols implicitly also throws errors
### Port, board and/or hardware

webassembly

### MicroPython version

MicroPython webassembly latest from *npm*

### Reproduction

In here we solved the `in` operation check https://github.com/micropython/micropython/pull/17604 but if any libraries tries to read the stringified version of a reference we have the same issue via the `get` proxy trap.

```js
function test(value) {
  return Object.prototype.toString.call(value);
}
```

```python
import js
js.test({})
```

Result:

```
RuntimeError: Aborted(Assertion failed: stringToUTF8Array expects a string (got symbol))
```

### Expected behaviour

We covered `Symbol.iterator` we should cover also `Symbol.toStringTag` as that's implicitly retrieved when `Object.prototype.toString.call(ref)` happens, which is a very common way to retrieve the *kind* of the reference as it returns:

* `[object Object]`
* `[object Array]`
* `[object Null]`
* `[object Function]`
* `[object Anything]`

This practice is particularly useful when unknown kinds are around and any serializer would like to guess how to handle these, and it's currently breaking a specific PyScript use case which is also pretty common when used in IoT contrained environments.

### Observed behaviour

An error is throws every single time.

### Additional Information

No, I've provided everything above.

### Code of Conduct

Yes, I agree


Discussion:
thanks @dpgeorge , any chance this can make it to npm too? 



Patch:
diff --git a/ports/webassembly/objpyproxy.js b/ports/webassembly/objpyproxy.js
index 0eafd0dec53de..64703d78a5589 100644
--- a/ports/webassembly/objpyproxy.js
+++ b/ports/webassembly/objpyproxy.js
@@ -165,34 +165,35 @@ const py_proxy_handler = {
         if (prop === "_ref") {
             return target._ref;
         }
-        if (prop === "then") {
-            return null;
-        }
 
-        if (prop === Symbol.iterator) {
-            // Get the Python object iterator, and return a JavaScript generator.
-            const iter_ref = Module.ccall(
-                "proxy_c_to_js_get_iter",
-                "number",
-                ["number"],
-                [target._ref],
-            );
-            return function* () {
-                const value = Module._malloc(3 * 4);
-                while (true) {
-                    const valid = Module.ccall(
-                        "proxy_c_to_js_iternext",
-                        "number",
-                        ["number", "pointer"],
-                        [iter_ref, value],
-                    );
-                    if (!valid) {
-                        break;
+        // ignore both then and all symbols but Symbol.iterator
+        if (prop === "then" || typeof prop !== "string") {
+            if (prop === Symbol.iterator) {
+                // Get the Python object iterator, and return a JavaScript generator.
+                const iter_ref = Module.ccall(
+                    "proxy_c_to_js_get_iter",
+                    "number",
+                    ["number"],
+                    [target._ref],
+                );
+                return function* () {
+                    const value = Module._malloc(3 * 4);
+                    while (true) {
+                        const valid = Module.ccall(
+                            "proxy_c_to_js_iternext",
+                            "number",
+                            ["number", "pointer"],
+                            [iter_ref, value],
+                        );
+                        if (!valid) {
+                            break;
+                        }
+                        yield proxy_convert_mp_to_js_obj_jsside(value);
                     }
-                    yield proxy_convert_mp_to_js_obj_jsside(value);
-                }
-                Module._free(value);
-            };
+                    Module._free(value);
+                };
+            }
+            return undefined;
         }
 
         const value = Module._malloc(3 * 4);


Test patch:
diff --git a/tests/ports/webassembly/py_proxy_get.mjs b/tests/ports/webassembly/py_proxy_get.mjs
new file mode 100644
index 0000000000000..825de7cabeb78
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_get.mjs
@@ -0,0 +1,14 @@
+// Test `<py-obj> get <attr>` on the JavaScript side, which tests PyProxy.get.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+x = {"a": 1}
+`);
+
+const x = mp.globals.get("x");
+console.log(x.a === 1);
+console.log(x.b === undefined);
+console.log(typeof x[Symbol.iterator] === "function");
+console.log(x[Symbol.toStringTag] === undefined);
+console.log(x.then === undefined);
diff --git a/tests/ports/webassembly/py_proxy_get.mjs.exp b/tests/ports/webassembly/py_proxy_get.mjs.exp
new file mode 100644
index 0000000000000..36c7afad66a16
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_get.mjs.exp
@@ -0,0 +1,5 @@
+true
+true
+true
+true
+true