Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/macros/events.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use protocol_types::meta::generate_serialize_to_fields;
comptime fn generate_event_interface(s: TypeDefinition) -> Quoted {
let name = s.name();
let typ = s.as_type();
let (serialization_fields, _) = generate_serialize_to_fields(quote { self }, typ, &[], false);
let (serialization_fields, _) = generate_serialize_to_fields(quote { self }, typ, false);
let content_len = serialization_fields.len();

let event_type_id = compute_event_selector(s);
Expand Down
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub(crate) comptime fn transform_public(f: FunctionDefinition) -> Quoted {
let original_params = f.parameters();
let args_len = original_params
.map(|(name, typ): (Quoted, Type)| {
generate_serialize_to_fields(name, typ, &[], false).0.len()
generate_serialize_to_fields(name, typ, false).0.len()
})
.fold(0, |acc: u32, val: u32| acc + val);

Expand Down
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/macros/notes.nr
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ comptime fn index_note_fields(
} else {
indexed_nullable_fields = indexed_nullable_fields.push_back((name, typ, counter));
}
let (serialization_fields, _) = generate_serialize_to_fields(name, typ, &[], true);
let (serialization_fields, _) = generate_serialize_to_fields(name, typ, true);
// Each struct member can occupy multiple fields so we need to increment the counter accordingly
counter += serialization_fields.len();
}
Expand Down
209 changes: 94 additions & 115 deletions noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ pub comptime fn generate_deserialize_from_fields(
/// # Parameters
/// - `name`: The base identifier (e.g., `self`, `some_var`).
/// - `typ`: The type being serialized (e.g., a custom struct, array, or primitive type).
/// - `omit`: A list of field names (as `Quoted`) to be excluded from the serialized output.
/// - `should_pack`: A boolean indicating whether the type should be packed.
///
/// # Returns
Expand All @@ -248,7 +247,7 @@ pub comptime fn generate_deserialize_from_fields(
///
/// Serializing the struct:
/// ```rust
/// generate_serialize_to_fields(quote { my_mock_struct }, MockStruct, &[], false)
/// generate_serialize_to_fields(quote { my_mock_struct }, MockStruct, false)
/// // Returns:
/// // ([`my_mock_struct.a`, `my_mock_struct.b`], [])
/// ```
Expand All @@ -264,29 +263,29 @@ pub comptime fn generate_deserialize_from_fields(
///
/// Serialization output:
/// ```rust
/// generate_serialize_to_fields(quote { self }, NestedStruct, &[], false)
/// generate_serialize_to_fields(quote { self }, NestedStruct, false)
/// // Returns:
/// // ([`self.m1.a`, `self.m1.b`, `self.m2.a`, `self.m2.b`], [])
/// ```
///
/// ## Array
/// For an array type:
/// ```rust
/// generate_serialize_to_fields(quote { my_array }, [Field; 3], &[], false)
/// generate_serialize_to_fields(quote { my_array }, [Field; 3], false)
/// // Returns:
/// // ([`my_array[0]`, `my_array[1]`, `my_array[2]`], [])
/// ```
///
/// ## String
/// For a string field, where each character is serialized as a `Field`:
/// ```rust
/// generate_serialize_to_fields(quote { my_string }, StringType, &[], false)
/// generate_serialize_to_fields(quote { my_string }, StringType, false)
/// // Returns:
/// // ([`my_string_as_bytes[0] as Field`, `my_string_as_bytes[1] as Field`, ...],
/// // [`let my_string_as_bytes = my_string.as_bytes()`])
/// ```
///
/// ## Nested Struct with Omitted Field and packing enabled
/// ## Nested Struct with packing enabled
/// - u128 has a `Packable` implementation hence it will be packed.
///
/// For a more complex struct:
Expand All @@ -297,128 +296,109 @@ pub comptime fn generate_deserialize_from_fields(
/// }
/// ```
///
/// Serializing while omitting `value2`:
/// ```rust
/// generate_serialize_to_fields(quote { self }, MyStruct, &[quote { self.value2 }], true)
/// // Returns:
/// // ([`value_packed[0]`], [`let value_packed = self.value.pack()`])
/// ```
///
/// # Panics
/// - If the type is unsupported for serialization.
/// - If the provided `typ` contains invalid constants or incompatible structures.
pub comptime fn generate_serialize_to_fields(
name: Quoted,
typ: Type,
omit: [Quoted],
should_pack: bool,
) -> ([Quoted], [Quoted]) {
let mut fields = &[];
let mut aux_vars = &[];

// Proceed if none of the omit rules omits this name
if !omit.any(|to_omit| to_omit == name) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide whitespace when reviewing this, it's mostly indentation changes.

// If the type implements `Packable`, its length will be assigned to the `maybe_packed_len_typ` variable.
let maybe_packed_len_typ = std::meta::typ::fresh_type_variable();
let packable_constraint =
quote { crate::traits::Packable<$maybe_packed_len_typ> }.as_trait_constraint();

if (should_pack & typ.implements(packable_constraint)) {
// Packing is enabled and the given type implements the `Packable` trait so we call the `pack()`
// method, add the resulting field array to `aux_vars` and each field to `fields`.
let packed_len = maybe_packed_len_typ.as_constant().unwrap();

// We collapse the name to a one that gets tokenized as a single token (e.g. "self.value" -> "self_value").
let name_at_one_token = collapse_to_one_token(name);
let packed_struct_name = f"{name_at_one_token}_aux_var".quoted_contents();

// We add the individual fields to the fields array
let pack_method = get_trait_impl_method(
typ,
quote { crate::traits::Packable<$packed_len> },
quote { pack },
);
let packed_struct = quote { let $packed_struct_name = $pack_method($name) };
for i in 0..packed_len {
fields = fields.push_back(quote { $packed_struct_name[$i] });
}
// If the type implements `Packable`, its length will be assigned to the `maybe_packed_len_typ` variable.
let maybe_packed_len_typ = std::meta::typ::fresh_type_variable();
let packable_constraint =
quote { crate::traits::Packable<$maybe_packed_len_typ> }.as_trait_constraint();

// We add the new auxiliary variable to the aux_vars array
aux_vars = aux_vars.push_back(packed_struct);
} else if typ.is_field() {
// For field we just add the value to fields
fields = fields.push_back(name);
} else if typ.as_integer().is_some() | typ.is_bool() {
// For integer and bool we just cast to Field and add the value to fields
fields = fields.push_back(quote { $name as Field });
} else if typ.as_data_type().is_some() {
// For struct we pref
let nested_struct = typ.as_data_type().unwrap();
let params = nested_struct.0.fields(nested_struct.1);
let struct_flattened = params.map(|(param_name, param_type): (Quoted, Type)| {
let maybe_prefixed_name = if name == quote {} {
// Triggered when the param name is of a value available in the current scope (e.g. a function
// argument) --> then we don't prefix the name with anything.
param_name
} else {
// Triggered when we want to prefix the param name with the `name` from function input. This
// can typically be `self` when implementing a method on a struct.
quote { $name.$param_name }
};
generate_serialize_to_fields(
quote {$maybe_prefixed_name},
param_type,
omit,
should_pack,
)
});
let struct_flattened_fields = struct_flattened.fold(
&[],
|acc: [Quoted], (fields, _): (_, [Quoted])| acc.append(fields),
);
let struct_flattened_aux_vars = struct_flattened.fold(
&[],
|acc: [Quoted], (_, aux_vars): ([Quoted], _)| acc.append(aux_vars),
);
fields = fields.append(struct_flattened_fields);
aux_vars = aux_vars.append(struct_flattened_aux_vars);
} else if typ.as_array().is_some() {
// For array we recursively call `generate_serialize_to_fields(...)` for each element
let (element_type, array_len) = typ.as_array().unwrap();
let array_len = array_len.as_constant().unwrap();
for i in 0..array_len {
let (element_fields, element_aux_vars) = generate_serialize_to_fields(
quote { $name[$i] },
element_type,
omit,
should_pack,
);
fields = fields.append(element_fields);
aux_vars = aux_vars.append(element_aux_vars);
}
} else if typ.as_str().is_some() {
// For string we convert the value to bytes, we store the `as_bytes` in an auxiliary variables and
// then we add each byte to fields as a Field
let length_type = typ.as_str().unwrap();
let str_len = length_type.as_constant().unwrap();
let as_member = name.as_expr().unwrap().as_member_access();
let var_name = if as_member.is_some() {
as_member.unwrap().1
if (should_pack & typ.implements(packable_constraint)) {
// Packing is enabled and the given type implements the `Packable` trait so we call the `pack()`
// method, add the resulting field array to `aux_vars` and each field to `fields`.
let packed_len = maybe_packed_len_typ.as_constant().unwrap();

// We collapse the name to a one that gets tokenized as a single token (e.g. "self.value" -> "self_value").
let name_at_one_token = collapse_to_one_token(name);
let packed_struct_name = f"{name_at_one_token}_aux_var".quoted_contents();

// We add the individual fields to the fields array
let pack_method = get_trait_impl_method(
typ,
quote { crate::traits::Packable<$packed_len> },
quote { pack },
);
let packed_struct = quote { let $packed_struct_name = $pack_method($name) };
for i in 0..packed_len {
fields = fields.push_back(quote { $packed_struct_name[$i] });
}

// We add the new auxiliary variable to the aux_vars array
aux_vars = aux_vars.push_back(packed_struct);
} else if typ.is_field() {
// For field we just add the value to fields
fields = fields.push_back(name);
} else if typ.as_integer().is_some() | typ.is_bool() {
// For integer and bool we just cast to Field and add the value to fields
fields = fields.push_back(quote { $name as Field });
} else if typ.as_data_type().is_some() {
// For struct we pref
let nested_struct = typ.as_data_type().unwrap();
let params = nested_struct.0.fields(nested_struct.1);
let struct_flattened = params.map(|(param_name, param_type): (Quoted, Type)| {
let maybe_prefixed_name = if name == quote {} {
// Triggered when the param name is of a value available in the current scope (e.g. a function
// argument) --> then we don't prefix the name with anything.
param_name
} else {
name
// Triggered when we want to prefix the param name with the `name` from function input. This
// can typically be `self` when implementing a method on a struct.
quote { $name.$param_name }
};
let as_bytes_name = f"{var_name}_as_bytes".quoted_contents();
let as_bytes = quote { let $as_bytes_name = $name.as_bytes() };
for i in 0..str_len {
fields = fields.push_back(quote { $as_bytes_name[$i] as Field });
}
aux_vars = aux_vars.push_back(as_bytes);
generate_serialize_to_fields(quote {$maybe_prefixed_name}, param_type, should_pack)
});
let struct_flattened_fields = struct_flattened.fold(
&[],
|acc: [Quoted], (fields, _): (_, [Quoted])| acc.append(fields),
);
let struct_flattened_aux_vars = struct_flattened.fold(
&[],
|acc: [Quoted], (_, aux_vars): ([Quoted], _)| acc.append(aux_vars),
);
fields = fields.append(struct_flattened_fields);
aux_vars = aux_vars.append(struct_flattened_aux_vars);
} else if typ.as_array().is_some() {
// For array we recursively call `generate_serialize_to_fields(...)` for each element
let (element_type, array_len) = typ.as_array().unwrap();
let array_len = array_len.as_constant().unwrap();
for i in 0..array_len {
let (element_fields, element_aux_vars) =
generate_serialize_to_fields(quote { $name[$i] }, element_type, should_pack);
fields = fields.append(element_fields);
aux_vars = aux_vars.append(element_aux_vars);
}
} else if typ.as_str().is_some() {
// For string we convert the value to bytes, we store the `as_bytes` in an auxiliary variables and
// then we add each byte to fields as a Field
let length_type = typ.as_str().unwrap();
let str_len = length_type.as_constant().unwrap();
let as_member = name.as_expr().unwrap().as_member_access();
let var_name = if as_member.is_some() {
as_member.unwrap().1
} else {
panic(
f"Unsupported type for serialization of argument {name} and type {typ}",
)
name
};
let as_bytes_name = f"{var_name}_as_bytes".quoted_contents();
let as_bytes = quote { let $as_bytes_name = $name.as_bytes() };
for i in 0..str_len {
fields = fields.push_back(quote { $as_bytes_name[$i] as Field });
}
aux_vars = aux_vars.push_back(as_bytes);
} else {
panic(
f"Unsupported type for serialization of argument {name} and type {typ}",
)
}

(fields, aux_vars)
}

Expand All @@ -441,7 +421,7 @@ comptime fn collapse_to_one_token(q: Quoted) -> Quoted {

pub(crate) comptime fn derive_serialize(s: TypeDefinition) -> Quoted {
let typ = s.as_type();
let (fields, aux_vars) = generate_serialize_to_fields(quote { self }, typ, &[], false);
let (fields, aux_vars) = generate_serialize_to_fields(quote { self }, typ, false);
let aux_vars_for_serialization = if aux_vars.len() > 0 {
let joint = aux_vars.join(quote {;});
quote { $joint; }
Expand All @@ -463,7 +443,7 @@ pub(crate) comptime fn derive_serialize(s: TypeDefinition) -> Quoted {

pub(crate) comptime fn derive_deserialize(s: TypeDefinition) -> Quoted {
let typ = s.as_type();
let (fields, _) = generate_serialize_to_fields(quote { self }, typ, &[], false);
let (fields, _) = generate_serialize_to_fields(quote { self }, typ, false);
let serialized_len = fields.len();
let (deserialized, _) =
generate_deserialize_from_fields(quote { self }, typ, quote { serialized }, 0, false);
Expand All @@ -485,8 +465,7 @@ pub comptime fn derive_packable_and_get_packed_len(s: TypeDefinition) -> (Quoted
let packing_enabled = true;

let typ = s.as_type();
let (fields, aux_vars) =
generate_serialize_to_fields(quote { self }, typ, &[], packing_enabled);
let (fields, aux_vars) = generate_serialize_to_fields(quote { self }, typ, packing_enabled);
let aux_vars_for_packing = if aux_vars.len() > 0 {
let joint = aux_vars.join(quote {;});
quote { $joint; }
Expand Down