Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extra processing. #823

Merged
merged 2 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 80 additions & 25 deletions codegen/src/custom_type.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{DeriveInput, Fields};
use syn::{spanned::Spanned, DeriveInput, Fields};

const ATTR_NAME: &str = "rhai_type_name";
const ATTR_SKIP: &str = "rhai_type_skip";
const ATTR_GET: &str = "rhai_type_get";
const ATTR_GET_MUT: &str = "rhai_type_get_mut";
const ATTR_SET: &str = "rhai_type_set";
const ATTR_READONLY: &str = "rhai_type_readonly";
const ATTR_EXTRA: &str = "rhai_type_extra";

pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
let type_name = input.ident;
let mut pretty_print_name = quote! { stringify!(#type_name) };
let mut extras = Vec::new();

for attr in input.attrs.iter() {
if attr.path().is_ident(ATTR_NAME) {
// Type name
match attr.parse_args::<TokenStream>() {
Ok(name) => pretty_print_name = quote! { #name },
Err(e) => return e.into_compile_error(),
}
} else if attr.path().is_ident(ATTR_EXTRA) {
extras.push(
attr.parse_args()
.unwrap_or_else(syn::Error::into_compile_error),
);
}
}

let accessors = match input.data {
// struct Foo;
Expand All @@ -23,53 +48,77 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
let iter = fields.enumerate().map(|(i, field)| {
let mut name = None;
let mut get_fn = None;
let mut get_mut_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 attr.path().is_ident(ATTR_SKIP) {
skip = true;
} else if attr.path().is_ident("rhai_custom_type_name") {

if get_fn.is_some()
|| get_mut_fn.is_some()
|| set_fn.is_some()
|| name.is_some()
|| readonly
{
return syn::Error::new(
attr.path().span(),
format!("cannot use '{ATTR_SKIP}' with other attributes"),
)
.into_compile_error();
}

continue;
}

if attr.path().is_ident(ATTR_NAME) {
name = Some(
attr.parse_args()
.unwrap_or_else(syn::Error::into_compile_error),
);
} else if attr.path().is_ident("rhai_custom_type_get") {
} else if attr.path().is_ident(ATTR_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") {
} else if attr.path().is_ident(ATTR_GET_MUT) {
get_mut_fn = Some(
attr.parse_args()
.unwrap_or_else(syn::Error::into_compile_error),
);
} else if attr.path().is_ident(ATTR_SET) {
if readonly {
return syn::Error::new(
Span::call_site(),
"cannot use 'rhai_custom_type_set' with 'rhai_custom_type_readonly'",
attr.path().span(),
format!("cannot use '{ATTR_SET}' with '{ATTR_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") {
} else if attr.path().is_ident(ATTR_READONLY) {
if set_fn.is_some() {
return syn::Error::new(
Span::call_site(),
"cannot use 'rhai_custom_type_readonly' with 'rhai_custom_type_set'",
attr.path().span(),
format!("cannot use '{ATTR_READONLY}' with '{ATTR_SET}'"),
)
.into_compile_error();
}
readonly = true;
}
}

if skip && (get_fn.is_some() || set_fn.is_some() || name.is_some() || readonly) {
return syn::Error::new(
Span::call_site(),
"cannot use 'rhai_custom_type_skip' with other attributes",
)
.into_compile_error();
if skip {
let attr_name = attr.path().get_ident().unwrap();
return syn::Error::new(
attr.path().span(),
format!("cannot use '{}' with '{ATTR_SKIP}'", attr_name),
)
.into_compile_error();
}
}

if !skip {
Expand All @@ -84,7 +133,7 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
quote! { #index }
};

generate_accessor_fns(field_name, name, get_fn, set_fn, readonly)
generate_accessor_fns(field_name, name, get_fn, get_mut_fn, set_fn, readonly)
} else {
quote! {}
}
Expand All @@ -94,18 +143,21 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
}

syn::Data::Enum(_) => {
syn::Error::new(Span::call_site(), "enums are not yet implemented").into_compile_error()
return 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()
return syn::Error::new(Span::call_site(), "unions are not yet supported")
.into_compile_error()
}
};

quote! {
impl CustomType for #type_name {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name(stringify!(#type_name));
builder.with_name(#pretty_print_name);
#accessors;
#(#extras(&mut builder);)*
}
}
}
Expand All @@ -115,13 +167,16 @@ fn generate_accessor_fns(
field: TokenStream,
name: Option<TokenStream>,
get: Option<TokenStream>,
get_mut: Option<TokenStream>,
set: Option<TokenStream>,
readonly: bool,
) -> proc_macro2::TokenStream {
let get = get.map_or_else(
|| quote! { |obj: &mut Self| obj.#field.clone() },
|func| quote! { #func },
);
let get = match (get_mut, get) {
(Some(func), _) => quote! { #func },
(None, Some(func)) => quote! { |obj: &mut Self| #func(&*obj) },
(None, None) => quote! { |obj: &mut Self| obj.#field.clone() },
};

let set = set.map_or_else(
|| quote! { |obj: &mut Self, val| obj.#field = val },
|func| quote! { #func },
Expand Down
11 changes: 6 additions & 5 deletions codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,11 +413,12 @@ pub fn set_exported_global_fn(args: proc_macro::TokenStream) -> proc_macro::Toke
#[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
rhai_type_name,
rhai_type_get,
rhai_type_set,
rhai_type_readonly,
rhai_type_skip,
rhai_type_extra
)
)]
pub fn derive_custom_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
Expand Down
36 changes: 23 additions & 13 deletions codegen/tests/test_derive.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,52 @@
use rhai::{CustomType, TypeBuilder, FLOAT, INT};
use rhai::{CustomType, Engine, TypeBuilder, FLOAT, INT};

// Sanity check to make sure everything compiles

#[derive(Clone, CustomType)]
pub struct Bar(
#[cfg(not(feature = "no_float"))] // check other attributes
#[rhai_custom_type_skip]
#[rhai_type_skip]
FLOAT,
INT,
#[rhai_custom_type_name("boo")]
#[rhai_custom_type_readonly]
#[rhai_type_name("boo")]
#[rhai_type_readonly]
String,
Vec<INT>,
);

#[derive(Clone, CustomType)]
#[derive(Clone, Default, CustomType)]
#[rhai_type_name("MyFoo")]
#[rhai_type_extra(Self::build_extra)]
pub struct Foo {
#[cfg(not(feature = "no_float"))] // check other attributes
#[rhai_custom_type_skip]
#[rhai_type_skip]
_dummy: FLOAT,
#[rhai_custom_type_get(get_bar)]
#[rhai_type_get(get_bar)]
pub bar: INT,
#[rhai_custom_type_name("boo")]
#[rhai_custom_type_readonly]
#[rhai_type_name("boo")]
#[rhai_type_readonly]
pub(crate) baz: String,
#[rhai_custom_type_set(Self::set_qux)]
#[rhai_type_set(Self::set_qux)]
pub qux: Vec<INT>,
}

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

fn build_extra(builder: &mut TypeBuilder<Self>) {
builder.with_fn("new_foo", || Self::default());
}
}

fn get_bar(_this: &mut Foo) -> INT {
fn get_bar(_this: &Foo) -> INT {
42
}

#[test]
fn test() {}
fn test() {
let mut engine = Engine::new();
engine.build_type::<Foo>().build_type::<Bar>();

engine.run("new_foo()").unwrap();
}
2 changes: 1 addition & 1 deletion src/api/build_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl<'s, T: Variant + Clone> TypeBuilder<'_, 's, T> {
self
}

/// Register a custom function.
/// Register a custom method.
#[inline(always)]
pub fn with_fn<A: 'static, const N: usize, const X: bool, R: Variant + Clone, const F: bool>(
&mut self,
Expand Down
38 changes: 20 additions & 18 deletions tests/build_type.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![cfg(not(feature = "no_object"))]
use rhai::{CustomType, Engine, EvalAltResult, Position, Scope, TypeBuilder, INT};
use rhai::{CustomType, Engine, EvalAltResult, Position, TypeBuilder, INT};

#[test]
fn test_build_type() {
Expand Down Expand Up @@ -32,7 +32,7 @@
fn set_z(&mut self, z: INT) {
self.z = z
}
fn get_component(&mut self, idx: INT) -> Result<INT, Box<EvalAltResult>> {

Check warning on line 35 in tests/build_type.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging, sta...

method `get_component` is never used
match idx {
0 => Ok(self.x),
1 => Ok(self.y),
Expand Down Expand Up @@ -160,18 +160,28 @@
#[test]
fn test_build_type_macro() {
#[derive(Debug, Clone, Eq, PartialEq, CustomType)]
#[rhai_type_name("MyFoo")]
#[rhai_type_extra(Self::build_extra)]
struct Foo {
#[rhai_custom_type_skip]
#[rhai_type_skip]
dummy: i64,
#[rhai_custom_type_readonly]
#[rhai_type_readonly]
bar: i64,
#[rhai_custom_type_name("emphasize")]
#[rhai_type_name("emphasize")]
baz: bool,
#[rhai_custom_type_set(Self::set_hello)]
#[rhai_type_set(Self::set_hello)]
hello: String,
}

impl Foo {
pub fn new() -> Self {
Self {
dummy: 0,
bar: 5,
baz: false,
hello: "hey".to_string(),
}
}
pub fn set_hello(&mut self, value: String) {
self.hello = if self.baz {
let mut s = self.hello.clone();
Expand All @@ -184,27 +194,19 @@
value
};
}
fn build_extra(builder: &mut TypeBuilder<Self>) {
builder.with_fn("new_foo", || Self::new());
}
}

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,
.eval::<Foo>(
r#"
let foo = new_foo();
foo.hello = "this should not be seen";
foo.hello = "world!";
foo.emphasize = true;
Expand Down
Loading