Skip to main content

zbus_macros/
iface.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use std::collections::BTreeMap;
4use syn::{
5    AngleBracketedGenericArguments, Attribute, Error, Expr, ExprLit, FnArg, GenericArgument, Ident,
6    ImplItem, ImplItemFn, ItemImpl,
7    Lit::Str,
8    Meta, MetaNameValue, PatType, PathArguments, ReturnType, Signature, Token, Type, TypePath,
9    Visibility,
10    parse::{Parse, ParseStream},
11    parse_quote, parse_str,
12    punctuated::Punctuated,
13    spanned::Spanned,
14    token::{Async, Comma},
15};
16use zvariant_utils::{case, def_attrs};
17
18use crate::utils::*;
19
20def_attrs! {
21    crate zbus;
22
23    pub ImplAttributes("impl block") {
24        interface str,
25        name str,
26        spawn bool,
27        introspection_docs bool,
28        crate_path str,
29        proxy {
30            // Keep this in sync with proxy's method attributes.
31            // TODO: Find a way to share code with proxy module.
32            pub ProxyAttributes("proxy") {
33                assume_defaults bool,
34                default_path str,
35                default_service str,
36                async_name str,
37                blocking_name str,
38                gen_async bool,
39                gen_blocking bool,
40                visibility str
41            }
42        }
43    };
44
45    pub MethodAttributes("method") {
46        name str,
47        signal none,
48        property {
49            pub PropertyAttributes("property") {
50                emits_changed_signal str
51            }
52        },
53        out_args [str],
54        proxy {
55            // Keep this in sync with proxy's method attributes.
56            // TODO: Find a way to share code with proxy module.
57            pub ProxyMethodAttributes("proxy") {
58                object str,
59                async_object str,
60                blocking_object str,
61                object_vec none,
62                no_reply none,
63                no_autostart none,
64                allow_interactive_auth none
65            }
66        }
67    };
68
69    pub ArgAttributes("argument") {
70        object_server none,
71        connection none,
72        header none,
73        signal_context none,
74        signal_emitter none
75    };
76}
77
78#[derive(Debug, Default)]
79struct Property {
80    read: bool,
81    write: bool,
82    emits_changed_signal: PropertyEmitsChangedSignal,
83    ty: Option<Type>,
84    doc_comments: TokenStream,
85}
86
87#[derive(Debug, PartialEq, Copy, Clone)]
88enum MethodType {
89    Signal,
90    Property(PropertyType),
91    Other,
92}
93
94#[derive(Debug, PartialEq, Copy, Clone)]
95enum PropertyType {
96    Setter,
97    Getter,
98}
99
100#[derive(Debug, Clone)]
101struct MethodInfo {
102    /// The method identifier
103    ident: Ident,
104    /// The type of method being parsed
105    method_type: MethodType,
106    /// Whether the method has inputs
107    has_inputs: bool,
108    /// Whether the method is async
109    is_async: bool,
110    /// Doc comments on the methods
111    doc_comments: TokenStream,
112    /// Whether self is passed as mutable to the method
113    is_mut: bool,
114    /// The await to append to method calls
115    method_await: TokenStream,
116    /// The typed inputs passed to the method
117    typed_inputs: Vec<PatType>,
118    /// The method arguments' introspection
119    intro_args: TokenStream,
120    /// Whether the output type is a Result
121    is_result_output: bool,
122    /// Code block to deserialize arguments from zbus message
123    args_from_msg: TokenStream,
124    /// Names of all arguments to the method
125    args_names: TokenStream,
126    /// Code stream to match on the reply of the method call
127    reply: TokenStream,
128    /// The signal context object argument
129    signal_emitter_arg: Option<PatType>,
130    /// The name of the method (setters are stripped of set_ prefix)
131    member_name: String,
132    /// The proxy method attributes, if any.
133    proxy_attrs: Option<ProxyMethodAttributes>,
134    /// The method output type.
135    output: ReturnType,
136    /// The cfg attributes of the method.
137    cfg_attrs: Vec<Attribute>,
138    /// The doc attributes of the method.
139    doc_attrs: Vec<Attribute>,
140}
141
142impl MethodInfo {
143    fn new(
144        zbus: &TokenStream,
145        method: &ImplItemFn,
146        attrs: &MethodAttributes,
147        cfg_attrs: &[&Attribute],
148        doc_attrs: &[&Attribute],
149        introspect_docs: bool,
150    ) -> syn::Result<MethodInfo> {
151        let is_async = method.sig.asyncness.is_some();
152        let Signature {
153            ident,
154            inputs,
155            output,
156            ..
157        } = &method.sig;
158        let doc_comments = if introspect_docs {
159            let docs = get_doc_attrs(&method.attrs)
160                .iter()
161                .filter_map(|attr| {
162                    if let Ok(MetaNameValue {
163                        value: Expr::Lit(ExprLit { lit: Str(s), .. }),
164                        ..
165                    }) = &attr.meta.require_name_value()
166                    {
167                        Some(s.value())
168                    } else {
169                        // non #[doc = "..."] attributes are not our concern
170                        // we leave them for rustc to handle
171                        None
172                    }
173                })
174                .collect();
175            to_xml_docs(docs)
176        } else {
177            quote!()
178        };
179        let is_property = attrs.property.is_some();
180        let is_signal = attrs.signal;
181        assert!(!is_property || !is_signal);
182
183        let mut typed_inputs = inputs
184            .iter()
185            .filter_map(typed_arg)
186            .cloned()
187            .collect::<Vec<_>>();
188
189        let has_inputs = count_regular_args(&typed_inputs) > 0;
190
191        let method_type = if is_signal {
192            MethodType::Signal
193        } else if is_property {
194            if has_inputs {
195                MethodType::Property(PropertyType::Setter)
196            } else {
197                MethodType::Property(PropertyType::Getter)
198            }
199        } else {
200            MethodType::Other
201        };
202
203        let is_mut = if let FnArg::Receiver(r) = inputs
204            .first()
205            .ok_or_else(|| Error::new_spanned(ident, "not &self method"))?
206        {
207            r.mutability.is_some()
208        } else if is_signal {
209            false
210        } else {
211            return Err(Error::new_spanned(method, "missing receiver"));
212        };
213        if is_signal && !is_async {
214            return Err(Error::new_spanned(method, "signals must be async"));
215        }
216        let method_await = if is_async {
217            quote! { .await }
218        } else {
219            quote! {}
220        };
221
222        let signal_emitter_arg: Option<PatType> = if is_signal {
223            if typed_inputs.is_empty() {
224                return Err(Error::new_spanned(
225                    inputs,
226                    "Expected a `&zbus::object_server::SignalEmitter<'_> argument",
227                ));
228            }
229            Some(typed_inputs.remove(0))
230        } else {
231            None
232        };
233
234        let mut intro_args = quote!();
235        intro_args.extend(introspect_input_args(&typed_inputs, is_signal, cfg_attrs));
236        let is_result_output = introspect_add_output_args(
237            &mut intro_args,
238            output,
239            attrs.out_args.as_deref(),
240            cfg_attrs,
241        )?;
242
243        let (args_from_msg, args_names) = get_args_from_inputs(&typed_inputs, method_type, zbus)?;
244
245        let reply = if is_result_output {
246            let ret = quote!(r);
247
248            quote!(match reply {
249                ::std::result::Result::Ok(r) => __zbus__connection.reply(&hdr, &#ret).await,
250                ::std::result::Result::Err(e) => __zbus__connection.reply_dbus_error(&hdr, e).await,
251            })
252        } else {
253            quote!(__zbus__connection.reply(&hdr, &reply).await)
254        };
255
256        let member_name = attrs.name.clone().unwrap_or_else(|| {
257            let mut name = ident.to_string();
258            if is_property && has_inputs {
259                assert!(name.starts_with("set_"));
260                name = name[4..].to_string();
261            } else if name.starts_with("r#") {
262                name = name[2..].to_string();
263            }
264            pascal_case(&name)
265        });
266
267        Ok(MethodInfo {
268            ident: ident.clone(),
269            method_type,
270            has_inputs,
271            is_async,
272            doc_comments,
273            is_mut,
274            method_await,
275            typed_inputs,
276            signal_emitter_arg,
277            intro_args,
278            is_result_output,
279            args_from_msg,
280            args_names,
281            reply,
282            member_name,
283            proxy_attrs: attrs.proxy.clone(),
284            output: output.clone(),
285            cfg_attrs: cfg_attrs.iter().cloned().cloned().collect(),
286            doc_attrs: doc_attrs.iter().cloned().cloned().collect(),
287        })
288    }
289}
290
291pub fn expand(args: Punctuated<Meta, Token![,]>, mut input: ItemImpl) -> syn::Result<TokenStream> {
292    let impl_attrs = ImplAttributes::parse_nested_metas(args)?;
293    let crate_path = parse_crate_path(impl_attrs.crate_path.as_deref())?;
294    let zbus = zbus_path(crate_path.as_ref());
295
296    let self_ty = &input.self_ty;
297    let mut properties = BTreeMap::new();
298    let mut set_dispatch = quote!();
299    let mut set_mut_dispatch = quote!();
300    let mut get_dispatch = quote!();
301    let mut get_all = quote!();
302    let mut call_dispatch = quote!();
303    let mut call_mut_dispatch = quote!();
304    let mut introspect = quote!();
305    let mut generated_signals = quote!();
306    let mut signals_trait_methods = quote!();
307    let mut signals_emitter_impl_methods = quote!();
308    let mut signals_interface_ref_impl_methods = quote!();
309
310    // the impl Type
311    let ty = match input.self_ty.as_ref() {
312        Type::Path(p) => {
313            &p.path
314                .segments
315                .last()
316                .ok_or_else(|| Error::new_spanned(p, "Unsupported 'impl' type"))?
317                .ident
318        }
319        _ => return Err(Error::new_spanned(&input.self_ty, "Invalid type")),
320    };
321    let iface_name = {
322        match (impl_attrs.name, impl_attrs.interface) {
323            // Ensure the interface name is valid.
324            (Some(name), None) | (None, Some(name)) => zbus_names::InterfaceName::try_from(name)
325                .map_err(|e| Error::new(input.span(), format!("{e}")))
326                .map(|i| i.to_string())?,
327            (None, None) => format!("org.freedesktop.{ty}"),
328            (Some(_), Some(_)) => {
329                return Err(syn::Error::new(
330                    input.span(),
331                    "`name` and `interface` attributes should not be specified at the same time",
332                ));
333            }
334        }
335    };
336    let with_spawn = impl_attrs.spawn.unwrap_or(true);
337    let mut proxy = impl_attrs
338        .proxy
339        .map(|p| Proxy::new(ty, &iface_name, p, &zbus));
340    let introspect_docs = impl_attrs.introspection_docs.unwrap_or(true);
341
342    // Store parsed information about each method
343    let mut methods = vec![];
344    for item in &mut input.items {
345        let (method, is_signal) = match item {
346            ImplItem::Fn(m) => (m, false),
347            // Since signals do not have a function body, they don't parse as ImplItemFn…
348            ImplItem::Verbatim(tokens) => {
349                // … thus parse them ourselves and construct an ImplItemFn from that
350                let decl = syn::parse2::<ImplItemSignal>(tokens.clone())?;
351                let ImplItemSignal { attrs, vis, sig } = decl;
352                *item = ImplItem::Fn(ImplItemFn {
353                    attrs,
354                    vis,
355                    defaultness: None,
356                    sig,
357                    // This empty block will be replaced below.
358                    block: parse_quote!({}),
359                });
360                match item {
361                    ImplItem::Fn(m) => (m, true),
362                    _ => unreachable!(),
363                }
364            }
365            _ => continue,
366        };
367
368        let method_attrs = MethodAttributes::parse(&method.attrs)?;
369
370        method.attrs.retain(|attr| !attr.path().is_ident("zbus"));
371
372        if is_signal && !method_attrs.signal {
373            return Err(syn::Error::new_spanned(
374                item,
375                "methods that are not signals must have a body",
376            ));
377        }
378
379        let cfg_attrs: Vec<_> = method
380            .attrs
381            .iter()
382            .filter(|a| a.path().is_ident("cfg"))
383            .collect();
384        let doc_attrs: Vec<_> = method
385            .attrs
386            .iter()
387            .filter(|a| a.path().is_ident("doc"))
388            .collect();
389
390        let method_info = MethodInfo::new(
391            &zbus,
392            method,
393            &method_attrs,
394            &cfg_attrs,
395            &doc_attrs,
396            introspect_docs,
397        )?;
398        let attr_property = method_attrs.property;
399        if let Some(prop_attrs) = &attr_property {
400            let property: &mut Property = properties
401                .entry(method_info.member_name.to_string())
402                .or_default();
403            if method_info.method_type == MethodType::Property(PropertyType::Getter) {
404                let emits_changed_signal = if let Some(s) = &prop_attrs.emits_changed_signal {
405                    PropertyEmitsChangedSignal::parse(s, method.span())?
406                } else {
407                    PropertyEmitsChangedSignal::True
408                };
409                property.read = true;
410                property.emits_changed_signal = emits_changed_signal;
411            } else {
412                property.write = true;
413                if prop_attrs.emits_changed_signal.is_some() {
414                    return Err(Error::new_spanned(
415                        method,
416                        "`emits_changed_signal` cannot be specified on setters",
417                    ));
418                }
419            }
420        }
421        methods.push((method, method_info));
422    }
423
424    for (method, method_info) in methods {
425        let info = method_info.clone();
426        let MethodInfo {
427            method_type,
428            has_inputs,
429            is_async,
430            doc_comments,
431            is_mut,
432            method_await,
433            typed_inputs,
434            signal_emitter_arg,
435            intro_args,
436            is_result_output,
437            args_from_msg,
438            args_names,
439            reply,
440            member_name,
441            cfg_attrs,
442            ..
443        } = method_info;
444
445        let mut method_clone = method.clone();
446        let Signature {
447            ident,
448            inputs,
449            output,
450            ..
451        } = &mut method.sig;
452
453        clear_input_arg_attrs(inputs);
454
455        match method_type {
456            MethodType::Signal => {
457                introspect.extend(doc_comments);
458                introspect.extend(introspect_signal(&member_name, &intro_args));
459                let signal_emitter = signal_emitter_arg.unwrap().pat;
460
461                method.block = parse_quote!({
462                    #signal_emitter.emit(
463                        <#self_ty as #zbus::object_server::Interface>::name(),
464                        #member_name,
465                        &(#args_names),
466                    )
467                    .await
468                });
469
470                method_clone.sig.asyncness = Some(Async(method_clone.span()));
471                *method_clone.sig.inputs.first_mut().unwrap() = parse_quote!(&self);
472                method_clone.vis = Visibility::Inherited;
473                let sig = &method_clone.sig;
474                signals_trait_methods.extend(quote! {
475                    #sig;
476                });
477                method_clone.block = parse_quote!({
478                    self.emit(
479                        #iface_name,
480                        #member_name,
481                        &(#args_names),
482                    )
483                    .await
484                });
485                signals_emitter_impl_methods.extend(quote! {
486                    #method_clone
487                });
488                method_clone.block = parse_quote!({
489                    <#zbus::object_server::InterfaceRef<#self_ty>>::signal_emitter(self)
490                        .emit(
491                            #iface_name,
492                            #member_name,
493                            &(#args_names),
494                        )
495                        .await
496                });
497                signals_interface_ref_impl_methods.extend(quote! {
498                    #method_clone
499                });
500            }
501            MethodType::Property(_) => {
502                let p = properties.get_mut(&member_name).unwrap();
503
504                let sk_member_name = case::snake_or_kebab_case(&member_name, true);
505                let prop_changed_method_name = format_ident!("{sk_member_name}_changed");
506                let prop_invalidate_method_name = format_ident!("{sk_member_name}_invalidate");
507
508                p.doc_comments.extend(doc_comments);
509                if has_inputs {
510                    let set_call = if is_result_output {
511                        quote!(self.#ident(#args_names)#method_await)
512                    } else if is_async {
513                        quote!(
514                            ::std::result::Result::<_, #zbus::fdo::Error>::Ok(
515                                self.#ident(#args_names).await,
516                            )
517                        )
518                    } else {
519                        quote!(
520                            ::std::result::Result::<_, #zbus::fdo::Error>::Ok(
521                                self.#ident(#args_names),
522                            )
523                        )
524                    };
525
526                    // * For reference arg, we convert from `&Value` (so `TryFrom<&Value<'_>>` is
527                    //   required).
528                    //
529                    // * For argument type with lifetimes, we convert from `Value` (so
530                    //   `TryFrom<Value<'_>>` is required).
531                    //
532                    // * For all other arg types, we convert the passed value to `OwnedValue` first
533                    //   and then pass it as `Value` (so `TryFrom<OwnedValue>` is required).
534                    let value_to_owned = quote! {
535                        match ::zbus::zvariant::Value::try_to_owned(value) {
536                            ::std::result::Result::Ok(val) => ::zbus::zvariant::Value::from(val),
537                            ::std::result::Result::Err(e) => {
538                                return ::std::result::Result::Err(
539                                    ::std::convert::Into::into(#zbus::Error::Variant(::std::convert::Into::into(e)))
540                                );
541                            }
542                        }
543                    };
544
545                    let value_param = typed_inputs
546                        .iter()
547                        .find(|input| {
548                            let a = ArgAttributes::parse(&input.attrs).unwrap();
549                            !a.object_server
550                                && !a.connection
551                                && !a.header
552                                && !a.signal_context
553                                && !a.signal_emitter
554                        })
555                        .ok_or_else(|| Error::new_spanned(inputs, "Expected a value argument"))?;
556
557                    // Use setter argument type as the property type if the getter is not
558                    // explicitly defined.
559                    if !p.read {
560                        p.ty = Some((*value_param.ty).clone());
561                        p.emits_changed_signal = PropertyEmitsChangedSignal::False;
562                    }
563
564                    let value_arg = match &*value_param.ty {
565                        Type::Reference(_) => quote!(value),
566                        Type::Path(path) => path
567                            .path
568                            .segments
569                            .first()
570                            .map(|segment| match &segment.arguments {
571                                PathArguments::AngleBracketed(angled) => angled
572                                    .args
573                                    .first()
574                                    .filter(|arg| matches!(arg, GenericArgument::Lifetime(_)))
575                                    .map(|_| quote!(match ::zbus::zvariant::Value::try_clone(value) {
576                                        ::std::result::Result::Ok(val) => val,
577                                        ::std::result::Result::Err(e) => {
578                                            return ::std::result::Result::Err(
579                                                ::std::convert::Into::into(#zbus::Error::Variant(::std::convert::Into::into(e)))
580                                            );
581                                        }
582                                    }))
583                                    .unwrap_or_else(|| value_to_owned.clone()),
584                                _ => value_to_owned.clone(),
585                            })
586                            .unwrap_or_else(|| value_to_owned.clone()),
587                        _ => value_to_owned,
588                    };
589
590                    let value_param_name = &value_param.pat;
591                    let prop_changed_method = match p.emits_changed_signal {
592                        PropertyEmitsChangedSignal::True => {
593                            quote!({
594                                self
595                                    .#prop_changed_method_name(&__zbus__signal_emitter)
596                                    .await
597                                    .map(|_| set_result)
598                            })
599                        }
600                        PropertyEmitsChangedSignal::Invalidates => {
601                            quote!({
602                                self
603                                    .#prop_invalidate_method_name(&__zbus__signal_emitter)
604                                    .await
605                                    .map(|_| set_result)
606                            })
607                        }
608                        PropertyEmitsChangedSignal::False | PropertyEmitsChangedSignal::Const => {
609                            quote!({ ::std::result::Result::<_, #zbus::Error>::Ok(()) })
610                        }
611                    };
612                    let do_set = quote!({
613                        #args_from_msg
614                        let value = #value_arg;
615                        match ::std::convert::TryInto::try_into(value) {
616                            ::std::result::Result::Ok(val) => {
617                                let #value_param_name = val;
618                                match #set_call {
619                                    ::std::result::Result::Ok(set_result) => {
620                                        (#prop_changed_method)
621                                            .map_err(|e| #zbus::fdo::Error::from(e))
622                                    }
623                                    ::std::result::Result::Err(e) => {
624                                        ::std::result::Result::Err(#zbus::fdo::Error::from(e))
625                                    }
626                                }
627                            }
628                            ::std::result::Result::Err(e) => {
629                                ::std::result::Result::Err(
630                                    ::std::convert::Into::into(#zbus::Error::Variant(::std::convert::Into::into(e))),
631                                )
632                            }
633                        }
634                    });
635
636                    if is_mut {
637                        let q = quote!(
638                            #(#cfg_attrs)*
639                            #member_name => {
640                                ::std::option::Option::Some((move || async move { #do_set }) ().await)
641                            }
642                        );
643                        set_mut_dispatch.extend(q);
644
645                        let q = quote!(
646                            #(#cfg_attrs)*
647                            #member_name => #zbus::object_server::DispatchResult2::RequiresMut,
648                        );
649                        set_dispatch.extend(q);
650                    } else {
651                        let q = quote!(
652                            #(#cfg_attrs)*
653                            #member_name => {
654                                #zbus::object_server::DispatchResult2::Async(::std::boxed::Box::pin(async move {
655                                    #do_set
656                                }))
657                            }
658                        );
659                        set_dispatch.extend(q);
660                    }
661                } else {
662                    let is_fallible_property = is_result_output;
663
664                    p.ty = Some(get_return_type(output)?.clone());
665
666                    let value_convert = quote!(
667                        <#zbus::zvariant::OwnedValue as ::std::convert::TryFrom<_>>::try_from(
668                            <#zbus::zvariant::Value as ::std::convert::From<_>>::from(
669                                value,
670                            ),
671                        )
672                        .map_err(|e| #zbus::fdo::Error::Failed(e.to_string()))
673                    );
674                    let inner = if is_fallible_property {
675                        quote!(self.#ident(#args_names) #method_await .and_then(|value| #value_convert))
676                    } else {
677                        quote!({
678                            let value = self.#ident(#args_names)#method_await;
679                            #value_convert
680                        })
681                    };
682
683                    let q = quote!(
684                        #(#cfg_attrs)*
685                        #member_name => {
686                            #args_from_msg
687                            ::std::option::Option::Some(#inner)
688                        },
689                    );
690                    get_dispatch.extend(q);
691
692                    let q = if is_fallible_property {
693                        quote!({
694                            #args_from_msg
695                            if let Ok(prop) = self.#ident(#args_names)#method_await {
696                            props.insert(
697                                ::std::string::ToString::to_string(#member_name),
698                                <#zbus::zvariant::OwnedValue as ::std::convert::TryFrom<_>>::try_from(
699                                    <#zbus::zvariant::Value as ::std::convert::From<_>>::from(
700                                        prop,
701                                    ),
702                                )
703                                .map_err(|e| #zbus::fdo::Error::Failed(e.to_string()))?,
704                            );
705                        }})
706                    } else {
707                        quote!({
708                            #args_from_msg
709                            props.insert(
710                                ::std::string::ToString::to_string(#member_name),
711                                <#zbus::zvariant::OwnedValue as ::std::convert::TryFrom<_>>::try_from(
712                                    <#zbus::zvariant::Value as ::std::convert::From<_>>::from(
713                                        self.#ident(#args_names)#method_await,
714                                    ),
715                                )
716                                .map_err(|e| #zbus::fdo::Error::Failed(e.to_string()))?,
717                            );
718                        })
719                    };
720
721                    get_all.extend(q);
722
723                    let prop_value_handled = if is_fallible_property {
724                        quote!(self.#ident(#args_names)#method_await?)
725                    } else {
726                        quote!(self.#ident(#args_names)#method_await)
727                    };
728
729                    if p.emits_changed_signal == PropertyEmitsChangedSignal::True {
730                        let changed_doc = format!(
731                            "Emit the “PropertiesChanged” signal with the new value for the\n\
732                             `{member_name}` property.\n\n\
733                             This method should be called if a property value changes outside\n\
734                             its setter method."
735                        );
736                        let prop_changed_method = quote!(
737                            #[doc = #changed_doc]
738                            pub async fn #prop_changed_method_name(
739                                &self,
740                                __zbus__signal_emitter: &#zbus::object_server::SignalEmitter<'_>,
741                            ) -> #zbus::Result<()> {
742                                let __zbus__header = ::std::option::Option::None::<&#zbus::message::Header<'_>>;
743                                let __zbus__connection = __zbus__signal_emitter.connection();
744                                let __zbus__object_server = __zbus__connection.object_server();
745                                #args_from_msg
746                                let mut changed = ::std::collections::HashMap::new();
747                                let value = <#zbus::zvariant::Value as ::std::convert::From<_>>::from(#prop_value_handled);
748                                changed.insert(#member_name, value);
749                                #zbus::fdo::Properties::properties_changed(
750                                    __zbus__signal_emitter,
751                                    #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name),
752                                    changed,
753                                    ::std::borrow::Cow::Borrowed(&[]),
754                                ).await
755                            }
756                        );
757
758                        generated_signals.extend(prop_changed_method);
759                    }
760
761                    if p.emits_changed_signal == PropertyEmitsChangedSignal::Invalidates {
762                        let invalidate_doc = format!(
763                            "Emit the “PropertiesChanged” signal for the `{member_name}` property\n\
764                             without including the new value.\n\n\
765                             It is usually better to call `{prop_changed_method_name}` instead so\n\
766                             that interested peers do not need to fetch the new value separately\n\
767                             (causing excess traffic on the bus)."
768                        );
769                        let prop_invalidate_method = quote!(
770                            #[doc = #invalidate_doc]
771                            pub async fn #prop_invalidate_method_name(
772                                &self,
773                                __zbus__signal_emitter: &#zbus::object_server::SignalEmitter<'_>,
774                            ) -> #zbus::Result<()> {
775                                #zbus::fdo::Properties::properties_changed(
776                                    __zbus__signal_emitter,
777                                    #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name),
778                                    ::std::collections::HashMap::new(),
779                                    ::std::borrow::Cow::Borrowed(&[#member_name]),
780                                ).await
781                            }
782                        );
783
784                        generated_signals.extend(prop_invalidate_method);
785                    }
786                }
787            }
788            MethodType::Other => {
789                introspect.extend(doc_comments);
790                introspect.extend(introspect_method(&member_name, &intro_args));
791
792                let m = quote! {
793                    #(#cfg_attrs)*
794                    #member_name => {
795                        let future = async move {
796                            #args_from_msg
797                            let reply = self.#ident(#args_names)#method_await;
798                            let hdr = __zbus__message.header();
799                            if hdr.primary().flags().contains(zbus::message::Flags::NoReplyExpected) {
800                                Ok(())
801                            } else {
802                                #reply
803                            }
804                        };
805                        #zbus::object_server::DispatchResult2::Async(::std::boxed::Box::pin(async move {
806                            future.await.map_err(|e| match e {
807                                #zbus::Error::FDO(e) => *e,
808                                e => #zbus::fdo::Error::Failed(::std::format!("{e}")),
809                            })
810                        }))
811                    },
812                };
813
814                if is_mut {
815                    call_dispatch.extend(quote! {
816                        #(#cfg_attrs)*
817                        #member_name => #zbus::object_server::DispatchResult2::RequiresMut,
818                    });
819                    call_mut_dispatch.extend(m);
820                } else {
821                    call_dispatch.extend(m);
822                }
823            }
824        }
825
826        if let Some(proxy) = &mut proxy {
827            proxy.add_method(info, &properties)?;
828        }
829    }
830
831    introspect_properties(&mut introspect, properties)?;
832
833    let generics = &input.generics;
834    let where_clause = &generics.where_clause;
835
836    let generated_signals_impl = if generated_signals.is_empty() {
837        quote!()
838    } else {
839        quote! {
840            impl #generics #self_ty
841            #where_clause
842            {
843                #generated_signals
844            }
845        }
846    };
847    let signals_trait_and_impl = if signals_trait_methods.is_empty() {
848        quote!()
849    } else {
850        let signals_trait_name = format_ident!("{}Signals", ty);
851        let signals_trait_doc = format!("Trait providing all signal emission methods for `{ty}`.");
852
853        quote! {
854            #[doc = #signals_trait_doc]
855            #[#zbus::export::async_trait::async_trait]
856            pub trait #signals_trait_name {
857                #signals_trait_methods
858            }
859
860            #[#zbus::export::async_trait::async_trait]
861            impl #signals_trait_name for #zbus::object_server::SignalEmitter<'_>
862            {
863                #signals_emitter_impl_methods
864            }
865
866            #[#zbus::export::async_trait::async_trait]
867            impl #generics #signals_trait_name for #zbus::object_server::InterfaceRef<#self_ty>
868            #where_clause
869            {
870                #signals_interface_ref_impl_methods
871            }
872        }
873    };
874
875    let proxy = proxy.map(|proxy| proxy.r#gen()).transpose()?;
876    let introspect_format_str = format!("{}<interface name=\"{iface_name}\">", "{:indent$}");
877
878    Ok(quote! {
879        #input
880
881        #generated_signals_impl
882
883        #signals_trait_and_impl
884
885        #[#zbus::export::async_trait::async_trait]
886        impl #generics #zbus::object_server::Interface for #self_ty
887        #where_clause
888        {
889            fn name() -> #zbus::names::InterfaceName<'static> {
890                #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name)
891            }
892
893            fn spawn_tasks_for_methods(&self) -> bool {
894                #with_spawn
895            }
896
897            async fn get(
898                &self,
899                __zbus__property_name: &str,
900                __zbus__object_server: &#zbus::ObjectServer,
901                __zbus__connection: &#zbus::Connection,
902                __zbus__header: Option<&#zbus::message::Header<'_>>,
903                __zbus__signal_emitter: &#zbus::object_server::SignalEmitter<'_>,
904            ) -> ::std::option::Option<#zbus::fdo::Result<#zbus::zvariant::OwnedValue>> {
905                match __zbus__property_name {
906                    #get_dispatch
907                    _ => ::std::option::Option::None,
908                }
909            }
910
911            async fn get_all(
912                &self,
913                __zbus__object_server: &#zbus::ObjectServer,
914                __zbus__connection: &#zbus::Connection,
915                __zbus__header: Option<&#zbus::message::Header<'_>>,
916                __zbus__signal_emitter: &#zbus::object_server::SignalEmitter<'_>,
917            ) -> #zbus::fdo::Result<::std::collections::HashMap<
918                ::std::string::String,
919                #zbus::zvariant::OwnedValue,
920            >> {
921                let mut props: ::std::collections::HashMap<
922                    ::std::string::String,
923                    #zbus::zvariant::OwnedValue,
924                > = ::std::collections::HashMap::new();
925                #get_all
926                Ok(props)
927            }
928
929            fn set<'call>(
930                &'call self,
931                __zbus__property_name: &'call str,
932                value: &'call #zbus::zvariant::Value<'_>,
933                __zbus__object_server: &'call #zbus::ObjectServer,
934                __zbus__connection: &'call #zbus::Connection,
935                __zbus__header: Option<&'call #zbus::message::Header<'_>>,
936                __zbus__signal_emitter: &'call #zbus::object_server::SignalEmitter<'_>,
937            ) -> #zbus::object_server::DispatchResult2<'call> {
938                match __zbus__property_name {
939                    #set_dispatch
940                    _ => #zbus::object_server::DispatchResult2::NotFound,
941                }
942            }
943
944            async fn set_mut(
945                &mut self,
946                __zbus__property_name: &str,
947                value: &#zbus::zvariant::Value<'_>,
948                __zbus__object_server: &#zbus::ObjectServer,
949                __zbus__connection: &#zbus::Connection,
950                __zbus__header: Option<&#zbus::message::Header<'_>>,
951                __zbus__signal_emitter: &#zbus::object_server::SignalEmitter<'_>,
952            ) -> ::std::option::Option<#zbus::fdo::Result<()>> {
953                match __zbus__property_name {
954                    #set_mut_dispatch
955                    _ => ::std::option::Option::None,
956                }
957            }
958
959            fn call<'call>(
960                &'call self,
961                __zbus__object_server: &'call #zbus::ObjectServer,
962                __zbus__connection: &'call #zbus::Connection,
963                __zbus__message: &'call #zbus::message::Message,
964                name: #zbus::names::MemberName<'call>,
965            ) -> #zbus::object_server::DispatchResult2<'call> {
966                match name.as_str() {
967                    #call_dispatch
968                    _ => #zbus::object_server::DispatchResult2::NotFound,
969                }
970            }
971
972            fn call_mut<'call>(
973                &'call mut self,
974                __zbus__object_server: &'call #zbus::ObjectServer,
975                __zbus__connection: &'call #zbus::Connection,
976                __zbus__message: &'call #zbus::message::Message,
977                name: #zbus::names::MemberName<'call>,
978            ) -> #zbus::object_server::DispatchResult2<'call> {
979                match name.as_str() {
980                    #call_mut_dispatch
981                    _ => #zbus::object_server::DispatchResult2::NotFound,
982                }
983            }
984
985            fn introspect_to_writer(&self, writer: &mut dyn ::std::fmt::Write, level: usize) {
986                ::std::writeln!(
987                    writer,
988                    #introspect_format_str,
989                    "",
990                    indent = level
991                ).unwrap();
992                {
993                    use #zbus::zvariant::Type;
994
995                    let level = level + 2;
996                    #introspect
997                }
998                ::std::writeln!(writer, r#"{:indent$}</interface>"#, "", indent = level).unwrap();
999            }
1000        }
1001
1002        #proxy
1003    })
1004}
1005
1006fn get_args_from_inputs(
1007    inputs: &[PatType],
1008    method_type: MethodType,
1009    zbus: &TokenStream,
1010) -> syn::Result<(TokenStream, TokenStream)> {
1011    if inputs.is_empty() {
1012        Ok((quote!(), quote!()))
1013    } else {
1014        let mut server_arg_decl = None;
1015        let mut conn_arg_decl = None;
1016        let mut header_arg_decl = None;
1017        let mut signal_emitter_arg_decl = None;
1018        let mut args_names = Vec::new();
1019        let mut tys = Vec::new();
1020
1021        for input in inputs {
1022            let ArgAttributes {
1023                object_server,
1024                connection,
1025                header,
1026                signal_emitter,
1027                signal_context,
1028            } = ArgAttributes::parse(&input.attrs)?;
1029
1030            if object_server {
1031                if server_arg_decl.is_some() {
1032                    return Err(Error::new_spanned(
1033                        input,
1034                        "There can only be one object_server argument",
1035                    ));
1036                }
1037
1038                let server_arg = &input.pat;
1039                server_arg_decl = Some(quote! { let #server_arg = &__zbus__object_server; });
1040            } else if connection {
1041                if conn_arg_decl.is_some() {
1042                    return Err(Error::new_spanned(
1043                        input,
1044                        "There can only be one connection argument",
1045                    ));
1046                }
1047
1048                let conn_arg = &input.pat;
1049                conn_arg_decl = Some(quote! { let #conn_arg = &__zbus__connection; });
1050            } else if header {
1051                if header_arg_decl.is_some() {
1052                    return Err(Error::new_spanned(
1053                        input,
1054                        "There can only be one header argument",
1055                    ));
1056                }
1057
1058                let header_arg = &input.pat;
1059
1060                header_arg_decl = match method_type {
1061                    MethodType::Property(_) => Some(quote! {
1062                        let #header_arg =
1063                            ::std::option::Option::<&#zbus::message::Header<'_>>::cloned(
1064                                __zbus__header,
1065                            );
1066                    }),
1067                    _ => Some(quote! { let #header_arg = __zbus__message.header(); }),
1068                };
1069            } else if signal_context || signal_emitter {
1070                if signal_emitter_arg_decl.is_some() {
1071                    return Err(Error::new_spanned(
1072                        input,
1073                        "There can only be one `signal_emitter` or `signal_context` argument",
1074                    ));
1075                }
1076
1077                let signal_context_arg = &input.pat;
1078
1079                signal_emitter_arg_decl = match method_type {
1080                    MethodType::Property(_) => Some(
1081                        quote! { let #signal_context_arg = ::std::clone::Clone::clone(__zbus__signal_emitter); },
1082                    ),
1083                    _ => Some(quote! {
1084                        let #signal_context_arg = match hdr.path() {
1085                            ::std::option::Option::Some(p) => {
1086                                #zbus::object_server::SignalEmitter::new(__zbus__connection, p).expect("Infallible conversion failed")
1087                            }
1088                            ::std::option::Option::None => {
1089                                let err = #zbus::fdo::Error::UnknownObject("Path Required".into());
1090                                return __zbus__connection.reply_dbus_error(&hdr, err).await;
1091                            }
1092                        };
1093                    }),
1094                };
1095            } else {
1096                args_names.push(pat_ident(input).unwrap());
1097                tys.push(&input.ty);
1098            }
1099        }
1100
1101        let (hdr_init, msg_init, args_decl) = match method_type {
1102            MethodType::Property(PropertyType::Getter) => (quote! {}, quote! {}, quote! {}),
1103            MethodType::Property(PropertyType::Setter) => (
1104                quote! { let hdr = __zbus__header.as_ref().unwrap(); },
1105                quote! {},
1106                quote! {},
1107            ),
1108            _ => (
1109                quote! { let hdr = __zbus__message.header(); },
1110                quote! { let msg_body = __zbus__message.body(); },
1111                quote! {
1112                    let (#(#args_names),*): (#(#tys),*) =
1113                        match msg_body.deserialize() {
1114                            ::std::result::Result::Ok(r) => r,
1115                            ::std::result::Result::Err(e) => {
1116                                let err = <#zbus::fdo::Error as ::std::convert::From<_>>::from(e);
1117                                return __zbus__connection.reply_dbus_error(&hdr, err).await;
1118                            }
1119                        };
1120                },
1121            ),
1122        };
1123
1124        let args_from_msg = quote! {
1125            #hdr_init
1126
1127            #msg_init
1128
1129            #server_arg_decl
1130
1131            #conn_arg_decl
1132
1133            #header_arg_decl
1134
1135            #signal_emitter_arg_decl
1136
1137            #args_decl
1138        };
1139
1140        let all_args_names = inputs.iter().filter_map(pat_ident);
1141        let all_args_names = quote! { #(#all_args_names,)* };
1142
1143        Ok((args_from_msg, all_args_names))
1144    }
1145}
1146
1147// Removes all `zbus` attributes from the given inputs.
1148fn clear_input_arg_attrs(inputs: &mut Punctuated<FnArg, Token![,]>) {
1149    for input in inputs {
1150        if let FnArg::Typed(t) = input {
1151            t.attrs.retain(|attr| !attr.path().is_ident("zbus"));
1152        }
1153    }
1154}
1155
1156fn introspect_signal(name: &str, args: &TokenStream) -> TokenStream {
1157    let format_str = format!("{}<signal name=\"{name}\">", "{:indent$}");
1158    quote!(
1159        ::std::writeln!(writer, #format_str, "", indent = level).unwrap();
1160        {
1161            let level = level + 2;
1162            #args
1163        }
1164        ::std::writeln!(writer, "{:indent$}</signal>", "", indent = level).unwrap();
1165    )
1166}
1167
1168fn introspect_method(name: &str, args: &TokenStream) -> TokenStream {
1169    let format_str = format!("{}<method name=\"{name}\">", "{:indent$}");
1170    quote!(
1171        ::std::writeln!(writer, #format_str, "", indent = level).unwrap();
1172        {
1173            let level = level + 2;
1174            #args
1175        }
1176        ::std::writeln!(writer, "{:indent$}</method>", "", indent = level).unwrap();
1177    )
1178}
1179
1180fn introspect_input_args<'i>(
1181    inputs: &'i [PatType],
1182    is_signal: bool,
1183    cfg_attrs: &'i [&'i syn::Attribute],
1184) -> impl Iterator<Item = TokenStream> + 'i {
1185    inputs
1186        .iter()
1187        .filter_map(move |pat_type @ PatType { ty, attrs, .. }| {
1188            if is_special_arg(attrs) {
1189                return None;
1190            }
1191
1192            let ident = pat_ident(pat_type).unwrap();
1193            let arg_name = quote!(#ident).to_string();
1194            let arg_name = arg_name.strip_prefix("r#").unwrap_or(arg_name.as_str());
1195            let dir = if is_signal { "" } else { " direction=\"in\"" };
1196            let format_str = format!(
1197                "{}<arg name=\"{arg_name}\" type=\"{}\"{dir}/>",
1198                "{:indent$}", "{}",
1199            );
1200            Some(quote!(
1201                #(#cfg_attrs)*
1202                ::std::writeln!(writer, #format_str, "", <#ty>::SIGNATURE, indent = level).unwrap();
1203            ))
1204        })
1205}
1206
1207fn count_regular_args(inputs: &[PatType]) -> usize {
1208    inputs
1209        .iter()
1210        .filter(|PatType { attrs, .. }| !is_special_arg(attrs))
1211        .count()
1212}
1213
1214fn is_special_arg(attrs: &[Attribute]) -> bool {
1215    attrs.iter().any(|attr| {
1216        if !attr.path().is_ident("zbus") {
1217            return false;
1218        }
1219
1220        let Ok(list) = &attr.meta.require_list() else {
1221            return false;
1222        };
1223        let Ok(nested) = list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
1224        else {
1225            return false;
1226        };
1227
1228        nested.iter().any(|nested_meta| {
1229            matches!(
1230                nested_meta,
1231                Meta::Path(path)
1232                if path.is_ident("object_server") ||
1233                    path.is_ident("connection") ||
1234                    path.is_ident("header") ||
1235                    path.is_ident("signal_context") ||
1236                    path.is_ident("signal_emitter")
1237            )
1238        })
1239    })
1240}
1241
1242fn introspect_output_arg(
1243    ty: &Type,
1244    arg_name: Option<&String>,
1245    cfg_attrs: &[&syn::Attribute],
1246) -> TokenStream {
1247    let arg_name_attr = match arg_name {
1248        Some(name) => format!("name=\"{name}\" "),
1249        None => String::from(""),
1250    };
1251
1252    let format_str = format!(
1253        "{}<arg {arg_name_attr}type=\"{}\" direction=\"out\"/>",
1254        "{:indent$}", "{}",
1255    );
1256    quote!(
1257        #(#cfg_attrs)*
1258        ::std::writeln!(writer, #format_str, "", <#ty>::SIGNATURE, indent = level).unwrap();
1259    )
1260}
1261
1262fn get_result_inner_type(p: &TypePath) -> syn::Result<&Type> {
1263    if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &p
1264        .path
1265        .segments
1266        .last()
1267        .ok_or_else(|| Error::new_spanned(p, "unsupported result type"))?
1268        .arguments
1269    {
1270        if let Some(syn::GenericArgument::Type(ty)) = args.first() {
1271            return Ok(ty);
1272        }
1273    }
1274
1275    Err(Error::new_spanned(p, "unhandled Result return"))
1276}
1277
1278fn introspect_add_output_args(
1279    args: &mut TokenStream,
1280    output: &ReturnType,
1281    arg_names: Option<&[String]>,
1282    cfg_attrs: &[&syn::Attribute],
1283) -> syn::Result<bool> {
1284    let mut is_result_output = false;
1285
1286    if let ReturnType::Type(_, ty) = output {
1287        let mut ty = ty.as_ref();
1288
1289        if let Type::Path(p) = ty {
1290            is_result_output = p
1291                .path
1292                .segments
1293                .last()
1294                .ok_or_else(|| Error::new_spanned(ty, "unsupported output type"))?
1295                .ident
1296                == "Result";
1297            if is_result_output {
1298                ty = get_result_inner_type(p)?;
1299            }
1300        }
1301
1302        if let Type::Tuple(t) = ty {
1303            if let Some(arg_names) = arg_names {
1304                if t.elems.len() != arg_names.len() {
1305                    return Err(Error::new_spanned(
1306                        ty,
1307                        format!(
1308                            "out_args specifies {} names but method returns {} values",
1309                            arg_names.len(),
1310                            t.elems.len()
1311                        ),
1312                    ));
1313                }
1314            }
1315            for i in 0..t.elems.len() {
1316                let name = arg_names.map(|names| &names[i]);
1317                args.extend(introspect_output_arg(&t.elems[i], name, cfg_attrs));
1318            }
1319        } else {
1320            // Note: The type might still serialize as a DBus struct/tuple if it has a custom
1321            // signature (e.g., `#[zvariant(signature = "(...)")]`), but we can't detect this
1322            // from the Rust type structure alone.
1323            //
1324            // If out_args has exactly one name, apply it to this output.
1325            // If out_args has multiple names, the user likely has a type with a tuple signature,
1326            // but we can't generate separate <arg> elements for each field since we don't have
1327            // access to the individual field types. In this case, we generate a single <arg>
1328            // with the type's full signature and no name. The signature will be correct at runtime.
1329            let name = arg_names.and_then(|names| {
1330                if names.len() == 1 {
1331                    Some(&names[0])
1332                } else {
1333                    None
1334                }
1335            });
1336            args.extend(introspect_output_arg(ty, name, cfg_attrs));
1337        }
1338    }
1339
1340    Ok(is_result_output)
1341}
1342
1343fn get_return_type(output: &ReturnType) -> syn::Result<&Type> {
1344    if let ReturnType::Type(_, ty) = output {
1345        let ty = ty.as_ref();
1346
1347        if let Type::Path(p) = ty {
1348            let is_result_output = p
1349                .path
1350                .segments
1351                .last()
1352                .ok_or_else(|| Error::new_spanned(ty, "unsupported property type"))?
1353                .ident
1354                == "Result";
1355            if is_result_output {
1356                return get_result_inner_type(p);
1357            }
1358        }
1359
1360        Ok(ty)
1361    } else {
1362        Err(Error::new_spanned(output, "Invalid return type"))
1363    }
1364}
1365
1366fn introspect_properties(
1367    introspection: &mut TokenStream,
1368    properties: BTreeMap<String, Property>,
1369) -> syn::Result<()> {
1370    for (name, prop) in properties {
1371        let access = if prop.read && prop.write {
1372            "readwrite"
1373        } else if prop.read {
1374            "read"
1375        } else if prop.write {
1376            "write"
1377        } else {
1378            unreachable!("Properties should have at least one access type");
1379        };
1380        let ty = prop.ty.unwrap();
1381
1382        let doc_comments = prop.doc_comments;
1383        if prop.emits_changed_signal == PropertyEmitsChangedSignal::True {
1384            let format_str = format!(
1385                "{}<property name=\"{name}\" type=\"{}\" access=\"{access}\"/>",
1386                "{:indent$}", "{}",
1387            );
1388            introspection.extend(quote!(
1389                #doc_comments
1390                ::std::writeln!(writer, #format_str, "", <#ty>::SIGNATURE, indent = level).unwrap();
1391            ));
1392        } else {
1393            let emits_changed_signal = prop.emits_changed_signal.to_string();
1394            let annot_name = "org.freedesktop.DBus.Property.EmitsChangedSignal";
1395            let format_str = format!(
1396                "{}<property name=\"{name}\" type=\"{}\" access=\"{access}\">\n\
1397                    {}<annotation name=\"{annot_name}\" value=\"{emits_changed_signal}\"/>\n\
1398                {}</property>",
1399                "{:indent$}", "{}", "{:annot_indent$}", "{:indent$}",
1400            );
1401            introspection.extend(quote!(
1402                #doc_comments
1403                ::std::writeln!(
1404                    writer,
1405                    #format_str,
1406                    "", <#ty>::SIGNATURE, "", "", indent = level, annot_indent = level + 2,
1407                ).unwrap();
1408            ));
1409        }
1410    }
1411
1412    Ok(())
1413}
1414
1415pub fn to_xml_docs(lines: Vec<String>) -> TokenStream {
1416    let mut docs = quote!();
1417
1418    let mut lines: Vec<&str> = lines
1419        .iter()
1420        .skip_while(|s| is_blank(s))
1421        .flat_map(|s| s.split('\n'))
1422        .collect();
1423
1424    while let Some(true) = lines.last().map(|s| is_blank(s)) {
1425        lines.pop();
1426    }
1427
1428    if lines.is_empty() {
1429        return docs;
1430    }
1431
1432    docs.extend(quote!(::std::writeln!(writer, "{:indent$}<!--", "", indent = level).unwrap();));
1433    for line in lines {
1434        if !line.is_empty() {
1435            docs.extend(
1436                quote!(::std::writeln!(writer, "{:indent$}{}", "", #line, indent = level).unwrap();),
1437            );
1438        } else {
1439            docs.extend(quote!(::std::writeln!(writer, "").unwrap();));
1440        }
1441    }
1442    docs.extend(quote!(::std::writeln!(writer, "{:indent$} -->", "", indent = level).unwrap();));
1443
1444    docs
1445}
1446
1447// Like ImplItemFn, but with a semicolon at the end instead of a body block
1448struct ImplItemSignal {
1449    attrs: Vec<Attribute>,
1450    vis: Visibility,
1451    sig: Signature,
1452}
1453
1454impl Parse for ImplItemSignal {
1455    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
1456        let attrs = input.call(Attribute::parse_outer)?;
1457        let vis = input.parse()?;
1458        let sig = input.parse()?;
1459        let _: Token![;] = input.parse()?;
1460
1461        Ok(ImplItemSignal { attrs, vis, sig })
1462    }
1463}
1464
1465#[derive(Debug)]
1466struct Proxy {
1467    // The type name
1468    ty: Ident,
1469    // The interface name
1470    iface_name: String,
1471    // The zbus crate
1472    zbus: TokenStream,
1473
1474    // Input
1475    attrs: ProxyAttributes,
1476
1477    // Output
1478    methods: TokenStream,
1479}
1480
1481impl Proxy {
1482    fn new(ty: &Ident, iface_name: &str, attrs: ProxyAttributes, zbus: &TokenStream) -> Self {
1483        Self {
1484            iface_name: iface_name.to_string(),
1485            ty: ty.clone(),
1486            zbus: zbus.clone(),
1487            attrs,
1488            methods: quote!(),
1489        }
1490    }
1491
1492    fn add_method(
1493        &mut self,
1494        method_info: MethodInfo,
1495        properties: &BTreeMap<String, Property>,
1496    ) -> syn::Result<()> {
1497        let inputs: Punctuated<PatType, Comma> = method_info
1498            .typed_inputs
1499            .iter()
1500            .filter(|input| {
1501                let a = ArgAttributes::parse(&input.attrs).unwrap();
1502                !a.object_server
1503                    && !a.connection
1504                    && !a.header
1505                    && !a.signal_context
1506                    && !a.signal_emitter
1507            })
1508            .cloned()
1509            .collect();
1510        let zbus = &self.zbus;
1511        let ret = match &method_info.output {
1512            ReturnType::Type(_, ty) => {
1513                let ty = ty.as_ref();
1514
1515                if let Type::Path(p) = ty {
1516                    let is_result_output = p
1517                        .path
1518                        .segments
1519                        .last()
1520                        .ok_or_else(|| Error::new_spanned(ty, "unsupported return type"))?
1521                        .ident
1522                        == "Result";
1523                    if is_result_output {
1524                        let is_prop = matches!(method_info.method_type, MethodType::Property(_));
1525
1526                        if is_prop {
1527                            // Proxy methods always return `zbus::Result<T>`
1528                            let inner_ty = get_result_inner_type(p)?;
1529                            quote! { #zbus::Result<#inner_ty> }
1530                        } else {
1531                            quote! { #ty }
1532                        }
1533                    } else {
1534                        quote! { #zbus::Result<#ty> }
1535                    }
1536                } else {
1537                    quote! { #zbus::Result<#ty> }
1538                }
1539            }
1540            ReturnType::Default => quote! { #zbus::Result<()> },
1541        };
1542        let ident = &method_info.ident;
1543        let member_name = method_info.member_name;
1544        let mut proxy_method_attrs = quote! { name = #member_name, };
1545        proxy_method_attrs.extend(match method_info.method_type {
1546            MethodType::Signal => quote!(signal,),
1547            MethodType::Property(_) => {
1548                let emits_changed_signal = properties
1549                    .get(&member_name)
1550                    .unwrap()
1551                    .emits_changed_signal
1552                    .to_string();
1553                let emits_changed_signal = quote! { emits_changed_signal = #emits_changed_signal };
1554
1555                quote! { property(#emits_changed_signal), }
1556            }
1557            MethodType::Other => quote!(),
1558        });
1559        if let Some(attrs) = method_info.proxy_attrs {
1560            if let Some(object) = attrs.object {
1561                proxy_method_attrs.extend(quote! { object = #object, });
1562            }
1563            if let Some(async_object) = attrs.async_object {
1564                proxy_method_attrs.extend(quote! { async_object = #async_object, });
1565            }
1566            if let Some(blocking_object) = attrs.blocking_object {
1567                proxy_method_attrs.extend(quote! { blocking_object = #blocking_object, });
1568            }
1569            if attrs.object_vec {
1570                proxy_method_attrs.extend(quote! { object_vec, });
1571            }
1572            if attrs.no_reply {
1573                proxy_method_attrs.extend(quote! { no_reply, });
1574            }
1575            if attrs.no_autostart {
1576                proxy_method_attrs.extend(quote! { no_autostart, });
1577            }
1578            if attrs.allow_interactive_auth {
1579                proxy_method_attrs.extend(quote! { allow_interactive_auth, });
1580            }
1581        }
1582        let cfg_attrs = method_info.cfg_attrs;
1583        let doc_attrs = method_info.doc_attrs;
1584        self.methods.extend(quote! {
1585            #(#cfg_attrs)*
1586            #(#doc_attrs)*
1587            #[zbus(#proxy_method_attrs)]
1588            fn #ident(&self, #inputs) -> #ret;
1589        });
1590
1591        Ok(())
1592    }
1593
1594    fn r#gen(&self) -> syn::Result<TokenStream> {
1595        let attrs = &self.attrs;
1596        let (
1597            assume_defaults,
1598            default_path,
1599            default_service,
1600            async_name,
1601            blocking_name,
1602            gen_async,
1603            gen_blocking,
1604            ty,
1605            methods,
1606        ) = (
1607            attrs
1608                .assume_defaults
1609                .map(|value| quote! { assume_defaults = #value, }),
1610            attrs
1611                .default_path
1612                .as_ref()
1613                .map(|value| quote! { default_path = #value, }),
1614            attrs
1615                .default_service
1616                .as_ref()
1617                .map(|value| quote! { default_service = #value, }),
1618            attrs
1619                .async_name
1620                .as_ref()
1621                .map(|value| quote! { async_name = #value, }),
1622            attrs
1623                .blocking_name
1624                .as_ref()
1625                .map(|value| quote! { blocking_name = #value, }),
1626            attrs.gen_async.map(|value| quote! { gen_async = #value, }),
1627            attrs
1628                .gen_blocking
1629                .map(|value| quote! { gen_blocking = #value, }),
1630            &self.ty,
1631            &self.methods,
1632        );
1633        let iface_name = &self.iface_name;
1634        let vis = match &self.attrs.visibility {
1635            Some(s) => parse_str::<Visibility>(s)?,
1636            None => Visibility::Public(Token![pub](ty.span())),
1637        };
1638        let zbus = &self.zbus;
1639        let proxy_doc = format!("Proxy for the `{iface_name}` interface.");
1640        Ok(quote! {
1641            #[doc = #proxy_doc]
1642            #[#zbus::proxy(
1643                name = #iface_name,
1644                #assume_defaults
1645                #default_path
1646                #default_service
1647                #async_name
1648                #blocking_name
1649                #gen_async
1650                #gen_blocking
1651            )]
1652            #vis trait #ty {
1653                #methods
1654            }
1655        })
1656    }
1657}