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 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 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}