From 377367bc67a9eac69951562aea1cdc5040e20b80 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 5 Jul 2026 01:37:21 +0200 Subject: feat(engine): make Reflection derivable on enums without C or primitive repr --- engine-macros/src/reflection/enum_impl.rs | 369 +++++------------------------- engine-reflection/src/lib.rs | 38 +-- engine/src/rendering.rs | 1 - engine/src/windowing/window.rs | 1 - 4 files changed, 70 insertions(+), 339 deletions(-) diff --git a/engine-macros/src/reflection/enum_impl.rs b/engine-macros/src/reflection/enum_impl.rs index f46b977..1cc8f0a 100644 --- a/engine-macros/src/reflection/enum_impl.rs +++ b/engine-macros/src/reflection/enum_impl.rs @@ -1,8 +1,6 @@ -use quote::{format_ident, quote, ToTokens}; -use syn::ItemEnum; +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; @@ -34,16 +32,6 @@ pub fn generate(input: syn::ItemEnum, options: OptionsAttr) -> proc_macro2::Toke .iter() .all(|variant| matches!(variant.fields, syn::Fields::Unit)); - let mod_name = format_ident!("__engine_private_{}", input.ident); - - let reprs = get_reprs(&input); - - if !reprs.has_c_repr && reprs.primitive_repr.is_none() { - panic!("Enums must have a C or primitive representation to derive Reflection"); - } - - let mod_content = generate_mod_content(&input, is_unit_only, &reprs); - let impls: &mut dyn Iterator = if input.generics.params.is_empty() { &mut [generate_impls( @@ -51,7 +39,6 @@ pub fn generate(input: syn::ItemEnum, options: OptionsAttr) -> proc_macro2::Toke None, is_unit_only, &variant_lookup_match_arms, - &mod_name, &engine_crate_path, )] .into_iter() @@ -62,19 +49,12 @@ pub fn generate(input: syn::ItemEnum, options: OptionsAttr) -> proc_macro2::Toke Some(generic_args), is_unit_only, &variant_lookup_match_arms, - &mod_name, &engine_crate_path, ) }) }; quote! { - #[doc(hidden)] - #[allow(non_snake_case)] - mod #mod_name { - #mod_content - } - #(#impls)* } } @@ -84,37 +64,12 @@ fn generate_impls( generic_args: Option<&syn::AngleBracketedGenericArguments>, is_unit_only: bool, variant_lookup_match_arms: &[proc_macro2::TokenStream], - mod_name: &proc_macro2::Ident, 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.ident, - &input.variants, - generic_args, - is_unit_only, - &engine_crate_path, - ); - - let tagged_union = if is_unit_only { - quote! { - None - } - } else { - quote! { - Some(#engine_crate_path::reflection::EnumTaggedUnion { - discriminant_layout: std::alloc::Layout::new::<#mod_name::Discriminant>(), - discriminant_byte_offset: - std::mem::offset_of!(#mod_name::Equivalent #generic_args, tag), - fields_layout: - std::alloc::Layout::new::<#mod_name::Fields #generic_args>(), - fields_byte_offset: - std::mem::offset_of!(#mod_name::Equivalent #generic_args, payload), - }) - } - }; + let variants = generate_variants(&input.variants, &engine_crate_path); let generics_type_aliases = input .generics @@ -139,16 +94,6 @@ fn generate_impls( let input_ident = &input.ident; - let discriminant_size = if is_unit_only { - quote! { - std::mem::size_of::() - } - } else { - quote! { - std::mem::size_of::<#mod_name::Discriminant>() - } - }; - quote! { unsafe impl #engine_crate_path::reflection::Reflection for #input_ident #generic_args @@ -161,8 +106,6 @@ fn generate_impls( #engine_crate_path::reflection::Enum { variants: &[#(#variants),*], is_unit_only: #is_unit_only, - discriminant_size: #discriminant_size, - tagged_union: #tagged_union, get_default_value: || { #get_default_value_fn }, @@ -203,51 +146,23 @@ fn generate_impls( } fn generate_variants<'a>( - input_ident: &proc_macro2::Ident, input_variants: &'a syn::punctuated::Punctuated, - generic_args: Option<&'a syn::AngleBracketedGenericArguments>, - is_unit_only: bool, engine_crate_path: &'a syn::Path, ) -> impl Iterator + use<'a> { - let mod_name = format_ident!("__engine_private_{input_ident}"); - input_variants.iter().map(move |variant| { let variant_name = syn::LitStr::new(&variant.ident.to_string(), variant.ident.span()); - let variant_ident = &variant.ident; - - let variant_fields_struct_ident = format_ident!("VariantFields{}", variant.ident); - 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_field( + generate_enum_variant_field( variant_field, variant_field_index, &engine_crate_path, - ReflectionFieldGenOptions { - // enum variant fields are always public - field_vis_override: Some(syn::Visibility::Public( - ::default(), - )), - gen_get_byte_offset: &|field| { - if let Some(field_ident) = &field.ident { - quote! { - std::mem::offset_of!( - #mod_name::#variant_fields_struct_ident - #generic_args, - #field_ident - ) - } - } else { - unreachable!(); - } - }, - }, ) }, ); @@ -261,34 +176,10 @@ fn generate_variants<'a>( syn::Fields::Unnamed(unnamed_fields) => { let fields = unnamed_fields.unnamed.iter().enumerate().map( |(variant_field_index, variant_field)| { - generate_field( + generate_enum_variant_field( variant_field, variant_field_index, &engine_crate_path, - ReflectionFieldGenOptions { - // enum variant fields are always public - field_vis_override: Some(syn::Visibility::Public( - ::default(), - )), - gen_get_byte_offset: &|field| { - if let Some(_) = &field.ident { - unreachable!() - } else { - let field_index = - proc_macro2::Literal::usize_unsuffixed( - variant_field_index, - ); - - quote! { - std::mem::offset_of!( - #mod_name::#variant_fields_struct_ident - #generic_args, - #field_index - ) - } - } - }, - }, ) }, ); @@ -301,46 +192,6 @@ fn generate_variants<'a>( } }; - let discriminant = match variant.fields { - syn::Fields::Unit if is_unit_only => { - quote! { - let mut buf = [0u8; std::mem::size_of::()]; - - // Self or any of it's type parameters might have a Drop impl and - // since that can not be const evaluated, the discriminant value has - // to be wrapped in ManuallyDrop - let discriminant = std::mem::ManuallyDrop::new(Self::#variant_ident); - - unsafe { - std::ptr::copy_nonoverlapping( - (&raw const discriminant).cast::(), - buf.as_mut_ptr(), - std::mem::size_of::(), - ); - } - - buf - } - } - _ => { - quote! { - let mut buf = [0u8; std::mem::size_of::()]; - - let discriminant = #mod_name::Discriminant::#variant_ident; - - unsafe { - std::ptr::copy_nonoverlapping( - (&raw const discriminant).cast::(), - buf.as_mut_ptr(), - std::mem::size_of::<#mod_name::Discriminant>(), - ); - } - - buf - } - } - }; - let variant_field_vars = variant.fields.iter().enumerate().map( |(variant_field_index, variant_field)| { let var_ident = match variant_field.ident.as_ref() { @@ -415,9 +266,6 @@ fn generate_variants<'a>( #engine_crate_path::reflection::EnumVariant { name: #variant_name, fields: #fields, - discriminant: #engine_crate_path::reflection::EnumDiscriminant { - buf: { #discriminant } - }, try_write_new_to: |dst, fields| { use std::any::Any; use #engine_crate_path::reflection::EnumVariantWriteNewToError; @@ -443,180 +291,83 @@ fn generate_variants<'a>( }) } -fn generate_mod_content( - input: &syn::ItemEnum, - is_unit_only: bool, - reprs: &Reprs, +pub fn generate_enum_variant_field( + field: &syn::Field, + field_index: usize, + engine_crate_path: &syn::Path, ) -> proc_macro2::TokenStream { - if is_unit_only { - return quote! {}; - } + let field_ident = &field.ident; - let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); + let field_type = &field.ty; - let variant_fields_structs = input.variants.iter().map(|variant| { - let fields = variant.fields.iter().map(|field| { - 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()); - if let Some(field_ident) = &field.ident { - quote! { pub #field_ident: #field_type } - } else { - quote! { pub #field_type } - } - }); - - let ident = format_ident!("VariantFields{}", variant.ident); - - let generics_phantom_data_elems = input.generics.params.iter().filter_map( - |generic_param| match generic_param { - syn::GenericParam::Type(type_param) => { - Some(type_param.ident.clone().into_token_stream()) - } - syn::GenericParam::Lifetime(_) => { - unimplemented!(); - } - syn::GenericParam::Const(_const_param) => None, - }, - ); - - let generics_phantom_data = quote! { - std::marker::PhantomData<(#(#generics_phantom_data_elems),*)> - }; - - match variant.fields { - syn::Fields::Named(_) => quote! { - #[repr(C)] - pub struct #ident #impl_generics - #where_clause - { - #(#fields,)* - _pd: #generics_phantom_data - } - }, - syn::Fields::Unnamed(_) => quote! { - #[repr(C)] - pub struct #ident #impl_generics ( - #(#fields,)* - #generics_phantom_data - ) - #where_clause; - }, - syn::Fields::Unit => quote! { - #[repr(C)] - pub struct #ident #impl_generics (#generics_phantom_data) - #where_clause; - }, - } - }); - - let fields_union_fields = input.variants.iter().map(|variant| { - let variant_ident = &variant.ident; - - let variant_fields_struct_ident = format_ident!("VariantFields{}", variant.ident); - - quote! { - pub #variant_ident: - std::mem::ManuallyDrop<#variant_fields_struct_ident #type_generics> - } - }); - - let discriminant_enum_variants = input.variants.iter().map(|variant| { - let variant_ident = &variant.ident; - - if let Some((_, discriminant)) = &variant.discriminant { - quote! { #variant_ident = #discriminant } - } else { - quote! { #variant_ident } - } - }); - - // If the enum has both a C & primitive repr, the primitive repr must be used - let discriminant_enum_repr = if let Some(primitive_repr) = &reprs.primitive_repr { - quote! { #primitive_repr } - } else if reprs.has_c_repr { - quote! { C } + quote! { Some(#field_name) } } else { - unreachable!(); + quote! { None } }; + let get_type_fn_body = + { gen_get_optional_type_reflection(field_type, engine_crate_path) }; + quote! { - #![allow(non_snake_case, dead_code)] + #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 + }), + } + } +} - use super::*; +fn gen_get_optional_type_reflection( + field_type: &syn::Type, + engine_crate_path: &syn::Path, +) -> proc_macro2::TokenStream +{ + quote! { + struct SpecializationTarget(std::marker::PhantomData); - #[repr(C)] - pub struct Equivalent #impl_generics - #where_clause + trait FieldHasReflection { - pub tag: Discriminant, - pub payload: Fields #type_generics + fn field_type_reflection(&self) + -> Option<&'static #engine_crate_path::reflection::Type>; } - #[repr(#discriminant_enum_repr)] - pub enum Discriminant + trait FieldDoesNotHaveReflection { - #(#discriminant_enum_variants),* + fn field_type_reflection(&self) + -> Option<&'static #engine_crate_path::reflection::Type>; } - #[repr(C)] - pub union Fields #impl_generics - #where_clause + impl FieldDoesNotHaveReflection for &SpecializationTarget { - #(#fields_union_fields),* + fn field_type_reflection(&self) + -> Option<&'static #engine_crate_path::reflection::Type> + { + None + } } - #(#variant_fields_structs)* - } -} - -fn get_reprs(input: &ItemEnum) -> Reprs -{ - let mut has_c_repr = false; - let mut primitive_repr: Option = None; - - for attr in &input.attrs { - let syn::Meta::List(attr_meta) = &attr.meta else { - continue; - }; - - if !attr_meta.path.is_ident("repr") { - continue; + 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()) + } } - attr_meta - .parse_nested_meta(|nested_meta| { - let Some(meta_ident) = nested_meta.path.get_ident() else { - return Ok(()); - }; - - if meta_ident == "C" { - has_c_repr = true; - } else if PRIMITIVE_REPRS.contains(&meta_ident.to_string().as_str()) { - primitive_repr = Some(meta_ident.clone()); - } else { - return Err(nested_meta.error(concat!( - "Unsupported representation. ", - "Only C and primitive representations are allowed" - ))); - } - - Ok(()) - }) - .unwrap(); + (&SpecializationTarget::<#field_type>(std::marker::PhantomData)) + .field_type_reflection() } - - Reprs { has_c_repr, primitive_repr } -} - -#[derive(Debug)] -struct Reprs -{ - has_c_repr: bool, - primitive_repr: Option, } - -const PRIMITIVE_REPRS: &[&'static str] = &[ - "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", - "isize", -]; diff --git a/engine-reflection/src/lib.rs b/engine-reflection/src/lib.rs index 2b8d01a..62fa4f5 100644 --- a/engine-reflection/src/lib.rs +++ b/engine-reflection/src/lib.rs @@ -135,10 +135,6 @@ pub struct Enum /// The enum only contains unit variants. pub is_unit_only: bool, - pub discriminant_size: usize, - - pub tagged_union: Option, - pub get_default_value: fn() -> Option, pub cast_dyn_any: CastDynAnyFn, @@ -160,15 +156,6 @@ impl Enum } } -#[derive(Debug, Clone)] -pub struct EnumTaggedUnion -{ - pub discriminant_layout: Layout, - pub discriminant_byte_offset: usize, - pub fields_layout: Layout, - pub fields_byte_offset: usize, -} - #[derive(Debug, Clone)] pub struct EnumVariant { @@ -177,8 +164,6 @@ pub struct EnumVariant /// The fields of this variant. If `None`, this variant is a unit variant. pub fields: Option, - pub discriminant: EnumDiscriminant, - pub try_write_new_to: fn( dst: &mut dyn Any, fields: &mut dyn Iterator>, @@ -206,17 +191,17 @@ pub enum EnumVariantFields { Named { - fields: &'static [Field] + fields: &'static [EnumVariantField] }, Unnamed { - fields: &'static [Field] + fields: &'static [EnumVariantField] }, } impl EnumVariantFields { - pub fn fields(&self) -> &'static [Field] + pub fn fields(&self) -> &'static [EnumVariantField] { match self { Self::Named { fields } => fields, @@ -225,13 +210,14 @@ impl EnumVariantFields } } -/// The discriminant of a enum variant. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct EnumDiscriminant +#[derive(Debug, Clone)] +pub struct EnumVariantField { - /// This buffer is the size of a u128/i128 because they are the largest possible enum - /// discriminant representations. - pub buf: [u8; size_of::()], + pub name: Option<&'static str>, + pub index: usize, + pub type_id: TypeId, + pub get_type_name: FnWithDebug<&'static str>, + pub get_type: FnWithDebug>, } #[derive(Debug, Clone)] @@ -242,10 +228,6 @@ pub struct Field pub layout: Layout, /// Byte offset to the field relative to the start of the type containing it. - /// - /// If the type containing the field is a enum variant, this offset is relative to - /// the payload field of the enum's equivalent tagged union, therefore this offset - /// will **NOT** include the enum tag pub byte_offset: usize, pub type_id: TypeId, diff --git a/engine/src/rendering.rs b/engine/src/rendering.rs index 47d6e98..279ad14 100644 --- a/engine/src/rendering.rs +++ b/engine/src/rendering.rs @@ -224,7 +224,6 @@ pub struct RenderPass #[derive(Debug, Reflection)] #[non_exhaustive] -#[repr(C)] pub enum Command { RemoveSurface(SurfaceId), diff --git a/engine/src/windowing/window.rs b/engine/src/windowing/window.rs index bbf7eb6..67554ca 100644 --- a/engine/src/windowing/window.rs +++ b/engine/src/windowing/window.rs @@ -207,7 +207,6 @@ pub struct Closed; #[derive( Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Reflection, )] -#[repr(usize)] pub enum CursorGrabMode { #[default] -- cgit v1.2.3-18-g5258