Skip to content

codegen: support array types everywhere (struct/record fields, mapping values, function I/O), recursively #48

Description

@iamalwaysuncomfortable

Summary

aleo.codegen does not support array types. codegen/_emit.py::resolve_ty maps only {"Primitive": …} and {"Struct": …}; every other shape falls through to raise ValueError("Unsupported ABI type: …"). Arrays are represented in the ABI as {"Array": {"element": <ty>, "length": N}}, which has no branch — so any struct/record field (or mapping value) that is an array breaks emit_module.

Structs (nested structs, cross-program ref checks) work; this is specific to arrays.

What "support arrays" means here

An Aleo array is just a fixed-length collection of a single element type, and that element can itself be any type:

  • a primitive[u8; 32], [field; 4], [bool; 8], [address; 3]
  • a struct[Foo; 2]
  • another array (arbitrary nesting) — [[u8; 4]; 3], [[Foo; 2]; 5]

So the fix must be recursive: resolve_ty should resolve the element type (which may recurse into a primitive, a struct, or a nested array) and build the Python representation from it. There is no fixed depth.

Scope — arrays must work in EVERY type position

Array support should be complete wherever the codegen resolves a type:

  1. Struct fieldsstruct Foo: xs as [u8; 4u32];
  2. Record fieldsrecord R: data as [field; 8u32].private;
  3. Mapping valuesmapping m: key as ...; value as [u8; 32u32];
  4. Function input / output positionsinput r0 as [u8; 4u32].private; / output r0 as [Foo; 2u32].private;

Note on (4): emit_module currently emits struct/record dataclasses, mapping-value decoders, and the raw ABI dict, but does not emit typed function signatures at all — so today array function I/O neither works nor errors, it's simply unbound. Completing this item therefore also means the codegen must resolve function input/output types (arrays included), not just struct/record/mapping types.

Reproduction

from aleo.abi import generate_abi
from aleo.mainnet import Program
from aleo.codegen._emit import emit_module

src = """program arr3.aleo;
struct Foo:
    xs as [u8; 4u32];
function usefoo:
    input r0 as Foo.private;
    output r0 as Foo.private;
"""
abi = generate_abi(Program.from_source(src), "mainnet")
emit_module(abi)
# ValueError: Unsupported ABI type:
#   {'Array': {'element': {'Primitive': {'UInt': 'U8'}}, 'length': 4}}

Runtime decoding is unaffected — codegen/runtime.py::_parse_array already turns array plaintext into Python lists (see the passing test_parse_nested_struct_and_array). The gap is purely on the emission/type-mapping side.

Suggested fix

  1. resolve_ty — add a recursive "Array" case. Resolve the element type et = resolve_ty(ty["Array"]["element"]), then:
    • annotation: list[<et.annotation>]
    • encode: "[ " + ", ".join(<et.encode(x)> for x in e) + " ]" (Aleo array literal). The element encoder is applied per element, so [Foo; 2] calls Foo.to_plaintext() per element and [[u8;4];3] recurses.
    • decode: map the element decoder over the list — pass-through for primitive elements, Elem.from_decoded for struct elements, and recurse for nested arrays. (runtime.parse_plaintext already yields nested lists, so decode just re-wraps struct elements.)
  2. _check_struct_refs — descend into array elements so a [SomeStruct; N] field/value still resolves its struct reference (currently only top-level {"Struct"} is checked; a struct nested in an array is skipped).
  3. Function I/O typing — if/when function signatures are emitted, route their input/output types through the same resolve_ty so arrays are covered there too.

Robust array test fixture (required)

Add a dedicated ABI fixture (sibling to tests/fixtures/shield_swap_v3.abi.json) that exercises arrays in every position and nesting, and drive emit + round-trip (to_plaintextfrom_plaintext) tests off it:

  • array of each primitive family: [u8; N]/int, [bool; N], [address; N], [field; N] (field/group/scalar)
  • array of struct: [Foo; N]
  • nested array: [[u8; 4]; 3], and array-of-array-of-struct [[Foo; 2]; 3]
  • an array field inside a struct, inside a record, as a mapping value, and in a function input and output
  • struct containing an array containing a struct (mixed recursion)

Round-trip assertions should confirm: emitted annotations (list[...], nested), to_plaintext produces valid Aleo array literals for each case, and from_plaintext/from_decoded reconstructs the exact Python value (including struct elements and nesting). Also keep a negative test asserting a clear error for genuinely unsupported shapes.

Why it wasn't caught

The only real ABI fixture (tests/fixtures/shield_swap_v3.abi.json) is 258 Primitive + 16 Struct with zero arrays, and test_codegen_emit.py only exercises resolve_ty on primitives + structs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions