From e6c76b0df26e73acc9aca32b0f3158cb59a9b6ca Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 26 Aug 2026 01:50:48 +0100 Subject: [PATCH] Emit ArrayConverter for array payload members An array payload member now generates ArrayConverter over the element type and count, so the generated module type-checks. Passing a sub-array dtype to IdentityConverter would type the member as np.void, which the NDArray annotation beside it does not accept. The converter builds the same IdentityConverter over the same dtype, so decoding is unchanged. The IdentityConverter import now belongs to the scalar path alone, so a module whose only passthrough is an array member no longer declares an import it does not use. Closes #123 --- src/Python.cs | 5 +++-- tests/ExpectedOutput/device.py | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Python.cs b/src/Python.cs index c66620c..bb696ec 100644 --- a/src/Python.cs +++ b/src/Python.cs @@ -454,18 +454,19 @@ static PythonField BuildPythonField( if (string.IsNullOrEmpty(member.InterfaceType)) { - module.ProtocolImports.Add("IdentityConverter"); if (memberLength > 0) { + module.ProtocolImports.Add("ArrayConverter"); module.UsesNDArray = true; return new PythonField { Name = name, Annotation = $"NDArray[{elementType}]", - Descriptor = $"Field(IdentityConverter(np.dtype(({elementType}, ({memberLength},)))){offsetArgument}{defaultArgument})" + Descriptor = $"Field(ArrayConverter({elementType}, {memberLength}){offsetArgument}{defaultArgument})" }; } + module.ProtocolImports.Add("IdentityConverter"); return new PythonField { Name = name, diff --git a/tests/ExpectedOutput/device.py b/tests/ExpectedOutput/device.py index eccfb82..b1ab291 100644 --- a/tests/ExpectedOutput/device.py +++ b/tests/ExpectedOutput/device.py @@ -8,6 +8,7 @@ from numpy.typing import NDArray from harp.protocol import ( AnonymousPayload, + ArrayConverter, BitMask, BoolConverter, Field, @@ -105,7 +106,7 @@ class AnalogDataPayload(StructPayload[np.float32], length=6): analog0: np.float32 = Field(IdentityConverter(np.float32)) analog1: np.float32 = Field(IdentityConverter(np.float32), offset=1) analog2: np.float32 = Field(IdentityConverter(np.float32), offset=2) - accelerometer: NDArray[np.float32] = Field(IdentityConverter(np.dtype((np.float32, (3,)))), offset=3) + accelerometer: NDArray[np.float32] = Field(ArrayConverter(np.float32, 3), offset=3) class ComplexConfigurationPayload(StructPayload[np.uint8], length=17): @@ -125,7 +126,7 @@ class VersionPayload(StructPayload[np.uint8], length=32): firmware_version: HarpVersion = Field(HarpVersionConverter(np.uint8), offset=3) hardware_version: HarpVersion = Field(HarpVersionConverter(np.uint8), offset=6) core_id: str = Field(StringConverter(3), offset=9) - interface_hash: NDArray[np.uint8] = Field(IdentityConverter(np.dtype((np.uint8, (20,)))), offset=12) + interface_hash: NDArray[np.uint8] = Field(ArrayConverter(np.uint8, 20), offset=12) class CustomPayloadPayload(AnonymousPayload[np.uint32]): @@ -164,8 +165,8 @@ class MixedMemberLengthPayload(StructPayload[np.uint8], length=4): """Represents the payload of the MixedMemberLength register.""" absent: np.uint8 = Field(IdentityConverter(np.uint8)) - single: NDArray[np.uint8] = Field(IdentityConverter(np.dtype((np.uint8, (1,)))), offset=1) - multiple: NDArray[np.uint8] = Field(IdentityConverter(np.dtype((np.uint8, (2,)))), offset=2) + single: NDArray[np.uint8] = Field(ArrayConverter(np.uint8, 1), offset=1) + multiple: NDArray[np.uint8] = Field(ArrayConverter(np.uint8, 2), offset=2) class StartPulsePayload(StructPayload[np.uint16]):