Skip to content

Commit

Permalink
Merge pull request #819 from schungx/master
Browse files Browse the repository at this point in the history
Refine derive macro.
  • Loading branch information
schungx committed Jan 25, 2024
2 parents 80a733e + ff00031 commit 05b624b
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 46 deletions.
137 changes: 100 additions & 37 deletions codegen/src/custom_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,100 @@ 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;
syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => {
let iter = fields.named.iter().map(|field| {
let mut name = None;
let mut get_fn = None;
let mut set_fn = None;
let mut readonly = false;
let mut skip = false;

for attr in field.attrs.iter() {
if attr.path().is_ident("rhai_custom_type_skip") {
if get_fn.is_some() || set_fn.is_some() || name.is_some() {
return syn::Error::new(
Span::call_site(),
"cannot use 'rhai_custom_type_skip' with other attributes",
)
.into_compile_error();
}

skip = true;
continue;
}

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! {},
},
if skip {
return syn::Error::new(
Span::call_site(),
"cannot use 'rhai_custom_type_skip' with other attributes",
)
.into_compile_error();
}

if attr.path().is_ident("rhai_custom_type_name") {
name = Some(
attr.parse_args()
.unwrap_or_else(syn::Error::into_compile_error),
);
} else if attr.path().is_ident("rhai_custom_type_get") {
get_fn = Some(
attr.parse_args()
.unwrap_or_else(syn::Error::into_compile_error),
);
} else if attr.path().is_ident("rhai_custom_type_set") {
if readonly {
return syn::Error::new(
Span::call_site(),
"cannot use 'rhai_custom_type_set' with 'rhai_custom_type_readonly'",
)
.into_compile_error();
}
set_fn = Some(
attr.parse_args()
.unwrap_or_else(syn::Error::into_compile_error),
);
} else if attr.path().is_ident("rhai_custom_type_readonly") {
if set_fn.is_some() {
return syn::Error::new(
Span::call_site(),
"cannot use 'rhai_custom_type_readonly' with 'rhai_custom_type_set'",
)
.into_compile_error();
}
readonly = true;
}
}

if !skip {
generate_accessor_fns(
&field.ident.as_ref().unwrap(),
name,
get_fn,
set_fn,
readonly,
)
} else {
quote! {}
}
});

quote! {#(#iter)*}
}

syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Unnamed(_),
..
}) => syn::Error::new(Span::call_site(), "tuple structs are not yet implemented")
.into_compile_error(),

syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Unit,
..
}) => quote! {},

syn::Data::Enum(_) => {
syn::Error::new(Span::call_site(), "enums are not yet implemented").into_compile_error()
}
Expand All @@ -47,8 +109,8 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
};

quote! {
impl ::rhai::CustomType for #name {
fn build(mut builder: ::rhai::TypeBuilder<Self>) {
impl CustomType for #name {
fn build(mut builder: TypeBuilder<Self>) {
#accessors;
}
}
Expand All @@ -57,6 +119,7 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {

fn generate_accessor_fns(
field: &Ident,
name: Option<TokenStream>,
get: Option<TokenStream>,
set: Option<TokenStream>,
readonly: bool,
Expand All @@ -69,17 +132,17 @@ fn generate_accessor_fns(
.map(|func| quote! {#func})
.unwrap_or_else(|| quote! {|obj: &mut Self, val| obj.#field = val});

let name = name
.map(|field| quote! { #field })
.unwrap_or_else(|| quote! { stringify!(#field) });

if readonly {
quote! {
builder.with_get("#field", #get);
builder.with_get(#name, #get);
}
} else {
quote! {
builder.with_get_set(
"#field",
#get,
#set,
);
builder.with_get_set(#name, #get, #set);
}
}
}
11 changes: 10 additions & 1 deletion codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,16 @@ pub fn set_exported_global_fn(args: proc_macro::TokenStream) -> proc_macro::Toke
}
}

#[proc_macro_derive(CustomType, attributes(get, set, readonly))]
#[proc_macro_derive(
CustomType,
attributes(
rhai_custom_type_name,
rhai_custom_type_get,
rhai_custom_type_set,
rhai_custom_type_readonly,
rhai_custom_type_skip
)
)]
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);
Expand Down
22 changes: 16 additions & 6 deletions codegen/tests/test_derive.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
use rhai_codegen::CustomType;
use rhai::{CustomType, TypeBuilder, INT};

// Sanity check to make sure everything compiles
#[derive(Clone, CustomType)]
pub struct Foo {
#[get(get_bar)]
bar: i32,
#[readonly]
#[rhai_custom_type_skip]
_dummy: INT,
#[rhai_custom_type_get(get_bar)]
bar: INT,
#[rhai_custom_type_name("boo")]
#[rhai_custom_type_readonly]
baz: String,
qux: Vec<i32>,
#[rhai_custom_type_set(Self::set_qux)]
qux: Vec<INT>,
}

fn get_bar(_this: &mut Foo) -> i32 {
impl Foo {
pub fn set_qux(&mut self, value: Vec<INT>) {
self.qux = value;
}
}

fn get_bar(_this: &mut Foo) -> INT {
42
}
70 changes: 68 additions & 2 deletions tests/build_type.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#![cfg(not(feature = "no_object"))]
use rhai::{CustomType, Engine, EvalAltResult, Position, TypeBuilder, INT};
use rhai::{CustomType, Engine, EvalAltResult, Position, Scope, TypeBuilder, INT};

#[test]
fn build_type() {
fn test_build_type() {
#[derive(Debug, Clone, PartialEq, Eq)]
struct Vec3 {
x: INT,
Expand Down Expand Up @@ -156,3 +156,69 @@ fn build_type() {
6,
);
}

#[test]
fn test_build_type_macro() {
#[derive(Debug, Clone, Eq, PartialEq)] // <- necessary for any custom type
#[derive(CustomType)] // <- auto-implement 'CustomType'
struct Foo {
#[rhai_custom_type_skip]
dummy: i64, // <- skip this field
#[rhai_custom_type_readonly] // <- only auto-implement getters
bar: i64,
#[rhai_custom_type_name("emphasize")]
baz: bool, // <- auto-implement getter/setter for 'baz'
#[rhai_custom_type_set(Self::set_hello)] // <- call custom setter for 'hello'
hello: String, // <- auto-implement getter for 'hello'
}

impl Foo {
pub fn set_hello(&mut self, value: String) {
self.hello = if self.baz {
let mut s = self.hello.clone();
s.push_str(&value);
for _ in 0..self.bar {
s.push('!');
}
s
} else {
value
};
}
}

let mut engine = Engine::new();
engine.build_type::<Foo>();

let mut scope = Scope::new();
scope.push(
"foo",
Foo {
dummy: 0,
bar: 5,
baz: false,
hello: "hey".to_string(),
},
);

assert_eq!(
engine
.eval_with_scope::<Foo>(
&mut scope,
r#"
foo.hello = "this should not be seen";
foo.hello = "world!";
foo.emphasize = true;
foo.hello = "yo";
foo
"#
)
.unwrap(),
Foo {
dummy: 0,
bar: 5,
baz: true,
hello: "world!yo!!!!!".into()
}
);
}

0 comments on commit 05b624b

Please sign in to comment.