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