diff options
Diffstat (limited to 'engine-macros/src')
| -rw-r--r-- | engine-macros/src/lib.rs | 10 | ||||
| -rw-r--r-- | engine-macros/src/reflection.rs | 75 | ||||
| -rw-r--r-- | engine-macros/src/reflection/default_value.rs | 42 | ||||
| -rw-r--r-- | engine-macros/src/reflection/enum_impl.rs | 507 | ||||
| -rw-r--r-- | engine-macros/src/reflection/field.rs | 100 | ||||
| -rw-r--r-- | engine-macros/src/reflection/options_attr.rs | 39 | ||||
| -rw-r--r-- | engine-macros/src/reflection/struct_impl.rs | 209 | ||||
| -rw-r--r-- | engine-macros/src/reflection/visibility.rs | 45 | ||||
| -rw-r--r-- | engine-macros/src/util.rs | 65 |
9 files changed, 1092 insertions, 0 deletions
diff --git a/engine-macros/src/lib.rs b/engine-macros/src/lib.rs new file mode 100644 index 0000000..deddd15 --- /dev/null +++ b/engine-macros/src/lib.rs @@ -0,0 +1,10 @@ +#![deny(clippy::all, clippy::pedantic)] + +mod reflection; +mod util; + +#[proc_macro_derive(Reflection, attributes(reflection))] +pub fn reflection_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream +{ + reflection::derive(input.into()).into() +} diff --git a/engine-macros/src/reflection.rs b/engine-macros/src/reflection.rs new file mode 100644 index 0000000..ad1df04 --- /dev/null +++ b/engine-macros/src/reflection.rs @@ -0,0 +1,75 @@ +use crate::reflection::options_attr::OptionsAttr; + +mod default_value; +mod enum_impl; +mod field; +mod options_attr; +mod struct_impl; +mod visibility; + +pub fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream +{ + let input = syn::parse2::<syn::Item>(input).unwrap(); + + let options = get_options(&input); + + match input { + syn::Item::Struct(input) => struct_impl::generate(&input, &options), + syn::Item::Enum(input) => enum_impl::generate(&input, &options), + _ => panic!("Invalid input"), + } +} + +pub fn get_options(input: &syn::Item) -> OptionsAttr +{ + let (attrs, generic_params) = match &input { + syn::Item::Struct(input) => (input.attrs.as_slice(), &input.generics.params), + syn::Item::Enum(input) => (input.attrs.as_slice(), &input.generics.params), + _ => ( + const { &[] }.as_slice(), + &syn::punctuated::Punctuated::<syn::GenericParam, syn::Token![,]>::new(), + ), + }; + + if attrs + .iter() + .filter(|attr| attr.path().is_ident("reflection")) + .count() + > 1 + { + panic!("Multiple 'reflection' attributes are not allowed"); + } + + let options = match attrs + .iter() + .find_map(|attr| { + if attr.path().is_ident("reflection") { + return Some(attr.parse_args::<OptionsAttr>()); + } + + None + }) + .transpose() + { + Ok(attr) => attr.unwrap_or_default(), + Err(err) => { + panic!("{err}"); + } + }; + + assert!( + !options.impl_with_generics.is_empty() || generic_params.is_empty(), + concat!( + "Generic types deriving Reflection must specify which ", + "generics to use in the generated impl(s) using the 'reflection' attribute\n", + "For example: #[reflection(impl_with_generics(<u32, String>, <u8, u16>))]" + ) + ); + + assert!( + options.impl_with_generics.is_empty() || !generic_params.is_empty(), + "Specifying 'impl_with_generics' for non-generic types is not allowed" + ); + + options +} diff --git a/engine-macros/src/reflection/default_value.rs b/engine-macros/src/reflection/default_value.rs new file mode 100644 index 0000000..6784129 --- /dev/null +++ b/engine-macros/src/reflection/default_value.rs @@ -0,0 +1,42 @@ +use quote::quote; + +pub fn gen_get_default_value_fn( + type_ident: &proc_macro2::Ident, + generic_args: Option<&syn::AngleBracketedGenericArguments>, +) -> proc_macro2::TokenStream +{ + quote! { + struct SpecializationTarget<T>(std::marker::PhantomData<T>); + + trait HasDefaultValue + { + fn default_value_fn(&self) -> Option<fn() -> Box<dyn std::any::Any>>; + } + + trait NoDefaultValue + { + fn default_value_fn(&self) -> Option<fn() -> Box<dyn std::any::Any>>; + } + + impl<T> NoDefaultValue for &SpecializationTarget<T> + { + fn default_value_fn(&self) -> Option<fn() -> Box<dyn std::any::Any>> + { + None + } + } + + impl<T> HasDefaultValue for SpecializationTarget<T> + where + T: std::default::Default + 'static + { + fn default_value_fn(&self) -> Option<fn() -> Box<dyn std::any::Any>> + { + Some(|| Box::new(T::default())) + } + } + + (&SpecializationTarget::<#type_ident #generic_args>(std::marker::PhantomData)) + .default_value_fn() + } +} diff --git a/engine-macros/src/reflection/enum_impl.rs b/engine-macros/src/reflection/enum_impl.rs new file mode 100644 index 0000000..96efd71 --- /dev/null +++ b/engine-macros/src/reflection/enum_impl.rs @@ -0,0 +1,507 @@ +use proc_macro2::TokenStream; +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::<Vec<_>>(); + + let is_unit_only = input + .variants + .iter() + .all(|variant| matches!(variant.fields, syn::Fields::Unit)); + + let impls: &mut dyn Iterator<Item = proc_macro2::TokenStream> = + 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() + .flat_map(|generic_args| &generic_args.args) + .filter_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::<Self>(), + get_variant_index: |target| { + use std::any::Any; + use #engine_crate_path::reflection::Reflection; + use #engine_crate_path::reflection::EnumReflectionExt; + + let target = target.downcast_ref::<Self>()?; + + let enum_reflection = unsafe { + <Self as Reflection>::TYPE_REFLECTION + .as_enum() + .unwrap_unchecked() + }; + + Some(enum_reflection.variants + .element_offset( + match target { + #(#variant_lookup_match_arms),* + } + ) + .unwrap()) + } + } + ) + }; + } + + 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 { + <Self as #engine_crate_path::reflection::Reflection>::TYPE_REFLECTION + .as_enum() + .unwrap_unchecked() + }; + + match self { + #(#variant_lookup_match_arms),* + } + } + } + } +} + +fn generate_variants<'a>( + input_variants: &'a syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>, + engine_crate_path: &'a syn::Path, +) -> impl Iterator<Item = proc_macro2::TokenStream> + use<'a> +{ + input_variants.iter().map(move |variant| { + let variant_name = + syn::LitStr::new(&variant.ident.to_string(), variant.ident.span()); + + let fields = gen_variant_fields(variant, engine_crate_path); + + 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),*) + } + } + }; + + let VariantTryGetFieldFns { + immutable: try_get_field_immutable_fn, + mutable: try_get_field_mutable_fn, + } = gen_variant_try_get_field_functions(variant, engine_crate_path); + + 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(()) + }, + try_get_field: #try_get_field_immutable_fn, + try_get_field_mut: #try_get_field_mutable_fn, + } + } + }) +} + +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<Field>(std::marker::PhantomData<Field>); + + 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<Field> FieldDoesNotHaveReflection for &SpecializationTarget<Field> + { + fn field_type_reflection(&self) + -> Option<&'static #engine_crate_path::reflection::Type> + { + None + } + } + + impl<Field> FieldHasReflection for SpecializationTarget<Field> + 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() + } +} + +fn gen_variant_fields( + variant: &syn::Variant, + engine_crate_path: &syn::Path, +) -> TokenStream +{ + 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),*] + }) + } + } + } +} + +struct VariantTryGetFieldFns +{ + immutable: TokenStream, + mutable: TokenStream, +} + +fn gen_variant_try_get_field_functions( + variant: &syn::Variant, + engine_crate_path: &syn::Path, +) -> VariantTryGetFieldFns +{ + let field_index_match_arms = match &variant.fields { + syn::Fields::Unit => quote! {}, + syn::Fields::Named(syn::FieldsNamed { named: named_fields, .. }) => { + let match_arms = + named_fields.iter().enumerate().map(|(field_index, field)| { + let Some(field_ident) = &field.ident else { + unreachable!(); + }; + + quote! { #field_index => { #field_ident } } + }); + + quote! { #(#match_arms)* } + } + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: unnamed_fields, .. }) => { + let match_arms = unnamed_fields.iter().enumerate().map(|(field_index, _)| { + let field_ident = format_ident!("field_{field_index}"); + + quote! { #field_index => { #field_ident } } + }); + + quote! { #(#match_arms)* } + } + }; + + let variant_ident = &variant.ident; + + let variant_pattern = match &variant.fields { + syn::Fields::Unit => quote! { Self::#variant_ident }, + syn::Fields::Named(syn::FieldsNamed { named: named_fields, .. }) => { + let field_idents = named_fields.iter().map(|field| { + let Some(field_ident) = &field.ident else { + unreachable!(); + }; + + field_ident + }); + + quote! { Self::#variant_ident { #(#field_idents),* } } + } + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: unnamed_fields, .. }) => { + let field_idents = unnamed_fields + .iter() + .enumerate() + .map(|(field_index, _)| format_ident!("field_{field_index}")); + + quote! { Self::#variant_ident(#(#field_idents),*) } + } + }; + + VariantTryGetFieldFns { + immutable: quote! { + |target, field_index| { { + #![allow(unreachable_code)] + + use std::any::Any; + use #engine_crate_path::reflection::GetError; + + let target = target + .downcast_ref::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let #variant_pattern = target else { + return Err(GetError::WrongTargetEnumVariant); + }; + + let field: &dyn Any = match field_index { + #field_index_match_arms + _ => { + return Err(GetError::IndexOutOfBounds); + } + }; + + Ok(field) + } } + }, + mutable: quote! { + |target, field_index| { { + #![allow(unreachable_code)] + + use std::any::Any; + use #engine_crate_path::reflection::GetError; + + let target = target + .downcast_mut::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let #variant_pattern = target else { + return Err(GetError::WrongTargetEnumVariant); + }; + + let field: &mut dyn Any = match field_index { + #field_index_match_arms + _ => { + return Err(GetError::IndexOutOfBounds); + } + }; + + Ok(field) + } } + }, + } +} diff --git a/engine-macros/src/reflection/field.rs b/engine-macros/src/reflection/field.rs new file mode 100644 index 0000000..105a35b --- /dev/null +++ b/engine-macros/src/reflection/field.rs @@ -0,0 +1,100 @@ +use quote::quote; + +use crate::reflection::visibility::generate as generate_visibility; + +pub struct ReflectionFieldGenOptions<'a> +{ + pub field_vis_override: Option<syn::Visibility>, + pub gen_get_byte_offset: &'a dyn Fn(&syn::Field) -> proc_macro2::TokenStream, +} + +pub fn generate( + field: &syn::Field, + field_index: usize, + engine_crate_path: &syn::Path, + options: &ReflectionFieldGenOptions<'_>, +) -> 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 field_byte_offset = (options.gen_get_byte_offset)(field); + + let field_vis = options.field_vis_override.as_ref().unwrap_or(&field.vis); + + let field_reflection_vis = generate_visibility(field_vis, engine_crate_path); + + let get_type_fn_body = + { gen_get_optional_type_reflection(field_type, engine_crate_path) }; + + quote! { + #engine_crate_path::reflection::Field { + name: #field_name, + index: #field_index, + layout: std::alloc::Layout::new::<#field_type>(), + byte_offset: #field_byte_offset, + 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 + }), + visibility: #field_reflection_vis + } + } +} + +fn gen_get_optional_type_reflection( + field_type: &syn::Type, + engine_crate_path: &syn::Path, +) -> proc_macro2::TokenStream +{ + quote! { + struct SpecializationTarget<Field>(std::marker::PhantomData<Field>); + + 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<Field> FieldDoesNotHaveReflection for &SpecializationTarget<Field> + { + fn field_type_reflection(&self) + -> Option<&'static #engine_crate_path::reflection::Type> + { + None + } + } + + impl<Field> FieldHasReflection for SpecializationTarget<Field> + 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() + } +} diff --git a/engine-macros/src/reflection/options_attr.rs b/engine-macros/src/reflection/options_attr.rs new file mode 100644 index 0000000..6fc2eea --- /dev/null +++ b/engine-macros/src/reflection/options_attr.rs @@ -0,0 +1,39 @@ +#[derive(Debug, Default)] +pub struct OptionsAttr +{ + pub impl_with_generics: + syn::punctuated::Punctuated<syn::AngleBracketedGenericArguments, syn::Token![,]>, +} + +impl syn::parse::Parse for OptionsAttr +{ + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> + { + let params = input.parse_terminated( + syn::parse::ParseBuffer::parse::<syn::Meta>, + syn::Token![,], + )?; + + let mut impl_with_generics = syn::punctuated::Punctuated::new(); + + for param in params { + let param = param.require_list()?; + + if !param.path.is_ident("impl_with_generics") { + return Err(syn::Error::new_spanned( + param, + "Unrecognized attribute parameter", + )); + } + + impl_with_generics = param.parse_args_with( + syn::punctuated::Punctuated::< + syn::AngleBracketedGenericArguments, + syn::Token![,], + >::parse_terminated, + )?; + } + + Ok(Self { impl_with_generics }) + } +} diff --git a/engine-macros/src/reflection/struct_impl.rs b/engine-macros/src/reflection/struct_impl.rs new file mode 100644 index 0000000..27d62ba --- /dev/null +++ b/engine-macros/src/reflection/struct_impl.rs @@ -0,0 +1,209 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::reflection::default_value::gen_get_default_value_fn; +use crate::reflection::field::{generate as generate_field, ReflectionFieldGenOptions}; +use crate::reflection::options_attr::OptionsAttr; +use crate::util::find_engine_crate_path; + +pub fn generate( + input: &syn::ItemStruct, + options: &OptionsAttr, +) -> proc_macro2::TokenStream +{ + let engine_crate_path = find_engine_crate_path().unwrap(); + + if input.generics.params.is_empty() { + return gen_impl(input, None, &engine_crate_path); + } + + let impls = options + .impl_with_generics + .iter() + .map(|generic_args| gen_impl(input, Some(generic_args), &engine_crate_path)); + + quote! { + #(#impls)* + } +} + +fn gen_impl( + input: &syn::ItemStruct, + generic_args: Option<&syn::AngleBracketedGenericArguments>, + engine_crate_path: &syn::Path, +) -> proc_macro2::TokenStream +{ + let fields = input.fields.iter().enumerate().map(|(field_index, field)| { + generate_field( + field, + field_index, + engine_crate_path, + &ReflectionFieldGenOptions { + field_vis_override: None, + gen_get_byte_offset: &|field| { + if let Some(field_ident) = &field.ident { + quote! { std::mem::offset_of!(Self, #field_ident) } + } else { + quote! { std::mem::offset_of!(Self, #field_index) } + } + }, + }, + ) + }); + + let get_default_value_fn = gen_get_default_value_fn(&input.ident, generic_args); + + let input_ident = &input.ident; + + let generics_type_aliases = input + .generics + .type_params() + .zip( + generic_args + .iter() + .flat_map(|generic_args| &generic_args.args) + .filter_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 TryGetFieldFns { + immutable: try_get_field_immutable_fn, + mutable: try_get_field_mutable_fn, + } = gen_try_get_field_functions(input); + + quote! { + unsafe impl #engine_crate_path::reflection::Reflection for + #input_ident #generic_args + { + const TYPE_REFLECTION: &#engine_crate_path::reflection::Type = + &const { + use std::any::Any; + + use #engine_crate_path::reflection::GetError; + + #(#generics_type_aliases)* + + #engine_crate_path::reflection::Type::Struct( + #engine_crate_path::reflection::Struct { + fields: &[ + #(#fields),* + ], + get_default_value: || { + #get_default_value_fn + }, + cast_dyn_any: |ptr| ptr.cast::<Self>(), + try_get_field: #try_get_field_immutable_fn, + try_get_field_mut: #try_get_field_mutable_fn, + } + ) + }; + } + } +} + +struct TryGetFieldFns +{ + immutable: TokenStream, + mutable: TokenStream, +} + +fn gen_try_get_field_functions(input: &syn::ItemStruct) -> TryGetFieldFns +{ + let struct_pattern = match &input.fields { + syn::Fields::Unit => quote! { Self }, + syn::Fields::Named(syn::FieldsNamed { named: named_fields, .. }) => { + let field_idents = named_fields.iter().map(|field| { + let Some(field_ident) = &field.ident else { + unreachable!(); + }; + + field_ident + }); + + quote! { Self { #(#field_idents),* } } + } + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: unnamed_fields, .. }) => { + let field_idents = unnamed_fields + .iter() + .enumerate() + .map(|(field_index, _)| format_ident!("field_{field_index}")); + + quote! { Self(#(#field_idents),*) } + } + }; + + let field_index_match_arms = match &input.fields { + syn::Fields::Unit => quote! {}, + syn::Fields::Named(syn::FieldsNamed { named: named_fields, .. }) => { + let match_arms = + named_fields.iter().enumerate().map(|(field_index, field)| { + let Some(field_ident) = &field.ident else { + unreachable!(); + }; + + quote! { #field_index => { #field_ident } } + }); + + quote! { #(#match_arms)* } + } + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: unnamed_fields, .. }) => { + let match_arms = unnamed_fields.iter().enumerate().map(|(field_index, _)| { + let field_ident = format_ident!("field_{field_index}"); + + quote! { #field_index => { #field_ident } } + }); + + quote! { #(#match_arms)* } + } + }; + + TryGetFieldFns { + immutable: quote! { + |target, field_index| { { + #![allow(unreachable_code)] + + let target = target + .downcast_ref::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let #struct_pattern = target; + + let field: &dyn Any = match field_index { + #field_index_match_arms + _ => { + return Err(GetError::IndexOutOfBounds); + } + }; + + Ok(field) + } } + }, + mutable: quote! { + |target, field_index| { { + #![allow(unreachable_code)] + + let target = target + .downcast_mut::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let #struct_pattern = target; + + let field: &mut dyn Any = match field_index { + #field_index_match_arms + _ => { + return Err(GetError::IndexOutOfBounds); + } + }; + + Ok(field) + } } + }, + } +} diff --git a/engine-macros/src/reflection/visibility.rs b/engine-macros/src/reflection/visibility.rs new file mode 100644 index 0000000..c9e8524 --- /dev/null +++ b/engine-macros/src/reflection/visibility.rs @@ -0,0 +1,45 @@ +use quote::quote; + +use crate::util::syn_path_to_string; + +pub fn generate( + visibility: &syn::Visibility, + engine_crate_path: &syn::Path, +) -> proc_macro2::TokenStream +{ + match visibility { + syn::Visibility::Public(_) => { + quote! { #engine_crate_path::reflection::Visibility::Pub } + } + syn::Visibility::Restricted(vis_restricted) => { + let vis_scope = if vis_restricted.in_token.is_some() { + let in_path = syn_path_to_string(&vis_restricted.path); + + quote! { + #engine_crate_path::reflection::VisibilityScope::In( + std::borrow::Cow::Borrowed(#in_path) + ) + } + } else { + let Some(scope) = vis_restricted.path.get_ident() else { + unreachable!(); + }; + + if scope == "crate" { + quote! { #engine_crate_path::reflection::VisibilityScope::Crate } + } else if scope == "super" { + quote! { #engine_crate_path::reflection::VisibilityScope::Super } + } else if scope == "self" { + quote! { #engine_crate_path::reflection::VisibilityScope::SelfModule } + } else { + unreachable!(); + } + }; + + quote! { #engine_crate_path::reflection::Visibility::PubScoped(#vis_scope) } + } + syn::Visibility::Inherited => { + quote! { #engine_crate_path::reflection::Visibility::Private } + } + } +} diff --git a/engine-macros/src/util.rs b/engine-macros/src/util.rs new file mode 100644 index 0000000..dbeb042 --- /dev/null +++ b/engine-macros/src/util.rs @@ -0,0 +1,65 @@ +use std::fmt::Write; + +use quote::ToTokens; + +macro_rules! syn_path { + ($first_segment: ident $(::$segment: ident)*) => { + ::syn::Path { + leading_colon: None, + segments: ::syn::punctuated::Punctuated::from_iter([ + syn_path_segment!($first_segment), + $(syn_path_segment!($segment),)* + ]) + } + }; +} + +macro_rules! syn_path_segment { + ($segment: ident) => { + ::syn::PathSegment { + ident: ::proc_macro2::Ident::new( + stringify!($segment), + ::proc_macro2::Span::call_site(), + ), + arguments: ::syn::PathArguments::None, + } + }; +} + +pub fn find_engine_crate_path() -> Option<syn::Path> +{ + let cargo_crate_name = std::env::var("CARGO_CRATE_NAME").ok()?; + let cargo_pkg_name = std::env::var("CARGO_PKG_NAME").ok()?; + + if cargo_pkg_name == "engine" && cargo_crate_name != "engine" { + // Macro is used by a crate example/test/benchmark + return Some(syn_path!(engine)); + } + + if cargo_crate_name == "engine" { + return Some(syn_path!(crate)); + } + + Some(syn_path!(engine)) +} + +pub fn syn_path_to_string(path: &syn::Path) -> String +{ + let mut output = String::with_capacity(2 + path.segments.len() * 8); + + if let Some(leading_colon) = path.leading_colon { + write!(output, "{}", leading_colon.to_token_stream()).unwrap(); + } + + for (segment, punct) in path.segments.pairs().map(syn::punctuated::Pair::into_tuple) { + let segment_ident = &segment.ident; + + write!(output, "{segment_ident}").unwrap(); + + if let Some(punct) = punct { + write!(output, "{}", punct.to_token_stream()).unwrap(); + } + } + + output +} |
