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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use proc_macro::TokenStream;
use quote::quote;
use rust2go_common::raw_file::TraitRepr;
use syn::{parse_macro_input, DeriveInput, Ident};

#[proc_macro_derive(R2G)]
pub fn r2g_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    // Skip derive when the type has generics.
    if !input.generics.params.is_empty() {
        return TokenStream::default();
    }
    // Skip derive when the type is not struct.
    let data = match input.data {
        syn::Data::Struct(d) => d,
        _ => return TokenStream::default(),
    };
    let type_name = input.ident;
    let type_name_str = type_name.to_string();

    let ref_type_name = Ident::new(&format!("{type_name_str}Ref"), type_name.span());
    let mut ref_fields = Vec::with_capacity(data.fields.len());
    for field in data.fields.iter() {
        let name = field.ident.as_ref().unwrap();
        let ty = &field.ty;
        let syn::Type::Path(path) = ty else {
            return TokenStream::default();
        };
        let Some(first_seg) = path.path.segments.first() else {
            return TokenStream::default();
        };
        match first_seg.ident.to_string().as_str() {
            "Vec" => {
                ref_fields.push(quote! {#name: ::rust2go::ListRef});
            }
            "String" => {
                ref_fields.push(quote! {#name: ::rust2go::StringRef});
            }
            "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize"
            | "f32" | "f64" | "bool" | "char" => {
                ref_fields.push(quote! {#name: #ty});
            }
            _ => {
                let ref_type = Ident::new(&format!("{name}Ref"), first_seg.ident.span());
                ref_fields.push(quote! {#name: #ref_type});
            }
        }
    }

    let mut owned_names = Vec::with_capacity(data.fields.len());
    let mut owned_types = Vec::with_capacity(data.fields.len());
    for field in data.fields.iter() {
        owned_names.push(field.ident.clone().unwrap());
        owned_types.push(field.ty.clone());
    }

    let expanded = quote! {
        #[repr(C)]
        pub struct #ref_type_name {
            #(#ref_fields),*
        }

        impl ::rust2go::ToRef for #type_name {
            const MEM_TYPE: ::rust2go::MemType = ::rust2go::max_mem_type!(#(#owned_types),*);
            type Ref = #ref_type_name;

            fn to_size<const INCLUDE_SELF: bool>(&self, acc: &mut usize) {
                match Self::MEM_TYPE {
                    ::rust2go::MemType::Primitive => (),
                    ::rust2go::MemType::SimpleWrapper => {
                        if INCLUDE_SELF {
                            *acc += std::mem::size_of::<Self::Ref>();
                        }
                    }
                    ::rust2go::MemType::Complex => {
                        if INCLUDE_SELF {
                            *acc += std::mem::size_of::<Self::Ref>();
                        }
                        #(self.#owned_names.to_size::<true>(acc);)*
                    }
                }
            }

            fn to_ref(&self, buffer: &mut ::rust2go::Writer) -> Self::Ref {
                #ref_type_name {
                    #(#owned_names: ::rust2go::ToRef::to_ref(&self.#owned_names, buffer),)*
                }
            }
        }

        impl ::rust2go::FromRef for #type_name {
            type Ref = #ref_type_name;

            fn from_ref(ref_: &Self::Ref) -> Self {
                Self {
                    #(#owned_names: ::rust2go::FromRef::from_ref(&ref_.#owned_names),)*
                }
            }
        }
    };
    TokenStream::from(expanded)
}

#[proc_macro_attribute]
pub fn r2g(attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut out = item.clone();
    let binding_path = if attr.is_empty() {
        None
    } else {
        match syn::parse::<syn::Path>(attr) {
            Ok(path) => Some(path),
            Err(e) => {
                out.extend(TokenStream::from(e.to_compile_error()));
                return out;
            }
        }
    };
    out.extend(
        syn::parse::<syn::ItemTrait>(item)
            .and_then(|trat| r2g_trait(binding_path, trat))
            .unwrap_or_else(|e| TokenStream::from(e.to_compile_error())),
    );
    out
}

fn r2g_trait(binding_path: Option<syn::Path>, trat: syn::ItemTrait) -> syn::Result<TokenStream> {
    let trat = TraitRepr::try_from(&trat)?;
    Ok(trat.generate_rs(binding_path.as_ref())?.into())
}