tlua_derive/
lib.rs

1use std::io::Write;
2
3use proc_macro::TokenStream as TokenStream1;
4use proc_macro2::{Span, TokenStream};
5use quote::{quote, ToTokens, TokenStreamExt};
6use syn::{parse_macro_input, AttrStyle, DeriveInput, Ident, Lifetime, Type};
7
8#[proc_macro_attribute]
9pub fn test(_attr: TokenStream1, item: TokenStream1) -> TokenStream1 {
10    let fn_item = parse_macro_input!(item as syn::ItemFn);
11    let fn_name = fn_item.sig.ident;
12    let test_name = fn_name.to_string();
13    let test_name_ident = syn::Ident::new(&test_name.to_uppercase(), fn_name.span());
14    let test_body = fn_item.block;
15    let unsafe_token = fn_item.sig.unsafety;
16    quote! {
17        #[::linkme::distributed_slice(crate::test::TLUA_TESTS)]
18        static #test_name_ident: crate::test::TestCase = crate::test::TestCase {
19            name: ::std::concat!(::std::module_path!(), "::", #test_name),
20            f: || #unsafe_token #test_body,
21        };
22    }
23    .into()
24}
25
26fn proc_macro_derive_push_impl(
27    input: proc_macro::TokenStream,
28    is_push_into: bool,
29) -> proc_macro::TokenStream {
30    let input = parse_macro_input!(input as DeriveInput);
31    // TODO(gmoshkin): add an attribute to specify path to tlua module (see serde)
32    // TODO(gmoshkin): add support for custom type param bounds
33    let name = &input.ident;
34    let info = Info::new(&input);
35    let ctx = Context::with_generics(&input.generics).set_is_push_into(is_push_into);
36    let (lifetimes, types, consts) = split_generics(&input.generics);
37    let (_, generics, where_clause) = input.generics.split_for_impl();
38    let type_bounds = where_clause.map(|w| &w.predicates);
39    let as_lua_bounds = info.push_bounds(&ctx);
40    let push_code = info.push();
41    let PushVariant {
42        push_fn,
43        push,
44        push_one,
45    } = ctx.push_variant();
46    let l = ctx.as_lua_type_param;
47
48    let expanded = quote! {
49        #[automatically_derived]
50        impl<#(#lifetimes,)* #(#types,)* #l, #(#consts,)*> tlua::#push<#l> for #name #generics
51        where
52            #l: tlua::AsLua,
53            #as_lua_bounds
54            #type_bounds
55        {
56            type Err = tlua::Void;
57
58            fn #push_fn -> ::std::result::Result<tlua::PushGuard<#l>, (Self::Err, #l)> {
59                Ok(#push_code)
60            }
61        }
62
63        impl<#(#lifetimes,)* #(#types,)* #l, #(#consts,)*> tlua::#push_one<#l> for #name #generics
64        where
65            #l: tlua::AsLua,
66            #as_lua_bounds
67            #type_bounds
68        {
69        }
70    };
71
72    expanded.into()
73}
74
75#[proc_macro_derive(Push)]
76pub fn proc_macro_derive_push(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
77    proc_macro_derive_push_impl(input, false)
78}
79
80#[proc_macro_derive(PushInto)]
81pub fn proc_macro_derive_push_into(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
82    proc_macro_derive_push_impl(input, true)
83}
84
85#[proc_macro_derive(LuaRead)]
86pub fn proc_macro_derive_lua_read(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
87    let input = parse_macro_input!(input as DeriveInput);
88    let name = &input.ident;
89    let info = Info::new(&input);
90    let ctx = Context::with_generics(&input.generics);
91    let (lifetimes, types, consts) = split_generics(&input.generics);
92    let (_, generics, where_clause) = input.generics.split_for_impl();
93    let type_bounds = where_clause.map(|w| &w.predicates);
94    let as_lua_bounds = info.read_bounds(&ctx);
95    let read_at_code = info.read(&ctx);
96    let maybe_n_values_expected = info.n_values(&ctx);
97    let maybe_lua_read = info.read_top(&ctx);
98
99    let l = ctx.as_lua_type_param;
100
101    let expanded = quote! {
102        #[automatically_derived]
103        impl<#(#lifetimes,)* #(#types,)* #l, #(#consts,)*> tlua::LuaRead<#l> for #name #generics
104        where
105            #l: tlua::AsLua,
106            #as_lua_bounds
107            #type_bounds
108        {
109            #maybe_n_values_expected
110
111            #maybe_lua_read
112
113            #[inline(always)]
114            fn lua_read_at_position(__lua: #l, __index: ::std::num::NonZeroI32)
115                -> tlua::ReadResult<Self, #l>
116            {
117                Self::lua_read_at_maybe_zero_position(__lua, __index.into())
118            }
119
120            fn lua_read_at_maybe_zero_position(__lua: #l, __index: i32)
121                -> tlua::ReadResult<Self, #l>
122            {
123                #read_at_code
124            }
125        }
126    };
127
128    expanded.into()
129}
130
131macro_rules! ident {
132    ($str:literal) => {
133        Ident::new($str, Span::call_site())
134    };
135    ($($args:tt)*) => {
136        Ident::new(&format!($($args)*), Span::call_site())
137    };
138}
139
140enum Info<'a> {
141    Struct(FieldsInfo<'a>),
142    Enum(VariantsInfo<'a>),
143}
144
145impl<'a> Info<'a> {
146    fn new(input: &'a DeriveInput) -> Self {
147        match input.data {
148            syn::Data::Struct(ref s) => {
149                if let Some(fields) = FieldsInfo::new(&s.fields) {
150                    Self::Struct(fields)
151                } else {
152                    unimplemented!("standalone unit structs aren't supproted yet")
153                }
154            }
155            syn::Data::Enum(ref e) => Self::Enum(VariantsInfo::new(e)),
156            syn::Data::Union(_) => unimplemented!("unions will never be supported"),
157        }
158    }
159
160    fn push(&self) -> TokenStream {
161        match self {
162            Self::Struct(f) => {
163                let fields = f.pattern();
164                let push_fields = f.push();
165                quote! {
166                    match self {
167                        Self #fields => #push_fields,
168                    }
169                }
170            }
171            Self::Enum(v) => {
172                let push_variants = v.variants.iter().map(VariantInfo::push).collect::<Vec<_>>();
173                quote! {
174                    match self {
175                        #( #push_variants )*
176                    }
177                }
178            }
179        }
180    }
181
182    fn push_bounds(&self, ctx: &Context) -> TokenStream {
183        let l = &ctx.as_lua_type_param;
184        let PushVariant { push, push_one, .. } = ctx.push_variant();
185
186        let field_bounds = |info: &FieldsInfo| match info {
187            FieldsInfo::Named {
188                field_types: ty, ..
189            } => {
190                let ty = ty.iter().filter(|ty| ctx.is_generic(ty));
191                quote! {
192                    #(
193                        #ty: tlua::#push_one<tlua::LuaState>,
194                        tlua::Void: ::std::convert::From<<#ty as tlua::#push<tlua::LuaState>>::Err>,
195                    )*
196                }
197            }
198            FieldsInfo::Unnamed {
199                field_types: ty, ..
200            } if ty.iter().any(|ty| ctx.is_generic(ty)) => {
201                quote! {
202                    (#(#ty),*): tlua::#push<#l>,
203                    tlua::Void: ::std::convert::From<<(#(#ty),*) as tlua::#push<#l>>::Err>,
204                }
205            }
206            FieldsInfo::Unnamed { .. } => {
207                quote! {}
208            }
209        };
210        match self {
211            Self::Struct(f) => field_bounds(f),
212            Self::Enum(v) => {
213                let bound = v.variants.iter().flat_map(|v| &v.info).map(field_bounds);
214                quote! {
215                    #(#bound)*
216                }
217            }
218        }
219    }
220
221    fn read(&self, ctx: &Context) -> TokenStream {
222        match self {
223            Self::Struct(f) => f.read_as(None),
224            Self::Enum(v) => {
225                let mut n_vals = vec![];
226                let mut read_and_maybe_return_variant = vec![];
227                for variant in &v.variants {
228                    n_vals.push(if let Some(ref fields) = variant.info {
229                        fields.n_values(ctx)
230                    } else {
231                        quote! { 1 }
232                    });
233                    read_and_maybe_return_variant.push(variant.read_and_maybe_return());
234                }
235                quote! {
236                    let mut errors: ::std::collections::LinkedList<tlua::WrongType> = Default::default();
237                    #(
238                        let n_vals = #n_vals;
239                        let __lua = #read_and_maybe_return_variant;
240                    )*
241                    let e = tlua::WrongType::info("reading any of the variants")
242                        .expected_type::<Self>()
243                        .actual("something else")
244                        .subtypes(errors);
245                    Err((__lua, e))
246                }
247            }
248        }
249    }
250
251    fn read_bounds(&self, ctx: &Context) -> TokenStream {
252        let l = &ctx.as_lua_type_param;
253        let lt = &ctx.as_lua_lifetime_param;
254
255        let field_bounds = |info: &FieldsInfo| {
256            match info {
257                FieldsInfo::Named {
258                    field_types: ty, ..
259                } => {
260                    // Structs fields are read as values from the lua tables and
261                    // this is how `LuaTable::get` bounds it's return values
262                    let ty = ty.iter().filter(|ty| ctx.is_generic(ty));
263                    quote! {
264                        #( #ty: for<#lt> tlua::LuaRead<tlua::PushGuard<&#lt #l>>, )*
265                    }
266                }
267                FieldsInfo::Unnamed {
268                    field_types: ty, ..
269                } if ty.iter().any(|ty| ctx.is_generic(ty)) => {
270                    // Tuple structs are read as tuples, so we bound they're
271                    // fields as if they were a tuple
272                    quote! {
273                        (#(#ty),*): tlua::LuaRead<#l>,
274                    }
275                }
276                FieldsInfo::Unnamed { .. } => {
277                    // Unit structs (as vairants of enums) are read as strings
278                    // so no need for type bounds
279                    quote! {}
280                }
281            }
282        };
283        match self {
284            Self::Struct(f) => field_bounds(f),
285            Self::Enum(v) => {
286                let bound = v.variants.iter().flat_map(|v| &v.info).map(field_bounds);
287                quote! {
288                    #(#bound)*
289                }
290            }
291        }
292    }
293
294    fn read_top(&self, ctx: &Context) -> TokenStream {
295        match self {
296            Self::Struct(_) => quote! {},
297            Self::Enum(v) => {
298                let mut n_vals = vec![];
299                let mut read_and_maybe_return = vec![];
300                for variant in &v.variants {
301                    n_vals.push(if let Some(ref fields) = variant.info {
302                        fields.n_values(ctx)
303                    } else {
304                        quote! { 1 }
305                    });
306                    read_and_maybe_return.push(variant.read_and_maybe_return());
307                }
308                let l = &ctx.as_lua_type_param;
309                quote! {
310                    fn lua_read(__lua: #l) -> tlua::ReadResult<Self, #l> {
311                        let top = unsafe { tlua::ffi::lua_gettop(__lua.as_lua()) };
312                        let mut errors: ::std::collections::LinkedList<tlua::WrongType> = Default::default();
313                        #(
314                            let n_vals = #n_vals;
315                            let __lua = if top >= n_vals {
316                                let __index = top - n_vals + 1;
317                                #read_and_maybe_return
318                            } else {
319                                __lua
320                            };
321                        )*
322                        let e = tlua::WrongType::info("reading any of the variants")
323                            .expected_type::<Self>()
324                            .actual("something else")
325                            .subtypes(errors);
326                        Err((__lua, e))
327                    }
328                }
329            }
330        }
331    }
332
333    fn n_values(&self, ctx: &Context) -> TokenStream {
334        match self {
335            Self::Struct(fields) => {
336                let n_values = fields.n_values(ctx);
337                quote! {
338                    #[inline(always)]
339                    fn n_values_expected() -> i32 {
340                        #n_values
341                    }
342                }
343            }
344            Self::Enum(_) => {
345                quote! {}
346            }
347        }
348    }
349}
350
351enum FieldsInfo<'a> {
352    Named {
353        n_rec: i32,
354        field_names: Vec<String>,
355        field_idents: Vec<&'a Ident>,
356        field_types: Vec<&'a Type>,
357    },
358    Unnamed {
359        field_idents: Vec<Ident>,
360        field_types: Vec<&'a syn::Type>,
361    },
362}
363
364impl<'a> FieldsInfo<'a> {
365    fn new(fields: &'a syn::Fields) -> Option<Self> {
366        match &fields {
367            syn::Fields::Named(ref fields) => {
368                let n_fields = fields.named.len();
369                let mut field_names = Vec::with_capacity(n_fields);
370                let mut field_idents = Vec::with_capacity(n_fields);
371                let mut field_types = Vec::with_capacity(n_fields);
372                for field in fields.named.iter() {
373                    let ident = field.ident.as_ref().unwrap();
374                    field_names.push(ident.to_string().trim_start_matches("r#").into());
375                    field_idents.push(ident);
376                    field_types.push(&field.ty);
377                }
378
379                Some(Self::Named {
380                    field_names,
381                    field_idents,
382                    field_types,
383                    n_rec: n_fields as _,
384                })
385            }
386            syn::Fields::Unnamed(ref fields) => {
387                let mut field_idents = Vec::with_capacity(fields.unnamed.len());
388                let mut field_types = Vec::with_capacity(fields.unnamed.len());
389                for (field, i) in fields.unnamed.iter().zip(0..) {
390                    field_idents.push(ident!("field_{}", i));
391                    field_types.push(&field.ty);
392                }
393
394                Some(Self::Unnamed {
395                    field_idents,
396                    field_types,
397                })
398            }
399            // TODO(gmoshkin): add attributes for changing string value, case
400            // sensitivity etc. (see serde)
401            syn::Fields::Unit => None,
402        }
403    }
404
405    fn push(&self) -> TokenStream {
406        match self {
407            Self::Named {
408                field_names,
409                field_idents,
410                n_rec,
411                ..
412            } => {
413                quote! {
414                    unsafe {
415                        tlua::ffi::lua_createtable(__lua.as_lua(), 0, #n_rec);
416                        #(
417                            tlua::AsLua::push_one(__lua.as_lua(), #field_idents)
418                                .assert_one_and_forget();
419                            tlua::ffi::lua_setfield(
420                                __lua.as_lua(), -2, ::std::concat!(#field_names, "\0").as_ptr() as _
421                            );
422                        )*
423                        tlua::PushGuard::new(__lua, 1)
424                    }
425                }
426            }
427            Self::Unnamed { field_idents, .. } => match field_idents.len() {
428                0 => unimplemented!("unit structs are not supported yet"),
429                1 => {
430                    let field_ident = &field_idents[0];
431                    quote! {
432                        tlua::AsLua::push(__lua, #field_ident)
433                    }
434                }
435                _ => {
436                    quote! {
437                        tlua::AsLua::push(__lua, ( #( #field_idents, )* ))
438                    }
439                }
440            },
441        }
442    }
443
444    fn read_as(&self, name: Option<TokenStream>) -> TokenStream {
445        let is_variant = name.is_some();
446        let name = name.unwrap_or_else(|| quote! { Self });
447        match self {
448            FieldsInfo::Named {
449                field_idents,
450                field_names,
451                field_types,
452                ..
453            } => {
454                let expected = if is_variant {
455                    let mut msg = vec![];
456                    write!(&mut msg, "struct with fields {{").unwrap();
457                    if let Some((n, t)) = field_names.iter().zip(field_types).next() {
458                        write!(&mut msg, " {}: {}", n, quote! { #t }).unwrap();
459                    }
460                    for (n, t) in field_names.iter().zip(field_types).skip(1) {
461                        write!(&mut msg, ", {}: {}", n, quote! { #t }).unwrap();
462                    }
463                    write!(&mut msg, " }}").unwrap();
464                    let msg = String::from_utf8(msg).unwrap();
465                    quote! { .expected(#msg) }
466                } else {
467                    quote! { .expected_type::<Self>() }
468                };
469                quote! {
470                    let t: tlua::LuaTable<_> = tlua::AsLua::read_at(__lua, __index)
471                        .map_err(|(lua, err)| {
472                            let err = err.when("converting Lua value to struct")
473                                .expected("Lua table");
474                            (lua, err)
475                        })?;
476                    Ok(
477                        #name {
478                            #(
479                                #field_idents: match tlua::Index::try_get(&t, #field_names) {
480                                    Ok(v) => v,
481                                    Err(err) => {
482                                        let l = t.into_inner();
483                                        let mut e = tlua::WrongType::info(
484                                            "converting Lua table to struct"
485                                        ) #expected;
486                                        match err {
487                                            tlua::LuaError::WrongType(subtype) => {
488                                                let actual_msg = ::std::concat!(
489                                                    "wrong field type for key '",
490                                                    #field_names,
491                                                    "'",
492                                                );
493                                                e = e.actual(actual_msg).subtype(subtype);
494                                            }
495                                            other => {
496                                                e = e.actual(format!(
497                                                    "error in meta method: {}", other
498                                                ));
499                                            }
500                                        }
501                                        return Err((l, e))
502                                    },
503                                },
504                            )*
505                        }
506                    )
507                }
508            }
509            FieldsInfo::Unnamed { field_idents, .. } => {
510                quote! {
511                    let (#(#field_idents,)*) = tlua::AsLua::read_at(__lua, __index)?;
512                    Ok(
513                        #name(#(#field_idents,)*)
514                    )
515                }
516            }
517        }
518    }
519
520    fn pattern(&self) -> TokenStream {
521        match self {
522            Self::Named { field_idents, .. } => {
523                quote! {
524                    { #( #field_idents, )* }
525                }
526            }
527            Self::Unnamed { field_idents, .. } => {
528                quote! {
529                    ( #( #field_idents, )* )
530                }
531            }
532        }
533    }
534
535    fn n_values(&self, ctx: &Context) -> TokenStream {
536        match self {
537            Self::Named { .. } => {
538                // Corresponds to a single lua table
539                quote! { 1 }
540            }
541            Self::Unnamed {
542                field_types: ty, ..
543            } if !ty.is_empty() => {
544                let l = &ctx.as_lua_type_param;
545                // Corresponds to multiple values on the stack (same as tuple)
546                quote! {
547                    <(#(#ty),*) as tlua::LuaRead<#l>>::n_values_expected()
548                }
549            }
550            Self::Unnamed { .. } => {
551                // Unit structs aren't supported yet, but when they are, they'll
552                // probably correspond to a single value
553                quote! { 1 }
554            }
555        }
556    }
557}
558
559struct VariantsInfo<'a> {
560    variants: Vec<VariantInfo<'a>>,
561}
562
563struct VariantInfo<'a> {
564    name: &'a Ident,
565    info: Option<FieldsInfo<'a>>,
566}
567
568impl<'a> VariantsInfo<'a> {
569    fn new(data: &'a syn::DataEnum) -> Self {
570        let variants = data
571            .variants
572            .iter()
573            .map(
574                |syn::Variant {
575                     ref ident,
576                     ref fields,
577                     ..
578                 }| VariantInfo {
579                    name: ident,
580                    info: FieldsInfo::new(fields),
581                },
582            )
583            .collect();
584
585        Self { variants }
586    }
587}
588
589impl VariantInfo<'_> {
590    fn push(&self) -> TokenStream {
591        let Self { name, info } = self;
592        if let Some(info) = info {
593            let fields = info.pattern();
594            let push_fields = info.push();
595            quote! {
596                Self::#name #fields => #push_fields,
597            }
598        } else {
599            let value = name.to_string().to_lowercase();
600            quote! {
601                Self::#name => {
602                    tlua::AsLua::push_one(__lua.as_lua(), #value)
603                        .assert_one_and_forget();
604                    unsafe { tlua::PushGuard::new(__lua, 1) }
605                }
606            }
607        }
608    }
609
610    fn read_and_maybe_return(&self) -> TokenStream {
611        let read_variant = self.read();
612        let pattern = self.pattern();
613        let constructor = self.constructor();
614        let (guard, catch_all) = self.optional_match();
615        let name = self.name.to_string();
616        quote! {
617            match #read_variant {
618                ::std::result::Result::Ok(#pattern) #guard
619                    => return ::std::result::Result::Ok(#constructor),
620                #catch_all
621                ::std::result::Result::Err((__lua, e)) => {
622                    let mut e = tlua::WrongType::info("reading enum variant")
623                        .expected(#name)
624                        .subtype(e);
625                    if let Some(i) = ::std::num::NonZeroI32::new(__index) {
626                        e = e.actual_multiple_lua_at(&__lua, i, n_vals)
627                    } else {
628                        e = e.actual("no value")
629                    }
630                    errors.push_back(e);
631                    __lua
632                }
633            }
634        }
635    }
636
637    fn read(&self) -> TokenStream {
638        let Self { name, info } = self;
639        match info {
640            Some(s @ FieldsInfo::Named { .. }) => {
641                let read_struct = s.read_as(Some(quote! { Self::#name }));
642                quote! {
643                    (|| { #read_struct })()
644                }
645            }
646            Some(FieldsInfo::Unnamed { .. }) => {
647                quote! {
648                    tlua::AsLua::read_at(__lua, __index)
649                }
650            }
651            None => {
652                quote! {
653                    tlua::AsLua::read_at::<tlua::StringInLua<_>>(__lua, __index)
654                }
655            }
656        }
657    }
658
659    fn pattern(&self) -> TokenStream {
660        let Self { info, .. } = self;
661        match info {
662            Some(FieldsInfo::Named { .. }) | None => quote! { v },
663            Some(FieldsInfo::Unnamed { field_idents, .. }) => match field_idents.len() {
664                0 => unimplemented!("unit structs aren't supported yet"),
665                1 => quote! { v },
666                _ => quote! { ( #(#field_idents,)* ) },
667            },
668        }
669    }
670
671    fn constructor(&self) -> TokenStream {
672        let Self { name, info } = self;
673        match info {
674            Some(FieldsInfo::Named { .. }) => quote! { v },
675            Some(FieldsInfo::Unnamed { field_idents, .. }) => match field_idents.len() {
676                0 => quote! { Self::#name },
677                1 => quote! { Self::#name(v) },
678                _ => quote! { Self::#name(#(#field_idents,)*) },
679            },
680            None => quote! { Self::#name },
681        }
682    }
683
684    fn optional_match(&self) -> (TokenStream, TokenStream) {
685        let Self { name, info } = self;
686        let value = name.to_string().to_lowercase();
687        if info.is_none() {
688            let expected = format!("case incensitive match with '{value}'");
689            (
690                quote! {
691                    if {
692                        let mut v_count = 0;
693                        v.chars()
694                            .flat_map(char::to_lowercase)
695                            .zip(
696                                #value.chars()
697                                    .map(::std::option::Option::Some)
698                                    .chain(::std::iter::repeat(::std::option::Option::None))
699                            )
700                            .all(|(l, r)| {
701                                v_count += 1;
702                                r.map(|r| l == r).unwrap_or(false)
703                            }) && v_count == #value.len()
704                    }
705                },
706                quote! {
707                    ::std::result::Result::Ok(v) => {
708                        let e = tlua::WrongType::info("reading unit struct")
709                            .expected(#expected)
710                            .actual(format!("'{}'", &*v));
711                        errors.push_back(e);
712                        v.into_inner()
713                    },
714                },
715            )
716        } else {
717            (quote! {}, quote! {})
718        }
719    }
720}
721
722struct Context<'a> {
723    as_lua_type_param: Ident,
724    as_lua_lifetime_param: Lifetime,
725    type_params: Vec<&'a Ident>,
726    is_push_into: bool,
727}
728
729struct PushVariant {
730    push_fn: TokenStream,
731    push: syn::Path,
732    push_one: syn::Path,
733}
734impl<'a> Context<'a> {
735    fn new() -> Self {
736        Self {
737            as_lua_type_param: Ident::new("__AsLuaTypeParam", Span::call_site()),
738            as_lua_lifetime_param: Lifetime::new("'as_lua_life_time_param", Span::call_site()),
739            type_params: Vec::new(),
740            is_push_into: false,
741        }
742    }
743
744    fn with_generics(generics: &'a syn::Generics) -> Self {
745        Self {
746            type_params: generics.type_params().map(|tp| &tp.ident).collect(),
747            ..Self::new()
748        }
749    }
750
751    fn set_is_push_into(self, is_push_into: bool) -> Self {
752        Self {
753            is_push_into,
754            ..self
755        }
756    }
757
758    fn push_variant(&self) -> PushVariant {
759        let l = &self.as_lua_type_param;
760        if self.is_push_into {
761            PushVariant {
762                push_fn: quote! {
763                    push_into_lua(self, __lua: #l)
764                },
765                push: ident!("PushInto").into(),
766                push_one: ident!("PushOneInto").into(),
767            }
768        } else {
769            PushVariant {
770                push_fn: quote! {
771                    push_to_lua(&self, __lua: #l)
772                },
773                push: ident!("Push").into(),
774                push_one: ident!("PushOne").into(),
775            }
776        }
777    }
778
779    fn is_generic(&self, ty: &Type) -> bool {
780        struct GenericTypeVisitor<'a> {
781            is_generic: bool,
782            type_params: &'a [&'a Ident],
783        }
784        impl<'ast> syn::visit::Visit<'ast> for GenericTypeVisitor<'_> {
785            // These cannot actually appear in struct/enum field types,
786            // but who cares
787            fn visit_type_impl_trait(&mut self, _: &'ast syn::TypeImplTrait) {
788                self.is_generic = true;
789            }
790
791            fn visit_type_path(&mut self, tp: &'ast syn::TypePath) {
792                for &typar in self.type_params {
793                    if tp.path.is_ident(typar) {
794                        self.is_generic = true;
795                        return;
796                    }
797                }
798                syn::visit::visit_type_path(self, tp)
799            }
800        }
801
802        let mut visitor = GenericTypeVisitor {
803            is_generic: false,
804            type_params: &self.type_params,
805        };
806        syn::visit::visit_type(&mut visitor, ty);
807        visitor.is_generic
808    }
809}
810
811#[derive(Copy, Clone)]
812struct ImplTypeParam<'a>(&'a syn::TypeParam);
813impl ToTokens for ImplTypeParam<'_> {
814    fn to_tokens(&self, tokens: &mut TokenStream) {
815        // Leave off the type parameter defaults
816        let param = self.0;
817        tokens.append_all(
818            param
819                .attrs
820                .iter()
821                .filter(|attr| matches!(attr.style, AttrStyle::Outer)),
822        );
823        param.ident.to_tokens(tokens);
824        if !param.bounds.is_empty() {
825            if let Some(colon) = &param.colon_token {
826                colon.to_tokens(tokens);
827            }
828            param.bounds.to_tokens(tokens);
829        }
830    }
831}
832
833#[derive(Copy, Clone)]
834struct ImplConstParam<'a>(&'a syn::ConstParam);
835impl ToTokens for ImplConstParam<'_> {
836    fn to_tokens(&self, tokens: &mut TokenStream) {
837        // Leave off the type parameter defaults
838        let param = self.0;
839        tokens.append_all(
840            param
841                .attrs
842                .iter()
843                .filter(|attr| matches!(attr.style, AttrStyle::Outer)),
844        );
845        param.const_token.to_tokens(tokens);
846        param.ident.to_tokens(tokens);
847        param.colon_token.to_tokens(tokens);
848        param.ty.to_tokens(tokens);
849    }
850}
851
852fn split_generics(
853    generics: &syn::Generics,
854) -> (
855    Vec<&syn::LifetimeDef>,
856    Vec<ImplTypeParam>,
857    Vec<ImplConstParam>,
858) {
859    let mut res = (vec![], vec![], vec![]);
860    for param in &generics.params {
861        match param {
862            syn::GenericParam::Lifetime(l) => res.0.push(l),
863            syn::GenericParam::Type(t) => res.1.push(ImplTypeParam(t)),
864            syn::GenericParam::Const(c) => res.2.push(ImplConstParam(c)),
865        }
866    }
867    res
868}