use quote::{format_ident, quote}; use crate::reflection::default_value::gen_get_default_value_fn; use crate::reflection::options_attr::OptionsAttr; use crate::util::find_engine_crate_path; pub fn generate(input: syn::ItemEnum, options: OptionsAttr) -> proc_macro2::TokenStream { let engine_crate_path = find_engine_crate_path().unwrap(); let variant_lookup_match_arms = input .variants .iter() .enumerate() .map(|(index, variant)| { let variant_ident = &variant.ident; let pattern = match variant.fields { syn::Fields::Unit => quote! { Self::#variant_ident }, syn::Fields::Named(_) => quote! { Self::#variant_ident { .. } }, syn::Fields::Unnamed(_) => quote! { Self::#variant_ident(..) }, }; quote! { #pattern => &enum_reflection.variants[#index] } }) .collect::>(); let is_unit_only = input .variants .iter() .all(|variant| matches!(variant.fields, syn::Fields::Unit)); let impls: &mut dyn Iterator = if input.generics.params.is_empty() { &mut [generate_impls( &input, None, is_unit_only, &variant_lookup_match_arms, &engine_crate_path, )] .into_iter() } else { &mut options.impl_with_generics.iter().map(|generic_args| { generate_impls( &input, Some(generic_args), is_unit_only, &variant_lookup_match_arms, &engine_crate_path, ) }) }; quote! { #(#impls)* } } fn generate_impls( input: &syn::ItemEnum, generic_args: Option<&syn::AngleBracketedGenericArguments>, is_unit_only: bool, variant_lookup_match_arms: &[proc_macro2::TokenStream], engine_crate_path: &syn::Path, ) -> proc_macro2::TokenStream { let get_default_value_fn = gen_get_default_value_fn(&input.ident, generic_args); let variants = generate_variants(&input.variants, &engine_crate_path); let generics_type_aliases = input .generics .type_params() .zip( generic_args .iter() .map(|generic_args| &generic_args.args) .flatten() .flat_map(|generic_arg| match generic_arg { syn::GenericArgument::Type(ty) => Some(ty), _ => None, }), ) .map(|(type_param, generic_arg_type)| { let type_param_ident = &type_param.ident; quote! { type #type_param_ident = #generic_arg_type; } }); let input_ident = &input.ident; quote! { unsafe impl #engine_crate_path::reflection::Reflection for #input_ident #generic_args { const TYPE_REFLECTION: &#engine_crate_path::reflection::Type = &const { #(#generics_type_aliases)* #engine_crate_path::reflection::Type::Enum( #engine_crate_path::reflection::Enum { variants: &[#(#variants),*], is_unit_only: #is_unit_only, get_default_value: || { #get_default_value_fn }, cast_dyn_any: |ptr| ptr.cast::(), get_variant_reflection: |target| { use std::any::Any; use #engine_crate_path::reflection::EnumReflectionExt; let target = target.downcast_ref::()?; Some(::get_variant_reflection( target )) } } ) }; } unsafe impl #engine_crate_path::reflection::EnumReflectionExt for #input_ident #generic_args { fn get_variant_reflection(&self) -> &'static #engine_crate_path::reflection::EnumVariant { let enum_reflection = unsafe { ::TYPE_REFLECTION .as_enum() .unwrap_unchecked() }; match self { #(#variant_lookup_match_arms),* } } } } } fn generate_variants<'a>( input_variants: &'a syn::punctuated::Punctuated, engine_crate_path: &'a syn::Path, ) -> impl Iterator + use<'a> { input_variants.iter().map(move |variant| { let variant_name = syn::LitStr::new(&variant.ident.to_string(), variant.ident.span()); let fields = match &variant.fields { syn::Fields::Unit => quote! { None }, syn::Fields::Named(named_fields) => { let fields = named_fields.named.iter().enumerate().map( |(variant_field_index, variant_field)| { generate_enum_variant_field( variant_field, variant_field_index, &engine_crate_path, ) }, ); quote! { Some(#engine_crate_path::reflection::EnumVariantFields::Named { fields: &[#(#fields),*] }) } } syn::Fields::Unnamed(unnamed_fields) => { let fields = unnamed_fields.unnamed.iter().enumerate().map( |(variant_field_index, variant_field)| { generate_enum_variant_field( variant_field, variant_field_index, &engine_crate_path, ) }, ); quote! { Some(#engine_crate_path::reflection::EnumVariantFields::Unnamed { fields: &[#(#fields),*] }) } } }; let variant_field_vars = variant.fields.iter().enumerate().map( |(variant_field_index, variant_field)| { let var_ident = match variant_field.ident.as_ref() { Some(field_ident) => format_ident!("field_{field_ident}"), None => format_ident!("field_{}", variant_field_index), }; let field_type = &variant_field.ty; quote! { let #var_ident = fields .next() .ok_or(EnumVariantWriteNewToError::TooFewFields)? .downcast::<#field_type>() .map_err(|_| EnumVariantWriteNewToError::WrongFieldType)?; } }, ); let variant_construction = match &variant.fields { syn::Fields::Unit => { let variant_ident = &variant.ident; quote! { Self::#variant_ident } } syn::Fields::Named(named_fields) => { let variant_ident = &variant.ident; let fields = named_fields.named.iter().map(|field| { let Some(field_ident) = field.ident.as_ref() else { unreachable!(); }; let field_var_ident = format_ident!("field_{field_ident}"); quote! { #field_ident: *#field_var_ident } }); quote! { Self::#variant_ident { #(#fields),* } } } syn::Fields::Unnamed(unnamed_fields) => { let variant_ident = &variant.ident; let fields = unnamed_fields.unnamed.iter().enumerate().map( |(field_index, _)| { let field_var_ident = format_ident!("field_{field_index}"); quote! { *#field_var_ident } }, ); quote! { Self::#variant_ident( #(#fields),* ) } } }; quote! { #engine_crate_path::reflection::EnumVariant { name: #variant_name, fields: #fields, try_write_new_to: |dst, fields| { use std::any::Any; use #engine_crate_path::reflection::EnumVariantWriteNewToError; let dst = dst .downcast_mut() .ok_or(EnumVariantWriteNewToError::WrongDstType)?; #(#variant_field_vars)*; let created = #variant_construction; if fields.next().is_some() { return Err(EnumVariantWriteNewToError::TooManyFields); } *dst = created; Ok(()) } } } }) } pub fn generate_enum_variant_field( field: &syn::Field, field_index: usize, engine_crate_path: &syn::Path, ) -> proc_macro2::TokenStream { let field_ident = &field.ident; let field_type = &field.ty; let field_name = if let Some(field_ident) = field_ident { let field_name = syn::LitStr::new(&field_ident.to_string(), field_ident.span()); quote! { Some(#field_name) } } else { quote! { None } }; let get_type_fn_body = { gen_get_optional_type_reflection(field_type, engine_crate_path) }; quote! { #engine_crate_path::reflection::EnumVariantField { name: #field_name, index: #field_index, type_id: std::any::TypeId::of::<#field_type>(), get_type_name: #engine_crate_path::reflection::FnWithDebug::new(|| { std::any::type_name::<#field_type>() }), get_type: #engine_crate_path::reflection::FnWithDebug::new(|| { #get_type_fn_body }), } } } fn gen_get_optional_type_reflection( field_type: &syn::Type, engine_crate_path: &syn::Path, ) -> proc_macro2::TokenStream { quote! { struct SpecializationTarget(std::marker::PhantomData); trait FieldHasReflection { fn field_type_reflection(&self) -> Option<&'static #engine_crate_path::reflection::Type>; } trait FieldDoesNotHaveReflection { fn field_type_reflection(&self) -> Option<&'static #engine_crate_path::reflection::Type>; } impl FieldDoesNotHaveReflection for &SpecializationTarget { fn field_type_reflection(&self) -> Option<&'static #engine_crate_path::reflection::Type> { None } } impl FieldHasReflection for SpecializationTarget where Field: #engine_crate_path::reflection::Reflection { fn field_type_reflection(&self) -> Option<&'static #engine_crate_path::reflection::Type> { Some(Field::type_reflection()) } } (&SpecializationTarget::<#field_type>(std::marker::PhantomData)) .field_type_reflection() } }