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}