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}