zbus_macros/
iface.rs

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