Skip to main content

zbus_macros/
proxy.rs

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