acir/
lib.rs

1//! C++ code generation for ACIR format, to be used by Barretenberg.
2//!
3//! To regenerate code run the following command:
4//! ```text
5//! NOIR_CODEGEN_OVERWRITE=1 cargo test -p acir cpp_codegen
6//! ```
7#![cfg_attr(not(test), forbid(unsafe_code))] // `std::env::set_var` is used in tests.
8#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))]
9
10#[doc = include_str!("../README.md")]
11pub mod circuit;
12pub mod native_types;
13mod parser;
14mod serialization;
15
16#[cfg(feature = "test-fixtures")]
17pub mod test_fixtures;
18
19pub use acir_field;
20pub use acir_field::{AcirField, FieldElement};
21pub use brillig;
22pub use circuit::black_box_functions::BlackBoxFunc;
23pub use circuit::opcodes::InvalidInputBitSize;
24pub use parser::parse_opcodes;
25pub use serialization::Format as SerializationFormat;
26
27#[cfg(test)]
28mod reflection {
29    //! Getting test failures? You've probably changed the ACIR serialization format.
30    //!
31    //! These tests generate C++ deserializers for [`ACIR bytecode`][super::circuit::Circuit]
32    //! and the [`WitnessMap`] structs. These get checked against the C++ files committed to the `codegen` folder
33    //! to see if changes have been to the serialization format. These are almost always a breaking change!
34    //!
35    //! If you want to make a breaking change to the ACIR serialization format, then just comment out the assertions
36    //! that the file hashes must match and rerun the tests. This will overwrite the `codegen` folder with the new
37    //! logic. Make sure to uncomment these lines afterwards and to commit the changes to the `codegen` folder.
38
39    use std::{
40        collections::BTreeMap,
41        fs::File,
42        hash::BuildHasher,
43        io::Write,
44        path::{Path, PathBuf},
45    };
46
47    use acir_field::{AcirField, FieldElement};
48    use brillig::{
49        BinaryFieldOp, BinaryIntOp, BitSize, BlackBoxOp, HeapValueType, IntegerBitSize,
50        MemoryAddress, Opcode as BrilligOpcode, ValueOrArray,
51    };
52    use msgpack_tagged::{MsgpackTagged, Product, Sum, TagRegistry};
53    use regex::Regex;
54    use serde::{Deserialize, Serialize};
55    use serde_generate::CustomCode;
56    use serde_reflection::{
57        ContainerFormat, Format, Named, Registry, Samples, Tracer, TracerConfig, VariantFormat,
58    };
59
60    use crate::{
61        circuit::{
62            AssertionPayload, Circuit, ExpressionOrMemory, Opcode, OpcodeLocation, Program,
63            brillig::{BrilligInputs, BrilligOutputs},
64            opcodes::{BlackBoxFuncCall, BlockType, FunctionInput, MemOp},
65        },
66        native_types::{Witness, WitnessMap, WitnessStack},
67    };
68
69    /// Technical DTO for deserializing in Barretenberg while ignoring
70    /// the Brillig opcodes, so that we can add more without affecting it.
71    ///
72    /// This could be achieved in other ways, for example by having a
73    /// version of `Program` that deserializes into opaque bytes,
74    /// which would require a 2 step (de)serialization process.
75    ///
76    /// This one is simpler. The cost is that msgpack will deserialize
77    /// into a JSON-like structure, but since we won't be interpreting it,
78    /// it's okay if new tags appear.
79    #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash, MsgpackTagged)]
80    struct ProgramWithoutBrillig<F: AcirField> {
81        #[tag(0)]
82        pub functions: Vec<Circuit<F>>,
83        /// We want to ignore this field. By setting its type as `unit`
84        /// it will not be deserialized, but it will correctly maintain
85        /// the position of the others (although in this case it doesn't)
86        /// matter since it's the last field.
87        #[tag(1)]
88        pub unconstrained_functions: (),
89    }
90
91    #[test]
92    fn serde_acir_cpp_codegen() {
93        // MemOp has a custom Deserialize impl with validation that rejects the zero-expression
94        // samples that trace_simple_type generates. Enable record_samples_for_structs so that
95        // pre-registered samples are used when the tracer encounters a known struct type.
96        let config = TracerConfig::default().record_samples_for_structs(true);
97        let mut tracer = Tracer::new(config);
98
99        let mut samples = Samples::new();
100        tracer
101            .trace_value(&mut samples, &MemOp::read_at_mem_index(Witness(0), Witness(0)))
102            .unwrap();
103        tracer
104            .trace_value(&mut samples, &MemOp::write_to_mem_index(Witness(0), Witness(0)))
105            .unwrap();
106
107        tracer.trace_simple_type::<BlockType>().unwrap();
108        tracer.trace_simple_type::<Program<FieldElement>>().unwrap();
109        tracer.trace_simple_type::<ProgramWithoutBrillig<FieldElement>>().unwrap();
110        tracer.trace_simple_type::<Circuit<FieldElement>>().unwrap();
111        tracer.trace_type::<Opcode<FieldElement>>(&samples).unwrap();
112        tracer.trace_simple_type::<OpcodeLocation>().unwrap();
113        tracer.trace_simple_type::<BinaryFieldOp>().unwrap();
114        tracer.trace_simple_type::<FunctionInput<FieldElement>>().unwrap();
115        tracer.trace_simple_type::<FunctionInput<FieldElement>>().unwrap();
116        tracer.trace_simple_type::<BlackBoxFuncCall<FieldElement>>().unwrap();
117        tracer.trace_simple_type::<BrilligInputs<FieldElement>>().unwrap();
118        tracer.trace_simple_type::<BrilligOutputs>().unwrap();
119        tracer.trace_simple_type::<BrilligOpcode<FieldElement>>().unwrap();
120        tracer.trace_simple_type::<BinaryIntOp>().unwrap();
121        tracer.trace_simple_type::<BlackBoxOp>().unwrap();
122        tracer.trace_simple_type::<ValueOrArray>().unwrap();
123        tracer.trace_simple_type::<HeapValueType>().unwrap();
124        tracer.trace_simple_type::<AssertionPayload<FieldElement>>().unwrap();
125        tracer.trace_simple_type::<ExpressionOrMemory<FieldElement>>().unwrap();
126        tracer.trace_simple_type::<BitSize>().unwrap();
127        tracer.trace_simple_type::<IntegerBitSize>().unwrap();
128        tracer.trace_simple_type::<MemoryAddress>().unwrap();
129
130        // The `msgpack_tagged` tag registry mirrors the type graph that
131        // serde-reflection traced above, but additionally carries the
132        // integer tag → field-name (and variant) mapping the C++ codegen
133        // needs for the new int-keyed dispatch branch. We register the
134        // same top-level types as the tracer: `Program<F>` covers the
135        // bulk of the graph via its transitive walk, and
136        // `ProgramWithoutBrillig<F>` is a sibling type that needs its own
137        // entry (its serde name `"ProgramWithoutBrillig"` is distinct
138        // from `"Program"`, and its `unconstrained_functions: ()` field
139        // doesn't introduce any types that Program's walk didn't already
140        // reach).
141        let mut tag_registry = TagRegistry::new();
142        <Program<FieldElement> as MsgpackTagged>::register_into(&mut tag_registry);
143        <ProgramWithoutBrillig<FieldElement> as MsgpackTagged>::register_into(&mut tag_registry);
144
145        serde_cpp_codegen(
146            "Acir",
147            PathBuf::from("./codegen/acir.cpp").as_path(),
148            &tracer.registry().unwrap(),
149            &tag_registry,
150            CustomCode::default(),
151        );
152    }
153
154    #[test]
155    fn serde_witness_map_cpp_codegen() {
156        let mut tracer = Tracer::new(TracerConfig::default());
157        tracer.trace_simple_type::<Witness>().unwrap();
158        tracer.trace_simple_type::<WitnessMap<FieldElement>>().unwrap();
159        tracer.trace_simple_type::<WitnessStack<FieldElement>>().unwrap();
160
161        let mut tag_registry = TagRegistry::new();
162        <Witness as MsgpackTagged>::register_into(&mut tag_registry);
163        <WitnessMap<FieldElement> as MsgpackTagged>::register_into(&mut tag_registry);
164        <WitnessStack<FieldElement> as MsgpackTagged>::register_into(&mut tag_registry);
165
166        let namespace = "Witnesses";
167        let mut code = CustomCode::default();
168        // The `WitnessMap` type will have a field of type `std::map<Witnesses::Witness, std::string>`,
169        // which requires us to implement the comparison operator.
170        code.insert(
171            vec![namespace.to_string(), "Witness".to_string()],
172            "bool operator<(Witness const& rhs) const { return value < rhs.value; }".to_string(),
173        );
174
175        serde_cpp_codegen(
176            namespace,
177            PathBuf::from("./codegen/witness.cpp").as_path(),
178            &tracer.registry().unwrap(),
179            &tag_registry,
180            code,
181        );
182    }
183
184    /// Regenerate C++ code for serializing our domain model based on serde,
185    /// intended to be used by Barretenberg.
186    ///
187    /// If `should_overwrite()` returns `false` then just check if the old file hash is the
188    /// same as the new one, to guard against unintended changes in the serialization format.
189    fn serde_cpp_codegen(
190        namespace: &str,
191        path: &Path,
192        registry: &Registry,
193        tag_registry: &TagRegistry,
194        code: CustomCode,
195    ) {
196        let old_hash = if path.is_file() {
197            let old_source = std::fs::read(path).expect("failed to read existing code");
198            let old_source = String::from_utf8(old_source).expect("old source not UTF-8");
199            Some(rustc_hash::FxBuildHasher.hash_one(&old_source))
200        } else {
201            None
202        };
203        let msgpack_code = MsgPackCodeGenerator::generate(
204            namespace,
205            registry,
206            tag_registry,
207            code,
208            MsgPackCodeConfig::from_env(),
209        );
210
211        // Create C++ class definitions.
212        let mut source = Vec::new();
213        // We use `serde_generate` to take advantage of its integration with `serde_reflection` but only use our
214        // custom msgpack code generation.
215        let config = serde_generate::CodeGeneratorConfig::new(namespace.to_string())
216            .with_encodings(vec![])
217            .with_custom_code(msgpack_code);
218        let generator = serde_generate::cpp::CodeGenerator::new(&config);
219        generator.output(&mut source, registry).expect("failed to generate C++ code");
220
221        // Further massaging of the generated code
222        let mut source = String::from_utf8(source).expect("not a UTF-8 string");
223        replace_throw(&mut source);
224        MsgPackCodeGenerator::add_preamble(&mut source);
225        MsgPackCodeGenerator::add_helpers(&mut source, namespace);
226        MsgPackCodeGenerator::replace_array_with_shared_ptr(&mut source);
227        MsgPackCodeGenerator::add_autogen_header(&mut source);
228
229        if !should_overwrite()
230            && let Some(old_hash) = old_hash
231        {
232            let new_hash = rustc_hash::FxBuildHasher.hash_one(&source);
233            assert_eq!(new_hash, old_hash, "Serialization format has changed",);
234        }
235
236        write_to_file(source.as_bytes(), path);
237    }
238
239    /// Get a boolean flag env var.
240    fn env_flag(name: &str, default: bool) -> bool {
241        let Ok(s) = std::env::var(name) else {
242            return default;
243        };
244        match s.as_str() {
245            "1" | "true" | "yes" => true,
246            "0" | "false" | "no" => false,
247            _ => default,
248        }
249    }
250
251    /// Check if it's okay for the generated source to be overwritten with a new version.
252    /// Otherwise any changes causes a test failure.
253    fn should_overwrite() -> bool {
254        env_flag("NOIR_CODEGEN_OVERWRITE", false)
255    }
256
257    fn write_to_file(bytes: &[u8], path: &Path) -> String {
258        let display = path.display();
259
260        let parent_dir = path.parent().unwrap();
261        if !parent_dir.is_dir() {
262            std::fs::create_dir_all(parent_dir).unwrap();
263        }
264
265        let mut file = match File::create(path) {
266            Err(why) => panic!("couldn't create {display}: {why}"),
267            Ok(file) => file,
268        };
269
270        match file.write_all(bytes) {
271            Err(why) => panic!("couldn't write to {display}: {why}"),
272            Ok(_) => display.to_string(),
273        }
274    }
275
276    /// Replace all `throw serde::deserialization_error` with `throw_or_abort`.
277    ///
278    /// Since we're generating msgpack code that works specifically with the Barretenberg
279    /// codebase only (these are custom functions), we might as well do the other alterations
280    /// described in [the DSL](https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg/cpp/src/barretenberg/dsl).
281    fn replace_throw(source: &mut String) {
282        *source = source.replace("throw serde::deserialization_error", "throw_or_abort");
283    }
284
285    struct MsgPackCodeConfig {
286        /// If `true`, use `ARRAY` format, otherwise use `MAP` when packing structs.
287        pack_compact: bool,
288        /// If `true`, skip generating `msgpack_pack` methods.
289        no_pack: bool,
290    }
291
292    impl MsgPackCodeConfig {
293        fn from_env() -> Self {
294            Self {
295                // We agreed on the default format to be compact, so it makes sense for Barretenberg to use it for serialization.
296                pack_compact: env_flag("NOIR_CODEGEN_PACK_COMPACT", true),
297                // Barretenberg didn't use serialization outside tests, so they decided they don't want to have this code at all.
298                // But in the latest code they seem to have kept it again.
299                no_pack: env_flag("NOIR_CODEGEN_NO_PACK", false),
300            }
301        }
302    }
303
304    /// Assert that every product / sum container in the serde-reflection
305    /// `registry` is also present in the `MsgpackTagged` `tag_registry`,
306    /// and that every product is in *canonical* (tag-ascending) source
307    /// order. Panics with a focused message on the first miss.
308    ///
309    /// **Why the coverage check.** The int-keyed dispatch branch in the
310    /// generated C++ needs each field's u8 tag (for structs) and each
311    /// variant's u8 tag (for enums). That metadata only exists on the
312    /// `MsgpackTagged` side. A type in `registry` but not in
313    /// `tag_registry` means somebody added a wire type without deriving
314    /// `MsgpackTagged` — fail loudly at codegen time, not silently at
315    /// runtime.
316    ///
317    /// **Why the order check.** Under `Format::MsgpackCompact` a struct
318    /// is emitted as a fixarray in *source* order; under
319    /// `Format::MsgpackTagged` with the `Array` strategy it's a fixarray
320    /// in *tag-ascending* order. Both shapes look identical to the C++
321    /// decoder (just `msgpack::type::ARRAY`), so byte-different wires
322    /// for the same logical value would silently land on the wrong
323    /// fields if the two orders ever diverge. Forcing
324    /// `tag_order_matches_source` keeps them lockstep — both formats
325    /// produce byte-identical arrays, and the codegen reads positionally
326    /// in tag-ascending order.
327    fn assert_tag_registry_covers(registry: &Registry, tag_registry: &TagRegistry) {
328        use serde_reflection::ContainerFormat;
329        for (name, container) in registry {
330            // Only named-struct and enum containers need an entry in the
331            // `MsgpackTagged` registry: their int-keyed dispatch branch
332            // depends on per-field / per-variant tag metadata. Unit
333            // structs, newtypes, and (currently `unimplemented!`) tuple
334            // structs pass through to their inner type (or are no-ops)
335            // and don't register themselves in `TagRegistry`.
336            let needs_tags =
337                matches!(container, ContainerFormat::Struct(_) | ContainerFormat::Enum(_),);
338            if !needs_tags {
339                continue;
340            }
341            let Some(entry) = tag_registry.get(name) else {
342                panic!(
343                    "MsgpackTagged tag registry is missing {name:?} — the type is on \
344                     the wire (serde-reflection traced it as a struct/enum) but \
345                     doesn't derive `MsgpackTagged`. Add `#[derive(MsgpackTagged)]` \
346                     to the type, and a `register_into` call at the codegen test \
347                     site if the type isn't reachable from a type that's already \
348                     registered.",
349                );
350            };
351            if let Some(p) = entry.tagged().as_product()
352                && !p.tag_order_matches_source
353            {
354                panic!(
355                    "MsgpackTagged product {name:?} declares its fields in an order \
356                     that doesn't match tag-ascending. The C++ codegen can't tell \
357                     `MsgpackCompact` (source-order array) from \
358                     `MsgpackTagged::Array` (tag-ascending array) on the wire — \
359                     reorder the Rust fields so `#[tag(N)]` values are increasing \
360                     in source order, or drop the type from the C++ wire types.",
361                );
362            }
363        }
364    }
365
366    /// Generate custom code for the msgpack machinery in Barretenberg.
367    /// See https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/serialize/msgpack.hpp
368    struct MsgPackCodeGenerator<'a> {
369        config: MsgPackCodeConfig,
370        namespace: Vec<String>,
371        code: CustomCode,
372        /// Carries the integer-tag metadata produced by `MsgpackTagged`
373        /// derives. The serde-reflection `Registry` only knows field/variant
374        /// *names*; consulting this registry lets us pair each name with its
375        /// stable u8 tag so the generated C++ can dispatch on int-keyed
376        /// `MsgpackTagged` wires the same way the Rust decoder does.
377        tag_registry: &'a TagRegistry,
378    }
379
380    impl<'a> MsgPackCodeGenerator<'a> {
381        /// Prepend a banner marking the file as auto-generated and pointing
382        /// readers at the generator. Without it, the committed `.cpp` files
383        /// look hand-written and an unsuspecting editor would lose their
384        /// changes the next time the codegen tests run.
385        pub(crate) fn add_autogen_header(source: &mut String) {
386            let header = "\
387// AUTO-GENERATED — DO NOT EDIT.
388//
389// Generated by the `cpp_codegen` tests in `acvm-repo/acir/src/lib.rs`.
390// To regenerate, run:
391//
392//     NOIR_CODEGEN_OVERWRITE=1 cargo test -p acir cpp_codegen
393//
394";
395            source.insert_str(0, header);
396        }
397
398        /// Add the import of the Barretenberg C++ header for msgpack.
399        pub(crate) fn add_preamble(source: &mut String) {
400            let inc = r#"#include "serde.hpp""#;
401            let pos = source.find(inc).expect("serde.hpp missing");
402            source.insert_str(
403                pos + inc.len(),
404                "\n#include \"barretenberg/serialize/msgpack_impl.hpp\"",
405            );
406        }
407
408        /// Add helper functions to cut down repetition in the generated code.
409        pub(crate) fn add_helpers(source: &mut String, namespace: &str) {
410            // Based on https://github.com/AztecProtocol/msgpack-c/blob/54e9865b84bbdc73cfbf8d1d437dbf769b64e386/include/msgpack/v1/adaptor/detail/cpp11_define_map.hpp#L75
411            // Using a `struct Helpers` with `static` methods, because top level functions turn up as duplicates in `wasm-ld`.
412            // cSpell:disable
413            let helpers = r#"
414    struct Helpers {
415        static std::map<std::string, msgpack::object const*> make_kvmap(
416            msgpack::object const& o,
417            std::string const& name
418        ) {
419            if (o.type != msgpack::type::MAP) {
420                std::cerr << o << std::endl;
421                throw_or_abort("expected MAP for " + name);
422            }
423            std::map<std::string, msgpack::object const*> kvmap;
424            for (uint32_t i = 0; i < o.via.map.size; ++i) {
425                if (o.via.map.ptr[i].key.type != msgpack::type::STR) {
426                    std::cerr << o << std::endl;
427                    throw_or_abort("expected STR for keys of " + name);
428                }
429                kvmap.emplace(
430                    std::string(
431                        o.via.map.ptr[i].key.via.str.ptr,
432                        o.via.map.ptr[i].key.via.str.size),
433                    &o.via.map.ptr[i].val);
434            }
435            return kvmap;
436        }
437
438        template<typename T>
439        static void conv_fld_from_kvmap(
440            std::map<std::string, msgpack::object const*> const& kvmap,
441            std::string const& struct_name,
442            std::string const& field_name,
443            T& field,
444            bool is_optional
445        ) {
446            auto it = kvmap.find(field_name);
447            if (it != kvmap.end()) {
448                if (!is_optional && it->second->type == msgpack::type::NIL) {
449                    throw_or_abort("nil value for required field: " + struct_name + "::" + field_name);
450                }
451                try {
452                    it->second->convert(field);
453                } catch (const msgpack::type_error&) {
454                    std::cerr << *it->second << std::endl;
455                    throw_or_abort("error converting into field " + struct_name + "::" + field_name);
456                }
457            } else if (!is_optional) {
458                throw_or_abort("missing field: " + struct_name + "::" + field_name);
459            }
460        }
461
462        template<typename T>
463        static void conv_fld_from_array(
464            msgpack::object_array const& array,
465            std::string const& struct_name,
466            std::string const& field_name,
467            T& field,
468            uint32_t index
469        ) {
470            if (index >= array.size) {
471                throw_or_abort("index out of bounds: " + struct_name + "::" + field_name + " at " + std::to_string(index));
472            }
473            auto element = array.ptr[index];
474            if (element.type == msgpack::type::NIL) {
475                throw_or_abort("nil value for required field: " + struct_name + "::" + field_name);
476            }
477            try {
478                element.convert(field);
479            } catch (const msgpack::type_error&) {
480                std::cerr << element << std::endl;
481                throw_or_abort("error converting into field " + struct_name + "::" + field_name);
482            }
483        }
484
485        /// Convert `val` into `field`, or throw a focused error mentioning
486        /// the struct + field name. Used by the int-keyed dispatch path
487        /// where each `switch` case populates one field directly.
488        template<typename T>
489        static void convert_or_throw(
490            msgpack::object const& val,
491            std::string const& struct_name,
492            std::string const& field_name,
493            T& field
494        ) {
495            try {
496                val.convert(field);
497            } catch (const msgpack::type_error&) {
498                std::cerr << val << std::endl;
499                throw_or_abort("error converting into field " + struct_name + "::" + field_name);
500            }
501        }
502
503        /// Whether `o` is a non-empty MAP whose first key is an integer.
504        /// This is the signature of `Format::MsgpackTagged`: int keys for
505        /// struct field tags and enum variant tags. Legacy `Format::Msgpack`
506        /// keys are always strings, so a positive-integer first key is a
507        /// reliable shape discriminator between the two.
508        static bool is_int_keyed_map(msgpack::object const& o) {
509            return o.type == msgpack::type::MAP
510                && o.via.map.size > 0
511                && o.via.map.ptr[0].key.type == msgpack::type::POSITIVE_INTEGER;
512        }
513
514        /// Iterate an int-keyed MAP and invoke `dispatch(tag, val)` for each
515        /// `(u8, msgpack::object)` entry. The per-tag `switch` inside the
516        /// caller's lambda decides which field (or variant) to populate;
517        /// unknown tags fall through to `default` and are silently skipped,
518        /// matching the `MsgpackTagged` decoder's forward-compat policy
519        /// (`allow_unknown_tags` / retired tags drained).
520        template<typename Dispatch>
521        static void int_map_dispatch(
522            msgpack::object const& o,
523            std::string const& name,
524            Dispatch&& dispatch
525        ) {
526            for (uint32_t i = 0; i < o.via.map.size; ++i) {
527                uint8_t tag;
528                try {
529                    o.via.map.ptr[i].key.convert(tag);
530                } catch (const msgpack::type_error&) {
531                    std::cerr << o.via.map.ptr[i].key << std::endl;
532                    throw_or_abort("expected u8 tag in int-keyed map for " + name);
533                }
534                dispatch(tag, o.via.map.ptr[i].val);
535            }
536        }
537
538        /// Cap a `MAP` or `ARRAY` entry count against `active + reserved`.
539        /// Under-length wires are caught downstream (`conv_fld_from_array`
540        /// errors out of bounds; `conv_fld_from_kvmap` errors on missing
541        /// required keys), so we only need the upper bound here.
542        ///
543        /// * Up to `reserved` extra trailing entries are tolerated as
544        ///   retired fields (`#[tagged(reserved(...))]` on the Rust side).
545        /// * Anything beyond that is forward-compat drift that the
546        ///   producer only emits when newer fields were added. The
547        ///   Rust-side cue is `#[tagged(allow_unknown_tags)]`; the
548        ///   message points at it so a reviewer can see the opt-in.
549        static void check_size(
550            uint32_t actual,
551            std::string const& name,
552            uint32_t active,
553            uint32_t reserved
554        ) {
555            uint32_t max_size = active + reserved;
556            if (actual > max_size) {
557                throw_or_abort(
558                    name + " has " + std::to_string(actual) +
559                    " entries but at most " + std::to_string(max_size) +
560                    " are expected (" + std::to_string(active) +
561                    " active + " + std::to_string(reserved) +
562                    " reserved); opt into `#[tagged(allow_unknown_tags)]` on the Rust type to accept extras");
563            }
564        }
565    };
566    "#;
567            // cSpell:enable
568            let pos = source.find(&format!("namespace {namespace}")).expect("namespace");
569            source.insert_str(pos, &format!("namespace {namespace} {{{helpers}}}\n\n"));
570        }
571
572        /// Reduce the opcode size in C++ by doing what Adam came up with in https://github.com/zefchain/serde-reflection/issues/75
573        fn replace_array_with_shared_ptr(source: &mut String) {
574            // Capture `std::array<$TYPE, $LEN>`
575            let re = Regex::new(r#"std::array<\s*([^,<>]+?)\s*,\s*([0-9]+)\s*>"#)
576                .expect("failed to create regex");
577
578            let fixed =
579                re.replace_all(source, "std::shared_ptr<std::array<${1}, ${2}>>").into_owned();
580
581            *source = fixed;
582        }
583
584        /// Add custom code for msgpack serialization and deserialization.
585        ///
586        /// Walks the serde-reflection `registry` and asserts that every
587        /// product/sum container is also present in the `MsgpackTagged`
588        /// `tag_registry` — every wire type must derive `MsgpackTagged`
589        /// for the int-keyed dispatch to have tag metadata available.
590        /// Products additionally have to be in *canonical* (tag-ascending)
591        /// source order — otherwise `MsgpackCompact` (source-order array)
592        /// and `MsgpackTagged::Array` (tag-ascending array) would produce
593        /// indistinguishable but byte-different wires on the same input,
594        /// and the C++ side can't tell which to expect.
595        pub(crate) fn generate(
596            namespace: &str,
597            registry: &Registry,
598            tag_registry: &'a TagRegistry,
599            code: CustomCode,
600            config: MsgPackCodeConfig,
601        ) -> CustomCode {
602            assert_tag_registry_covers(registry, tag_registry);
603            let mut g = Self { namespace: vec![namespace.to_string()], code, config, tag_registry };
604            for (name, container) in registry {
605                g.generate_container(name, container);
606            }
607            g.code
608        }
609
610        /// Append custom code of an item in the current namespace.
611        fn add_code(&mut self, name: &str, code: &str) {
612            let mut ns = self.namespace.clone();
613            ns.push(name.to_string());
614            let c = self.code.entry(ns).or_default();
615            if !c.is_empty() && code.contains('\n') {
616                c.push('\n');
617            }
618            c.push_str(code);
619            c.push('\n');
620        }
621
622        fn generate_container(&mut self, name: &str, container: &ContainerFormat) {
623            use serde_reflection::ContainerFormat::*;
624            match container {
625                UnitStruct => {
626                    self.generate_unit_struct(name);
627                }
628                NewTypeStruct(_format) => {
629                    self.generate_newtype(name);
630                }
631                TupleStruct(formats) => {
632                    self.generate_tuple(name, formats);
633                }
634                Struct(fields) => {
635                    // Top-level struct — look up its Product in the
636                    // tag registry. `assert_tag_registry_covers` has
637                    // already validated coverage.
638                    let product: Product = self
639                        .tag_registry
640                        .get(name)
641                        .expect("assert_tag_registry_covers should have caught this")
642                        .tagged()
643                        .as_product()
644                        .expect("struct name should map to a Product");
645                    self.generate_struct(name, fields, product);
646                }
647                Enum(variants) => {
648                    self.generate_enum(name, variants);
649                }
650            }
651        }
652
653        /// Unit structs don't have fields to put into the data.
654        fn generate_unit_struct(&mut self, name: &str) {
655            // Ostensibly we could use `MSGPACK_FIELDS();`, but because of how enum unpacking
656            // expects each variant to have `msgpack_unpack`, we generate two empty methods.
657            // self.msgpack_fields(name, std::iter::empty());
658            self.msgpack_pack(name, "");
659            self.msgpack_unpack(name, "");
660        }
661
662        /// Regular structs pack into a map. `product` is the
663        /// `MsgpackTagged` metadata for this shape: either the top-level
664        /// type's entry (for ordinary structs) or the variant's payload
665        /// (when recursing into a struct-variant). It carries the
666        /// per-field u8 tag the int-keyed dispatch branch needs.
667        fn generate_struct(&mut self, name: &str, fields: &[Named<Format>], product: Product) {
668            // We could use the `MSGPACK_FIELDS` macro with the following:
669            // self.msgpack_fields(name, fields.iter().map(|f| f.name.clone()));
670            // Unfortunately it doesn't seem to deal with missing optional fields,
671            // which would mean we can't delete fields even if they were optional:
672            // https://github.com/AztecProtocol/msgpack-c/blob/54e9865b84bbdc73cfbf8d1d437dbf769b64e386/include/msgpack/v1/adaptor/detail/cpp11_define_map.hpp#L33-L45
673
674            // Or we can generate code for individual fields, which relies on
675            // the `add_helpers` to add some utility functions. This way the
676            // code is more verbose, but also easier to control, e.g. we can
677            // raise errors telling specifically which field was wrong,
678            // or we could reject the data if there was a new field we could
679            // not recognize, or we could even handle aliases.
680
681            // We treat unit fields as special, using them to ignore fields during deserialization:
682            // * in 'map' format we skip over them, never try to deserialize them from the map
683            // * in 'tuple' format we jump over their index, ignoring whatever is in that position
684            fn is_unit(field: &Named<Format>) -> bool {
685                matches!(field.value, Format::Unit)
686            }
687
688            let non_unit_field_count = fields.iter().filter(|f| !is_unit(f)).count();
689
690            self.msgpack_pack(name, &{
691                if self.config.pack_compact {
692                    // Pack as ARRAY
693                    let mut body = format!(
694                        "
695    packer.pack_array({});",
696                        fields.len()
697                    );
698                    for field in fields {
699                        let field_name = &field.name;
700                        body.push_str(&format!(
701                            r#"
702    packer.pack({field_name});"#
703                        ));
704                    }
705                    body
706                } else {
707                    // Pack as MAP
708                    let mut body = format!(
709                        "
710    packer.pack_map({non_unit_field_count});",
711                    );
712                    for field in fields {
713                        if is_unit(field) {
714                            continue;
715                        }
716                        let field_name = &field.name;
717                        body.push_str(&format!(
718                            r#"
719    packer.pack(std::make_pair("{field_name}", {field_name}));"#
720                        ));
721                    }
722                    body
723                }
724            });
725
726            self.msgpack_unpack(name, &{
727                // Dispatch on wire shape:
728                //   * MAP with INT first key → `Format::MsgpackTagged`
729                //     (int-keyed: dispatch by u8 tag).
730                //   * MAP with STR keys → legacy `Format::Msgpack`
731                //     (string-keyed: look up by field name).
732                //   * ARRAY → legacy `Format::MsgpackCompact` and also
733                //     `Format::MsgpackTagged::Array` (both emit fields in
734                //     tag-ascending order under the codegen invariant
735                //     `tag_order_matches_source`, so positional indexing
736                //     decodes both correctly).
737                // cSpell:disable
738                let mut body = format!(
739                    r#"
740    std::string name = "{name}";
741    if (o.type == msgpack::type::MAP) {{
742        if (Helpers::is_int_keyed_map(o)) {{
743            Helpers::int_map_dispatch(o, name, [&](uint8_t tag, msgpack::object const& val) {{
744                switch (tag) {{"#
745                );
746                // cSpell:enable
747                for field in fields {
748                    let field_name = &field.name;
749                    let tag = product.tag_for(field_name).unwrap_or_else(|| {
750                        panic!(
751                            "field {field_name:?} of {name:?} is not in the MsgpackTagged \
752                             Product — serde-reflection and #[derive(MsgpackTagged)] disagree \
753                             on which fields are on the wire",
754                        )
755                    });
756                    if is_unit(field) {
757                        // Field is `()` in Rust / `std::monostate` in C++.
758                        // The wire still carries a value at this tag; we
759                        // consume it without binding to any C++ member,
760                        // mirroring `deserialize_unit`'s skip-any semantics.
761                        // cSpell:disable
762                        body.push_str(&format!(
763                            r#"
764                    case {tag}:
765                        // Field is `std::monostate` — wire entry intentionally discarded.
766                        break;"#
767                        ));
768                        // cSpell:enable
769                        continue;
770                    }
771                    // cSpell:disable
772                    body.push_str(&format!(
773                        r#"
774                    case {tag}:
775                        Helpers::convert_or_throw(val, name, "{field_name}", {field_name});
776                        break;"#
777                    ));
778                    // cSpell:enable
779                }
780                // Reserved tags: retired in the Rust type via
781                // `#[tagged(reserved(...))]`. The Rust decoder skips
782                // them silently regardless of `allow_unknown_tags`; do
783                // the same here, with an explicit case so the strict
784                // default below can distinguish "retired" from "never
785                // declared".
786                if !product.reserved.is_empty() {
787                    for &reserved_tag in product.reserved {
788                        body.push_str(&format!(
789                            "
790                    case {reserved_tag}:"
791                        ));
792                    }
793                    body.push_str(
794                        r#"
795                        // Reserved tag (retired field) — skip silently.
796                        break;"#,
797                    );
798                }
799                // Default branch: strict by default (reject unknown tags
800                // so a C++ reviewer can see the type-level intent). Opt
801                // into silent forward-compat with `#[tagged(allow_unknown_tags)]`
802                // on the Rust struct.
803                if product.allow_unknown_tags {
804                    body.push_str(
805                        r#"
806                    default:
807                        // `#[tagged(allow_unknown_tags)]` on the Rust side:
808                        // silently skip any tag we don't recognize.
809                        break;"#,
810                    );
811                } else {
812                    // cSpell:disable
813                    body.push_str(&format!(
814                        r#"
815                    default:
816                        std::cerr << val << std::endl;
817                        throw_or_abort("unknown tag for {name}: " + std::to_string(tag));"#
818                    ));
819                    // cSpell:enable
820                }
821                // cSpell:disable
822                body.push_str(
823                    r#"
824                }
825            });
826        } else {"#,
827                );
828                // cSpell:enable
829                // String-keyed map branch (`Format::Msgpack`): the
830                // lookup is by field name, so extra wire keys would
831                // otherwise pass unnoticed. Same `active + reserved`
832                // ceiling and same `allow_unknown_tags` opt-out as the
833                // ARRAY branch.
834                if !product.allow_unknown_tags {
835                    body.push_str(&format!(
836                        r#"
837            Helpers::check_size(o.via.map.size, name, {active}, {reserved});"#,
838                        active = fields.len(),
839                        reserved = product.reserved.len(),
840                    ));
841                }
842                // cSpell:disable
843                body.push_str(
844                    r#"
845            auto kvmap = Helpers::make_kvmap(o, name);"#,
846                );
847                // cSpell:enable
848                for field in fields {
849                    if is_unit(field) {
850                        continue;
851                    }
852                    let field_name = &field.name;
853                    let is_optional = matches!(field.value, Format::Option(_));
854                    // cSpell:disable
855                    body.push_str(&format!(
856                        r#"
857            Helpers::conv_fld_from_kvmap(kvmap, name, "{field_name}", {field_name}, {is_optional});"#
858                    ));
859                    // cSpell:enable
860                }
861                body.push_str(
862                    "
863        }
864    } else if (o.type == msgpack::type::ARRAY) {
865        auto array = o.via.array;",
866                );
867                // Cap the array length: an older reader of a newer wire
868                // (forward-compat) should reject extra trailing items
869                // unless the type opts into `#[tagged(allow_unknown_tags)]`;
870                // a newer reader of an older wire that retired trailing
871                // fields (backward-compat) gets `reserved.len()` extra
872                // trailing positions tolerated either way.
873                //
874                // Under-length wires are caught downstream by
875                // `Helpers::conv_fld_from_array` (it errors when its
876                // index is past `array.size`), so we only need the
877                // upper bound here.
878                if !product.allow_unknown_tags {
879                    body.push_str(&format!(
880                        r#"
881        Helpers::check_size(array.size, name, {active}, {reserved});"#,
882                        active = fields.len(),
883                        reserved = product.reserved.len(),
884                    ));
885                }
886                for (index, field) in fields.iter().enumerate() {
887                    if is_unit(field) {
888                        continue;
889                    }
890                    let field_name = &field.name;
891                    // cSpell:disable
892                    body.push_str(&format!(
893                        r#"
894        Helpers::conv_fld_from_array(array, name, "{field_name}", {field_name}, {index});"#
895                    ));
896                    // cSpell:enable
897                }
898
899                body.push_str(
900                    r#"
901    } else {
902        throw_or_abort("expected MAP or ARRAY for " + name);
903    }"#,
904                );
905                body
906            });
907        }
908
909        /// Newtypes serialize as their underlying `value` that the C++ generator creates.
910        fn generate_newtype(&mut self, name: &str) {
911            self.msgpack_pack(name, "packer.pack(value);");
912            self.msgpack_unpack(
913                name,
914                // cSpell:disable
915                &format!(
916                    r#"
917    try {{
918        o.convert(value);
919    }} catch (const msgpack::type_error&) {{
920        std::cerr << o << std::endl;
921        throw_or_abort("error converting into newtype '{name}'");
922    }}
923            "#
924                ),
925                // cSpell:enable
926            );
927        }
928
929        /// Tuples serialize as a vector of underlying data.
930        fn generate_tuple(&self, _name: &str, _formats: &[Format]) {
931            unimplemented!("Until we have a tuple enum in our schema we don't need this.");
932        }
933
934        /// Enums serialize as a single element map keyed by the variant type name.
935        fn generate_enum(&mut self, name: &str, variants: &BTreeMap<u32, Named<VariantFormat>>) {
936            // Look up the `Sum` upfront — both the per-variant recursion
937            // (each variant's payload Product comes from here) and the
938            // int-keyed `msgpack_unpack` body below need it. Coverage is
939            // already enforced by `assert_tag_registry_covers`.
940            let sum: Sum = self
941                .tag_registry
942                .get(name)
943                .expect("assert_tag_registry_covers should have caught this")
944                .tagged()
945                .as_sum()
946                .expect("enum name should map to a Sum");
947
948            // Resolve a variant's payload Product by its serde name. The
949            // `MsgpackTagged` macro and serde-derive agree on names, so
950            // a miss here is an internal bug.
951            let payload_for = |variant_name: &str| -> Product {
952                sum.variants.iter().find(|v| v.name == variant_name).map_or_else(
953                    || {
954                        panic!(
955                            "variant {variant_name:?} of enum {name:?} is not in the \
956                             MsgpackTagged Sum — serde-reflection and \
957                             #[derive(MsgpackTagged)] disagree on variants",
958                        )
959                    },
960                    |v| v.payload,
961                )
962            };
963
964            // Recurse into the variants
965            self.namespace.push(name.to_string());
966            for variant in variants.values() {
967                let payload = payload_for(&variant.name);
968                self.generate_variant(&variant.name, &variant.value, payload);
969            }
970            self.namespace.pop();
971
972            // Pack the enum itself
973            self.msgpack_pack(name, &{
974                let cases = variants
975                    .iter()
976                    .map(|(i, v)| {
977                        format!(
978                            r#"
979        case {i}:
980            tag = "{}";
981            is_unit = {};
982            break;"#,
983                            v.name,
984                            matches!(v.value, VariantFormat::Unit)
985                        )
986                    })
987                    .collect::<Vec<_>>()
988                    .join("");
989
990                format!(
991                    r#"
992    std::string tag;
993    bool is_unit;
994    switch (value.index()) {{
995        {cases}
996        default:
997            throw_or_abort("unknown enum '{name}' variant index: " + std::to_string(value.index()));
998    }}
999    if (is_unit) {{
1000        packer.pack(tag);
1001    }} else {{
1002        std::visit([&packer, tag](const auto& arg) {{
1003            packer.pack_map(1);
1004            packer.pack(tag);
1005            packer.pack(arg);
1006        }}, value);
1007    }}"#
1008                )
1009            });
1010
1011            // Unpack the enum:
1012            //   * MAP with INT key → `Format::MsgpackTagged` variant
1013            //     (int-keyed dispatch on the u8 tag).
1014            //   * MAP with STR key → legacy `Format::Msgpack` variant
1015            //     (string-keyed dispatch on variant name).
1016            //   * STR top-level → legacy `Format::MsgpackCompact` unit
1017            //     variant (bare variant-name string).
1018            // The 1-entry-size invariant of the MAP cases is enforced
1019            // upfront.
1020            self.msgpack_unpack(name, &{
1021                // cSpell:disable
1022                let mut body = format!(
1023                    r#"
1024
1025    if (o.type != msgpack::type::object_type::MAP && o.type != msgpack::type::object_type::STR) {{
1026        std::cerr << o << std::endl;
1027        throw_or_abort("expected MAP or STR for enum '{name}'; got type " + std::to_string(o.type));
1028    }}
1029    if (o.type == msgpack::type::object_type::MAP && o.via.map.size != 1) {{
1030        throw_or_abort("expected 1 entry for enum '{name}'; got " + std::to_string(o.via.map.size));
1031    }}
1032    if (Helpers::is_int_keyed_map(o)) {{
1033        // `Format::MsgpackTagged` — int-keyed variant.
1034        uint8_t tag;
1035        try {{
1036            o.via.map.ptr[0].key.convert(tag);
1037        }} catch (const msgpack::type_error&) {{
1038            std::cerr << o << std::endl;
1039            throw_or_abort("expected u8 variant tag for enum '{name}'");
1040        }}
1041        switch (tag) {{"#
1042                );
1043                // cSpell:enable
1044
1045                for v in variants.values() {
1046                    let variant = &v.name;
1047                    let tag = sum.variants.iter().find(|reg_v| reg_v.name == variant).map_or_else(
1048                        || {
1049                            panic!(
1050                                "variant {variant:?} of enum {name:?} is not in the MsgpackTagged \
1051                                 Sum — serde-reflection and #[derive(MsgpackTagged)] disagree",
1052                            )
1053                        },
1054                        |reg_v| reg_v.tag,
1055                    );
1056                    if matches!(v.value, VariantFormat::Unit) {
1057                        // Unit variants carry a `nil` payload that the
1058                        // variant's empty `msgpack_unpack` would accept,
1059                        // but constructing the value and assigning is
1060                        // simpler and avoids reading `o.via.map.ptr[0].val`
1061                        // (which Barretenberg's `-Werror=unused-variable`
1062                        // flags when all variants of an enum are unit).
1063                        body.push_str(&format!(
1064                            r#"
1065            case {tag}: {{
1066                {variant} v;
1067                value = v;
1068                break;
1069            }}"#
1070                        ));
1071                    } else {
1072                        // cSpell:disable
1073                        body.push_str(&format!(
1074                            r#"
1075            case {tag}: {{
1076                {variant} v;
1077                try {{
1078                    o.via.map.ptr[0].val.convert(v);
1079                }} catch (const msgpack::type_error&) {{
1080                    std::cerr << o << std::endl;
1081                    throw_or_abort("error converting into enum variant '{name}::{variant}'");
1082                }}
1083                value = v;
1084                break;
1085            }}"#
1086                        ));
1087                        // cSpell:enable
1088                    }
1089                }
1090
1091                // Reserved variant tags: retired variants on the Rust side
1092                // (`#[tagged(reserved(...))]` on the enum). If the enum
1093                // marks a unit variant with `#[tagged(on_reserved)]`,
1094                // legacy wires carrying a retired tag route to that
1095                // fallback; otherwise we throw with a "retired" message
1096                // that's distinguishable from the "unknown" case below.
1097                let lookup_unit_variant = |fallback_tag: u8| -> &str {
1098                    sum.variants.iter().find(|v| v.tag == fallback_tag).map_or_else(
1099                        || {
1100                            panic!(
1101                                "MsgpackTagged Sum for enum {name:?} declares a fallback \
1102                                 tag {fallback_tag} that doesn't match any registered variant",
1103                            )
1104                        },
1105                        |v| v.name,
1106                    )
1107                };
1108                if !sum.reserved.is_empty() {
1109                    for &reserved_tag in sum.reserved {
1110                        body.push_str(&format!(
1111                            "
1112            case {reserved_tag}:"
1113                        ));
1114                    }
1115                    if let Some(fallback_tag) = sum.on_reserved_tag {
1116                        let fallback_name = lookup_unit_variant(fallback_tag);
1117                        // cSpell:disable
1118                        body.push_str(&format!(
1119                            r#" {{
1120                // `#[tagged(on_reserved)]` fallback: retired tag routes to
1121                // the designated unit variant (payload discarded).
1122                {fallback_name} v;
1123                value = v;
1124                break;
1125            }}"#
1126                        ));
1127                        // cSpell:enable
1128                    } else {
1129                        // cSpell:disable
1130                        body.push_str(&format!(
1131                            r#"
1132                std::cerr << o << std::endl;
1133                throw_or_abort("retired variant tag for enum '{name}' (declare `#[tagged(on_reserved)]` on a unit variant to route legacy data here): " + std::to_string(tag));"#
1134                        ));
1135                        // cSpell:enable
1136                    }
1137                }
1138
1139                // Default branch for unknown variant tags (not active,
1140                // not reserved). Routes to `#[tagged(on_unknown)]` if
1141                // set — the forward-compat opt-in for newer producers
1142                // introducing variants this code doesn't know about.
1143                if let Some(fallback_tag) = sum.on_unknown_tag {
1144                    let fallback_name = lookup_unit_variant(fallback_tag);
1145                    // cSpell:disable
1146                    body.push_str(&format!(
1147                        r#"
1148            default: {{
1149                // `#[tagged(on_unknown)]` fallback: any tag we don't recognize
1150                // (and isn't reserved) routes here.
1151                {fallback_name} v;
1152                value = v;
1153                break;
1154            }}"#
1155                    ));
1156                    // cSpell:enable
1157                } else {
1158                    // cSpell:disable
1159                    body.push_str(&format!(
1160                        r#"
1161            default:
1162                std::cerr << o << std::endl;
1163                throw_or_abort("unknown '{name}' enum variant tag: " + std::to_string(tag));"#
1164                    ));
1165                    // cSpell:enable
1166                }
1167                // cSpell:disable
1168                body.push_str(
1169                    r#"
1170        }
1171    } else {"#,
1172                );
1173                // cSpell:enable
1174                // Reuse the existing format-string body for the legacy
1175                // string-keyed dispatch path. The `format!(name = ...)`
1176                // substitution from the previous body is preserved in the
1177                // already-appended text; the section below is plain C++
1178                // with no further interpolation.
1179                body.push_str(&format!(
1180                    r#"
1181        // `Format::Msgpack` (MAP, string-keyed) or `Format::MsgpackCompact`
1182        // unit variant (bare STR) — both dispatch on the variant name.
1183        std::string tag;
1184        try {{
1185            if (o.type == msgpack::type::object_type::MAP) {{
1186                o.via.map.ptr[0].key.convert(tag);
1187            }} else {{
1188                o.convert(tag);
1189            }}
1190        }} catch(const msgpack::type_error&) {{
1191            std::cerr << o << std::endl;
1192            throw_or_abort("error converting tag to string for enum '{name}'");
1193        }}"#
1194                ));
1195                // cSpell:enable
1196
1197                for (i, v) in variants {
1198                    let variant = &v.name;
1199                    body.push_str(&format!(
1200                        r#"
1201        {}if (tag == "{variant}") {{
1202            {variant} v;"#,
1203                        if *i == 0 { "" } else { "else " }
1204                    ));
1205
1206                    if !matches!(v.value, VariantFormat::Unit) {
1207                        // cSpell:disable
1208                        body.push_str(&format!(
1209                            r#"
1210            try {{
1211                o.via.map.ptr[0].val.convert(v);
1212            }} catch (const msgpack::type_error&) {{
1213                std::cerr << o << std::endl;
1214                throw_or_abort("error converting into enum variant '{name}::{variant}'");
1215            }}
1216            "#
1217                        ));
1218                        // cSpell:enable
1219                    }
1220                    // Closing brace of if statement
1221                    body.push_str(
1222                        r#"
1223            value = v;
1224        }"#,
1225                    );
1226                }
1227                // cSpell:disable
1228                body.push_str(&format!(
1229                    r#"
1230        else {{
1231            std::cerr << o << std::endl;
1232            throw_or_abort("unknown '{name}' enum variant: " + tag);
1233        }}
1234    }}"#
1235                ));
1236                // cSpell:enable
1237
1238                body
1239            });
1240        }
1241
1242        /// Generate msgpack code for nested enum variants. `payload` is
1243        /// the variant's `MsgpackTagged` `Product`, looked up from the
1244        /// parent enum's `Sum`. Only the `Struct` variant case consults
1245        /// it (it carries the int-keyed dispatch metadata for that
1246        /// variant's named fields); the other cases are tag-irrelevant
1247        /// (unit / passthrough newtype / unimplemented tuple).
1248        fn generate_variant(&mut self, name: &str, variant: &VariantFormat, payload: Product) {
1249            match variant {
1250                VariantFormat::Variable(_) => {
1251                    unreachable!("internal construct")
1252                }
1253                VariantFormat::Unit => self.generate_unit_struct(name),
1254                VariantFormat::NewType(_format) => self.generate_newtype(name),
1255                VariantFormat::Tuple(formats) => self.generate_tuple(name, formats),
1256                VariantFormat::Struct(fields) => self.generate_struct(name, fields, payload),
1257            }
1258        }
1259
1260        /// Use the `MSGPACK_FIELDS` macro with a list of fields.
1261        /// This one takes care of serializing and deserializing as well.
1262        ///
1263        /// Uses [define_map](https://github.com/AztecProtocol/msgpack-c/blob/54e9865b84bbdc73cfbf8d1d437dbf769b64e386/include/msgpack/v1/adaptor/detail/cpp11_define_map.hpp#L75-L88) under the hood.
1264        #[allow(dead_code)]
1265        fn msgpack_fields(&mut self, name: &str, fields: impl Iterator<Item = String>) {
1266            let fields = fields.collect::<Vec<_>>().join(", ");
1267            let code = format!("MSGPACK_FIELDS({fields});");
1268            self.add_code(name, &code);
1269        }
1270
1271        /// Add a `msgpack_pack` implementation.
1272        fn msgpack_pack(&mut self, name: &str, body: &str) {
1273            if self.config.no_pack {
1274                return;
1275            }
1276            let code = Self::make_fn("void msgpack_pack(auto& packer) const", body);
1277            self.add_code(name, &code);
1278        }
1279
1280        /// Add a `msgpack_unpack` implementation.
1281        fn msgpack_unpack(&mut self, name: &str, body: &str) {
1282            // Using `msgpack::object const& o` instead of `auto o`, because the latter is passed as `msgpack::object::implicit_type`,
1283            // which would have to be cast like `msgpack::object obj = o;`. This `const&` pattern exists in `msgpack-c` codebase.
1284
1285            // Instead of implementing the `msgpack_unpack` method as suggested by `msgpack.hpp` in Barretenberg,
1286            // we could implement an extension method on `msgpack::object` as below. However, it has to be in
1287            // the `msgpack::adaptor` namespace, which would mean it has to be appended at the end of the code,
1288            // rather than into the structs, where `CustomCode` goes.
1289            //
1290            // namespace msgpack {
1291            // namespace adaptor {
1292            // // For Opcode
1293            // template <> struct msgpack::adaptor::convert<Acir::Opcode> {
1294            //     msgpack::object const& operator()(msgpack::object const& o, Acir::Opcode& v) const
1295            //     {
1296            //         return o;
1297            //         if (o.type != msgpack::type::MAP || o.via.map.size != 1) {
1298            //             throw_or_abort("expected single element map for 'Opcode'");
1299            //         }
1300
1301            //         auto& kv = o.via.map.ptr[0];
1302            //         std::string key = kv.key.as<std::string>();
1303
1304            //         if (key == "BrilligCall") {
1305            //             Acir::Opcode::BrilligCall bc = kv.val.as<Acir::Opcode::BrilligCall>();
1306            //             v.value = bc;
1307            //         } else if (key == "AssertZero") {
1308            //             Acir::Opcode::AssertZero az = kv.val.as<Acir::Opcode::AssertZero>();
1309            //             v.value = az;
1310            //         } else {
1311            //             throw_or_abort("unknown tag for 'Opcode': " + key);
1312            //         }
1313            //         return o;
1314            //     }
1315            // };
1316            // } // namespace adaptor
1317            // } // namespace msgpack
1318
1319            let code = Self::make_fn("void msgpack_unpack(msgpack::object const& o)", body);
1320            self.add_code(name, &code);
1321        }
1322
1323        fn make_fn(header: &str, body: &str) -> String {
1324            let body = body.trim_end();
1325            if body.is_empty() {
1326                format!("{header} {{}}")
1327            } else if !body.contains('\n') {
1328                format!("{header} {{ {body} }}")
1329            } else if body.starts_with('\n') {
1330                format!("{header} {{{body}\n}}")
1331            } else {
1332                format!("{header} {{\n{body}\n}}")
1333            }
1334        }
1335    }
1336}