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 support for tuple structs. #821

Merged
merged 4 commits into from
Jan 26, 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
111 changes: 49 additions & 62 deletions codegen/src/custom_type.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
use proc_macro2::{Ident, Span, TokenStream};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::DeriveInput;
use syn::{DeriveInput, Fields};

pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
let name = input.ident;

let accessors = match input.data {
// struct Foo;
syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
fields: Fields::Unit,
..
}) => {
let iter = fields.named.iter().map(|field| {
}) => quote! {},

// struct Foo { ... }
syn::Data::Struct(syn::DataStruct { fields, .. }) => {
let fields = match fields {
Fields::Named(ref f) => f.named.iter(),
Fields::Unnamed(ref f) => f.unnamed.iter(),
Fields::Unit => unreachable!(),
};

let iter = fields.enumerate().map(|(i, field)| {
let mut name = None;
let mut get_fn = None;
let mut set_fn = None;
Expand All @@ -19,27 +29,8 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {

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;
}

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") {
} else if attr.path().is_ident("rhai_custom_type_name") {
name = Some(
attr.parse_args()
.unwrap_or_else(syn::Error::into_compile_error),
Expand Down Expand Up @@ -73,33 +64,35 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
}
}

if !skip {
generate_accessor_fns(
&field.ident.as_ref().unwrap(),
name,
get_fn,
set_fn,
readonly,
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 field_name = if let Some(ref field_name) = field.ident {
quote! { #field_name }
} else {
if name.is_none() {
let map_name = format!("field{i}");
name = Some(quote! { #map_name });
}
let index = proc_macro2::Literal::usize_unsuffixed(i);
quote! { #index }
};

generate_accessor_fns(field_name, name, get_fn, set_fn, readonly)
} else {
quote! {}
}
});

quote! {#(#iter)*}
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 @@ -118,31 +111,25 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
}

fn generate_accessor_fns(
field: &Ident,
field: TokenStream,
name: Option<TokenStream>,
get: Option<TokenStream>,
set: Option<TokenStream>,
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});

let name = name
.map(|field| quote! { #field })
.unwrap_or_else(|| quote! { stringify!(#field) });
let get = get.map_or_else(
|| quote! { |obj: &mut Self| obj.#field.clone() },
|func| quote! { #func },
);
let set = set.map_or_else(
|| quote! { |obj: &mut Self, val| obj.#field = val },
|func| quote! { #func },
);
let name = name.map_or_else(|| quote! { stringify!(#field) }, |expr| quote! { #expr });

if readonly {
quote! {
builder.with_get(#name, #get);
}
quote! { builder.with_get(#name, #get) }
} else {
quote! {
builder.with_get_set(#name, #get, #set);
}
quote! { builder.with_get_set(#name, #get, #set) }
}
}
24 changes: 20 additions & 4 deletions codegen/tests/test_derive.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
use rhai::{CustomType, TypeBuilder, INT};
use rhai::{CustomType, TypeBuilder, FLOAT, INT};

// Sanity check to make sure everything compiles

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

#[derive(Clone, CustomType)]
pub struct Foo {
#[rhai_custom_type_skip]
_dummy: INT,
#[rhai_custom_type_get(get_bar)]
bar: INT,
pub bar: INT,
#[rhai_custom_type_name("boo")]
#[rhai_custom_type_readonly]
baz: String,
pub(crate) baz: String,
#[rhai_custom_type_set(Self::set_qux)]
qux: Vec<INT>,
pub qux: Vec<INT>,
}

impl Foo {
Expand All @@ -23,3 +36,6 @@ impl Foo {
fn get_bar(_this: &mut Foo) -> INT {
42
}

#[test]
fn test() {}
1 change: 1 addition & 0 deletions src/packages/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ pub mod decimal_functions {
#[rhai_fn(return_raw)]
pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
// Raising to a very large power can take exponential time, so limit it to 1 million.
// TODO: Remove this limit when `rust-decimal` is updated with the fix.
if std::convert::TryInto::<u32>::try_into(y.round()).map_or(true, |v| v > 1000000) {
return Err(make_err(format!("Exponential overflow: {x} ** {y}")));
}
Expand Down
13 changes: 6 additions & 7 deletions tests/build_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,16 @@ fn test_build_type() {

#[test]
fn test_build_type_macro() {
#[derive(Debug, Clone, Eq, PartialEq)] // <- necessary for any custom type
#[derive(CustomType)] // <- auto-implement 'CustomType'
#[derive(Debug, Clone, Eq, PartialEq, CustomType)]
struct Foo {
#[rhai_custom_type_skip]
dummy: i64, // <- skip this field
#[rhai_custom_type_readonly] // <- only auto-implement getters
dummy: i64,
#[rhai_custom_type_readonly]
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'
baz: bool,
#[rhai_custom_type_set(Self::set_hello)]
hello: String,
}

impl Foo {
Expand Down
Loading