diff --git a/Cargo.lock b/Cargo.lock index 707a33d749..5f3a4ad91c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4919,6 +4919,7 @@ dependencies = [ "cairo-lang-utils", "dojo-types 1.0.10", "itertools 0.12.1", + "regex", "serde", "smol_str", "starknet 0.12.0", diff --git a/crates/dojo/core-cairo-test/Scarb.lock b/crates/dojo/core-cairo-test/Scarb.lock index 2c1a7ab14c..0e3efc0db1 100644 --- a/crates/dojo/core-cairo-test/Scarb.lock +++ b/crates/dojo/core-cairo-test/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "1.0.0-rc.0" +version = "1.0.9" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo/core-cairo-test/src/tests/meta/introspect.cairo b/crates/dojo/core-cairo-test/src/tests/meta/introspect.cairo index bfa89e3023..f82f696f33 100644 --- a/crates/dojo/core-cairo-test/src/tests/meta/introspect.cairo +++ b/crates/dojo/core-cairo-test/src/tests/meta/introspect.cairo @@ -12,6 +12,12 @@ struct WithArray { arr: Array } +#[derive(Drop, Introspect)] +struct WithFixedArray { + value: u32, + arr: [u8; 3] +} + #[derive(Drop, Introspect)] struct WithByteArray { value: u32, @@ -111,6 +117,31 @@ struct Generic { value: T, } +#[derive(Drop, Introspect)] +struct StructWithFixedArray { + x: [u8; 3] +} + +#[derive(Drop, Introspect)] +struct StructWithComplexFixedArray { + x: [[EnumWithSameData; 2]; 3] +} + +#[derive(Drop, Introspect)] +enum EnumWithFixedArray { + A: [u8; 3] +} + +#[derive(Drop, IntrospectPacked)] +struct StructWithFixedArrayPacked { + x: [u8; 3] +} + +#[derive(Drop, IntrospectPacked)] +enum EnumWithFixedArrayPacked { + A: [u8; 3] +} + fn field(selector: felt252, layout: Layout) -> FieldLayout { FieldLayout { selector, layout } } @@ -123,6 +154,10 @@ fn tuple(values: Array) -> Layout { Layout::Tuple(values.span()) } +fn fixed_array(inner_layout: Layout, size: u32) -> Layout { + Layout::FixedArray(array![(inner_layout, size)].span()) +} + fn _enum(values: Array>) -> Layout { let mut items = array![]; let mut i = 0; @@ -166,6 +201,12 @@ fn test_size_with_array() { assert!(Introspect::::size().is_none()); } +// fn test_size_with_fixed_array() { +// let size = Introspect::::size(); +// assert!(size.is_some()); +// assert!(size.unwrap() == 4); +// } + #[test] fn test_size_with_byte_array() { assert!(Introspect::::size().is_none()); @@ -212,7 +253,6 @@ fn test_size_of_enum_with_same_tuple_variant_data() { assert!(size.unwrap() == 4); } - #[test] fn test_size_of_struct_with_option() { let size = Introspect::::size(); @@ -225,6 +265,41 @@ fn test_size_of_enum_with_variant_data() { assert!(size.is_none()); } +#[test] +fn test_size_of_struct_with_fixed_array() { + let size = Introspect::::size(); + assert!(size.is_some()); + assert!(size.unwrap() == 3); +} + +#[test] +fn test_size_of_struct_with_complex_fixed_array() { + let size = Introspect::::size(); + assert!(size.is_some()); + assert!(size.unwrap() == 18); +} + +#[test] +fn test_size_of_packed_struct_with_fixed_array() { + let size = Introspect::::size(); + assert!(size.is_some()); + assert!(size.unwrap() == 3); +} + +#[test] +fn test_size_of_enum_with_fixed_array() { + let size = Introspect::::size(); + assert!(size.is_some()); + assert!(size.unwrap() == 4); +} + +#[test] +fn test_size_of_packed_enum_with_fixed_array() { + let size = Introspect::::size(); + assert!(size.is_some()); + assert!(size.unwrap() == 4); +} + #[test] fn test_layout_of_enum_without_variant_data() { let layout = Introspect::::layout(); @@ -264,6 +339,56 @@ fn test_layout_of_struct_with_option() { assert!(layout == expected); } +#[test] +fn test_layout_of_struct_with_fixed_array() { + let layout = Introspect::::layout(); + let expected = Layout::Struct( + array![field(selector!("x"), fixed_array(Introspect::::layout(), 3))].span() + ); + + assert!(layout == expected); +} + +#[test] +fn test_layout_of_struct_with_complex_fixed_array() { + let layout = Introspect::::layout(); + let expected = Layout::Struct( + array![ + field( + selector!("x"), + fixed_array(fixed_array(Introspect::::layout(), 2), 3) + ) + ] + .span() + ); + + assert!(layout == expected); +} + +#[test] +fn test_layout_of_packed_struct_with_fixed_array() { + let layout = Introspect::::layout(); + let expected = Layout::Fixed([8, 8, 8].span()); + + assert!(layout == expected); +} + +#[test] +fn test_layout_of_enum_with_fixed_array() { + let layout = Introspect::::layout(); + let expected = _enum(array![Option::Some(fixed_array(Introspect::::layout(), 3))]); + + assert!(layout == expected); +} + +#[test] +fn test_layout_of_packed_enum_with_fixed_array() { + let layout = Introspect::::layout(); + let expected = Layout::Fixed([8, 8, 8, 8].span()); + + assert!(layout == expected); +} + #[test] fn test_layout_of_packed_struct() { let layout = Introspect::::layout(); @@ -286,7 +411,6 @@ fn test_layout_of_not_packed_inner_struct() { let _ = Introspect::::layout(); } - #[test] fn test_layout_of_packed_enum() { let layout = Introspect::::layout(); diff --git a/crates/dojo/core-cairo-test/src/tests/model/model.cairo b/crates/dojo/core-cairo-test/src/tests/model/model.cairo index ff48b33192..56f0fcd3e3 100644 --- a/crates/dojo/core-cairo-test/src/tests/model/model.cairo +++ b/crates/dojo/core-cairo-test/src/tests/model/model.cairo @@ -25,11 +25,21 @@ struct Foo2 { v2: u32 } + +#[derive(Copy, Drop, Serde, Debug)] +#[dojo::model] +struct ModelWithFixedArray { + #[key] + k1: u8, + v1: [u16; 3] +} + fn namespace_def() -> NamespaceDef { NamespaceDef { namespace: "dojo_cairo_test", resources: [ TestResource::Model(m_Foo::TEST_CLASS_HASH.try_into().unwrap()), TestResource::Model(m_Foo2::TEST_CLASS_HASH.try_into().unwrap()), + TestResource::Model(m_ModelWithFixedArray::TEST_CLASS_HASH.try_into().unwrap()), ].span() } } @@ -181,3 +191,19 @@ fn test_model_ptr_from_entity_id() { let v1 = world.read_member(ptr, selector!("v1")); assert!(foo.v1 == v1); } + +#[test] +fn test_model_with_fixed_array() { + let mut world = spawn_foo_world(); + let model = ModelWithFixedArray { k1: 1, v1: [4, 32, 256] }; + + world.write_model(@model); + let read_model: ModelWithFixedArray = world.read_model(model.keys()); + + assert!(model.v1 == read_model.v1); + + world.erase_model(@model); + let read_model: ModelWithFixedArray = world.read_model(model.keys()); + + assert!(read_model.v1 == [0, 0, 0]); +} diff --git a/crates/dojo/core/src/meta/introspect.cairo b/crates/dojo/core/src/meta/introspect.cairo index 448ae94f21..5a8f1f53f3 100644 --- a/crates/dojo/core/src/meta/introspect.cairo +++ b/crates/dojo/core/src/meta/introspect.cairo @@ -11,6 +11,7 @@ pub enum Ty { // And `Box` is not serializable. So using a Span, even if it's to have // one element, does the trick. Array: Span, + FixedArray: Span<(Ty, u32)>, ByteArray, } @@ -90,6 +91,22 @@ pub impl Introspect_bool of Introspect { } } +pub impl Introspect_FixedArray> of Introspect<[T; N]> { + fn size() -> Option { + match Introspect::::size() { + Option::Some(size) => Option::Some(size * N), + Option::None => Option::None + } + } + fn layout() -> Layout { + Layout::FixedArray([(Introspect::::layout(), N)].span()) + } + fn ty() -> Ty { + Ty::FixedArray([(Introspect::::ty(), N)].span()) + } +} + + pub impl Introspect_u8 of Introspect { fn size() -> Option { Option::Some(1) diff --git a/crates/dojo/core/src/meta/layout.cairo b/crates/dojo/core/src/meta/layout.cairo index ce82b527f0..96585df88b 100644 --- a/crates/dojo/core/src/meta/layout.cairo +++ b/crates/dojo/core/src/meta/layout.cairo @@ -6,6 +6,7 @@ pub struct FieldLayout { pub layout: Layout } + #[derive(Copy, Drop, Serde, Debug, PartialEq)] pub enum Layout { Fixed: Span, @@ -15,6 +16,7 @@ pub enum Layout { // And `Box` is not serializable. So using a Span, even if it's to have // one element, does the trick. Array: Span, + FixedArray: Span<(Layout, u32)>, ByteArray, // there is one layout per variant. // the `selector` field identifies the variant @@ -30,6 +32,7 @@ pub impl LayoutCompareImpl of LayoutCompareTrait { (Layout::Struct(_), Layout::Struct(_)) => true, (Layout::Tuple(_), Layout::Tuple(_)) => true, (Layout::Array(_), Layout::Array(_)) => true, + (Layout::FixedArray(_), Layout::FixedArray(_)) => true, (Layout::ByteArray, Layout::ByteArray) => true, (Layout::Enum(_), Layout::Enum(_)) => true, _ => false diff --git a/crates/dojo/core/src/storage/layout.cairo b/crates/dojo/core/src/storage/layout.cairo index d29ca3235e..286577ce12 100644 --- a/crates/dojo/core/src/storage/layout.cairo +++ b/crates/dojo/core/src/storage/layout.cairo @@ -22,6 +22,9 @@ pub fn write_layout( Layout::Fixed(layout) => { write_fixed_layout(model, key, values, ref offset, layout); }, Layout::Struct(layout) => { write_struct_layout(model, key, values, ref offset, layout); }, Layout::Array(layout) => { write_array_layout(model, key, values, ref offset, layout); }, + Layout::FixedArray(layout) => { + write_fixed_array_layout(model, key, values, ref offset, layout); + }, Layout::Tuple(layout) => { write_tuple_layout(model, key, values, ref offset, layout); }, Layout::ByteArray => { write_byte_array_layout(model, key, values, ref offset); }, Layout::Enum(layout) => { write_enum_layout(model, key, values, ref offset, layout); } @@ -68,17 +71,37 @@ pub fn write_array_layout( // and then, write array items let item_layout = *item_layout.at(0); - let mut i = 0; - loop { - if i >= array_len { - break; - } - let key = combine_key(key, i.into()); + for i in 0 + ..array_len { + let key = combine_key(key, i.into()); + write_layout(model, key, values, ref offset, item_layout); + }; +} - write_layout(model, key, values, ref offset, item_layout); +/// Write fixed array layout model record to the world storage. +/// +/// # Arguments +/// * `model` - the model selector. +/// * `key` - the model record key. +/// * `values` - the model record values. +/// * `offset` - the start of model record values in the `values` parameter. +/// * `item_layout` - the model record layout (temporary a Span because of type recursion issue). +pub fn write_fixed_array_layout( + model: felt252, + key: felt252, + values: Span, + ref offset: u32, + mut item_layout: Span<(Layout, u32)> +) { + let (item_layout, array_len): (Layout, u32) = *item_layout.pop_front().unwrap(); - i += 1; - }; + // Note: no need to write the array length as it is fixed at compile-time + // and stored in the layout. + + for i in 0 + ..array_len { + write_layout(model, combine_key(key, i.into()), values, ref offset, item_layout); + }; } /// @@ -120,19 +143,14 @@ pub fn write_byte_array_layout( pub fn write_struct_layout( model: felt252, key: felt252, values: Span, ref offset: u32, layout: Span ) { - let mut i = 0; - loop { - if i >= layout.len() { - break; - } - - let field_layout = *layout.at(i); - let field_key = combine_key(key, field_layout.selector); - - write_layout(model, field_key, values, ref offset, field_layout.layout); - - i += 1; - } + for i in 0 + ..layout + .len() { + let field_layout = *layout.at(i); + let field_key = combine_key(key, field_layout.selector); + + write_layout(model, field_key, values, ref offset, field_layout.layout); + }; } /// Write tuple layout model record to the world storage. @@ -146,19 +164,14 @@ pub fn write_struct_layout( pub fn write_tuple_layout( model: felt252, key: felt252, values: Span, ref offset: u32, layout: Span ) { - let mut i = 0; - loop { - if i >= layout.len() { - break; - } - - let field_layout = *layout.at(i); - let key = combine_key(key, i.into()); - - write_layout(model, key, values, ref offset, field_layout); - - i += 1; - }; + for i in 0 + ..layout + .len() { + let field_layout = *layout.at(i); + let key = combine_key(key, i.into()); + + write_layout(model, key, values, ref offset, field_layout); + }; } pub fn write_enum_layout( @@ -212,6 +225,19 @@ pub fn delete_array_layout(model: felt252, key: felt252) { database::delete(model, key, [packing::PACKING_MAX_BITS].span()); } +/// Delete a fixed array layout model record from the world storage. +/// +/// # Arguments +/// * `model` - the model selector. +/// * `key` - the model record key. +/// * `layout` - the model layout. +pub fn delete_fixed_array_layout(model: felt252, key: felt252, mut layout: Span<(Layout, u32)>) { + let (item_layout, array_len): (Layout, u32) = *layout.pop_front().unwrap(); + for i in 0..array_len { + delete_layout(model, combine_key(key, i.into()), item_layout); + } +} + /// pub fn delete_byte_array_layout(model: felt252, key: felt252) { // The ByteArray internal structure is @@ -241,6 +267,7 @@ pub fn delete_layout(model: felt252, key: felt252, layout: Layout) { Layout::Fixed(layout) => { delete_fixed_layout(model, key, layout); }, Layout::Struct(layout) => { delete_struct_layout(model, key, layout); }, Layout::Array(_) => { delete_array_layout(model, key); }, + Layout::FixedArray(layout) => { delete_fixed_array_layout(model, key, layout); }, Layout::Tuple(layout) => { delete_tuple_layout(model, key, layout); }, Layout::ByteArray => { delete_byte_array_layout(model, key); }, Layout::Enum(layout) => { delete_enum_layout(model, key, layout); } @@ -254,19 +281,14 @@ pub fn delete_layout(model: felt252, key: felt252, layout: Layout) { /// * `key` - the model record key. /// * `layout` - list of field layouts. pub fn delete_struct_layout(model: felt252, key: felt252, layout: Span) { - let mut i = 0; - loop { - if i >= layout.len() { - break; - } - - let field_layout = *layout.at(i); - let key = combine_key(key, field_layout.selector); - - delete_layout(model, key, field_layout.layout); - - i += 1; - } + for i in 0 + ..layout + .len() { + let field_layout = *layout.at(i); + let key = combine_key(key, field_layout.selector); + + delete_layout(model, key, field_layout.layout); + }; } /// Delete a tuple layout model record from the world storage. @@ -276,19 +298,14 @@ pub fn delete_struct_layout(model: felt252, key: felt252, layout: Span) { - let mut i = 0; - loop { - if i >= layout.len() { - break; - } - - let field_layout = *layout.at(i); - let key = combine_key(key, i.into()); - - delete_layout(model, key, field_layout); - - i += 1; - } + for i in 0 + ..layout + .len() { + let field_layout = *layout.at(i); + let key = combine_key(key, i.into()); + + delete_layout(model, key, field_layout); + }; } pub fn delete_enum_layout(model: felt252, key: felt252, variant_layouts: Span) { @@ -323,6 +340,7 @@ pub fn read_layout(model: felt252, key: felt252, ref read_data: Array, Layout::Fixed(layout) => read_fixed_layout(model, key, ref read_data, layout), Layout::Struct(layout) => read_struct_layout(model, key, ref read_data, layout), Layout::Array(layout) => read_array_layout(model, key, ref read_data, layout), + Layout::FixedArray(layout) => read_fixed_array_layout(model, key, ref read_data, layout), Layout::Tuple(layout) => read_tuple_layout(model, key, ref read_data, layout), Layout::ByteArray => read_byte_array_layout(model, key, ref read_data), Layout::Enum(layout) => read_enum_layout(model, key, ref read_data, layout), @@ -365,17 +383,28 @@ pub fn read_array_layout( let item_layout = *layout.at(0); let array_len: u32 = array_len.try_into().unwrap(); - let mut i = 0; - loop { - if i >= array_len { - break; - } - - let field_key = combine_key(key, i.into()); - read_layout(model, field_key, ref read_data, item_layout); + for i in 0 + ..array_len { + let field_key = combine_key(key, i.into()); + read_layout(model, field_key, ref read_data, item_layout); + }; +} - i += 1; - }; +/// Read a fixed array layout model record. +/// +/// # Arguments +/// * `model` - the model selector +/// * `key` - model record key. +/// * `read_data` - the read data. +/// * `layout` - the array item layout +pub fn read_fixed_array_layout( + model: felt252, key: felt252, ref read_data: Array, mut layout: Span<(Layout, u32)> +) { + let (item_layout, array_len): (Layout, u32) = *layout.pop_front().unwrap(); + for i in 0 + ..array_len { + read_layout(model, combine_key(key, i.into()), ref read_data, item_layout); + }; } /// @@ -414,19 +443,14 @@ pub fn read_byte_array_layout(model: felt252, key: felt252, ref read_data: Array pub fn read_struct_layout( model: felt252, key: felt252, ref read_data: Array, layout: Span ) { - let mut i = 0; - loop { - if i >= layout.len() { - break; - } - - let field_layout = *layout.at(i); - let field_key = combine_key(key, field_layout.selector); - - read_layout(model, field_key, ref read_data, field_layout.layout); - - i += 1; - } + for i in 0 + ..layout + .len() { + let field_layout = *layout.at(i); + let field_key = combine_key(key, field_layout.selector); + + read_layout(model, field_key, ref read_data, field_layout.layout); + }; } /// Read a tuple layout model record. @@ -439,18 +463,14 @@ pub fn read_struct_layout( pub fn read_tuple_layout( model: felt252, key: felt252, ref read_data: Array, layout: Span ) { - let mut i = 0; - loop { - if i >= layout.len() { - break; - } - - let field_layout = *layout.at(i); - let field_key = combine_key(key, i.into()); - read_layout(model, field_key, ref read_data, field_layout); - - i += 1; - }; + for i in 0 + ..layout + .len() { + let field_layout = *layout.at(i); + let field_key = combine_key(key, i.into()); + + read_layout(model, field_key, ref read_data, field_layout); + }; } pub fn read_enum_layout( diff --git a/crates/dojo/lang/Cargo.toml b/crates/dojo/lang/Cargo.toml index 07647d5dc6..1d139a2b46 100644 --- a/crates/dojo/lang/Cargo.toml +++ b/crates/dojo/lang/Cargo.toml @@ -20,6 +20,7 @@ cairo-lang-syntax.workspace = true cairo-lang-utils.workspace = true dojo-types.workspace = true itertools.workspace = true +regex.workspace = true serde.workspace = true smol_str.workspace = true starknet.workspace = true diff --git a/crates/dojo/lang/src/derive_macros/introspect/layout.rs b/crates/dojo/lang/src/derive_macros/introspect/layout.rs index 43667fe502..2a93f60dbb 100644 --- a/crates/dojo/lang/src/derive_macros/introspect/layout.rs +++ b/crates/dojo/lang/src/derive_macros/introspect/layout.rs @@ -7,10 +7,16 @@ use cairo_lang_syntax::node::{ids, Terminal, TypedSyntaxNode}; use starknet::core::utils::get_selector_from_name; use super::utils::{ - get_array_item_type, get_tuple_item_types, is_array, is_byte_array, is_tuple, - is_unsupported_option_type, primitive_type_introspection, + get_array_item_type, get_fixed_array_inner_type_and_size, get_tuple_item_types, is_array, + is_byte_array, is_fixed_array, is_tuple, is_unsupported_option_type, + primitive_type_introspection, }; +pub enum Wrapper { + Introspect, + Array(String), +} + /// build the full layout for every field in the Struct. pub fn build_field_layouts( db: &dyn SyntaxGroup, @@ -90,6 +96,14 @@ pub fn get_layout_from_type_clause( let tuple_type = expr.as_syntax_node().get_text(db); build_tuple_layout_from_type(diagnostics, type_clause.stable_ptr().0, &tuple_type) } + Expr::FixedSizeArray(expr) => { + let fixed_array_type = expr.as_syntax_node().get_text(db); + build_fixed_array_layout_from_type( + diagnostics, + type_clause.stable_ptr().0, + &fixed_array_type, + ) + } _ => { diagnostics.push(PluginDiagnostic { stable_ptr: type_clause.stable_ptr().0, @@ -110,26 +124,36 @@ pub fn build_array_layout_from_type( ) -> String { let array_item_type = get_array_item_type(item_type); - if is_tuple(&array_item_type) { - format!( - "dojo::meta::Layout::Array( - array![ - {} - ].span() - )", - build_item_layout_from_type(diagnostics, diagnostic_item, &array_item_type) - ) - } else if is_array(&array_item_type) { - format!( - "dojo::meta::Layout::Array( - array![ - {} - ].span() - )", - build_array_layout_from_type(diagnostics, diagnostic_item, &array_item_type) - ) + match build_member_layout_from_type(diagnostics, diagnostic_item, &array_item_type) { + Wrapper::Introspect => { + format!("dojo::meta::introspect::Introspect::<{}>::layout()", item_type) + } + Wrapper::Array(layout) => { + format!( + "dojo::meta::Layout::Array( + array![ + {} + ].span(), + )", + layout + ) + } + } +} + +pub fn build_member_layout_from_type( + diagnostics: &mut Vec, + diagnostic_item: ids::SyntaxStablePtrId, + item_type: &str, +) -> Wrapper { + if is_array(item_type) { + Wrapper::Array(build_array_layout_from_type(diagnostics, diagnostic_item, item_type)) + } else if is_fixed_array(item_type) { + Wrapper::Array(build_fixed_array_layout_from_type(diagnostics, diagnostic_item, item_type)) + } else if is_tuple(item_type) { + Wrapper::Array(build_tuple_layout_from_type(diagnostics, diagnostic_item, item_type)) } else { - format!("dojo::meta::introspect::Introspect::<{}>::layout()", item_type) + Wrapper::Introspect } } @@ -155,6 +179,30 @@ pub fn build_tuple_layout_from_type( ) } +pub fn build_fixed_array_layout_from_type( + diagnostics: &mut Vec, + diagnostic_item: ids::SyntaxStablePtrId, + item_type: &str, +) -> String { + let (array_item_type, array_size) = get_fixed_array_inner_type_and_size(item_type); + match build_member_layout_from_type(diagnostics, diagnostic_item, &array_item_type) { + Wrapper::Introspect => { + format!( + "dojo::meta::introspect::Introspect::<[{array_item_type}; {array_size}]>::layout()", + ) + } + Wrapper::Array(layout) => { + format!( + "dojo::meta::Layout::FixedArray( + array![ + ({layout}, {array_size}) + ].span(), + )", + ) + } + } +} + /// Build the layout describing the provided type. /// item_type could be any type (array, tuple, struct, ...) pub fn build_item_layout_from_type( @@ -166,6 +214,8 @@ pub fn build_item_layout_from_type( build_array_layout_from_type(diagnostics, diagnostic_item, item_type) } else if is_tuple(item_type) { build_tuple_layout_from_type(diagnostics, diagnostic_item, item_type) + } else if is_fixed_array(item_type) { + build_fixed_array_layout_from_type(diagnostics, diagnostic_item, item_type) } else { // For Option, T cannot be a tuple if is_unsupported_option_type(item_type) { @@ -231,27 +281,15 @@ pub fn generate_cairo_code_for_fixed_layout_with_custom_types(layouts: &[String] .join(",\n"); format!( - "let mut layouts = array![ + "let layouts = array![ {layouts_repr} ]; let mut merged_layout = ArrayTrait::::new(); - loop {{ - match ArrayTrait::pop_front(ref layouts) {{ - Option::Some(mut layout) => {{ - match layout {{ - dojo::meta::Layout::Fixed(mut l) => {{ - loop {{ - match SpanTrait::pop_front(ref l) {{ - Option::Some(x) => merged_layout.append(*x), - Option::None(_) => {{ break; }} - }}; - }}; - }}, - _ => panic!(\"A packed model layout must contain Fixed layouts only.\"), - }}; - }}, - Option::None(_) => {{ break; }} + for layout in layouts {{ + match layout {{ + dojo::meta::Layout::Fixed(mut l) => merged_layout.append_span(l), + _ => panic!(\"A packed model layout must contain Fixed layouts only.\"), }}; }}; @@ -317,6 +355,14 @@ pub fn get_packed_field_layout_from_type_clause( let tuple_type = expr.as_syntax_node().get_text(db); get_packed_tuple_layout_from_type(diagnostics, type_clause.stable_ptr().0, &tuple_type) } + Expr::FixedSizeArray(expr) => { + let array_type = expr.as_syntax_node().get_text(db); + get_packed_fixed_array_layout_from_type( + diagnostics, + type_clause.stable_ptr().0, + &array_type, + ) + } _ => { diagnostics.push(PluginDiagnostic { stable_ptr: type_clause.stable_ptr().0, @@ -343,6 +389,8 @@ pub fn get_packed_item_layout_from_type( vec!["ERROR".to_string()] } else if is_tuple(item_type) { get_packed_tuple_layout_from_type(diagnostics, diagnostic_item, item_type) + } else if is_fixed_array(item_type) { + get_packed_fixed_array_layout_from_type(diagnostics, diagnostic_item, item_type) } else { let primitives = primitive_type_introspection(); @@ -368,3 +416,15 @@ pub fn get_packed_tuple_layout_from_type( .flat_map(|x| get_packed_item_layout_from_type(diagnostics, diagnostic_item, x)) .collect::>() } + +pub fn get_packed_fixed_array_layout_from_type( + diagnostics: &mut Vec, + diagnostic_item: ids::SyntaxStablePtrId, + array_type: &str, +) -> Vec { + let (inner_type, array_size) = get_fixed_array_inner_type_and_size(array_type); + let inner_type_layout = + get_packed_item_layout_from_type(diagnostics, diagnostic_item, &inner_type).join(","); + + (0..array_size.trim().parse().unwrap()).map(|_| inner_type_layout.clone()).collect::>() +} diff --git a/crates/dojo/lang/src/derive_macros/introspect/size.rs b/crates/dojo/lang/src/derive_macros/introspect/size.rs index efb81ea25d..3c34c9e72b 100644 --- a/crates/dojo/lang/src/derive_macros/introspect/size.rs +++ b/crates/dojo/lang/src/derive_macros/introspect/size.rs @@ -4,7 +4,8 @@ use cairo_lang_syntax::node::helpers::QueryAttrs; use cairo_lang_syntax::node::TypedSyntaxNode; use super::utils::{ - get_tuple_item_types, is_array, is_byte_array, is_tuple, primitive_type_introspection, + get_fixed_array_inner_type_and_size, get_tuple_item_types, is_array, is_byte_array, + is_fixed_array, is_tuple, primitive_type_introspection, }; pub fn compute_struct_layout_size( @@ -149,6 +150,10 @@ pub fn get_field_size_from_type_clause( let tuple_type = expr.as_syntax_node().get_text(db).trim().to_string(); compute_tuple_size_from_type(&tuple_type) } + Expr::FixedSizeArray(expr) => { + let array_type = expr.as_syntax_node().get_text(db).trim().to_string(); + compute_fixed_array_size_from_type(&array_type) + } _ => { // field type already checked while building the layout vec!["ERROR".to_string()] @@ -181,6 +186,8 @@ pub fn compute_item_size_from_type(item_type: &String) -> Vec { vec!["Option::None".to_string()] } else if is_tuple(item_type) { compute_tuple_size_from_type(item_type) + } else if is_fixed_array(item_type) { + compute_fixed_array_size_from_type(item_type) } else { let primitives = primitive_type_introspection(); @@ -198,3 +205,12 @@ pub fn compute_tuple_size_from_type(tuple_type: &str) -> Vec { .flat_map(compute_item_size_from_type) .collect::>() } + +pub fn compute_fixed_array_size_from_type(array_type: &str) -> Vec { + let (inner_type, array_size) = get_fixed_array_inner_type_and_size(array_type); + let inner_type_size = compute_item_size_from_type(&inner_type); + + (0..array_size.trim().parse().unwrap()) + .flat_map(|_| inner_type_size.clone()) + .collect::>() +} diff --git a/crates/dojo/lang/src/derive_macros/introspect/ty.rs b/crates/dojo/lang/src/derive_macros/introspect/ty.rs index d9e9e40a11..10fd5581e5 100644 --- a/crates/dojo/lang/src/derive_macros/introspect/ty.rs +++ b/crates/dojo/lang/src/derive_macros/introspect/ty.rs @@ -5,7 +5,10 @@ use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::helpers::QueryAttrs; use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode}; -use super::utils::{get_array_item_type, get_tuple_item_types, is_array, is_byte_array, is_tuple}; +use super::utils::{ + get_array_item_type, get_fixed_array_inner_type_and_size, get_tuple_item_types, is_array, + is_byte_array, is_fixed_array, is_tuple, +}; pub fn build_struct_ty(db: &dyn SyntaxGroup, name: &String, struct_ast: &ItemStruct) -> String { let members_ty = struct_ast @@ -89,6 +92,10 @@ pub fn build_ty_from_type_clause(db: &dyn SyntaxGroup, type_clause: &TypeClause) let tuple_type = expr.as_syntax_node().get_text(db).trim().to_string(); build_tuple_ty_from_type(&tuple_type) } + Expr::FixedSizeArray(expr) => { + let array_type = expr.as_syntax_node().get_text(db).trim().to_string(); + build_fixed_array_ty_from_type(&array_type) + } _ => { // diagnostic message already handled in layout building "ERROR".to_string() @@ -107,10 +114,23 @@ pub fn build_item_ty_from_type(item_type: &String) -> String { )", build_item_ty_from_type(&array_item_type) ) + } else if is_fixed_array(item_type) { + let (array_item_type, size) = get_fixed_array_inner_type_and_size(item_type); + format!( + "dojo::meta::introspect::Ty::FixedArray( + array![ + ({}, {}) + ].span() + )", + build_item_ty_from_type(&array_item_type), + size + ) } else if is_byte_array(item_type) { "dojo::meta::introspect::Ty::ByteArray".to_string() } else if is_tuple(item_type) { build_tuple_ty_from_type(item_type) + } else if is_fixed_array(item_type) { + build_fixed_array_ty_from_type(item_type) } else { format!("dojo::meta::introspect::Introspect::<{}>::ty()", item_type) } @@ -131,3 +151,14 @@ pub fn build_tuple_ty_from_type(item_type: &str) -> String { tuple_items ) } + +pub fn build_fixed_array_ty_from_type(array_type: &str) -> String { + let (inner_type, array_size) = get_fixed_array_inner_type_and_size(array_type); + let inner_type_ty = build_item_ty_from_type(&inner_type); + + format!( + "dojo::meta::introspect::Ty::FixedArray( + array![({inner_type_ty}, {array_size})].span() + )" + ) +} diff --git a/crates/dojo/lang/src/derive_macros/introspect/utils.rs b/crates/dojo/lang/src/derive_macros/introspect/utils.rs index f57f6b6335..d3f532c882 100644 --- a/crates/dojo/lang/src/derive_macros/introspect/utils.rs +++ b/crates/dojo/lang/src/derive_macros/introspect/utils.rs @@ -1,5 +1,10 @@ use std::collections::HashMap; +use regex::Regex; + +// Matches [*;(u32 or variable_name)] +const FIXED_ARRAY_REGEX: &str = r"\[.+;\s*(\d{1,10}|([a-zA-Z_{1}][a-zA-Z0-9_]*))\s*\]"; + #[derive(Clone, Default, Debug)] pub struct TypeIntrospection(pub usize, pub Vec); @@ -35,6 +40,11 @@ pub fn is_array(ty: &str) -> bool { ty.starts_with("Array<") || ty.starts_with("Span<") } +pub fn is_fixed_array(ty: &str) -> bool { + let re = Regex::new(FIXED_ARRAY_REGEX).unwrap(); + re.is_match(ty) +} + pub fn is_tuple(ty: &str) -> bool { ty.starts_with('(') } @@ -46,6 +56,18 @@ pub fn get_array_item_type(ty: &str) -> String { ty.trim().strip_prefix("Span<").unwrap().strip_suffix('>').unwrap().to_string() } } +pub fn trim_first_and_last_chars(s: &str) -> &str { + let mut chars = s.trim().chars(); + chars.next(); + chars.next_back(); + chars.as_str().trim() +} + +pub fn get_fixed_array_inner_type_and_size(ty: &str) -> (String, String) { + let ty = trim_first_and_last_chars(ty); + let res: Vec<&str> = ty.rsplitn(2, ';').collect(); + (res[1].trim().to_string(), res[0].trim().to_string()) +} /// split a tuple in array of items (nested tuples are not splitted). /// example (u8, (u16, u32), u128) -> ["u8", "(u16, u32)", "u128"]