use cairo_lang_macro::{Diagnostic, ProcMacroResult, TokenStream};
use cairo_lang_parser::utils::SimpleParserDatabase;
use cairo_lang_syntax::node::ast::{ItemStruct, Member};
use cairo_lang_syntax::node::helpers::QueryAttrs;
use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode};
use starknet::core::utils::get_selector_from_name;
use crate::constants::CAIRO_DELIMITERS;
use crate::helpers::{DiagnosticsExt, DojoChecker, DojoFormatter, ProcMacroResultExt};
#[derive(Debug)]
pub struct DojoStructIntrospect {
pub diagnostics: Vec<Diagnostic>,
}
impl DojoStructIntrospect {
pub fn new() -> Self {
Self { diagnostics: vec![] }
}
pub fn process(
db: &SimpleParserDatabase,
struct_ast: &ItemStruct,
is_packed: bool,
) -> ProcMacroResult {
let mut introspect = DojoStructIntrospect::new();
let attributes = struct_ast.attributes(db);
let derive_attrs = attributes.query_attr(db, "derive");
DojoChecker::check_derive_conflicts(db, &mut introspect.diagnostics, derive_attrs);
let token = introspect.generate(db, struct_ast, is_packed);
ProcMacroResult::finalize(token, introspect.diagnostics)
}
fn generate(
&mut self,
db: &SimpleParserDatabase,
struct_ast: &ItemStruct,
is_packed: bool,
) -> TokenStream {
let struct_name = struct_ast.name(db).text(db).into();
let struct_size = self.compute_struct_layout_size(db, struct_ast);
let ty = self.build_struct_ty(db, &struct_name, struct_ast);
let layout = if is_packed {
self.build_packed_struct_layout(db, struct_ast)
} else {
format!(
"dojo::meta::Layout::Struct(
array![
{}
].span()
)",
self.build_struct_field_layouts(db, struct_ast)
)
};
let gen_types = DojoFormatter::build_generic_types(db, struct_ast.generic_params(db));
let inspect_gen_impls = DojoFormatter::build_generic_impls(
&gen_types,
&["+dojo::meta::introspect::Introspect".to_string()],
&[],
);
super::generate_introspect(
&struct_name,
&struct_size,
&gen_types,
inspect_gen_impls,
&layout,
&ty,
)
}
fn compute_struct_layout_size(
&self,
db: &SimpleParserDatabase,
struct_ast: &ItemStruct,
) -> String {
let mut sizes = struct_ast
.members(db)
.elements(db)
.filter_map(|m| {
if m.has_attr(db, "key") {
return None;
}
let member_size =
super::size::get_field_size_from_type_clause(db, &m.type_clause(db));
Some(member_size)
})
.flatten()
.collect::<Vec<_>>();
super::size::build_size_function_body(&mut sizes)
}
pub fn build_member_ty(&self, db: &SimpleParserDatabase, member: &Member) -> String {
let name = member.name(db).text(db).to_string();
let attrs = if member.has_attr(db, "key") { vec!["'key'"] } else { vec![] };
format!(
"dojo::meta::introspect::Member {{
name: '{name}',
attrs: array![{}].span(),
ty: {}
}}",
attrs.join(","),
super::ty::build_ty_from_type_clause(db, &member.type_clause(db))
)
}
fn build_struct_ty(
&self,
db: &SimpleParserDatabase,
name: &String,
struct_ast: &ItemStruct,
) -> String {
let members_ty = struct_ast
.members(db)
.elements(db)
.map(|m| self.build_member_ty(db, &m))
.collect::<Vec<_>>();
format!(
"dojo::meta::introspect::Ty::Struct(
dojo::meta::introspect::Struct {{
name: '{name}',
attrs: array![].span(),
children: array![
{}\n
].span()
}}
)",
members_ty.join(",\n")
)
}
pub fn build_struct_field_layouts(
&mut self,
db: &SimpleParserDatabase,
struct_ast: &ItemStruct,
) -> String {
let mut members = vec![];
for member in struct_ast.members(db).elements(db) {
if member.has_attr(db, "key") {
let member_type =
member.type_clause(db).ty(db).as_syntax_node().get_text_without_trivia(db);
if type_contains_usize(&member_type) {
self.diagnostics.push_error(
"Use u32 rather than usize for model keys, as usize size is architecture \
dependent."
.to_string(),
);
}
} else {
let field_name = member.name(db).text(db);
let field_selector = get_selector_from_name(&field_name).unwrap();
let field_layout = super::layout::get_layout_from_type_clause(
db,
&mut self.diagnostics,
&member.type_clause(db),
);
members.push(format!(
"dojo::meta::FieldLayout {{
selector: {field_selector},
layout: {field_layout}
}}"
));
}
}
members.join(",\n")
}
fn build_packed_struct_layout(
&mut self,
db: &SimpleParserDatabase,
struct_ast: &ItemStruct,
) -> String {
let mut layouts = vec![];
for member in struct_ast.members(db).elements(db).filter(|m| !m.has_attr(db, "key")) {
let layout = super::layout::get_packed_field_layout_from_type_clause(
db,
&mut self.diagnostics,
&member.type_clause(db),
);
layouts.push(layout)
}
let layouts = layouts.into_iter().flatten().collect::<Vec<_>>();
if layouts.iter().any(|v| super::layout::is_custom_layout(v.as_str())) {
super::layout::generate_cairo_code_for_fixed_layout_with_custom_types(&layouts)
} else {
format!(
"dojo::meta::Layout::Fixed(
array![
{}
].span()
)",
layouts.join(",")
)
}
}
}
fn type_contains_usize(type_str: &str) -> bool {
type_str.contains("usize")
&& type_str.split(CAIRO_DELIMITERS).map(|s| s.trim()).collect::<Vec<_>>().contains(&"usize")
}