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:
- Struct fields —
struct Foo: xs as [u8; 4u32];
- Record fields —
record R: data as [field; 8u32].private;
- Mapping values —
mapping m: key as ...; value as [u8; 32u32];
- Function input / output positions —
input 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
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.)
_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).
- 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_plaintext → from_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.
Summary
aleo.codegendoes not support array types.codegen/_emit.py::resolve_tymaps only{"Primitive": …}and{"Struct": …}; every other shape falls through toraise 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 breaksemit_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:
[u8; 32],[field; 4],[bool; 8],[address; 3][Foo; 2][[u8; 4]; 3],[[Foo; 2]; 5]So the fix must be recursive:
resolve_tyshould 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:
struct Foo: xs as [u8; 4u32];record R: data as [field; 8u32].private;mapping m: key as ...; value as [u8; 32u32];input r0 as [u8; 4u32].private;/output r0 as [Foo; 2u32].private;Note on (4):
emit_modulecurrently emits struct/record dataclasses, mapping-value decoders, and the rawABIdict, 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
Runtime decoding is unaffected —
codegen/runtime.py::_parse_arrayalready turns array plaintext into Python lists (see the passingtest_parse_nested_struct_and_array). The gap is purely on the emission/type-mapping side.Suggested fix
resolve_ty— add a recursive"Array"case. Resolve the element typeet = resolve_ty(ty["Array"]["element"]), then:list[<et.annotation>]"[ " + ", ".join(<et.encode(x)> for x in e) + " ]"(Aleo array literal). The element encoder is applied per element, so[Foo; 2]callsFoo.to_plaintext()per element and[[u8;4];3]recurses.Elem.from_decodedfor struct elements, and recurse for nested arrays. (runtime.parse_plaintextalready yields nested lists, so decode just re-wraps struct elements.)_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).resolve_tyso 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_plaintext→from_plaintext) tests off it:[u8; N]/int,[bool; N],[address; N],[field; N](field/group/scalar)[Foo; N][[u8; 4]; 3], and array-of-array-of-struct[[Foo; 2]; 3]Round-trip assertions should confirm: emitted annotations (
list[...], nested),to_plaintextproduces valid Aleo array literals for each case, andfrom_plaintext/from_decodedreconstructs 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 258Primitive+ 16Structwith zero arrays, andtest_codegen_emit.pyonly exercisesresolve_tyon primitives + structs.