zvariant_derive/
type.rs

1use proc_macro2::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{
4    spanned::Spanned, Attribute, Data, DataEnum, DeriveInput, Error, Fields, Generics, Ident,
5};
6
7use crate::utils::*;
8
9pub fn expand_derive(ast: DeriveInput) -> Result<TokenStream, Error> {
10    let StructAttributes { signature, .. } = StructAttributes::parse(&ast.attrs)?;
11
12    let zv = zvariant_path();
13    if let Some(signature) = signature {
14        let signature = match signature.as_str() {
15            "dict" => "a{sv}".to_string(),
16            _ => signature,
17        };
18
19        // Signature already provided, easy then!
20        let name = ast.ident;
21        let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
22        return Ok(quote! {
23            impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
24                #[inline]
25                fn signature() -> #zv::Signature<'static> {
26                    // FIXME: Would be nice if we had a parsed `Signature` in the macro code already so
27                    // it's checked at the build time but currently that's not easily possible w/o
28                    // zvariant_derive requiring zvaraint and we don't want it as it creates a cyclic
29                    // dep. Maybe we can find a way to share the `Signature` type between the two
30                    // crates?
31                    #zv::Signature::from_static_str(#signature).unwrap()
32                }
33            }
34        });
35    }
36
37    match ast.data {
38        Data::Struct(ds) => match ds.fields {
39            Fields::Named(_) if ds.fields.is_empty() => {
40                impl_empty_struct(ast.ident, ast.generics, &zv)
41            }
42            Fields::Named(_) | Fields::Unnamed(_) => {
43                impl_struct(ast.ident, ast.generics, ds.fields, &zv)
44            }
45            Fields::Unit => impl_unit_struct(ast.ident, ast.generics, &zv),
46        },
47        Data::Enum(data) => impl_enum(ast.ident, ast.generics, ast.attrs, data, &zv),
48        _ => Err(Error::new(
49            ast.span(),
50            "only structs and enums supported at the moment",
51        )),
52    }
53    .map(|implementation| {
54        quote! {
55            #[allow(deprecated)]
56            #implementation
57        }
58    })
59}
60
61fn impl_struct(
62    name: Ident,
63    generics: Generics,
64    fields: Fields,
65    zv: &TokenStream,
66) -> Result<TokenStream, Error> {
67    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
68    let signature = signature_for_struct(&fields, zv, false);
69
70    Ok(quote! {
71        impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
72            #[inline]
73            fn signature() -> #zv::Signature<'static> {
74                #signature
75            }
76        }
77    })
78}
79
80fn signature_for_struct(
81    fields: &Fields,
82    zv: &TokenStream,
83    insert_enum_variant: bool,
84) -> TokenStream {
85    let field_types = fields.iter().map(|field| field.ty.to_token_stream());
86    let new_type = match fields {
87        Fields::Named(_) => false,
88        Fields::Unnamed(_) if field_types.len() == 1 => true,
89        Fields::Unnamed(_) => false,
90        Fields::Unit => panic!("signature_for_struct must not be called for unit fields"),
91    };
92    let inner_impl = if new_type {
93        quote! {
94            #(
95                <#field_types as #zv::Type>::signature()
96             )*
97        }
98    } else {
99        quote! {
100            let mut s = <::std::string::String as ::std::convert::From<_>>::from("(");
101            #(
102                s.push_str(<#field_types as #zv::Type>::signature().as_str());
103            )*
104            s.push_str(")");
105
106            #zv::Signature::from_string_unchecked(s)
107        }
108    };
109
110    if insert_enum_variant {
111        quote! {
112            let inner_signature = {
113                #inner_impl
114            };
115            let mut s = <::std::string::String as ::std::convert::From<_>>::from("(");
116            s.push_str(<u32 as #zv::Type>::signature().as_str());
117            s.push_str(inner_signature.as_str());
118            s.push_str(")");
119
120            #zv::Signature::from_string_unchecked(s)
121        }
122    } else {
123        inner_impl
124    }
125}
126
127fn impl_unit_struct(
128    name: Ident,
129    generics: Generics,
130    zv: &TokenStream,
131) -> Result<TokenStream, Error> {
132    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
133
134    Ok(quote! {
135        impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
136            #[inline]
137            fn signature() -> #zv::Signature<'static> {
138                #zv::Signature::from_static_str_unchecked("")
139            }
140        }
141    })
142}
143
144fn impl_empty_struct(
145    name: Ident,
146    generics: Generics,
147    zv: &TokenStream,
148) -> Result<TokenStream, Error> {
149    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
150
151    Ok(quote! {
152        impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
153            #[inline]
154            fn signature() -> #zv::Signature<'static> {
155                #zv::Signature::from_static_str_unchecked("y")
156            }
157        }
158    })
159}
160
161fn impl_enum(
162    name: Ident,
163    generics: Generics,
164    attrs: Vec<Attribute>,
165    data: DataEnum,
166    zv: &TokenStream,
167) -> Result<TokenStream, Error> {
168    let mut all_signatures: Vec<Result<TokenStream, Error>> = data
169        .variants
170        .iter()
171        .map(|variant| signature_for_variant(variant, &attrs, zv))
172        .collect();
173    let signature = all_signatures.pop().unwrap()?;
174    // Ensure all variants of the enum have the same number and type of fields.
175    for sig in all_signatures {
176        if sig?.to_string() != signature.to_string() {
177            return Err(Error::new(
178                name.span(),
179                "all variants must have the same number and type of fields",
180            ));
181        }
182    }
183
184    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
185
186    Ok(quote! {
187        impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
188            #[inline]
189            fn signature() -> #zv::Signature<'static> {
190                #signature
191            }
192        }
193    })
194}
195
196fn signature_for_variant(
197    variant: &syn::Variant,
198    attrs: &[Attribute],
199    zv: &TokenStream,
200) -> Result<TokenStream, Error> {
201    let repr = attrs.iter().find(|attr| attr.path.is_ident("repr"));
202    match &variant.fields {
203        Fields::Unit => {
204            let repr = match repr {
205                Some(repr_attr) => repr_attr.parse_args()?,
206                None => quote! { u32 },
207            };
208
209            Ok(quote! { <#repr as #zv::Type>::signature() })
210        }
211        Fields::Named(_) => Ok(signature_for_struct(&variant.fields, zv, true)),
212        Fields::Unnamed(_) => Ok(signature_for_struct(&variant.fields, zv, true)),
213    }
214}