type_id_derive_impl/
lib.rs

1use quote2::{
2    proc_macro2::{Delimiter, Group},
3    quote, IntoTokens, Quote, Token,
4};
5use syn::{
6    __private::{Span, TokenStream2 as TokenStream},
7    spanned::Spanned,
8    *,
9};
10
11pub fn expand(crate_path: impl IntoTokens, input: &DeriveInput, output: &mut TokenStream) {
12    let DeriveInput {
13        attrs,
14        ident,
15        generics,
16        data,
17        ..
18    } = input;
19
20    let doc = get_comments_from(attrs);
21    let fmt_str = format!("{{}}::{ident}");
22
23    if let Some(param) = generics.type_params().next() {
24        return output.extend(
25            Error::new(param.span(), "Support for generic type isn't complete yet.")
26                .to_compile_error(),
27        );
28    }
29
30    let mut body = TokenStream::new();
31    let kind = match data {
32        Data::Struct(data) => match &data.fields {
33            Fields::Named(fields) => {
34                to_object(&mut body, fields);
35                "Struct"
36            }
37            Fields::Unnamed(fields) => {
38                to_tuple(&mut body, fields);
39                "Tuple"
40            }
41            Fields::Unit => panic!("`{ident}` struct needs at most one field"),
42        },
43        Data::Enum(data) => {
44            let is_unit = data
45                .variants
46                .iter()
47                .all(|v| v.discriminant.is_some() || matches!(v.fields, Fields::Unit));
48
49            let variants = data
50                .variants
51                .iter()
52                .map(|v| (get_comments_from(&v.attrs), v.ident.to_string(), v));
53
54            if is_unit {
55                let mut value: isize = -1;
56                for (doc, name, v) in variants {
57                    value = match &v.discriminant {
58                        Some((_, expr)) => parse_int(expr),
59                        None => value + 1,
60                    };
61                    quote!(body, {
62                        __crate::UnitField::new(#doc, #name, #value),
63                    });
64                }
65                "Unit"
66            } else {
67                for (doc, name, v) in variants {
68                    let kind = quote(|o| match &v.fields {
69                        Fields::Named(fields) => {
70                            let body = quote(|o| to_object(o, fields));
71                            quote!(o, { Struct(::std::vec![#body]) });
72                        }
73                        Fields::Unnamed(fields) => {
74                            let body = quote(|o| to_tuple(o, fields));
75                            quote!(o, { Tuple(::std::vec![#body]) });
76                        }
77                        Fields::Unit => {
78                            quote!(o, { Unit });
79                        }
80                    });
81                    quote!(body, {
82                        __crate::EnumField::new(#doc, #name, __crate::EnumKind::#kind),
83                    });
84                }
85                "Enum"
86            }
87        }
88        Data::Union(_) => panic!("`Message` implementation for `union` is not yet stabilized"),
89    };
90
91    let kind = Ident::new(kind, Span::call_site());
92    let body = Token(Group::new(Delimiter::Bracket, body));
93    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
94
95    quote!(output, {
96        const _: () = {
97            use #crate_path as __crate;
98            impl #impl_generics __crate::TypeId for #ident #ty_generics #where_clause {
99                fn ty(__c: &mut __crate::CostomTypes) -> __crate::Ty {
100                    __c.register(
101                        ::std::format!(#fmt_str, ::std::module_path!()),
102                        |__c| __crate::CustomTypeKind::#kind(__crate::CustomType::new(#doc, ::std::vec!#body))
103                    )
104                }
105            }
106        };
107    });
108}
109
110fn to_tuple(body: &mut TokenStream, fields: &FieldsUnnamed) {
111    for Field { attrs, ty, .. } in &fields.unnamed {
112        let doc: String = get_comments_from(attrs);
113        quote!(body, {
114            __crate::TupleField::new(#doc, <#ty as __crate::TypeId>::ty(__c)),
115        });
116    }
117}
118
119fn to_object(body: &mut TokenStream, fields: &FieldsNamed) {
120    for Field {
121        attrs, ident, ty, ..
122    } in &fields.named
123    {
124        let doc = get_comments_from(attrs);
125        let ident = ident.as_ref().map(|v| v.to_string());
126        quote!(body, {
127            __crate::StructField::new(#doc, #ident, <#ty as __crate::TypeId>::ty(__c)),
128        });
129    }
130}
131
132fn parse_int(expr: &Expr) -> isize {
133    match expr {
134        Expr::Lit(expr_lit) => match &expr_lit.lit {
135            Lit::Int(int) => int.base10_parse().unwrap(),
136            _ => panic!("Expect integer"),
137        },
138        _ => panic!("Not a number"),
139    }
140}
141
142fn get_comments_from(attrs: &Vec<Attribute>) -> String {
143    let mut string = String::new();
144    for attr in attrs {
145        if let Meta::NameValue(MetaNameValue { path, value, .. }) = &attr.meta {
146            if path.is_ident("doc") {
147                if let Expr::Lit(expr) = value {
148                    if let Lit::Str(data) = &expr.lit {
149                        string += &data.value();
150                        string += "\n"
151                    }
152                }
153            }
154        }
155    }
156    string
157}