Skip to main content

zvariant_derive/
type.rs

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