zbus_macros/
proxy.rs

1use crate::utils::{pat_ident, typed_arg, zbus_path, PropertyEmitsChangedSignal};
2use proc_macro2::{Literal, Span, TokenStream};
3use quote::{format_ident, quote, quote_spanned, ToTokens};
4use syn::{
5    fold::Fold, parse_quote, parse_str, punctuated::Punctuated, spanned::Spanned, Error, FnArg,
6    Ident, ItemTrait, Meta, Path, ReturnType, Token, TraitItemFn, Visibility,
7};
8use zvariant_utils::{case, def_attrs};
9
10def_attrs! {
11    crate zbus;
12
13    // Keep this in sync with interface's proxy method attributes.
14    pub TraitAttributes("trait") {
15        interface str,
16        name str,
17        assume_defaults bool,
18        default_path str,
19        default_service str,
20        async_name str,
21        blocking_name str,
22        gen_async bool,
23        gen_blocking bool
24    };
25
26    // Keep this in sync with interface's proxy method attributes.
27    pub MethodAttributes("method") {
28        name str,
29        property {
30            pub PropertyAttributes("property") {
31                emits_changed_signal str
32            }
33        },
34        signal none,
35        object str,
36        async_object str,
37        blocking_object str,
38        no_reply none,
39        no_autostart none,
40        allow_interactive_auth none
41    };
42}
43
44struct AsyncOpts {
45    blocking: bool,
46    usage: TokenStream,
47    wait: TokenStream,
48}
49
50impl AsyncOpts {
51    fn new(blocking: bool) -> Self {
52        let (usage, wait) = if blocking {
53            (quote! {}, quote! {})
54        } else {
55            (quote! { async }, quote! { .await })
56        };
57        Self {
58            blocking,
59            usage,
60            wait,
61        }
62    }
63}
64
65pub fn expand(args: Punctuated<Meta, Token![,]>, input: ItemTrait) -> Result<TokenStream, Error> {
66    let attrs = TraitAttributes::parse_nested_metas(args)?;
67
68    let iface_name = match (attrs.interface, attrs.name) {
69        (Some(name), None) | (None, Some(name)) => Ok(Some(name)),
70        (None, None) => Ok(None),
71        (Some(_), Some(_)) => Err(syn::Error::new(
72            input.span(),
73            "both `interface` and `name` attributes shouldn't be specified at the same time",
74        )),
75    }?;
76    let gen_async = attrs.gen_async.unwrap_or(true);
77    #[cfg(feature = "blocking-api")]
78    let gen_blocking = attrs.gen_blocking.unwrap_or(true);
79    #[cfg(not(feature = "blocking-api"))]
80    let gen_blocking = false;
81
82    // Some sanity checks
83    assert!(
84        gen_blocking || gen_async,
85        "Can't disable both asynchronous and blocking proxy. 😸",
86    );
87    assert!(
88        gen_blocking || attrs.blocking_name.is_none(),
89        "Can't set blocking proxy's name if you disabled it. 😸",
90    );
91    assert!(
92        gen_async || attrs.async_name.is_none(),
93        "Can't set asynchronous proxy's name if you disabled it. 😸",
94    );
95
96    let blocking_proxy = if gen_blocking {
97        let proxy_name = attrs.blocking_name.unwrap_or_else(|| {
98            if gen_async {
99                format!("{}ProxyBlocking", input.ident)
100            } else {
101                // When only generating blocking proxy, there is no need for a suffix.
102                format!("{}Proxy", input.ident)
103            }
104        });
105        create_proxy(
106            &input,
107            iface_name.as_deref(),
108            attrs.assume_defaults,
109            attrs.default_path.as_deref(),
110            attrs.default_service.as_deref(),
111            &proxy_name,
112            true,
113            // Signal args structs are shared between the two proxies so always generate it for
114            // async proxy only unless async proxy generation is disabled.
115            !gen_async,
116        )?
117    } else {
118        quote! {}
119    };
120    let async_proxy = if gen_async {
121        let proxy_name = attrs
122            .async_name
123            .unwrap_or_else(|| format!("{}Proxy", input.ident));
124        create_proxy(
125            &input,
126            iface_name.as_deref(),
127            attrs.assume_defaults,
128            attrs.default_path.as_deref(),
129            attrs.default_service.as_deref(),
130            &proxy_name,
131            false,
132            true,
133        )?
134    } else {
135        quote! {}
136    };
137
138    Ok(quote! {
139        #blocking_proxy
140
141        #async_proxy
142    })
143}
144
145#[allow(clippy::too_many_arguments)]
146pub fn create_proxy(
147    input: &ItemTrait,
148    iface_name: Option<&str>,
149    assume_defaults: Option<bool>,
150    default_path: Option<&str>,
151    default_service: Option<&str>,
152    proxy_name: &str,
153    blocking: bool,
154    gen_sig_args: bool,
155) -> Result<TokenStream, Error> {
156    let zbus = zbus_path();
157
158    let other_attrs: Vec<_> = input
159        .attrs
160        .iter()
161        .filter(|a| !a.path().is_ident("zbus"))
162        .collect();
163    let proxy_name = Ident::new(proxy_name, Span::call_site());
164    let ident = input.ident.to_string();
165    let iface_name = iface_name
166        .map(|iface| {
167            // Ensure the interface name is valid.
168            zbus_names::InterfaceName::try_from(iface)
169                .map_err(|e| Error::new(input.span(), format!("{e}")))
170                .map(|i| i.to_string())
171        })
172        .transpose()?
173        .unwrap_or(format!("org.freedesktop.{ident}"));
174    let assume_defaults = assume_defaults.unwrap_or(false);
175    let default_path = default_path
176        .map(|path| {
177            // Ensure the path is valid.
178            zvariant::ObjectPath::try_from(path)
179                .map_err(|e| Error::new(input.span(), format!("{e}")))
180                .map(|p| p.to_string())
181        })
182        .transpose()?;
183    let default_service = default_service
184        .map(|srv| {
185            // Ensure the service is valid.
186            zbus_names::BusName::try_from(srv)
187                .map_err(|e| Error::new(input.span(), format!("{e}")))
188                .map(|n| n.to_string())
189        })
190        .transpose()?;
191    let (default_path, default_service) = if assume_defaults {
192        let path = default_path.or_else(|| Some(format!("/org/freedesktop/{ident}")));
193        let svc = default_service.or_else(|| Some(iface_name.clone()));
194        (path, svc)
195    } else {
196        (default_path, default_service)
197    };
198    let mut methods = TokenStream::new();
199    let mut stream_types = TokenStream::new();
200    let mut has_properties = false;
201    let mut uncached_properties: Vec<String> = vec![];
202
203    let async_opts = AsyncOpts::new(blocking);
204    let visibility = &input.vis;
205
206    for i in input.items.iter() {
207        if let syn::TraitItem::Fn(m) = i {
208            let method_attrs = MethodAttributes::parse(&m.attrs)?;
209            let property = method_attrs.property.as_ref();
210
211            let method_name = m.sig.ident.to_string();
212
213            let is_signal = method_attrs.signal;
214            let is_property = property.is_some();
215            let has_inputs = m.sig.inputs.len() > 1;
216
217            let member_name = method_attrs.name.clone().unwrap_or_else(|| {
218                case::pascal_or_camel_case(
219                    if is_property && has_inputs {
220                        assert!(method_name.starts_with("set_"));
221                        &method_name[4..]
222                    } else {
223                        &method_name
224                    },
225                    true,
226                )
227            });
228
229            let m = if let Some(prop_attrs) = property {
230                has_properties = true;
231
232                let emits_changed_signal = if let Some(s) = &prop_attrs.emits_changed_signal {
233                    PropertyEmitsChangedSignal::parse(s, m.span())?
234                } else {
235                    PropertyEmitsChangedSignal::True
236                };
237
238                if let PropertyEmitsChangedSignal::False = emits_changed_signal {
239                    uncached_properties.push(member_name.clone());
240                }
241
242                gen_proxy_property(
243                    &member_name,
244                    &method_name,
245                    m,
246                    &async_opts,
247                    emits_changed_signal,
248                )
249            } else if is_signal {
250                let (method, types) = gen_proxy_signal(
251                    &proxy_name,
252                    &iface_name,
253                    &member_name,
254                    &method_name,
255                    m,
256                    &async_opts,
257                    visibility,
258                    gen_sig_args,
259                );
260                stream_types.extend(types);
261
262                method
263            } else {
264                gen_proxy_method_call(&member_name, &method_name, m, method_attrs, &async_opts)?
265            };
266            methods.extend(m);
267        }
268    }
269
270    let AsyncOpts { usage, wait, .. } = async_opts;
271    let (proxy_struct, connection, builder, proxy_trait) = if blocking {
272        let connection = quote! { #zbus::blocking::Connection };
273        let proxy = quote! { #zbus::blocking::Proxy };
274        let builder = quote! { #zbus::blocking::proxy::Builder };
275        let proxy_trait = quote! { #zbus::blocking::proxy::ProxyImpl };
276
277        (proxy, connection, builder, proxy_trait)
278    } else {
279        let connection = quote! { #zbus::Connection };
280        let proxy = quote! { #zbus::Proxy };
281        let builder = quote! { #zbus::proxy::Builder };
282        let proxy_trait = quote! { #zbus::proxy::ProxyImpl };
283
284        (proxy, connection, builder, proxy_trait)
285    };
286
287    let proxy_method_new = match (&default_path, &default_service) {
288        (None, None) => {
289            quote! {
290                /// Creates a new proxy with the given service destination and path.
291                pub #usage fn new<D, P>(conn: &#connection, destination: D, path: P) -> #zbus::Result<#proxy_name<'p>>
292                where
293                    D: ::std::convert::TryInto<#zbus::names::BusName<'p>>,
294                    D::Error: ::std::convert::Into<#zbus::Error>,
295                    P: ::std::convert::TryInto<#zbus::zvariant::ObjectPath<'p>>,
296                    P::Error: ::std::convert::Into<#zbus::Error>,
297                {
298                    let obj_path = path.try_into().map_err(::std::convert::Into::into)?;
299                    let obj_destination = destination.try_into().map_err(::std::convert::Into::into)?;
300                    Self::builder(conn)
301                        .path(obj_path)?
302                        .destination(obj_destination)?
303                        .build()#wait
304                }
305            }
306        }
307        (Some(_), None) => {
308            quote! {
309                /// Creates a new proxy with the given destination, and the default path.
310                pub #usage fn new<D>(conn: &#connection, destination: D) -> #zbus::Result<#proxy_name<'p>>
311                where
312                    D: ::std::convert::TryInto<#zbus::names::BusName<'p>>,
313                    D::Error: ::std::convert::Into<#zbus::Error>,
314                {
315                    let obj_dest = destination.try_into().map_err(::std::convert::Into::into)?;
316                    Self::builder(conn)
317                        .destination(obj_dest)?
318                        .build()#wait
319                }
320            }
321        }
322        (None, Some(_)) => {
323            quote! {
324                /// Creates a new proxy with the given path, and the default destination.
325                pub #usage fn new<P>(conn: &#connection, path: P) -> #zbus::Result<#proxy_name<'p>>
326                where
327                    P: ::std::convert::TryInto<#zbus::zvariant::ObjectPath<'p>>,
328                    P::Error: ::std::convert::Into<#zbus::Error>,
329                {
330                    let obj_path = path.try_into().map_err(::std::convert::Into::into)?;
331                    Self::builder(conn)
332                        .path(obj_path)?
333                        .build()#wait
334                }
335            }
336        }
337        (Some(_), Some(_)) => {
338            quote! {
339                /// Creates a new proxy with the default service and path.
340                pub #usage fn new(conn: &#connection) -> #zbus::Result<#proxy_name<'p>> {
341                    Self::builder(conn).build()#wait
342                }
343            }
344        }
345    };
346    let default_path = match default_path {
347        Some(p) => quote! { &Some(#zbus::zvariant::ObjectPath::from_static_str_unchecked(#p)) },
348        None => quote! { &None },
349    };
350    let default_service = match default_service {
351        Some(d) => {
352            if d.starts_with(':') || d == "org.freedesktop.DBus" {
353                quote! {
354                    &Some(#zbus::names::BusName::Unique(
355                        #zbus::names::UniqueName::from_static_str_unchecked(#d),
356                    ))
357                }
358            } else {
359                quote! {
360                    &Some(#zbus::names::BusName::WellKnown(
361                        #zbus::names::WellKnownName::from_static_str_unchecked(#d),
362                    ))
363                }
364            }
365        }
366        None => quote! { &None },
367    };
368
369    Ok(quote! {
370        impl<'a> #zbus::proxy::Defaults for #proxy_name<'a> {
371            const INTERFACE: &'static Option<#zbus::names::InterfaceName<'static>> =
372                &Some(#zbus::names::InterfaceName::from_static_str_unchecked(#iface_name));
373            const DESTINATION: &'static Option<#zbus::names::BusName<'static>> = #default_service;
374            const PATH: &'static Option<#zbus::zvariant::ObjectPath<'static>> = #default_path;
375        }
376
377        #(#other_attrs)*
378        #[derive(Clone, Debug)]
379        #visibility struct #proxy_name<'p>(#proxy_struct<'p>);
380
381        impl<'p> #proxy_name<'p> {
382            #proxy_method_new
383
384            /// Returns a customizable builder for this proxy.
385            pub fn builder(conn: &#connection) -> #builder<'p, Self> {
386                let mut builder = #builder::new(conn) ;
387                if #has_properties {
388                    let uncached = vec![#(#uncached_properties),*];
389                    builder.cache_properties(#zbus::proxy::CacheProperties::default())
390                           .uncached_properties(&uncached)
391                } else {
392                    builder.cache_properties(#zbus::proxy::CacheProperties::No)
393                }
394            }
395
396            /// Consumes `self`, returning the underlying `zbus::Proxy`.
397            pub fn into_inner(self) -> #proxy_struct<'p> {
398                self.0
399            }
400
401            /// The reference to the underlying `zbus::Proxy`.
402            pub fn inner(&self) -> &#proxy_struct<'p> {
403                &self.0
404            }
405
406            /// The mutable reference to the underlying `zbus::Proxy`.
407            pub fn inner_mut(&mut self) -> &mut #proxy_struct<'p> {
408                &mut self.0
409            }
410
411            #methods
412        }
413
414        impl<'p> #proxy_trait<'p> for #proxy_name<'p> {
415            fn builder(conn: &#connection) -> #builder<'p, Self> {
416                Self::builder(conn)
417            }
418
419            fn into_inner(self) -> #proxy_struct<'p> {
420                self.into_inner()
421            }
422
423            fn inner(&self) -> &#proxy_struct<'p> {
424                self.inner()
425            }
426        }
427
428        impl<'p> ::std::convert::From<#zbus::Proxy<'p>> for #proxy_name<'p> {
429            fn from(proxy: #zbus::Proxy<'p>) -> Self {
430                #proxy_name(::std::convert::Into::into(proxy))
431            }
432        }
433
434        impl<'p> ::std::convert::AsRef<#proxy_struct<'p>> for #proxy_name<'p> {
435            fn as_ref(&self) -> &#proxy_struct<'p> {
436                self.inner()
437            }
438        }
439
440        impl<'p> ::std::convert::AsMut<#proxy_struct<'p>> for #proxy_name<'p> {
441            fn as_mut(&mut self) -> &mut #proxy_struct<'p> {
442                self.inner_mut()
443            }
444        }
445
446        impl<'p> #zbus::zvariant::Type for #proxy_name<'p> {
447            const SIGNATURE: &'static #zbus::zvariant::Signature =
448                &#zbus::zvariant::Signature::ObjectPath;
449        }
450
451        impl<'p> #zbus::export::serde::ser::Serialize for #proxy_name<'p> {
452            fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
453            where
454                S: #zbus::export::serde::ser::Serializer,
455            {
456                ::std::string::String::serialize(
457                    &::std::string::ToString::to_string(self.inner().path()),
458                    serializer,
459                )
460            }
461        }
462
463        #stream_types
464    })
465}
466
467fn gen_proxy_method_call(
468    method_name: &str,
469    snake_case_name: &str,
470    m: &TraitItemFn,
471    method_attrs: MethodAttributes,
472    async_opts: &AsyncOpts,
473) -> Result<TokenStream, Error> {
474    let AsyncOpts {
475        usage,
476        wait,
477        blocking,
478    } = async_opts;
479    let zbus = zbus_path();
480    let other_attrs: Vec<_> = m
481        .attrs
482        .iter()
483        .filter(|a| !a.path().is_ident("zbus"))
484        .collect();
485    let args: Vec<_> = m
486        .sig
487        .inputs
488        .iter()
489        .filter_map(typed_arg)
490        .filter_map(pat_ident)
491        .collect();
492
493    let proxy_object = method_attrs.object.as_ref().map(|o| {
494        if *blocking {
495            // FIXME: for some reason Rust doesn't let us move `blocking_proxy_object` so we've to
496            // clone.
497            method_attrs
498                .blocking_object
499                .as_ref()
500                .cloned()
501                .unwrap_or_else(|| format!("{o}ProxyBlocking"))
502        } else {
503            method_attrs
504                .async_object
505                .as_ref()
506                .cloned()
507                .unwrap_or_else(|| format!("{o}Proxy"))
508        }
509    });
510
511    let method_flags = match (
512        method_attrs.no_reply,
513        method_attrs.no_autostart,
514        method_attrs.allow_interactive_auth,
515    ) {
516        (true, false, false) => Some(quote!(::std::convert::Into::into(
517            zbus::proxy::MethodFlags::NoReplyExpected
518        ))),
519        (false, true, false) => Some(quote!(::std::convert::Into::into(
520            zbus::proxy::MethodFlags::NoAutoStart
521        ))),
522        (false, false, true) => Some(quote!(::std::convert::Into::into(
523            zbus::proxy::MethodFlags::AllowInteractiveAuth
524        ))),
525
526        (true, true, false) => Some(quote!(
527            zbus::proxy::MethodFlags::NoReplyExpected | zbus::proxy::MethodFlags::NoAutoStart
528        )),
529        (true, false, true) => Some(quote!(
530            zbus::proxy::MethodFlags::NoReplyExpected
531                | zbus::proxy::MethodFlags::AllowInteractiveAuth
532        )),
533        (false, true, true) => Some(quote!(
534            zbus::proxy::MethodFlags::NoAutoStart | zbus::proxy::MethodFlags::AllowInteractiveAuth
535        )),
536
537        (true, true, true) => Some(quote!(
538            zbus::proxy::MethodFlags::NoReplyExpected
539                | zbus::proxy::MethodFlags::NoAutoStart
540                | zbus::proxy::MethodFlags::AllowInteractiveAuth
541        )),
542        _ => None,
543    };
544
545    let method = Ident::new(snake_case_name, Span::call_site());
546    let inputs = &m.sig.inputs;
547    let mut generics = m.sig.generics.clone();
548    let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
549    for param in generics
550        .params
551        .iter()
552        .filter(|a| matches!(a, syn::GenericParam::Type(_)))
553    {
554        let is_input_type = inputs.iter().any(|arg| {
555            // FIXME: We want to only require `Serialize` from input types and `DeserializeOwned`
556            // from output types but since we don't have type introspection, we employ this
557            // workaround of matching on the string representation of the the types to figure out
558            // which generic types are input types.
559            if let FnArg::Typed(pat) = arg {
560                let pat = pat.ty.to_token_stream().to_string();
561
562                if let Some(ty_name) = pat.strip_prefix('&') {
563                    let ty_name = ty_name.trim_start();
564
565                    ty_name == param.to_token_stream().to_string()
566                } else {
567                    false
568                }
569            } else {
570                false
571            }
572        });
573        let serde_bound: TokenStream = if is_input_type {
574            parse_quote!(#zbus::export::serde::ser::Serialize)
575        } else {
576            parse_quote!(#zbus::export::serde::de::DeserializeOwned)
577        };
578        where_clause.predicates.push(parse_quote!(
579            #param: #serde_bound + #zbus::zvariant::Type
580        ));
581    }
582    let (_, ty_generics, where_clause) = generics.split_for_impl();
583
584    if let Some(proxy_path) = proxy_object {
585        let proxy_path = parse_str::<Path>(&proxy_path)?;
586        let signature = quote! {
587            fn #method #ty_generics(#inputs) -> #zbus::Result<#proxy_path<'p>>
588            #where_clause
589        };
590
591        Ok(quote! {
592            #(#other_attrs)*
593            pub #usage #signature {
594                let object_path: #zbus::zvariant::OwnedObjectPath =
595                    self.0.call(
596                        #method_name,
597                        &#zbus::zvariant::DynamicTuple((#(#args,)*)),
598                    )
599                    #wait?;
600                #proxy_path::builder(&self.0.connection())
601                    .path(object_path)?
602                    .build()
603                    #wait
604            }
605        })
606    } else {
607        let body = if args.len() == 1 {
608            // Wrap single arg in a tuple so if it's a struct/tuple itself, zbus will only remove
609            // the '()' from the signature that we add and not the actual intended ones.
610            let arg = &args[0];
611            quote! {
612                &#zbus::zvariant::DynamicTuple((#arg,))
613            }
614        } else {
615            quote! {
616                &#zbus::zvariant::DynamicTuple((#(#args),*))
617            }
618        };
619
620        let output = &m.sig.output;
621        let signature = quote! {
622            fn #method #ty_generics(#inputs) #output
623            #where_clause
624        };
625
626        if let Some(method_flags) = method_flags {
627            if method_attrs.no_reply {
628                Ok(quote! {
629                    #(#other_attrs)*
630                    pub #usage #signature {
631                        self.0.call_with_flags::<_, _, ()>(#method_name, #method_flags, #body)#wait?;
632                        ::std::result::Result::Ok(())
633                    }
634                })
635            } else {
636                Ok(quote! {
637                    #(#other_attrs)*
638                    pub #usage #signature {
639                        let reply = self.0.call_with_flags(#method_name, #method_flags, #body)#wait?;
640
641                        // SAFETY: This unwrap() cannot fail due to the guarantees in
642                        // call_with_flags, which can only return Ok(None) if the
643                        // NoReplyExpected is set. By not passing NoReplyExpected,
644                        // we are guaranteed to get either an Err variant (handled
645                        // in the previous statement) or Ok(Some(T)) which is safe to
646                        // unwrap
647                        ::std::result::Result::Ok(reply.unwrap())
648                    }
649                })
650            }
651        } else {
652            Ok(quote! {
653                #(#other_attrs)*
654                pub #usage #signature {
655                    let reply = self.0.call(#method_name, #body)#wait?;
656                    ::std::result::Result::Ok(reply)
657                }
658            })
659        }
660    }
661}
662
663fn gen_proxy_property(
664    property_name: &str,
665    method_name: &str,
666    m: &TraitItemFn,
667    async_opts: &AsyncOpts,
668    emits_changed_signal: PropertyEmitsChangedSignal,
669) -> TokenStream {
670    let AsyncOpts {
671        usage,
672        wait,
673        blocking,
674    } = async_opts;
675    let zbus = zbus_path();
676    let other_attrs: Vec<_> = m
677        .attrs
678        .iter()
679        .filter(|a| !a.path().is_ident("zbus"))
680        .collect();
681    let signature = &m.sig;
682    if signature.inputs.len() > 1 {
683        let value = pat_ident(typed_arg(signature.inputs.last().unwrap()).unwrap()).unwrap();
684        quote! {
685            #(#other_attrs)*
686            #[allow(clippy::needless_question_mark)]
687            pub #usage #signature {
688                ::std::result::Result::Ok(self.0.set_property(#property_name, #value)#wait?)
689            }
690        }
691    } else {
692        // This should fail to compile only if the return type is wrong,
693        // so use that as the span.
694        let body_span = if let ReturnType::Type(_, ty) = &signature.output {
695            ty.span()
696        } else {
697            signature.span()
698        };
699        let body = quote_spanned! {body_span =>
700            ::std::result::Result::Ok(self.0.get_property(#property_name)#wait?)
701        };
702        let ret_type = if let ReturnType::Type(_, ty) = &signature.output {
703            Some(ty)
704        } else {
705            None
706        };
707
708        let (proxy_name, prop_stream) = if *blocking {
709            (
710                "zbus::blocking::Proxy",
711                quote! { #zbus::blocking::proxy::PropertyIterator },
712            )
713        } else {
714            ("zbus::Proxy", quote! { #zbus::proxy::PropertyStream })
715        };
716
717        let receive_method = match emits_changed_signal {
718            PropertyEmitsChangedSignal::True | PropertyEmitsChangedSignal::Invalidates => {
719                let (_, ty_generics, where_clause) = m.sig.generics.split_for_impl();
720                let receive = format_ident!("receive_{}_changed", method_name);
721                let gen_doc = format!(
722                    "Create a stream for the `{property_name}` property changes. \
723                This is a convenient wrapper around [`{proxy_name}::receive_property_changed`]."
724                );
725                quote! {
726                    #(#other_attrs)*
727                    #[doc = #gen_doc]
728                    pub #usage fn #receive #ty_generics(
729                        &self
730                    ) -> #prop_stream<'p, <#ret_type as #zbus::ResultAdapter>::Ok>
731                    #where_clause
732                    {
733                        self.0.receive_property_changed(#property_name)#wait
734                    }
735                }
736            }
737            PropertyEmitsChangedSignal::False | PropertyEmitsChangedSignal::Const => {
738                quote! {}
739            }
740        };
741
742        let cached_getter_method = match emits_changed_signal {
743            PropertyEmitsChangedSignal::True
744            | PropertyEmitsChangedSignal::Invalidates
745            | PropertyEmitsChangedSignal::Const => {
746                let cached_getter = format_ident!("cached_{}", method_name);
747                let cached_doc = format!(
748                    " Get the cached value of the `{property_name}` property, or `None` if the property is not cached.",
749                );
750                quote! {
751                    #(#other_attrs)*
752                    #[doc = #cached_doc]
753                    pub fn #cached_getter(&self) -> ::std::result::Result<
754                        ::std::option::Option<<#ret_type as #zbus::ResultAdapter>::Ok>,
755                        <#ret_type as #zbus::ResultAdapter>::Err>
756                    {
757                        self.0.cached_property(#property_name).map_err(::std::convert::Into::into)
758                    }
759                }
760            }
761            PropertyEmitsChangedSignal::False => quote! {},
762        };
763
764        quote! {
765            #(#other_attrs)*
766            #[allow(clippy::needless_question_mark)]
767            pub #usage #signature {
768                #body
769            }
770
771            #cached_getter_method
772
773            #receive_method
774        }
775    }
776}
777
778struct SetLifetimeS;
779
780impl Fold for SetLifetimeS {
781    fn fold_type_reference(&mut self, node: syn::TypeReference) -> syn::TypeReference {
782        let mut t = syn::fold::fold_type_reference(self, node);
783        t.lifetime = Some(syn::Lifetime::new("'s", Span::call_site()));
784        t
785    }
786
787    fn fold_lifetime(&mut self, _node: syn::Lifetime) -> syn::Lifetime {
788        syn::Lifetime::new("'s", Span::call_site())
789    }
790}
791
792#[allow(clippy::too_many_arguments)]
793fn gen_proxy_signal(
794    proxy_name: &Ident,
795    iface_name: &str,
796    signal_name: &str,
797    snake_case_name: &str,
798    method: &TraitItemFn,
799    async_opts: &AsyncOpts,
800    visibility: &Visibility,
801    gen_sig_args: bool,
802) -> (TokenStream, TokenStream) {
803    let AsyncOpts {
804        usage,
805        wait,
806        blocking,
807    } = async_opts;
808    let zbus = zbus_path();
809    let other_attrs: Vec<_> = method
810        .attrs
811        .iter()
812        .filter(|a| !a.path().is_ident("zbus"))
813        .collect();
814    let input_types: Vec<_> = method
815        .sig
816        .inputs
817        .iter()
818        .filter_map(|arg| match arg {
819            FnArg::Typed(p) => Some(&*p.ty),
820            _ => None,
821        })
822        .collect();
823    let input_types_s: Vec<_> = SetLifetimeS
824        .fold_signature(method.sig.clone())
825        .inputs
826        .iter()
827        .filter_map(|arg| match arg {
828            FnArg::Typed(p) => Some(p.ty.clone()),
829            _ => None,
830        })
831        .collect();
832    let args: Vec<Ident> = method
833        .sig
834        .inputs
835        .iter()
836        .filter_map(typed_arg)
837        .filter_map(|arg| pat_ident(arg).cloned())
838        .collect();
839    let args_nth: Vec<Literal> = args
840        .iter()
841        .enumerate()
842        .map(|(i, _)| Literal::usize_unsuffixed(i))
843        .collect();
844
845    let mut generics = method.sig.generics.clone();
846    let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
847    for param in generics
848        .params
849        .iter()
850        .filter(|a| matches!(a, syn::GenericParam::Type(_)))
851    {
852        where_clause
853                .predicates
854                .push(parse_quote!(#param: #zbus::export::serde::de::Deserialize<'s> + #zbus::zvariant::Type + ::std::fmt::Debug));
855    }
856    generics.params.push(parse_quote!('s));
857    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
858
859    let (
860        proxy_path,
861        receive_signal_link,
862        receive_signal_with_args_link,
863        trait_name,
864        trait_link,
865        signal_type,
866    ) = if *blocking {
867        (
868            "zbus::blocking::Proxy",
869            "https://docs.rs/zbus/latest/zbus/blocking/proxy/struct.Proxy.html#method.receive_signal",
870            "https://docs.rs/zbus/latest/zbus/blocking/proxy/struct.Proxy.html#method.receive_signal_with_args",
871            "Iterator",
872            "https://doc.rust-lang.org/std/iter/trait.Iterator.html",
873            quote! { blocking::proxy::SignalIterator },
874        )
875    } else {
876        (
877            "zbus::Proxy",
878            "https://docs.rs/zbus/latest/zbus/proxy/struct.Proxy.html#method.receive_signal",
879            "https://docs.rs/zbus/latest/zbus/proxy/struct.Proxy.html#method.receive_signal_with_args",
880            "Stream",
881            "https://docs.rs/futures/0.3.15/futures/stream/trait.Stream.html",
882            quote! { proxy::SignalStream },
883        )
884    };
885    let receiver_name = format_ident!("receive_{snake_case_name}");
886    let receiver_with_args_name = format_ident!("receive_{snake_case_name}_with_args");
887    let stream_name = format_ident!("{signal_name}{trait_name}");
888    let signal_args = format_ident!("{signal_name}Args");
889    let signal_name_ident = format_ident!("{signal_name}");
890
891    let receive_gen_doc = format!(
892        "Create a stream that receives `{signal_name}` signals.\n\
893            \n\
894            This a convenient wrapper around [`{proxy_path}::receive_signal`]({receive_signal_link}).",
895    );
896    let receive_with_args_gen_doc = format!(
897        "Create a stream that receives `{signal_name}` signals.\n\
898            \n\
899            This a convenient wrapper around [`{proxy_path}::receive_signal_with_args`]({receive_signal_with_args_link}).",
900    );
901    let receive_signal_with_args = if args.is_empty() {
902        quote!()
903    } else {
904        quote! {
905            #[doc = #receive_with_args_gen_doc]
906            #(#other_attrs)*
907            pub #usage fn #receiver_with_args_name(&self, args: &[(u8, &str)]) -> #zbus::Result<#stream_name>
908            {
909                self.0.receive_signal_with_args(#signal_name, args)#wait.map(#stream_name)
910            }
911        }
912    };
913    let receive_signal = quote! {
914        #[doc = #receive_gen_doc]
915        #(#other_attrs)*
916        pub #usage fn #receiver_name(&self) -> #zbus::Result<#stream_name>
917        {
918            self.0.receive_signal(#signal_name)#wait.map(#stream_name)
919        }
920
921        #receive_signal_with_args
922    };
923
924    let stream_gen_doc = format!(
925        "A [`{trait_name}`] implementation that yields [`{signal_name}`] signals.\n\
926            \n\
927            Use [`{proxy_name}::{receiver_name}`] to create an instance of this type.\n\
928            \n\
929            [`{trait_name}`]: {trait_link}",
930    );
931    let signal_args_gen_doc = format!("`{signal_name}` signal arguments.");
932    let args_struct_gen_doc = format!("A `{signal_name}` signal.");
933    let args_struct_decl = if gen_sig_args {
934        quote! {
935            #[doc = #args_struct_gen_doc]
936            #[derive(Debug, Clone)]
937            #visibility struct #signal_name_ident(#zbus::message::Body);
938
939            impl #signal_name_ident {
940                #[doc = "Try to construct a "]
941                #[doc = #signal_name]
942                #[doc = " from a [`zbus::Message`]."]
943                pub fn from_message<M>(msg: M) -> ::std::option::Option<Self>
944                where
945                    M: ::std::convert::Into<#zbus::message::Message>,
946                {
947                    let msg = msg.into();
948                    let hdr = msg.header();
949                    let message_type = msg.message_type();
950                    let interface = hdr.interface();
951                    let member = hdr.member();
952                    let interface = interface.as_ref().map(|i| i.as_str());
953                    let member = member.as_ref().map(|m| m.as_str());
954
955                    match (message_type, interface, member) {
956                        (#zbus::message::Type::Signal, Some(#iface_name), Some(#signal_name)) => {
957                            Some(Self(msg.body()))
958                        }
959                        _ => None,
960                    }
961                }
962
963                #[doc = "The reference to the underlying [`zbus::Message`]."]
964                pub fn message(&self) -> &#zbus::message::Message {
965                    self.0.message()
966                }
967            }
968
969            impl ::std::convert::From<#signal_name_ident> for #zbus::message::Message {
970                fn from(signal: #signal_name_ident) -> Self {
971                    signal.0.message().clone()
972                }
973            }
974        }
975    } else {
976        quote!()
977    };
978    let args_impl = if args.is_empty() || !gen_sig_args {
979        quote!()
980    } else {
981        let arg_fields_init = if args.len() == 1 {
982            quote! { #(#args)*: args }
983        } else {
984            quote! { #(#args: args.#args_nth),* }
985        };
986
987        quote! {
988            impl #signal_name_ident {
989                /// Retrieve the signal arguments.
990                pub fn args #ty_generics(&'s self) -> #zbus::Result<#signal_args #ty_generics>
991                #where_clause
992                {
993                    ::std::convert::TryFrom::try_from(&self.0)
994                }
995            }
996
997            #[doc = #signal_args_gen_doc]
998            #visibility struct #signal_args #ty_generics {
999                phantom: std::marker::PhantomData<&'s ()>,
1000                #(
1001                    pub #args: #input_types_s
1002                 ),*
1003            }
1004
1005            impl #impl_generics #signal_args #ty_generics
1006                #where_clause
1007            {
1008                #(
1009                    pub fn #args(&self) -> &#input_types_s {
1010                        &self.#args
1011                    }
1012                 )*
1013            }
1014
1015            impl #impl_generics std::fmt::Debug for #signal_args #ty_generics
1016                #where_clause
1017            {
1018                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1019                    f.debug_struct(#signal_name)
1020                    #(
1021                     .field(stringify!(#args), &self.#args)
1022                    )*
1023                     .finish()
1024                }
1025            }
1026
1027            impl #impl_generics ::std::convert::TryFrom<&'s #zbus::message::Body> for #signal_args #ty_generics
1028                #where_clause
1029            {
1030                type Error = #zbus::Error;
1031
1032                fn try_from(msg_body: &'s #zbus::message::Body) -> #zbus::Result<Self> {
1033                    msg_body.deserialize::<(#(#input_types),*)>()
1034                        .map_err(::std::convert::Into::into)
1035                        .map(|args| {
1036                            #signal_args {
1037                                phantom: ::std::marker::PhantomData,
1038                                #arg_fields_init
1039                            }
1040                        })
1041                }
1042            }
1043        }
1044    };
1045    let stream_impl = if *blocking {
1046        quote! {
1047            impl ::std::iter::Iterator for #stream_name {
1048                type Item = #signal_name_ident;
1049
1050                fn next(&mut self) -> ::std::option::Option<Self::Item> {
1051                    ::std::iter::Iterator::next(&mut self.0)
1052                        .map(|msg| #signal_name_ident(msg.body()))
1053                }
1054            }
1055        }
1056    } else {
1057        quote! {
1058            impl #zbus::export::futures_core::stream::Stream for #stream_name {
1059                type Item = #signal_name_ident;
1060
1061                fn poll_next(
1062                    self: ::std::pin::Pin<&mut Self>,
1063                    cx: &mut ::std::task::Context<'_>,
1064                    ) -> ::std::task::Poll<::std::option::Option<Self::Item>> {
1065                    #zbus::export::futures_core::stream::Stream::poll_next(
1066                        ::std::pin::Pin::new(&mut self.get_mut().0),
1067                        cx,
1068                    )
1069                    .map(|msg| msg.map(|msg| #signal_name_ident(msg.body())))
1070                }
1071            }
1072
1073            impl #zbus::export::ordered_stream::OrderedStream for #stream_name {
1074                type Data = #signal_name_ident;
1075                type Ordering = #zbus::message::Sequence;
1076
1077                fn poll_next_before(
1078                    self: ::std::pin::Pin<&mut Self>,
1079                    cx: &mut ::std::task::Context<'_>,
1080                    before: ::std::option::Option<&Self::Ordering>
1081                    ) -> ::std::task::Poll<#zbus::export::ordered_stream::PollResult<Self::Ordering, Self::Data>> {
1082                    #zbus::export::ordered_stream::OrderedStream::poll_next_before(
1083                        ::std::pin::Pin::new(&mut self.get_mut().0),
1084                        cx,
1085                        before,
1086                    )
1087                    .map(|msg| msg.map_data(|msg| #signal_name_ident(msg.body())))
1088                }
1089            }
1090
1091            impl #zbus::export::futures_core::stream::FusedStream for #stream_name {
1092                fn is_terminated(&self) -> bool {
1093                    self.0.is_terminated()
1094                }
1095            }
1096
1097            #[#zbus::export::async_trait::async_trait]
1098            impl #zbus::AsyncDrop for #stream_name {
1099                async fn async_drop(self) {
1100                    self.0.async_drop().await
1101                }
1102            }
1103        }
1104    };
1105    let stream_types = quote! {
1106        #[doc = #stream_gen_doc]
1107        #[derive(Debug)]
1108        #visibility struct #stream_name(#zbus::#signal_type<'static>);
1109
1110        impl #stream_name {
1111            /// Consumes `self`, returning the underlying `zbus::#signal_type`.
1112            pub fn into_inner(self) -> #zbus::#signal_type<'static> {
1113                self.0
1114            }
1115
1116            /// The reference to the underlying `zbus::#signal_type`.
1117            pub fn inner(&self) -> & #zbus::#signal_type<'static> {
1118                &self.0
1119            }
1120        }
1121
1122        #stream_impl
1123
1124        #args_struct_decl
1125
1126        #args_impl
1127    };
1128
1129    (receive_signal, stream_types)
1130}