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
12pub enum ValueType {
14 Value,
15 OwnedValue,
16}
17
18pub 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#[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 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 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 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}