zvariant_derive/
type.rs

1use std::str::FromStr;
2
3use proc_macro2::TokenStream;
4use quote::{quote, ToTokens};
5use syn::{
6    spanned::Spanned, Attribute, Data, DataEnum, DeriveInput, Error, Fields, Generics, Ident,
7};
8use zvariant_utils::signature::Signature;
9
10use crate::utils::*;
11
12pub fn expand_derive(ast: DeriveInput) -> Result<TokenStream, Error> {
13    let StructAttributes { signature, .. } = StructAttributes::parse(&ast.attrs)?;
14
15    let zv = zvariant_path();
16    if let Some(signature_str) = signature {
17        // Signature already provided, easy then!
18
19        let signature = match signature_str.as_str() {
20            "dict" => Signature::dict(Signature::Str, Signature::Variant),
21            s => Signature::from_str(s).map_err(|e| Error::new(ast.span(), e))?,
22        };
23        let signature_tokens = signature_to_tokens(&signature, &zv);
24
25        let name = ast.ident;
26        let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
27        return Ok(quote! {
28            impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
29                const SIGNATURE: &'static #zv::Signature = &#signature_tokens;
30            }
31        });
32    }
33
34    match ast.data {
35        Data::Struct(ds) => match ds.fields {
36            Fields::Named(_) if ds.fields.is_empty() => {
37                impl_empty_struct(ast.ident, ast.generics, &zv)
38            }
39            Fields::Named(_) | Fields::Unnamed(_) => {
40                impl_struct(ast.ident, ast.generics, ds.fields, &zv)
41            }
42            Fields::Unit => impl_unit_struct(ast.ident, ast.generics, &zv),
43        },
44        Data::Enum(data) => impl_enum(ast.ident, ast.generics, ast.attrs, data, &zv),
45        _ => Err(Error::new(
46            ast.span(),
47            "only structs and enums supported at the moment",
48        )),
49    }
50    .map(|implementation| {
51        quote! {
52            #[allow(deprecated)]
53            #implementation
54        }
55    })
56}
57
58fn impl_struct(
59    name: Ident,
60    generics: Generics,
61    fields: Fields,
62    zv: &TokenStream,
63) -> Result<TokenStream, Error> {
64    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
65    let signature = signature_for_struct(&fields, zv, false);
66
67    Ok(quote! {
68        impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
69            const SIGNATURE: &'static #zv::Signature = #signature;
70        }
71    })
72}
73
74fn signature_for_struct(
75    fields: &Fields,
76    zv: &TokenStream,
77    insert_enum_variant: bool,
78) -> TokenStream {
79    let field_types = fields.iter().map(|field| field.ty.to_token_stream());
80    let new_type = match fields {
81        Fields::Named(_) => false,
82        Fields::Unnamed(_) if field_types.len() == 1 => true,
83        Fields::Unnamed(_) => false,
84        Fields::Unit => panic!("signature_for_struct must not be called for unit fields"),
85    };
86    let field_types_clone = field_types.clone();
87    let signature = if new_type {
88        quote! {#(
89            <#field_types_clone as #zv::Type>::SIGNATURE
90        )*}
91    } else {
92        quote! {
93            &#zv::Signature::Structure(#zv::signature::Fields::Static {
94                fields: &[#(
95                    <#field_types_clone as #zv::Type>::SIGNATURE
96                ),*],
97            })
98        }
99    };
100
101    if insert_enum_variant {
102        quote! {
103            &#zv::Signature::Structure(#zv::signature::Fields::Static {
104                fields: &[
105                    <u32 as #zv::Type>::SIGNATURE,
106                    #signature
107                ],
108            })
109        }
110    } else {
111        signature
112    }
113}
114
115fn impl_unit_struct(
116    name: Ident,
117    generics: Generics,
118    zv: &TokenStream,
119) -> Result<TokenStream, Error> {
120    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
121
122    Ok(quote! {
123        impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
124            const SIGNATURE: &'static #zv::Signature = &#zv::Signature::Unit;
125        }
126    })
127}
128
129fn impl_empty_struct(
130    name: Ident,
131    generics: Generics,
132    zv: &TokenStream,
133) -> Result<TokenStream, Error> {
134    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
135
136    Ok(quote! {
137        impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
138            const SIGNATURE: &'static #zv::Signature = &#zv::Signature::U8;
139        }
140    })
141}
142
143fn impl_enum(
144    name: Ident,
145    generics: Generics,
146    attrs: Vec<Attribute>,
147    data: DataEnum,
148    zv: &TokenStream,
149) -> Result<TokenStream, Error> {
150    let mut all_signatures: Vec<Result<TokenStream, Error>> = data
151        .variants
152        .iter()
153        .map(|variant| signature_for_variant(variant, &attrs, zv))
154        .collect();
155    let signature = all_signatures.pop().unwrap()?;
156    // Ensure all variants of the enum have the same number and type of fields.
157    for sig in all_signatures {
158        if sig?.to_string() != signature.to_string() {
159            return Err(Error::new(
160                name.span(),
161                "all variants must have the same number and type of fields",
162            ));
163        }
164    }
165
166    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
167
168    Ok(quote! {
169        impl #impl_generics #zv::Type for #name #ty_generics #where_clause {
170            const SIGNATURE: &'static #zv::Signature = #signature;
171        }
172    })
173}
174
175fn signature_for_variant(
176    variant: &syn::Variant,
177    attrs: &[Attribute],
178    zv: &TokenStream,
179) -> Result<TokenStream, Error> {
180    let repr = attrs.iter().find(|attr| attr.path().is_ident("repr"));
181    match &variant.fields {
182        Fields::Unit => {
183            let repr = match repr {
184                Some(repr_attr) => repr_attr.parse_args()?,
185                None => quote! { u32 },
186            };
187
188            Ok(quote! { <#repr as #zv::Type>::SIGNATURE })
189        }
190        Fields::Named(_) => Ok(signature_for_struct(&variant.fields, zv, true)),
191        Fields::Unnamed(_) => Ok(signature_for_struct(&variant.fields, zv, true)),
192    }
193}
194
195fn signature_to_tokens(signature: &Signature, zv: &TokenStream) -> TokenStream {
196    match signature {
197        Signature::Unit => quote! { #zv::Signature::Unit },
198        Signature::Bool => quote! { #zv::Signature::Bool },
199        Signature::U8 => quote! { #zv::Signature::U8 },
200        Signature::I16 => quote! { #zv::Signature::I16 },
201        Signature::U16 => quote! { #zv::Signature::U16 },
202        Signature::I32 => quote! { #zv::Signature::I32 },
203        Signature::U32 => quote! { #zv::Signature::U32 },
204        Signature::I64 => quote! { #zv::Signature::I64 },
205        Signature::U64 => quote! { #zv::Signature::U64 },
206        Signature::F64 => quote! { #zv::Signature::F64 },
207        Signature::Str => quote! { #zv::Signature::Str },
208        Signature::Signature => quote! { #zv::Signature::Signature },
209        Signature::ObjectPath => quote! { #zv::Signature::ObjectPath },
210        Signature::Variant => quote! { #zv::Signature::Variant },
211        #[cfg(unix)]
212        Signature::Fd => quote! { #zv::Signature::Fd },
213        Signature::Array(child) => {
214            let signature = signature_to_tokens(child.signature(), zv);
215            quote! {
216                #zv::Signature::Array(#zv::signature::Child::Static {
217                    child: &#signature,
218                })
219            }
220        }
221        Signature::Dict { key, value } => {
222            let key_sig = signature_to_tokens(key.signature(), zv);
223            let value_sig = signature_to_tokens(value.signature(), zv);
224            quote! {
225                #zv::Signature::Dict {
226                    key: #zv::signature::Child::Static {
227                        child: &#key_sig,
228                    },
229                    value: #zv::signature::Child::Static {
230                        child: &#value_sig,
231                    },
232                }
233            }
234        }
235        Signature::Structure(fields) => {
236            let fields = fields.iter().map(|f| signature_to_tokens(f, zv));
237            quote! {
238                #zv::Signature::Structure(#zv::signature::Fields::Static {
239                    fields: &[#(&#fields),*],
240                })
241            }
242        }
243        #[cfg(feature = "gvariant")]
244        Signature::Maybe(child) => {
245            let signature = signature_to_tokens(child.signature(), zv);
246            quote! {
247                #zv::Signature::Maybe(#zv::signature::Child::Static {
248                    child: &#signature,
249                })
250            }
251        }
252    }
253}