use proc_macro2::{Ident, TokenStream};
use quote::quote;
use crate::class::data_models::fields::Fields;
use crate::class::data_models::group_export::FieldGroup;
use crate::class::{Field, FieldVar, GetSet, GetterSetterImpl, UsageFlags};
use crate::util::{format_funcs_collection_constant, format_funcs_collection_struct, ident};
#[derive(Default, Clone, Debug)]
pub enum FieldHint {
#[default]
Inferred,
Hint(Ident),
HintWithString {
hint: Ident,
hint_string: TokenStream,
},
}
impl FieldHint {
pub fn new(hint: Ident, hint_string: Option<TokenStream>) -> Self {
match hint_string {
None => Self::Hint(hint),
Some(hint_string) => Self::HintWithString { hint, hint_string },
}
}
}
pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
let mut getter_setter_impls = Vec::new();
let mut phantom_var_dummy_uses = Vec::new();
let mut func_name_consts = Vec::new();
let mut export_tokens = Vec::new();
for field in &fields.all_fields {
let Field {
name: field_ident,
ty: field_type,
var,
export,
group,
subgroup,
..
} = field;
let var = match (export, var) {
(Some(export), None) => {
let usage_flags = if let Some(usage) = export.to_export_usage() {
UsageFlags::Custom(vec![usage])
} else {
UsageFlags::InferredExport
};
FieldVar {
usage_flags,
..Default::default()
}
}
(_, Some(var)) => var.clone(),
_ => continue,
};
make_groups_registrations(group, subgroup, &mut export_tokens, class_name);
let FieldVar {
rename,
getter,
setter,
hint,
mut usage_flags,
rust_public,
..
} = var;
let rename = rename.as_deref();
let field_name = if let Some(rename) = rename {
rename.to_owned()
} else {
field_ident.to_string()
};
let export_hint;
let registration_fn;
if let Some(export) = export {
if usage_flags.is_inferred() {
usage_flags = UsageFlags::InferredExport;
}
export_hint = export.to_export_hint();
registration_fn = quote! { register_export };
} else {
export_hint = None;
registration_fn = quote! { register_var };
}
let usage_flags = match usage_flags {
UsageFlags::Inferred | UsageFlags::InferredExport => {
quote! { None }
}
UsageFlags::Custom(flags) => quote! {
Some(#(
::godot::register::info::PropertyUsageFlags::#flags
)|*)
},
};
let hint = match hint {
FieldHint::Inferred => match export_hint {
Some(hint) => quote! { Some(#hint) },
None => quote! { None },
},
FieldHint::Hint(hint) => {
quote! {
Some(::godot::register::info::PropertyHintInfo {
hint: ::godot::register::info::PropertyHint::#hint,
hint_string: ::godot::builtin::GString::new(),
})
}
}
FieldHint::HintWithString { hint, hint_string } => quote! {
Some(::godot::register::info::PropertyHintInfo {
hint: ::godot::register::info::PropertyHint::#hint,
hint_string: ::godot::builtin::GString::from(#hint_string),
})
},
};
let getter_func_constant = make_accessor_func_constant(
getter.to_impl(class_name, GetSet::Get, field, rename, rust_public),
&mut getter_setter_impls,
&mut func_name_consts,
&mut export_tokens,
class_name,
);
let setter_func_constant = make_accessor_func_constant(
setter.to_impl(class_name, GetSet::Set, field, rename, rust_public),
&mut getter_setter_impls,
&mut func_name_consts,
&mut export_tokens,
class_name,
);
if field.is_phantomvar {
let field_name = field.name.clone();
phantom_var_dummy_uses.push(quote! {
let _ = &self.#field_name;
});
}
export_tokens.push(quote! {
type FieldType = #field_type;
::godot::register::private::#registration_fn::<#class_name, FieldType>(
#field_name,
#getter_func_constant,
#setter_func_constant,
#hint,
#usage_flags,
);
});
}
let phantom_var_dummy_use_fn = if phantom_var_dummy_uses.is_empty() {
quote! {}
} else {
quote! {
#[expect(dead_code)]
#[doc(hidden)]
fn __phantom_var_dummy_uses(&self) {
#(#phantom_var_dummy_uses)*
}
}
};
let class_functions_name = format_funcs_collection_struct(class_name);
quote! {
impl #class_name {
#(#getter_setter_impls)*
#phantom_var_dummy_use_fn
}
impl #class_functions_name {
#(#func_name_consts)*
}
impl ::godot::obj::cap::ImplementsGodotExports for #class_name {
fn __register_exports() {
#(
{
#export_tokens
}
)*
}
}
}
}
fn make_accessor_func_constant(
getter_setter_impl: Option<GetterSetterImpl>,
getter_setter_impls: &mut Vec<TokenStream>,
func_name_consts: &mut Vec<TokenStream>,
export_tokens: &mut Vec<TokenStream>,
class_name: &Ident,
) -> TokenStream {
let Some(gs) = getter_setter_impl else {
return quote! { "" };
};
getter_setter_impls.push(gs.function_impl);
func_name_consts.push(gs.funcs_collection_constant);
export_tokens.push(gs.export_token);
let funcs_collection = format_funcs_collection_struct(class_name);
let constant = format_funcs_collection_constant(class_name, &gs.rust_accessor);
quote! { #funcs_collection::#constant }
}
fn make_groups_registrations(
group: &Option<FieldGroup>,
subgroup: &Option<FieldGroup>,
export_tokens: &mut Vec<TokenStream>,
class_name: &Ident,
) {
export_tokens.push(make_group_registration(
group,
ident("register_group"),
class_name,
));
export_tokens.push(make_group_registration(
subgroup,
ident("register_subgroup"),
class_name,
));
}
fn make_group_registration(
group: &Option<FieldGroup>,
register_fn: Ident,
class_name: &Ident,
) -> TokenStream {
let Some(FieldGroup { name, prefix }) = group else {
return TokenStream::new();
};
quote! {
::godot::register::private::#register_fn::<#class_name>(
#name,
#prefix
);
}
}