rlua_builders_derive/
lib.rs

1//! The rlua-builders-derive crate provides derivers for the [`rlua-builders`] crate.
2//!
3//! This crate provides a deriver for [`LuaBuilder`] from [`rlua-builders`], as well
4//! as for [`UserData`] from [`rlua`].
5//! This is not usually imported directly. See [`rlua-builders`] for more
6//! documentation.
7//!
8//! [`UserData`]: https://docs.rs/rlua/*/rlua/trait.UserData.html
9//! [`LuaBuilder`]: https://docs.rs/rlua-builders/*/rlua_builders/trait.LuaBuilder.html
10//! [`rlua-builders`]: https://crates.io/crates/rlua-builders
11//! [`rlua`]: https://crates.io/crates/rlua
12
13use proc_macro::TokenStream;
14use proc_macro2::TokenStream as TokenStream2;
15use quote::quote;
16use syn::{
17    parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, FieldsNamed,
18    FieldsUnnamed, Ident, Index, Meta, MetaNameValue,
19};
20
21const DEFAULT: &'static str = "default";
22
23fn create_type_and_unwrap<'s>(
24    fields: impl Iterator<Item = &'s Field>,
25) -> (Vec<TokenStream2>, Vec<TokenStream2>) {
26    fields
27        .map(|f| {
28            let ty = &f.ty;
29            const SYNTAX_ERR: &'static str = "Invalid syntax for default";
30            let def = f.attrs.iter().find(|a| a.path.is_ident(DEFAULT)).map(|a| {
31                match a.parse_meta().expect(SYNTAX_ERR) {
32                    Meta::NameValue(MetaNameValue { lit, .. }) => quote!( .unwrap_or(#lit) ),
33                    _ => panic!(SYNTAX_ERR),
34                }
35            });
36            match def {
37                Some(ts) => (quote! { ::std::option::Option<#ty> }, ts),
38                None => (quote! (#ty), quote!()),
39            }
40        })
41        .unzip()
42}
43
44fn builder_for_unnamed(name: TokenStream2, fields: &FieldsUnnamed) -> TokenStream2 {
45    let i = (0..fields.unnamed.len()).map(Index::from);
46    let (types, unwraps): (Vec<_>, Vec<_>) = create_type_and_unwrap(fields.unnamed.iter());
47    quote! {
48        ctx.create_function(|_, args: (#(#types,)*)| {
49            Ok(#name (#(args.#i #unwraps ,)*))
50        })
51    }
52}
53
54fn builder_for_named(name: TokenStream2, fields: &FieldsNamed) -> TokenStream2 {
55    let names = fields.named.iter().map(|x| &x.ident);
56    let (types, unwraps): (Vec<_>, Vec<_>) = create_type_and_unwrap(fields.named.iter());
57
58    quote! {
59        ctx.create_function(|_, data: rlua::Table<'s>| {
60            Ok(#name {
61                #( #names: data.get::<_, #types>(stringify!(#names))? #unwraps , )*
62            })
63        })
64    }
65}
66
67fn builder_for_fields(name: TokenStream2, fields: &Fields) -> TokenStream2 {
68    match fields {
69        Fields::Unit => quote! { Ok(#name) },
70        Fields::Unnamed(unnamed) => builder_for_unnamed(name, unnamed),
71        Fields::Named(named) => builder_for_named(name, named),
72    }
73}
74
75fn function_struct_builder(name: Ident, builder: TokenStream2) -> TokenStream2 {
76    quote! {
77        impl<'s> ::rlua_builders::LuaBuilder<'s, rlua::Function<'s>> for #name {
78            fn builder(ctx: rlua::Context<'s>) -> rlua::Result<rlua::Function<'s>> {
79                #builder
80            }
81        }
82    }
83}
84
85fn self_struct_builder(name: Ident, builder: TokenStream2) -> TokenStream2 {
86    quote! {
87        impl<'s> ::rlua_builders::LuaBuilder<'s, Self> for #name {
88            fn builder(ctx: rlua::Context<'s>) -> rlua::Result<Self> {
89                #builder
90            }
91        }
92    }
93}
94
95fn struct_builder(name: Ident, ds: DataStruct) -> TokenStream2 {
96    let code = builder_for_fields(quote! {Self}, &ds.fields);
97
98    match ds.fields {
99        Fields::Unit => self_struct_builder(name, code),
100        Fields::Unnamed(..) | Fields::Named(..) => function_struct_builder(name, code),
101    }
102}
103
104fn enum_builder(name: Ident, de: DataEnum) -> TokenStream2 {
105    let (names, builders): (Vec<_>, Vec<_>) = de
106        .variants
107        .iter()
108        .map(|v| {
109            let var_name = &v.ident;
110            (
111                var_name,
112                builder_for_fields(quote! {#name::#var_name}, &v.fields),
113            )
114        })
115        .unzip();
116
117    quote! {
118        impl<'s> ::rlua_builders::LuaBuilder<'s, rlua::Table<'s>> for #name {
119            fn builder(ctx: rlua::Context<'s>) -> rlua::Result<rlua::Table<'s>> {
120                let t = ctx.create_table()?;
121                #( t.set(stringify!(#names), #builders?)?; )*
122                Ok(t)
123            }
124        }
125    }
126}
127
128/// Automatically derive the [`LuaBuilder`] trait for structs and enums
129///
130/// See the [`rlua-builders`] documentation for specifics of how this works.
131///
132/// [`LuaBuilder`]: https://docs.rs/rlua-builders/*/rlua_builders/trait.LuaBuilder.html
133/// [`rlua-builders`]: https://crates.io/crates/rlua-builders
134#[proc_macro_derive(LuaBuilder, attributes(default))]
135pub fn derive_struct_builder(input: TokenStream) -> TokenStream {
136    let input = parse_macro_input!(input as DeriveInput);
137    let name = input.ident;
138
139    let code = match input.data {
140        Data::Struct(ds) => struct_builder(name, ds),
141        Data::Enum(de) => enum_builder(name, de),
142        _ => panic!("Must annotate struct or enum"),
143    };
144
145    TokenStream::from(code)
146}
147
148/// Automatically derive [`UserData`] trait
149///
150/// This derive macro derives an **empty** [`UserData`] for the struct or enum.
151/// That means it won't have any custom methods. This is separate from the
152/// [`LuaBuilder`] deriver in case you want to derive [`UserData`] with a custom
153/// implementation.
154///
155/// [`UserData`]: https://docs.rs/rlua/*/rlua/trait.UserData.html
156/// [`LuaBuilder`]: https://docs.rs/rlua-builders/*/rlua_builders/trait.LuaBuilder.html
157#[proc_macro_derive(UserData)]
158pub fn derive_user_data(input: TokenStream) -> TokenStream {
159    let input = parse_macro_input!(input as DeriveInput);
160    let name = input.ident;
161
162    let expanded = quote! {
163        impl ::rlua::UserData for #name {}
164    };
165
166    TokenStream::from(expanded)
167}