diff options
Diffstat (limited to 'engine-macros/src')
| -rw-r--r-- | engine-macros/src/lib.rs | 389 | ||||
| -rw-r--r-- | engine-macros/src/reflection.rs | 15 | ||||
| -rw-r--r-- | engine-macros/src/reflection/enum_impl.rs | 125 | ||||
| -rw-r--r-- | engine-macros/src/reflection/field.rs | 96 | ||||
| -rw-r--r-- | engine-macros/src/reflection/struct_impl.rs | 46 | ||||
| -rw-r--r-- | engine-macros/src/reflection/visibility.rs | 45 | ||||
| -rw-r--r-- | engine-macros/src/util.rs | 65 |
7 files changed, 396 insertions, 385 deletions
diff --git a/engine-macros/src/lib.rs b/engine-macros/src/lib.rs index 3eecab3..5d16ad2 100644 --- a/engine-macros/src/lib.rs +++ b/engine-macros/src/lib.rs @@ -1,391 +1,10 @@ #![deny(clippy::all, clippy::pedantic)] -use std::fmt::Write; - -use proc_macro::TokenStream; -use quote::{quote, ToTokens}; -use syn::{ - parse, - Field as SynField, - Fields, - Item, - ItemEnum, - ItemStruct, - LitStr, - Path as SynPath, - Visibility as SynVisibility, -}; - -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, - } - }; -} +mod reflection; +mod util; #[proc_macro_derive(Reflection)] -pub fn reflection_derive(input: TokenStream) -> TokenStream -{ - let input = parse::<Item>(input).unwrap(); - - match input { - Item::Struct(input) => generate_struct_reflection_impl(input), - Item::Enum(input) => generate_enum_reflection_impl(input), - _ => panic!("Invalid input"), - } -} - -fn generate_struct_reflection_impl(input: ItemStruct) -> TokenStream +pub fn reflection_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let engine_crate_path = find_engine_crate_path().unwrap(); - - let input_ident = input.ident; - - let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); - - let fields = input - .fields - .into_iter() - .enumerate() - .map(|(field_index, field)| { - gen_reflection_field( - &field, - field_index, - &engine_crate_path, - ReflectionFieldGenOptions { - field_vis_override: None, - include_byte_offset: true, - }, - ) - }); - - quote! { - unsafe impl #impl_generics #engine_crate_path::reflection::Reflection for - #input_ident #type_generics #where_clause - { - const TYPE_REFLECTION: &#engine_crate_path::reflection::Type = - &const { - #engine_crate_path::reflection::Type::Struct( - #engine_crate_path::reflection::Struct { - fields: &[ - #(#fields),* - ] - } - ) - }; - } - } - .into() -} - -fn generate_enum_reflection_impl(input: ItemEnum) -> TokenStream -{ - let engine_crate_path = find_engine_crate_path().unwrap(); - - let input_ident = input.ident; - - let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); - - let variants = input.variants.iter().map(|variant| { - let variant_name = LitStr::new(&variant.ident.to_string(), variant.ident.span()); - - let fields = match &variant.fields { - Fields::Unit => quote! { None }, - Fields::Named(named_fields) => { - let fields = named_fields.named.iter().enumerate().map( - |(variant_field_index, variant_field)| { - gen_reflection_field( - variant_field, - variant_field_index, - &engine_crate_path, - ReflectionFieldGenOptions { - field_vis_override: None, - include_byte_offset: false, - }, - ) - }, - ); - - quote! { - Some(#engine_crate_path::reflection::EnumVariantFields::Named { - fields: &[#(#fields),*] - }) - } - } - Fields::Unnamed(unnamed_fields) => { - let fields = unnamed_fields.unnamed.iter().enumerate().map( - |(variant_field_index, variant_field)| { - gen_reflection_field( - variant_field, - variant_field_index, - &engine_crate_path, - ReflectionFieldGenOptions { - field_vis_override: None, - include_byte_offset: false, - }, - ) - }, - ); - - quote! { - Some(#engine_crate_path::reflection::EnumVariantFields::Unnamed { - fields: &[#(#fields),*] - }) - } - } - }; - - quote! { - #engine_crate_path::reflection::EnumVariant { - name: #variant_name, - fields: #fields - } - } - }); - - let variant_lookup_match_arms = - input.variants.iter().enumerate().map(|(index, variant)| { - let variant_ident = &variant.ident; - - let pattern = match variant.fields { - Fields::Unit => quote! { Self::#variant_ident }, - Fields::Named(_) => quote! { Self::#variant_ident { .. } }, - Fields::Unnamed(_) => quote! { Self::#variant_ident(..) }, - }; - - quote! { - #pattern => &enum_reflection.variants[#index] - } - }); - - let is_unit_only = input - .variants - .iter() - .all(|variant| matches!(variant.fields, Fields::Unit)); - - quote! { - unsafe impl #impl_generics #engine_crate_path::reflection::Reflection for - #input_ident #type_generics #where_clause - { - const TYPE_REFLECTION: &#engine_crate_path::reflection::Type = - &const { - #engine_crate_path::reflection::Type::Enum( - #engine_crate_path::reflection::Enum { - variants: &[#(#variants),*], - is_unit_only: #is_unit_only - } - ) - }; - } - - unsafe impl #impl_generics #engine_crate_path::reflection::EnumReflectionExt for - #input_ident #type_generics #where_clause - { - 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),* - } - } - } - } - .into() -} - -struct ReflectionFieldGenOptions -{ - field_vis_override: Option<SynVisibility>, - include_byte_offset: bool, -} - -fn gen_reflection_field( - field: &SynField, - field_index: usize, - engine_crate_path: &SynPath, - 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 = LitStr::new(&field_ident.to_string(), field_ident.span()); - - quote! { Some(#field_name) } - } else { - quote! { None } - }; - - let field_byte_offset = if options.include_byte_offset { - if let Some(field_ident) = field_ident { - quote! { Some(std::mem::offset_of!(Self, #field_ident)) } - } else { - quote! { Some(std::mem::offset_of!(Self, #field_index)) } - } - } else { - quote! { None } - }; - - // since std::any::type_name as const is not stable yet - let field_type_name = field_type.to_token_stream().to_string(); - - let field_vis = options.field_vis_override.as_ref().unwrap_or(&field.vis); - - let field_reflection_vis = - gen_reflection_visibility_path(field_vis, &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>(), - type_name: #field_type_name, - get_type: #engine_crate_path::reflection::FnWithDebug::new(|| { - 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() - }), - visibility: #field_reflection_vis - } - } -} - -fn gen_reflection_visibility_path( - visibility: &SynVisibility, - engine_crate_path: &SynPath, -) -> proc_macro2::TokenStream -{ - match visibility { - SynVisibility::Public(_) => { - quote! { #engine_crate_path::reflection::Visibility::Pub } - } - SynVisibility::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) } - } - SynVisibility::Inherited => { - quote! { #engine_crate_path::reflection::Visibility::Private } - } - } -} - -fn find_engine_crate_path() -> Option<SynPath> -{ - 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)) -} - -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 + 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..825094b --- /dev/null +++ b/engine-macros/src/reflection.rs @@ -0,0 +1,15 @@ +mod enum_impl; +mod field; +mod struct_impl; +mod visibility; + +pub fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream +{ + let input = syn::parse2::<syn::Item>(input).unwrap(); + + match input { + syn::Item::Struct(input) => struct_impl::generate(input), + syn::Item::Enum(input) => enum_impl::generate(input), + _ => panic!("Invalid input"), + } +} diff --git a/engine-macros/src/reflection/enum_impl.rs b/engine-macros/src/reflection/enum_impl.rs new file mode 100644 index 0000000..0d9ff8b --- /dev/null +++ b/engine-macros/src/reflection/enum_impl.rs @@ -0,0 +1,125 @@ +use quote::quote; + +use crate::reflection::field::{generate as generate_field, ReflectionFieldGenOptions}; +use crate::util::find_engine_crate_path; + +pub fn generate(input: syn::ItemEnum) -> proc_macro2::TokenStream +{ + let engine_crate_path = find_engine_crate_path().unwrap(); + + let input_ident = input.ident; + + let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); + + let variants = input.variants.iter().map(|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_field( + variant_field, + variant_field_index, + &engine_crate_path, + ReflectionFieldGenOptions { + field_vis_override: None, + include_byte_offset: false, + }, + ) + }, + ); + + 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_field( + variant_field, + variant_field_index, + &engine_crate_path, + ReflectionFieldGenOptions { + field_vis_override: None, + include_byte_offset: false, + }, + ) + }, + ); + + quote! { + Some(#engine_crate_path::reflection::EnumVariantFields::Unnamed { + fields: &[#(#fields),*] + }) + } + } + }; + + quote! { + #engine_crate_path::reflection::EnumVariant { + name: #variant_name, + fields: #fields + } + } + }); + + 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] + } + }); + + let is_unit_only = input + .variants + .iter() + .all(|variant| matches!(variant.fields, syn::Fields::Unit)); + + quote! { + unsafe impl #impl_generics #engine_crate_path::reflection::Reflection for + #input_ident #type_generics #where_clause + { + const TYPE_REFLECTION: &#engine_crate_path::reflection::Type = + &const { + #engine_crate_path::reflection::Type::Enum( + #engine_crate_path::reflection::Enum { + variants: &[#(#variants),*], + is_unit_only: #is_unit_only + } + ) + }; + } + + unsafe impl #impl_generics #engine_crate_path::reflection::EnumReflectionExt for + #input_ident #type_generics #where_clause + { + 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),* + } + } + } + } +} diff --git a/engine-macros/src/reflection/field.rs b/engine-macros/src/reflection/field.rs new file mode 100644 index 0000000..489d165 --- /dev/null +++ b/engine-macros/src/reflection/field.rs @@ -0,0 +1,96 @@ +use quote::{quote, ToTokens}; + +use crate::reflection::visibility::generate as generate_visibility; + +pub struct ReflectionFieldGenOptions +{ + pub field_vis_override: Option<syn::Visibility>, + pub include_byte_offset: bool, +} + +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 = if options.include_byte_offset { + if let Some(field_ident) = field_ident { + quote! { Some(std::mem::offset_of!(Self, #field_ident)) } + } else { + quote! { Some(std::mem::offset_of!(Self, #field_index)) } + } + } else { + quote! { None } + }; + + // since std::any::type_name as const is not stable yet + let field_type_name = field_type.to_token_stream().to_string(); + + let field_vis = options.field_vis_override.as_ref().unwrap_or(&field.vis); + + let field_reflection_vis = generate_visibility(field_vis, &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>(), + type_name: #field_type_name, + get_type: #engine_crate_path::reflection::FnWithDebug::new(|| { + 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() + }), + visibility: #field_reflection_vis + } + } +} diff --git a/engine-macros/src/reflection/struct_impl.rs b/engine-macros/src/reflection/struct_impl.rs new file mode 100644 index 0000000..57e3e21 --- /dev/null +++ b/engine-macros/src/reflection/struct_impl.rs @@ -0,0 +1,46 @@ +use quote::quote; + +use crate::reflection::field::{generate as generate_field, ReflectionFieldGenOptions}; +use crate::util::find_engine_crate_path; + +pub fn generate(input: syn::ItemStruct) -> proc_macro2::TokenStream +{ + let engine_crate_path = find_engine_crate_path().unwrap(); + + let input_ident = input.ident; + + let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); + + let fields = input + .fields + .into_iter() + .enumerate() + .map(|(field_index, field)| { + generate_field( + &field, + field_index, + &engine_crate_path, + ReflectionFieldGenOptions { + field_vis_override: None, + include_byte_offset: true, + }, + ) + }); + + quote! { + unsafe impl #impl_generics #engine_crate_path::reflection::Reflection for + #input_ident #type_generics #where_clause + { + const TYPE_REFLECTION: &#engine_crate_path::reflection::Type = + &const { + #engine_crate_path::reflection::Type::Struct( + #engine_crate_path::reflection::Struct { + fields: &[ + #(#fields),* + ] + } + ) + }; + } + } +} 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..515b066 --- /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 +} |
