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