Skip to main content

zvariant_utils/derive/
value.rs

1use proc_macro2::{Span, TokenStream};
2use quote::{ToTokens, quote};
3use syn::{
4    Attribute, Data, DataEnum, DeriveInput, Error, Fields, Generics, Ident, Lifetime,
5    LifetimeParam, Variant, spanned::Spanned,
6};
7
8use crate::macros;
9
10use super::{Config, attrs::*};
11
12/// Which of the `Value`-family traits `expand_value_derive` should implement.
13pub enum ValueType {
14    Value,
15    OwnedValue,
16}
17
18/// Implements the `Value` or `OwnedValue` conversions, per `value_type`, for structs and enums.
19pub fn expand_value_derive(
20    ast: DeriveInput,
21    value_type: ValueType,
22    config: &Config,
23) -> Result<TokenStream, Error> {
24    let StructAttributes {
25        signature,
26        rename_all,
27        crate_path: crate_attr,
28        ..
29    } = StructAttributes::parse_with_lists(&ast.attrs, config.attr_lists)?;
30    let zv = config.resolve_path(crate_attr.as_deref())?;
31
32    let signature = signature.map(|signature| match signature.as_str() {
33        "dict" => "a{sv}".to_string(),
34        _ => signature,
35    });
36
37    let ctx = Ctx {
38        zv: &zv,
39        attr_lists: config.attr_lists,
40    };
41    match &ast.data {
42        Data::Struct(ds) => match &ds.fields {
43            Fields::Named(_) | Fields::Unnamed(_) => impl_struct(
44                value_type,
45                ast.ident,
46                ast.generics,
47                &ds.fields,
48                signature,
49                rename_all,
50                &ctx,
51            ),
52            Fields::Unit => Err(Error::new(ast.span(), "Unit structures not supported")),
53        },
54        Data::Enum(data) => impl_enum(value_type, ast.ident, ast.generics, ast.attrs, data, &ctx),
55        _ => Err(Error::new(
56            ast.span(),
57            "only structs and enums are supported",
58        )),
59    }
60}
61
62/// Codegen context threaded through the per-field/per-variant attribute parsing helpers.
63#[derive(Clone, Copy)]
64struct Ctx<'a> {
65    zv: &'a TokenStream,
66    attr_lists: &'static [&'static str],
67}
68
69fn impl_struct(
70    value_type: ValueType,
71    name: Ident,
72    generics: Generics,
73    fields: &Fields,
74    signature: Option<String>,
75    rename_all: Option<String>,
76    ctx: &Ctx<'_>,
77) -> Result<TokenStream, Error> {
78    let Ctx { zv, attr_lists } = *ctx;
79    let statc_lifetime = LifetimeParam::new(Lifetime::new("'static", Span::call_site()));
80    let (
81        value_type,
82        value_lifetime,
83        into_value_trait,
84        into_value_method,
85        into_value_error_decl,
86        into_value_ret,
87        into_value_error_transform,
88    ) = match value_type {
89        ValueType::Value => {
90            let mut lifetimes = generics.lifetimes();
91            let value_lifetime = lifetimes
92                .next()
93                .cloned()
94                .unwrap_or_else(|| statc_lifetime.clone());
95            if lifetimes.next().is_some() {
96                return Err(Error::new(
97                    name.span(),
98                    "Type with more than 1 lifetime not supported",
99                ));
100            }
101
102            (
103                quote! { #zv::Value<#value_lifetime> },
104                value_lifetime,
105                quote! { From },
106                quote! { from },
107                quote! {},
108                quote! { Self },
109                quote! {},
110            )
111        }
112        ValueType::OwnedValue => (
113            quote! { #zv::OwnedValue },
114            statc_lifetime,
115            quote! { TryFrom },
116            quote! { try_from },
117            quote! { type Error = #zv::Error; },
118            quote! { #zv::Result<Self> },
119            quote! { .map_err(::std::convert::Into::into) },
120        ),
121    };
122
123    let type_params = generics.type_params().cloned().collect::<Vec<_>>();
124    let (from_value_where_clause, into_value_where_clause) = if !type_params.is_empty() {
125        (
126            Some(quote! {
127                where
128                #(
129                    #type_params: ::std::convert::TryFrom<#zv::Value<#value_lifetime>> + #zv::Type,
130                    <#type_params as ::std::convert::TryFrom<#zv::Value<#value_lifetime>>>::Error: ::std::convert::Into<#zv::Error>
131                ),*
132            }),
133            Some(quote! {
134                where
135                #(
136                    #type_params: ::std::convert::Into<#zv::Value<#value_lifetime>> + #zv::Type
137                ),*
138            }),
139        )
140    } else {
141        (None, None)
142    };
143    let (impl_generics, ty_generics, _) = generics.split_for_impl();
144    match fields {
145        Fields::Named(_) => {
146            let field_names: Vec<_> = fields
147                .iter()
148                .map(|field| field.ident.to_token_stream())
149                .collect();
150            let (from_value_impl, into_value_impl) = match signature {
151                Some(signature) if signature == "a{sv}" => {
152                    // User wants the type to be encoded as a dict.
153                    // FIXME: Not the most efficient implementation.
154                    let (fields_init, entries_init): (TokenStream, TokenStream) = fields
155                        .iter()
156                        .map(|field| {
157                            let FieldAttributes { rename, .. } =
158                                FieldAttributes::parse_with_lists(&field.attrs, attr_lists)?;
159                            let field_name = field.ident.to_token_stream();
160                            let key_name = rename_identifier(
161                                field.ident.as_ref().unwrap().to_string(),
162                                field.span(),
163                                rename,
164                                rename_all.as_deref(),
165                            )
166                            .unwrap_or(field_name.to_string());
167                            let convert = if macros::ty_is_option(&field.ty) {
168                                quote! {
169                                    .map(#zv::Value::downcast)
170                                    .transpose()?
171                                }
172                            } else {
173                                quote! {
174                                    .ok_or_else(|| #zv::Error::IncorrectType)?
175                                    .downcast()?
176                                }
177                            };
178
179                            let fields_init = quote! {
180                                #field_name: fields
181                                    .remove(#key_name)
182                                    #convert,
183                            };
184                            let entries_init = if macros::ty_is_option(&field.ty) {
185                                quote! {
186                                    if let Some(v) = s.#field_name {
187                                        fields.insert(
188                                            #key_name,
189                                            #zv::Value::from(v),
190                                        );
191                                    }
192                                }
193                            } else {
194                                quote! {
195                                    fields.insert(
196                                        #key_name,
197                                        #zv::Value::from(s.#field_name),
198                                    );
199                                }
200                            };
201
202                            Ok((fields_init, entries_init))
203                        })
204                        .collect::<Result<Vec<_>, Error>>()?
205                        .into_iter()
206                        .unzip();
207
208                    (
209                        quote! {
210                            let mut fields = <::std::collections::HashMap::<
211                                ::std::string::String,
212                                #zv::Value,
213                            >>::try_from(value)?;
214
215                            ::std::result::Result::Ok(Self { #fields_init })
216                        },
217                        quote! {
218                            let mut fields = ::std::collections::HashMap::new();
219                            #entries_init
220
221                            <#value_type>::#into_value_method(#zv::Value::from(fields))
222                                #into_value_error_transform
223                        },
224                    )
225                }
226                Some(_) | None => (
227                    quote! {
228                        let mut fields = #zv::Structure::try_from(value)?.into_fields();
229
230                        ::std::result::Result::Ok(Self {
231                            #(
232                                #field_names: fields.remove(0).downcast()?
233                            ),*
234                        })
235                    },
236                    quote! {
237                        <#value_type>::#into_value_method(#zv::StructureBuilder::new()
238                        #(
239                            .add_field(s.#field_names)
240                        )*
241                        .build().unwrap())
242                        #into_value_error_transform
243                    },
244                ),
245            };
246            Ok(quote! {
247                impl #impl_generics ::std::convert::TryFrom<#value_type> for #name #ty_generics
248                    #from_value_where_clause
249                {
250                    type Error = #zv::Error;
251
252                    #[inline]
253                    fn try_from(value: #value_type) -> #zv::Result<Self> {
254                        #from_value_impl
255                    }
256                }
257
258                impl #impl_generics #into_value_trait<#name #ty_generics> for #value_type
259                    #into_value_where_clause
260                {
261                    #into_value_error_decl
262
263                    #[inline]
264                    fn #into_value_method(s: #name #ty_generics) -> #into_value_ret {
265                        #into_value_impl
266                    }
267                }
268            })
269        }
270        Fields::Unnamed(_) if fields.iter().next().is_some() => {
271            // Newtype struct.
272            Ok(quote! {
273                impl #impl_generics ::std::convert::TryFrom<#value_type> for #name #ty_generics
274                    #from_value_where_clause
275                {
276                    type Error = #zv::Error;
277
278                    #[inline]
279                    fn try_from(value: #value_type) -> #zv::Result<Self> {
280                        ::std::convert::TryInto::try_into(value).map(Self)
281                    }
282                }
283
284                impl #impl_generics #into_value_trait<#name #ty_generics> for #value_type
285                    #into_value_where_clause
286                {
287                    #into_value_error_decl
288
289                    #[inline]
290                    fn #into_value_method(s: #name #ty_generics) -> #into_value_ret {
291                        <#value_type>::#into_value_method(s.0) #into_value_error_transform
292                    }
293                }
294            })
295        }
296        Fields::Unnamed(_) => panic!("impl_struct must not be called for tuples"),
297        Fields::Unit => panic!("impl_struct must not be called for unit structures"),
298    }
299}
300
301fn impl_enum(
302    value_type: ValueType,
303    name: Ident,
304    _generics: Generics,
305    attrs: Vec<Attribute>,
306    data: &DataEnum,
307    ctx: &Ctx<'_>,
308) -> Result<TokenStream, Error> {
309    let Ctx { zv, attr_lists } = *ctx;
310    let repr: TokenStream = match attrs.iter().find(|attr| attr.path().is_ident("repr")) {
311        Some(repr_attr) => repr_attr.parse_args()?,
312        None => quote! { u32 },
313    };
314    let enum_attrs = EnumAttributes::parse_with_lists(&attrs, attr_lists)?;
315    let str_enum = enum_attrs
316        .signature
317        .map(|sig| sig == "s")
318        .unwrap_or_default();
319
320    let mut variant_names = vec![];
321    let mut str_values = vec![];
322    for variant in &data.variants {
323        let variant_attrs = VariantAttributes::parse_with_lists(&variant.attrs, attr_lists)?;
324        // Ensure all variants of the enum are unit type
325        match variant.fields {
326            Fields::Unit => {
327                variant_names.push(&variant.ident);
328                if str_enum {
329                    let str_value = enum_name_for_variant(
330                        variant,
331                        variant_attrs.rename,
332                        enum_attrs.rename_all.as_ref().map(AsRef::as_ref),
333                    )?;
334                    str_values.push(str_value);
335                }
336            }
337            _ => return Err(Error::new(variant.span(), "must be a unit variant")),
338        }
339    }
340
341    let into_val = if str_enum {
342        quote! {
343            match e {
344                #(
345                    #name::#variant_names => #str_values,
346                )*
347            }
348        }
349    } else {
350        quote! { e as #repr }
351    };
352
353    let (value_type, into_value) = match value_type {
354        ValueType::Value => (
355            quote! { #zv::Value<'_> },
356            quote! {
357                impl ::std::convert::From<#name> for #zv::Value<'_> {
358                    #[inline]
359                    fn from(e: #name) -> Self {
360                        <#zv::Value as ::std::convert::From<_>>::from(#into_val)
361                    }
362                }
363            },
364        ),
365        ValueType::OwnedValue => (
366            quote! { #zv::OwnedValue },
367            quote! {
368                impl ::std::convert::TryFrom<#name> for #zv::OwnedValue {
369                    type Error = #zv::Error;
370
371                    #[inline]
372                    fn try_from(e: #name) -> #zv::Result<Self> {
373                        <#zv::OwnedValue as ::std::convert::TryFrom<_>>::try_from(
374                            <#zv::Value as ::std::convert::From<_>>::from(#into_val)
375                        )
376                    }
377                }
378            },
379        ),
380    };
381
382    let from_val = if str_enum {
383        quote! {
384            let v: #zv::Str = ::std::convert::TryInto::try_into(value)?;
385
386            ::std::result::Result::Ok(match v.as_str() {
387                #(
388                    #str_values => #name::#variant_names,
389                )*
390                _ => return ::std::result::Result::Err(#zv::Error::IncorrectType),
391            })
392        }
393    } else {
394        quote! {
395            let v: #repr = ::std::convert::TryInto::try_into(value)?;
396
397            ::std::result::Result::Ok(match v {
398                #(
399                    x if x == #name::#variant_names as #repr => #name::#variant_names
400                 ),*,
401                _ => return ::std::result::Result::Err(#zv::Error::IncorrectType),
402            })
403        }
404    };
405
406    Ok(quote! {
407        impl ::std::convert::TryFrom<#value_type> for #name {
408            type Error = #zv::Error;
409
410            #[inline]
411            fn try_from(value: #value_type) -> #zv::Result<Self> {
412                #from_val
413            }
414        }
415
416        #into_value
417    })
418}
419
420fn enum_name_for_variant(
421    v: &Variant,
422    rename_attr: Option<String>,
423    rename_all_attr: Option<&str>,
424) -> Result<String, Error> {
425    let ident = v.ident.to_string();
426
427    rename_identifier(ident, v.span(), rename_attr, rename_all_attr)
428}
429
430#[cfg(test)]
431mod tests {
432    use quote::quote;
433    use syn::{DeriveInput, parse_quote};
434
435    use super::*;
436
437    fn config() -> Config {
438        Config {
439            attr_lists: &["zbus", "zvariant"],
440            default_path: quote! { ::zvariant },
441        }
442    }
443
444    #[test]
445    fn dict_signature_rejects_cross_namespace_duplicate_rename() {
446        let ast: DeriveInput = parse_quote! {
447            #[zvariant(signature = "dict")]
448            struct Foo {
449                #[zvariant(rename = "first")]
450                #[zbus(rename = "second")]
451                field: String,
452            }
453        };
454
455        let err = expand_value_derive(ast, ValueType::Value, &config()).unwrap_err();
456
457        assert!(
458            err.to_string().contains("duplicate"),
459            "unexpected error: {err}",
460        );
461    }
462
463    #[test]
464    fn dict_signature_expands_with_single_rename() {
465        let ast: DeriveInput = parse_quote! {
466            #[zvariant(signature = "dict")]
467            struct Foo {
468                #[zvariant(rename = "first")]
469                field: String,
470            }
471        };
472
473        assert!(expand_value_derive(ast, ValueType::Value, &config()).is_ok());
474    }
475}