1use proc_macro::TokenStream;
20use quote::quote;
21use syn::{
22 parse_macro_input, Data, DeriveInput, Fields, FnArg, ImplItem, ItemImpl, LitStr, ReturnType,
23 Type,
24};
25
26struct FieldCfg {
31 ident: syn::Ident,
32 ty: Type,
33 lua_name: String,
34 skip: bool,
35 readonly: bool,
36 force_field: bool,
37}
38
39fn parse_field_cfg(field: &syn::Field) -> syn::Result<FieldCfg> {
40 let ident = field
41 .ident
42 .clone()
43 .ok_or_else(|| syn::Error::new_spanned(field, "LuaUserData requires named fields"))?;
44 let mut cfg = FieldCfg {
45 lua_name: ident.to_string(),
46 ident,
47 ty: field.ty.clone(),
48 skip: false,
49 readonly: false,
50 force_field: false,
51 };
52 for attr in &field.attrs {
53 if !attr.path().is_ident("lua") {
54 continue;
55 }
56 attr.parse_nested_meta(|meta| {
57 if meta.path.is_ident("skip") {
58 cfg.skip = true;
59 Ok(())
60 } else if meta.path.is_ident("readonly") {
61 cfg.readonly = true;
62 Ok(())
63 } else if meta.path.is_ident("field") {
64 cfg.force_field = true;
65 Ok(())
66 } else if meta.path.is_ident("name") {
67 let lit: LitStr = meta.value()?.parse()?;
68 cfg.lua_name = lit.value();
69 Ok(())
70 } else {
71 Err(meta.error(
72 "unknown #[lua(...)] attribute; expected skip, readonly, field, or name",
73 ))
74 }
75 })?;
76 }
77 Ok(cfg)
78}
79
80struct StructCfg {
82 register_methods: bool,
83 impl_display: bool,
84 impl_partial_eq: bool,
85 impl_partial_ord: bool,
86}
87
88fn parse_struct_cfg(input: &DeriveInput) -> syn::Result<StructCfg> {
89 let mut cfg = StructCfg {
90 register_methods: false,
91 impl_display: false,
92 impl_partial_eq: false,
93 impl_partial_ord: false,
94 };
95 for attr in &input.attrs {
96 if attr.path().is_ident("lua") {
97 attr.parse_nested_meta(|meta| {
98 if meta.path.is_ident("methods") {
99 cfg.register_methods = true;
100 Ok(())
101 } else {
102 Err(meta.error("unknown #[lua(...)] attribute on struct; expected methods"))
103 }
104 })?;
105 } else if attr.path().is_ident("lua_impl") {
106 attr.parse_nested_meta(|meta| {
107 if meta.path.is_ident("Display") {
108 cfg.impl_display = true;
109 Ok(())
110 } else if meta.path.is_ident("PartialEq") {
111 cfg.impl_partial_eq = true;
112 Ok(())
113 } else if meta.path.is_ident("PartialOrd") {
114 cfg.impl_partial_ord = true;
115 Ok(())
116 } else {
117 Err(meta.error(
118 "unknown #[lua_impl(...)] trait; expected Display, PartialEq, or PartialOrd",
119 ))
120 }
121 })?;
122 }
123 }
124 Ok(cfg)
125}
126
127#[proc_macro_derive(LuaUserData, attributes(lua, lua_impl))]
129pub fn derive_lua_user_data(input: TokenStream) -> TokenStream {
130 let input = parse_macro_input!(input as DeriveInput);
131 expand_derive(input).unwrap_or_else(|e| e.to_compile_error().into())
132}
133
134fn expand_derive(input: DeriveInput) -> syn::Result<TokenStream> {
135 let name = &input.ident;
136
137 if !input.generics.params.is_empty() {
138 return Err(syn::Error::new_spanned(
139 &input.generics,
140 "LuaUserData does not yet support generic types",
141 ));
142 }
143
144 let scfg = parse_struct_cfg(&input)?;
145
146 let fields: Vec<&syn::Field> = match &input.data {
147 Data::Struct(s) => match &s.fields {
148 Fields::Named(named) => named.named.iter().collect(),
149 Fields::Unnamed(_) | Fields::Unit => Vec::new(),
154 },
155 _ => {
156 return Err(syn::Error::new_spanned(
157 &input.ident,
158 "LuaUserData currently supports only structs",
159 ))
160 }
161 };
162
163 let mut field_regs = Vec::new();
164 for field in fields {
165 let cfg = parse_field_cfg(field)?;
166 let is_pub = matches!(field.vis, syn::Visibility::Public(_));
173 if cfg.skip || (!is_pub && !cfg.force_field) {
174 continue;
175 }
176 let ident = &cfg.ident;
177 let ty = &cfg.ty;
178 let lua_name = &cfg.lua_name;
179 field_regs.push(quote! {
180 __m.add_field_method_get(#lua_name, |_, __this| {
181 ::core::result::Result::Ok(::core::clone::Clone::clone(&__this.#ident))
182 });
183 });
184 if !cfg.readonly {
185 field_regs.push(quote! {
186 __m.add_field_method_set(#lua_name, |_, __this, __value: #ty| {
187 __this.#ident = __value;
188 ::core::result::Result::Ok(())
189 });
190 });
191 }
192 }
193
194 let methods_call = if scfg.register_methods {
195 quote! { <Self>::__lua_register_methods(__m); }
196 } else {
197 quote! {}
198 };
199
200 let mut meta_regs = Vec::new();
201 if scfg.impl_display {
202 meta_regs.push(quote! {
203 __m.add_meta_method(::lua_rs_runtime::MetaMethod::ToString, |_, __this, ()| {
204 ::core::result::Result::Ok(::std::string::ToString::to_string(__this))
205 });
206 });
207 }
208 if scfg.impl_partial_eq {
209 meta_regs.push(quote! {
210 __m.add_meta_method(
211 ::lua_rs_runtime::MetaMethod::Eq,
212 |_, __this, __other: ::lua_rs_runtime::Value| {
213 if let ::lua_rs_runtime::Value::UserData(__ud) = __other {
214 if let ::core::result::Result::Ok(__o) = __ud.borrow::<#name>() {
215 return ::core::result::Result::Ok(*__this == *__o);
216 }
217 }
218 ::core::result::Result::Ok(false)
219 },
220 );
221 });
222 }
223 if scfg.impl_partial_ord {
224 meta_regs.push(quote! {
225 __m.add_meta_method(
226 ::lua_rs_runtime::MetaMethod::Lt,
227 |_, __this, __other: ::lua_rs_runtime::Value| {
228 if let ::lua_rs_runtime::Value::UserData(__ud) = __other {
229 if let ::core::result::Result::Ok(__o) = __ud.borrow::<#name>() {
230 return ::core::result::Result::Ok(*__this < *__o);
231 }
232 }
233 ::core::result::Result::Ok(false)
234 },
235 );
236 __m.add_meta_method(
237 ::lua_rs_runtime::MetaMethod::Le,
238 |_, __this, __other: ::lua_rs_runtime::Value| {
239 if let ::lua_rs_runtime::Value::UserData(__ud) = __other {
240 if let ::core::result::Result::Ok(__o) = __ud.borrow::<#name>() {
241 return ::core::result::Result::Ok(*__this <= *__o);
242 }
243 }
244 ::core::result::Result::Ok(false)
245 },
246 );
247 });
248 }
249
250 let add_meta_methods = if meta_regs.is_empty() {
251 quote! {}
252 } else {
253 quote! {
254 fn add_meta_methods<__M: ::lua_rs_runtime::UserDataMethods<Self>>(__m: &mut __M) {
255 #(#meta_regs)*
256 }
257 }
258 };
259
260 let expanded = quote! {
261 impl ::lua_rs_runtime::UserData for #name {
262 fn add_methods<__M: ::lua_rs_runtime::UserDataMethods<Self>>(__m: &mut __M) {
263 #(#field_regs)*
264 #methods_call
265 }
266 #add_meta_methods
267 }
268 };
269
270 Ok(expanded.into())
271}
272
273#[proc_macro_attribute]
279pub fn lua_methods(_attr: TokenStream, item: TokenStream) -> TokenStream {
280 let item = parse_macro_input!(item as ItemImpl);
281 expand_methods(item).unwrap_or_else(|e| e.to_compile_error().into())
282}
283
284fn expand_methods(item: ItemImpl) -> syn::Result<TokenStream> {
285 let self_ty = &item.self_ty;
286 let mut regs = Vec::new();
287
288 for impl_item in &item.items {
289 let ImplItem::Fn(method) = impl_item else {
290 continue;
291 };
292 if !matches!(method.vis, syn::Visibility::Public(_)) {
293 continue;
294 }
295
296 let receiver = method.sig.inputs.first().and_then(|arg| match arg {
298 FnArg::Receiver(r) => Some(r),
299 _ => None,
300 });
301 let Some(receiver) = receiver else {
302 continue;
303 };
304 let is_mut = receiver.mutability.is_some();
305
306 let name = &method.sig.ident;
307 let lua_name = name.to_string();
308
309 let mut arg_names = Vec::new();
311 let mut arg_types = Vec::new();
312 for (i, arg) in method.sig.inputs.iter().enumerate().skip(1) {
313 let FnArg::Typed(pat) = arg else {
314 return Err(syn::Error::new_spanned(
315 arg,
316 "#[lua_methods] does not support a second receiver",
317 ));
318 };
319 let ident = syn::Ident::new(&format!("__a{i}"), proc_macro2::Span::call_site());
320 arg_names.push(ident);
321 arg_types.push((*pat.ty).clone());
322 }
323
324 let arg_binding = match arg_names.len() {
326 0 => quote! { () },
327 1 => {
328 let n = &arg_names[0];
329 let t = &arg_types[0];
330 quote! { #n: #t }
331 }
332 _ => {
333 quote! { ( #(#arg_names),* ): ( #(#arg_types),* ) }
334 }
335 };
336
337 if let ReturnType::Type(_, ty) = &method.sig.output {
343 if let Type::Reference(r) = &**ty {
344 let referent = &*r.elem;
345 let ret_is_mut = r.mutability.is_some();
346 if !ret_is_mut && is_mut {
347 return Err(syn::Error::new_spanned(
348 &method.sig,
349 "#[lua_methods]: a method returning `&T` must take `&self`; \
350 use `-> &mut T` to expose a mutable delegate",
351 ));
352 }
353 let func_binding = if arg_names.is_empty() {
354 quote! { __ud: ::lua_rs_runtime::AnyUserData }
355 } else {
356 quote! {
357 ( __ud #(, #arg_names)* ):
358 ( ::lua_rs_runtime::AnyUserData #(, #arg_types)* )
359 }
360 };
361 let accessor = quote! { move |__this| <#self_ty>::#name(__this #(, #arg_names)*) };
362 let reg = if ret_is_mut {
363 quote! {
364 __m.add_function(#lua_name, |__lua, #func_binding| {
365 __ud.delegate::<Self, #referent, _>(__lua, #accessor)
366 });
367 }
368 } else {
369 quote! {
370 __m.add_function(#lua_name, |__lua, #func_binding| {
371 __ud.delegate_ref::<Self, #referent, _>(__lua, #accessor)
372 });
373 }
374 };
375 regs.push(reg);
376 continue;
377 }
378 }
379
380 let call = quote! { <#self_ty>::#name(__this #(, #arg_names)*) };
381 let returns_unit = matches!(&method.sig.output, ReturnType::Default)
382 || matches!(&method.sig.output, ReturnType::Type(_, ty) if is_unit_type(ty));
383 let body = if returns_unit {
384 quote! { { #call; ::core::result::Result::Ok(()) } }
385 } else {
386 quote! { ::core::result::Result::Ok(#call) }
387 };
388
389 if is_mut {
390 regs.push(quote! {
391 __m.add_method_mut(#lua_name, |_, __this, #arg_binding| #body);
392 });
393 } else {
394 regs.push(quote! {
395 __m.add_method(#lua_name, |_, __this, #arg_binding| #body);
396 });
397 }
398 }
399
400 let expanded = quote! {
401 #item
402
403 impl #self_ty {
404 #[doc(hidden)]
405 fn __lua_register_methods<__M: ::lua_rs_runtime::UserDataMethods<Self>>(__m: &mut __M) {
406 #(#regs)*
407 }
408 }
409 };
410
411 Ok(expanded.into())
412}
413
414fn is_unit_type(ty: &Type) -> bool {
415 matches!(ty, Type::Tuple(t) if t.elems.is_empty())
416}