mlua_magic_macros/lib.rs
1mod compile;
2mod load;
3
4extern crate proc_macro;
5
6use ::proc_macro::TokenStream;
7
8use ::proc_macro2;
9use ::proc_macro2::Ident;
10
11use ::quote::quote;
12
13// use syn::parse::Parse;
14use ::syn::{Fields, Pat, parse_macro_input};
15use syn::{/*Path,*/ TypePath, };
16
17use crate::compile::parse_compile_args;
18
19/// Implements a helper function `_to_mlua_fields` for a Rust struct,
20/// enabling automatic registration of named fields with `mlua::UserData`.
21///
22/// When applied to a struct, this macro generates an implementation
23/// of a private helper function that is later invoked by the
24/// `mlua_magic_macros::compile!` macro. This ensures the struct’s fields
25/// are visible in Lua as userdata fields.
26///
27/// # Behavior
28/// * Public and private named fields are exported as readable fields in Lua.
29/// * Getter methods are automatically generated via `add_field_method_get`.
30/// * Fields must implement `Clone` for successful conversion to Lua values.
31///
32/// # Limitations
33/// * Only structs with **named fields** are currently supported.
34/// * Setter support is not yet implemented.
35///
36/// # Usage
37/// Apply the macro directly to the struct definition:
38///
39/// ```ignore
40/// #[derive(Clone, Copy, Default)]
41/// #[mlua_magic_macros::structure]
42/// struct Player {
43/// name: String,
44/// hp: i32,
45/// }
46///
47/// // Later, compile userdata:
48/// mlua_magic_macros::compile!(Player, fields, methods);
49/// ```
50///
51/// After registration through `mlua::UserData`,
52/// Lua scripts may access the fields:
53///
54/// ```lua
55/// print(player.name)
56/// print(player.hp)
57/// ```
58///
59/// This macro is designed to work together with:
60/// * `#[mlua_magic_macros::implementation]` — for methods
61/// * `#[mlua_magic_macros::enumeration]` — for enum variants
62/// * `mlua_magic_macros::compile!` — final hookup to `mlua::UserData`
63///
64/// This simplifies mlua integration by reducing boilerplate and
65/// ensuring a consistent interface between Rust types and Lua scripts.
66#[proc_macro_attribute]
67pub fn structure(_attr: TokenStream, item: TokenStream) -> TokenStream {
68 let ast: syn::ItemStruct = parse_macro_input!(item as syn::ItemStruct);
69 let name: &Ident = &ast.ident;
70
71 // TODO: Add type validation?
72 let mut user_data_fields: Vec<proc_macro2::TokenStream> = Vec::new();
73
74 for field in &ast.fields {
75 let field_name: &Ident = field.ident.as_ref().expect("Field must have a name");
76 let field_name_str: String = field_name.to_string();
77 let field_ty: &syn::Type = &field.ty;
78
79 user_data_fields.push(quote! {
80 fields.add_field_method_get(#field_name_str, |_, this| {
81 return Ok(this.#field_name.clone());
82 });
83 });
84
85 user_data_fields.push(quote! {
86 fields.add_field_method_set(#field_name_str, |_, this, val: #field_ty| {
87 this.#field_name = val;
88 return Ok(());
89 });
90 });
91 }
92
93 // Create the helper function `_to_mlua_fields`
94 let helper_fn: proc_macro2::TokenStream = quote! {
95 impl #name {
96 #[doc(hidden)]
97 pub fn _to_mlua_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) -> () {
98 #(#user_data_fields)*
99 }
100 }
101 };
102
103 let original_tokens: proc_macro2::TokenStream = quote! {
104 #ast
105 };
106 let helper_tokens: proc_macro2::TokenStream = quote! {
107 #helper_fn
108 };
109
110 let mut output: proc_macro2::TokenStream = original_tokens;
111 output.extend(helper_tokens);
112
113
114 return output.into();
115}
116
117/// Implements a helper function `_to_mlua_variants` for a Rust `enum'.
118///
119/// This function registers all *unit variants* (e.g., `MyEnum::VariantA`)
120/// as static properties on the Lua UserData. This allows accessing
121/// them in Lua as `MyEnum.VariantA`.
122///
123/// Variants with data (e.g., `MyEnum::VariantB(i32)`) are *not*
124/// automatically registered. You should expose these by creating
125/// a static constructor function in an `#[mlua_magic::implementation]`
126/// block.
127///
128/// # Example:
129/// ```ignore
130/// #[derive(Clone, Copy)] // Required for UserData methods
131/// #[mlua_magic::enumeration]
132/// enum MyEnum {
133/// VariantA,
134/// VariantB(i32),
135/// }
136///
137/// #[mlua_magic::implementation]
138/// impl MyEnum {
139/// // This will expose `MyEnum.new_variant_b(123)` in Lua
140/// pub fn new_variant_b(val: i32) -> Self {
141/// Self::VariantB(val)
142/// }
143/// }
144/// ```
145///
146/// This is intended to be used with `impl mlua::UserData`.
147#[proc_macro_attribute]
148pub fn enumeration(__attr: TokenStream, item: TokenStream) -> TokenStream {
149 let ast: syn::ItemEnum = parse_macro_input!(item as syn::ItemEnum);
150 let name: &Ident = &ast.ident;
151 // let name_str: String = name.to_string();
152
153 // Build registrations for unit variants (register as static constructors)
154 let mut variant_registrations: Vec<proc_macro2::TokenStream> = Vec::new();
155 for variant in &ast.variants {
156 if let Fields::Unit = &variant.fields {
157 let variant_name: &Ident = &variant.ident;
158 let variant_name_str: String = variant_name.to_string();
159
160 // use add_function to register an associated/static function that returns the enum
161 variant_registrations.push(quote! {
162 // e.g. methods.add_function("Idle", |_, (): ()| Ok(PlayerStatus::Idle));
163 methods.add_function(#variant_name_str, |_, (): ()| {
164 Ok(#name::#variant_name)
165 });
166 });
167 }
168 }
169
170 // Create helper fn _to_mlua_variants, plus FromLua and IntoLua impls for lossless userdata round-trip.
171 // FromLua requires Clone so we can return owned values from borrowed userdata.
172 let helper_fn: proc_macro2::TokenStream = quote! {
173 impl #name {
174 #[doc(hidden)]
175 pub fn _to_mlua_variants<M: mlua::UserDataMethods<Self>>(methods: &mut M) -> () {
176 #(#variant_registrations)*;
177 }
178 }
179 };
180
181 let original_tokens: proc_macro2::TokenStream = quote! {
182 #ast
183 };
184 let helper_tokens: proc_macro2::TokenStream = quote! {
185 #helper_fn
186 };
187
188 let mut output: proc_macro2::TokenStream = original_tokens;
189 output.extend(helper_tokens);
190
191 return output.into();
192}
193
194
195/// Implements a helper function `_to_mlua_methods` for a Rust `impl` block,
196/// enabling automatic registration of its methods with `mlua::UserData`.
197///
198/// When applied to an `impl` block, this macro scans for functions and
199/// generates an implementation of a private helper function. This function
200/// is later invoked by the `mlua_magic_macros::compile!` macro.
201///
202/// # Behavior
203/// * **Static Functions** (e.g., `fn new() -> Self`) are registered as static
204/// functions on the userdata, accessible in Lua as `MyType.new()`.
205/// * **Immutable Methods** (e.g., `fn my_method(&self)`) are registered as
206/// immutable methods, accessible in Lua as `my_instance:my_method()`.
207/// * **Mutable Methods** (e.g., `fn my_mut_method(&mut self)`) are registered as
208/// mutable methods, accessible in Lua as `my_instance:my_mut_method()`.
209///
210/// # Usage
211/// Apply the macro directly to the `impl` block for the type:
212///
213/// ```ignore
214/// #[mlua_magic_macros::structure]
215/// struct Player { hp: i32 }
216///
217/// #[mlua_magic_macros::implementation]
218/// impl Player {
219/// pub fn new() -> Self { Self { hp: 100 } }
220/// pub fn is_alive(&self) -> bool { self.hp > 0 }
221/// pub fn take_damage(&mut self, amount: i32) { self.hp -= amount; }
222/// }
223///
224/// // Later, compile userdata:
225/// mlua_magic_macros::compile!(Player, fields, methods);
226/// ```
227///
228/// Lua scripts may then call these methods:
229///
230/// ```lua
231/// local p = Player.new()
232/// p:take_damage(30)
233/// print(p:is_alive())
234/// ```
235///
236/// This macro is designed to work together with:
237/// * `#[mlua_magic_macros::structure]` — for fields
238/// * `#[mlua_magic_macros::enumeration]` — for enum variants
239/// * `mlua_magic_macros::compile!` — final hookup to `mlua::UserData`
240#[proc_macro_attribute]
241pub fn implementation(_attr: TokenStream, item: TokenStream) -> TokenStream {
242 let ast: syn::ItemImpl = parse_macro_input!(item as syn::ItemImpl);
243 let name: &syn::Type = &ast.self_ty;
244
245 let mut method_registrations: Vec<proc_macro2::TokenStream> = Vec::new();
246
247 for item in &ast.items {
248 if let syn::ImplItem::Fn(fn_item) = item {
249 let fn_name: &Ident = &fn_item.sig.ident;
250 let fn_name_str: String = fn_name.to_string();
251
252 // Extract argument names and types, skipping the `self` receiver
253 let (arg_names, arg_tys): (Vec<_>, Vec<_>) = fn_item
254 .sig
255 .inputs
256 .iter()
257 .filter_map(|arg| {
258 if let syn::FnArg::Typed(pat_type) = arg {
259 if let Pat::Ident(pat_ident) = &*pat_type.pat {
260 Some((&pat_ident.ident, &pat_type.ty))
261 } else {
262 None
263 }
264 } else {
265 None
266 }
267 })
268 .unzip();
269
270 // Check for `&self`, `&mut self`, or static
271 if let Some(receiver) = &fn_item.sig.receiver() {
272 if receiver.mutability.is_some() {
273 // Here, `this`` is is `&mut self`
274 method_registrations.push(quote! {
275 methods.add_method_mut(#fn_name_str, |_, this, (#(#arg_names,)*): (#(#arg_tys,)*)| {
276 return Ok(this.#fn_name(#(#arg_names,)*));
277 });
278 });
279 } else {
280 // Here, `this`` is `&self`
281 method_registrations.push(quote! {
282 methods.add_method(#fn_name_str, |_, this, (#(#arg_names,)*): (#(#arg_tys,)*)| {
283 return Ok(this.#fn_name(#(#arg_names,)*));
284 });
285 });
286 };
287 } else {
288 // This is a static function (like `new`)
289 method_registrations.push(quote! {
290 methods.add_function(#fn_name_str, |_, (#(#arg_names,)*): (#(#arg_tys,)*)| {
291 return Ok(#name::#fn_name(#(#arg_names,)*));
292 });
293 });
294 };
295 };
296 }
297
298 // Create the helper function `_to_mlua_methods`
299 let helper_fn: proc_macro2::TokenStream = quote! {
300 impl #name {
301 #[doc(hidden)]
302 pub fn _to_mlua_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) -> () {
303 #(#method_registrations)*
304 }
305 }
306 };
307
308 let original_tokens: proc_macro2::TokenStream = quote! {
309 #ast
310 };
311 let helper_tokens: proc_macro2::TokenStream = quote! {
312 #helper_fn
313 };
314
315 let mut output: proc_macro2::TokenStream = original_tokens;
316 output.extend(helper_tokens);
317
318 return output.into();
319}
320
321// # Bottom of file
322// TODO: Move out of lib.rs when possible
323
324/// Generates the final `impl mlua::UserData` block for a type.
325///
326/// This macro calls the helper functions generated by `#[structure]`,
327/// `#[implementation]`, and `#[enumeration]`.
328///
329/// You must specify which helpers to include.
330///
331/// # Example (for a struct):
332/// ```ignore
333/// #[mlua_magic::structure]
334/// struct Player { health: i32 }
335///
336/// #[mlua_magic::implementation]
337/// impl Player {
338/// // ... methods ...
339/// }
340///
341/// // Generates `impl mlua::UserData for Player`
342/// mlua_magic::compile!(Player, fields, methods);
343/// ```
344///
345/// # Example (for an enum):
346/// ```ignore
347/// #[mlua_magic::enumeration]
348/// enum Status { Idle, Busy }
349///
350/// #[mlua_magic::implementation]
351/// impl Status {
352/// // ... methods ...
353/// }
354///
355/// // Generates `impl mlua::UserData for Status` and `impl mlua::IntoLua for Status`
356/// mlua_magic::compile!(Status, variants, methods);
357/// ```
358#[proc_macro]
359pub fn compile(input: TokenStream) -> TokenStream {
360 let compile_args: compile::CompileArgs = parse_compile_args(input).unwrap();
361 let type_path: TypePath = compile_args.type_path.clone().expect("Type is required.");
362
363 // Conditionally generate the call to the helper function
364 let fields_call: proc_macro2::TokenStream = if compile_args.fields.unwrap_or(false) {
365 quote! {
366 Self::_to_mlua_fields(fields);
367 }
368 } else {
369 quote! { /* Do nothing */ }
370 };
371
372 let methods_call: proc_macro2::TokenStream = if compile_args.methods.unwrap_or(false) {
373 quote! {
374 Self::_to_mlua_methods(methods);
375 }
376 } else {
377 quote! { /* Do nothing */ }
378 };
379
380 let variants_call: proc_macro2::TokenStream = if compile_args.variants.unwrap_or(false) {
381 quote! {
382 Self::_to_mlua_variants(methods);
383 }
384 } else {
385 quote! { /* Do nothing */ }
386 };
387
388 // Assemble the final `impl mlua::UserData` block
389 let output: proc_macro2::TokenStream = quote! {
390 impl mlua::UserData for #type_path {
391 fn add_fields<'lua, F: mlua::UserDataFields<Self>>(fields: &mut F) -> () {
392 #fields_call
393 }
394
395 fn add_methods<'lua, M: mlua::UserDataMethods<Self>>(methods: &mut M) -> () {
396 #methods_call
397 #variants_call
398 }
399 }
400 impl mlua::FromLua for #type_path {
401 fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
402 let output: mlua::Result<Self> = match value {
403 mlua::Value::UserData(user_data) => {
404 return match user_data.borrow::<Self>() {
405 Ok(b) => Ok((*b).clone()),
406 Err(_) => Err(mlua::Error::FromLuaConversionError {
407 from: "UserData",
408 to: stringify!(#type_path).to_string(),
409 message: Some("userdata is not this exact Rust type".into()),
410 })
411 };
412 },
413 _ => Err(mlua::Error::FromLuaConversionError {
414 from: value.type_name(),
415 to: stringify!(#type_path).to_string(),
416 message: Some("expected userdata created by mlua_magic_macros".into()),
417 }),
418 };
419
420 return output;
421 }
422 }
423 /*impl #type_path {
424 #[doc(hidden)]
425 pub fn _to_mlua_skeleton(lua: &mlua::Lua) -> Result<mlua::AnyUserData, mlua::Error> { // Spooky scary skeletons
426 let skeleton: mlua::AnyUserData = lua.create_any_userdata(Self::default())?;
427
428 // TODO: Implement this
429
430 return Ok(skeleton);
431 }
432 }*/
433 /*impl mlua::IntoLua for #type_path {
434 fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
435 let user_data: mlua::AnyUserData = lua.create_any_userdata(self)?;
436 let value: mlua::Value = user_data.to_value();
437
438 return Ok(value);
439 }
440 }*/
441 };
442
443 return output.into();
444}
445
446/// Registers one or more Rust types implementing `mlua::UserData` as global
447/// variables in a `mlua::Lua` instance.
448///
449/// This macro is the final step to make your Rust types accessible from Lua.
450/// It creates a "proxy" for each type (which acts as a constructor table)
451/// and assigns it to a global variable in Lua with the same name as the Rust type.
452///
453/// # Usage
454/// The macro takes the `lua` instance as the first argument, followed by a
455/// comma-separated list of types to register.
456///
457/// ```ignore
458/// // (Assuming Player and PlayerStatus implement mlua::UserData)
459/// use mlua::prelude::*;
460///
461/// fn main() -> LuaResult<()> {
462/// let lua = Lua::new();
463///
464/// // This call...
465/// mlua_magic_macros::load!(lua, Player, PlayerStatus);
466///
467/// // ...is equivalent to this Lua code:
468/// // Player = (proxy for Player UserData)
469/// // PlayerStatus = (proxy for PlayerStatus UserData)
470///
471/// lua.load(r#"
472/// print(Player) -- "userdata: Player"
473/// print(PlayerStatus) -- "userdata: PlayerStatus"
474///
475/// local p = Player.new("Hero")
476/// p.status = PlayerStatus.Walking()
477/// "#).exec()?;
478///
479/// Ok(())
480/// }
481/// ```
482///
483/// # Prerequisites
484/// All types passed to `load!` must implement `mlua::UserData`. This is
485/// typically handled by using the `mlua_magic_macros::compile!` macro.
486#[proc_macro]
487pub fn load(input: TokenStream) -> TokenStream {
488 let load::LoadInput {
489 lua_expr,
490 type_paths,
491 } = parse_macro_input!(input as load::LoadInput);
492
493 let output: proc_macro2::TokenStream = quote! {{
494 let lua: &mlua::Lua = &#lua_expr;
495 let globals: mlua::Table = lua.globals();
496
497 #(
498 // Register type globally under its Rust name
499 globals.set(stringify!(#type_paths), lua.create_proxy::<#type_paths>()?)?;
500 )*
501 }};
502
503 return output.into();
504}