1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use proc_macro2::{Ident, Span, TokenStream};
use syn::{self, Data, DeriveInput, Generics};

use crate::{
    attr::{Container, CrateName},
    shared::{map_lifetimes, map_type_params, split_for_impl},
};

pub fn derive(input: TokenStream) -> TokenStream {
    let derive_input = syn::parse2(input).expect("Input is checked by rustc");

    let container = Container::from_ast(&derive_input);

    let DeriveInput {
        ident,
        data,
        generics,
        ..
    } = derive_input;

    let tokens = match data {
        Data::Struct(_) | Data::Enum(_) => gen_impl(&container, ident, generics),
        Data::Union(_) => panic!("Unions are not supported"),
    };

    tokens.into()
}

fn gen_impl(container: &Container, ident: Ident, generics: Generics) -> TokenStream {
    let trait_bounds = &map_type_params(&generics, |ty| {
        quote! { #ty: 'static + ::std::fmt::Debug + _gluon_gc::Trace + Sync + Send }
    });

    let lifetime_bounds = &map_lifetimes(&generics, |lifetime| quote! { #lifetime: 'static });

    let (impl_generics, ty_generics, where_clause) = split_for_impl(&generics, &[], &[]);

    let gluon = match container.crate_name {
        CrateName::Some(ref ident) => quote! {
            use #ident::api as _gluon_api;
            use #ident::gc as _gluon_gc;
            use #ident::Result as _gluon_Result;
        },
        CrateName::GluonVm => quote! {
            use crate::api as _gluon_api;
            use crate::thread as _gluon_gc;
            use crate::Result as _gluon_Result;
        },
        CrateName::None => quote! {
            use gluon::vm::api as _gluon_api;
            use gluon::vm::gc as _gluon_gc;
            use gluon::vm::Result as _gluon_Result;
        },
    };

    let dummy_const = Ident::new(&format!("_IMPL_USERDATA_FOR_{}", ident), Span::call_site());

    let deep_clone = if container.clone {
        quote! {
            fn deep_clone<'gc>(
                &self,
                deep_cloner: &'gc mut _gluon_api::Cloner
            ) -> _gluon_Result<_gluon_gc::GcRef<'gc, Box<dyn _gluon_api::Userdata>>> {
                let data: Box<dyn _gluon_api::Userdata> = Box::new(self.clone());
                deep_cloner.gc().alloc(_gluon_gc::Move(data))
            }
        }
    } else {
        quote! {}
    };

    quote! {
        #[allow(non_upper_case_globals)]
        const #dummy_const: () = {
            #gluon

            #[automatically_derived]
            #[allow(unused_attributes, unused_variables)]
            impl #impl_generics _gluon_api::Userdata for #ident #ty_generics
            #where_clause #(#trait_bounds,)* #(#lifetime_bounds),*
            {
                #deep_clone
            }
        };
    }
}