diff --git a/codegen/src/custom_type.rs b/codegen/src/custom_type.rs new file mode 100644 index 000000000..8b08af1fb --- /dev/null +++ b/codegen/src/custom_type.rs @@ -0,0 +1,85 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::DeriveInput; + +pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream { + let name = input.ident; + + let accessors = match input.data { + syn::Data::Struct(ref data) => match data.fields { + syn::Fields::Named(ref fields) => { + let iter = fields.named.iter().map(|field| { + let mut get_fn = None; + let mut set_fn = None; + let mut readonly = false; + for attr in field.attrs.iter() { + if attr.path().is_ident("get") { + get_fn = Some( + attr.parse_args() + .unwrap_or_else(syn::Error::into_compile_error), + ); + } else if attr.path().is_ident("set") { + set_fn = Some( + attr.parse_args() + .unwrap_or_else(syn::Error::into_compile_error), + ); + } else if attr.path().is_ident("readonly") { + readonly = true; + } + } + + generate_accessor_fns(&field.ident.as_ref().unwrap(), get_fn, set_fn, readonly) + }); + quote! {#(#iter)*} + } + syn::Fields::Unnamed(_) => { + syn::Error::new(Span::call_site(), "tuple structs are not yet implemented") + .into_compile_error() + } + syn::Fields::Unit => quote! {}, + }, + syn::Data::Enum(_) => { + syn::Error::new(Span::call_site(), "enums are not yet implemented").into_compile_error() + } + syn::Data::Union(_) => { + syn::Error::new(Span::call_site(), "unions are not supported").into_compile_error() + } + }; + + quote! { + impl ::rhai::CustomType for #name { + fn build(mut builder: ::rhai::TypeBuilder<'_, Self>) { + #accessors; + } + } + } +} + +fn generate_accessor_fns( + field: &Ident, + get: Option, + set: Option, + readonly: bool, +) -> proc_macro2::TokenStream { + let get = get + .map(|func| quote! {#func}) + .unwrap_or_else(|| quote! {|obj: &mut Self| obj.#field.clone()}); + + let set = set + .map(|func| quote! {#func}) + .unwrap_or_else(|| quote! {|obj: &mut Self, val| obj.#field = val}); + + if readonly { + quote! { + builder.with_get("#field", #get); + } + } else { + quote! { + builder.with_get_set( + "#field", + #get, + #set, + ); + } + } +} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index aa4af2705..09469d3b9 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -88,9 +88,10 @@ //! use quote::quote; -use syn::{parse_macro_input, spanned::Spanned}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput}; mod attrs; +mod custom_type; mod function; mod module; mod register; @@ -410,3 +411,10 @@ pub fn set_exported_global_fn(args: proc_macro::TokenStream) -> proc_macro::Toke Err(e) => e.to_compile_error().into(), } } + +#[proc_macro_derive(CustomType, attributes(get, set, readonly))] +pub fn derive_custom_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = custom_type::derive_custom_type_impl(input); + expanded.into() +} diff --git a/codegen/tests/test_derive.rs b/codegen/tests/test_derive.rs new file mode 100644 index 000000000..c24938284 --- /dev/null +++ b/codegen/tests/test_derive.rs @@ -0,0 +1,15 @@ +use rhai_codegen::CustomType; + +// Sanity check to make sure everything compiles +#[derive(Clone, CustomType)] +pub struct Foo { + #[get(get_bar)] + bar: i32, + #[readonly] + baz: String, + qux: Vec, +} + +fn get_bar(_this: &mut Foo) -> i32 { + 42 +}