From 22ef420b7c457cfd4b95107e5ca9d8242d643e54 Mon Sep 17 00:00:00 2001 From: Bedis Nbiba Date: Wed, 14 Aug 2024 20:26:00 +0100 Subject: [PATCH] fix: add signature to callbacks (#67) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Elias Sjögreen --- src/python.ts | 18 ++++++++++++++---- test/test.ts | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/python.ts b/src/python.ts index 9c41ea1..3ccd23a 100644 --- a/src/python.ts +++ b/src/python.ts @@ -458,9 +458,9 @@ export class PyObject { const view = new DataView(pyMethodDef.buffer); const LE = new Uint8Array(new Uint32Array([0x12345678]).buffer)[0] !== 0x7; - const nameBuf = new TextEncoder().encode( - "JSCallback:" + (v.callback.name || "anonymous") + "\0", - ); + + const name = "JSCallback:" + (v.callback.name || "anonymous"); + const nameBuf = new TextEncoder().encode(`${name}\0`); view.setBigUint64( 0, BigInt(Deno.UnsafePointer.value(Deno.UnsafePointer.of(nameBuf)!)), @@ -472,9 +472,19 @@ export class PyObject { LE, ); view.setInt32(16, 0x1 | 0x2, LE); + // https://github.com/python/cpython/blob/f27593a87c344f3774ca73644a11cbd5614007ef/Objects/typeobject.c#L688 + const SIGNATURE_END_MARKER = ")\n--\n\n"; + // We're not using the correct arguments name, but just using dummy ones (because they're not accessible in js) + const fnArgs = [...Array(v.callback.length).keys()] + .map((_, i) => String.fromCharCode(97 + i)).join(","); + const docBuf = `${name}(${fnArgs}${SIGNATURE_END_MARKER}\0`; view.setBigUint64( 24, - BigInt(Deno.UnsafePointer.value(Deno.UnsafePointer.of(nameBuf)!)), + BigInt( + Deno.UnsafePointer.value( + Deno.UnsafePointer.of(new TextEncoder().encode(docBuf))!, + ), + ), LE, ); const fn = py.PyCFunction_NewEx( diff --git a/test/test.ts b/test/test.ts index d111598..785cb66 100644 --- a/test/test.ts +++ b/test/test.ts @@ -324,3 +324,19 @@ Deno.test("exceptions", async (t) => { assertThrows(() => array.shape = [3, 6]); }); }); + +Deno.test("callbacks have signature", async (t) => { + const inspect = python.import("inspect"); + + await t.step("empty arguments", () => { + const fn = python.callback(() => {}); + assertEquals(inspect.signature(fn).toString(), "()"); + fn.destroy(); + }); + + await t.step("with no arguments", () => { + const fn = python.callback((_f, _b, _c) => {}); + assertEquals(inspect.signature(fn).toString(), "(a, b, c)"); + fn.destroy(); + }); +});