zbus_macros/
iface.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use std::collections::BTreeMap;
4use syn::{
5    parse_quote, punctuated::Punctuated, spanned::Spanned, AngleBracketedGenericArguments,
6    AttributeArgs, Error, FnArg, GenericArgument, ImplItem, ItemImpl, Lit::Str, Meta,
7    Meta::NameValue, MetaList, MetaNameValue, NestedMeta, PatType, PathArguments, ReturnType,
8    Signature, Token, Type, TypePath,
9};
10use zvariant_utils::{case, def_attrs};
11
12use crate::utils::*;
13
14// FIXME: The list name should once be "zbus" instead of "dbus_interface" (like in serde).
15def_attrs! {
16    crate dbus_interface;
17
18    pub TraitAttributes("trait") {
19        interface str,
20        name str
21    };
22
23    pub MethodAttributes("method") {
24        name str,
25        signal none,
26        property none,
27        out_args [str]
28    };
29}
30
31mod arg_attrs {
32    use zvariant_utils::def_attrs;
33
34    def_attrs! {
35        crate zbus;
36
37        pub ArgAttributes("argument") {
38            object_server none,
39            connection none,
40            header none,
41            signal_context none
42        };
43    }
44}
45
46use arg_attrs::ArgAttributes;
47
48#[derive(Debug)]
49struct Property<'a> {
50    read: bool,
51    write: bool,
52    ty: Option<&'a Type>,
53    doc_comments: TokenStream,
54}
55
56impl<'a> Property<'a> {
57    fn new() -> Self {
58        Self {
59            read: false,
60            write: false,
61            ty: None,
62            doc_comments: quote!(),
63        }
64    }
65}
66
67pub fn expand(args: AttributeArgs, mut input: ItemImpl) -> syn::Result<TokenStream> {
68    let zbus = zbus_path();
69
70    let self_ty = &input.self_ty;
71    let mut properties = BTreeMap::new();
72    let mut set_dispatch = quote!();
73    let mut set_mut_dispatch = quote!();
74    let mut get_dispatch = quote!();
75    let mut get_all = quote!();
76    let mut call_dispatch = quote!();
77    let mut call_mut_dispatch = quote!();
78    let mut introspect = quote!();
79    let mut generated_signals = quote!();
80
81    // the impl Type
82    let ty = match input.self_ty.as_ref() {
83        Type::Path(p) => {
84            &p.path
85                .segments
86                .last()
87                .ok_or_else(|| Error::new_spanned(p, "Unsupported 'impl' type"))?
88                .ident
89        }
90        _ => return Err(Error::new_spanned(&input.self_ty, "Invalid type")),
91    };
92
93    let iface_name =
94        {
95            let TraitAttributes { name, interface } = TraitAttributes::parse_nested_metas(&args)?;
96
97            match (name, interface) {
98                (Some(name), None) | (None, Some(name)) => name,
99                (None, None) => format!("org.freedesktop.{ty}"),
100                (Some(_), Some(_)) => return Err(syn::Error::new(
101                    input.span(),
102                    "`name` and `interface` attributes should not be specified at the same time",
103                )),
104            }
105        };
106
107    for method in &mut input.items {
108        let method = match method {
109            ImplItem::Method(m) => m,
110            _ => continue,
111        };
112
113        let is_async = method.sig.asyncness.is_some();
114
115        let Signature {
116            ident,
117            inputs,
118            output,
119            ..
120        } = &mut method.sig;
121
122        let attrs = MethodAttributes::parse(&method.attrs)?;
123        method
124            .attrs
125            .retain(|attr| !attr.path.is_ident("dbus_interface"));
126
127        let docs = get_doc_attrs(&method.attrs)
128            .iter()
129            .filter_map(|attr| {
130                if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() {
131                    Some(s.value())
132                } else {
133                    // non #[doc = "..."] attributes are not our concern
134                    // we leave them for rustc to handle
135                    None
136                }
137            })
138            .collect();
139
140        let doc_comments = to_xml_docs(docs);
141        let is_property = attrs.property;
142        let is_signal = attrs.signal;
143        let out_args = attrs.out_args.as_deref();
144        assert!(!is_property || !is_signal);
145
146        let has_inputs = inputs.len() > 1;
147
148        let is_mut = if let FnArg::Receiver(r) = inputs
149            .first()
150            .ok_or_else(|| Error::new_spanned(&ident, "not &self method"))?
151        {
152            r.mutability.is_some()
153        } else if is_signal {
154            false
155        } else {
156            return Err(Error::new_spanned(&method, "missing receiver"));
157        };
158        if is_signal && !is_async {
159            return Err(Error::new_spanned(&method, "signals must be async"));
160        }
161        let method_await = if is_async {
162            quote! { .await }
163        } else {
164            quote! {}
165        };
166
167        let handle_fallible_property = quote! { .map(|e| <#zbus::zvariant::Value as ::std::convert::From<_>>::from(e).to_owned()) };
168
169        let mut typed_inputs = inputs
170            .iter()
171            .filter_map(typed_arg)
172            .cloned()
173            .collect::<Vec<_>>();
174        let signal_context_arg = if is_signal {
175            if typed_inputs.is_empty() {
176                return Err(Error::new_spanned(
177                    &inputs,
178                    "Expected a `&zbus::SignalContext<'_> argument",
179                ));
180            }
181            Some(typed_inputs.remove(0))
182        } else {
183            None
184        };
185
186        let mut intro_args = quote!();
187        intro_args.extend(introspect_input_args(&typed_inputs, is_signal));
188        let is_result_output = introspect_add_output_args(&mut intro_args, output, out_args)?;
189
190        let (args_from_msg, args_names) = get_args_from_inputs(&typed_inputs, &zbus)?;
191
192        clean_input_args(inputs);
193
194        let reply = if is_result_output {
195            let ret = quote!(r);
196
197            quote!(match reply {
198                ::std::result::Result::Ok(r) => c.reply(m, &#ret).await,
199                ::std::result::Result::Err(e) => {
200                    let hdr = m.header()?;
201                    c.reply_dbus_error(&hdr, e).await
202                }
203            })
204        } else {
205            quote!(c.reply(m, &reply).await)
206        };
207
208        let member_name = attrs.name.clone().unwrap_or_else(|| {
209            let mut name = ident.to_string();
210            if is_property && has_inputs {
211                assert!(name.starts_with("set_"));
212                name = name[4..].to_string();
213            }
214            pascal_case(&name)
215        });
216
217        if is_signal {
218            introspect.extend(doc_comments);
219            introspect.extend(introspect_signal(&member_name, &intro_args));
220            let signal_context = signal_context_arg.unwrap().pat;
221
222            method.block = parse_quote!({
223                #signal_context.connection().emit_signal(
224                    #signal_context.destination(),
225                    #signal_context.path(),
226                    <#self_ty as #zbus::Interface>::name(),
227                    #member_name,
228                    &(#args_names),
229                )
230                .await
231            });
232        } else if is_property {
233            let p = properties.entry(member_name.to_string());
234
235            let sk_member_name = case::snake_case(&member_name);
236            let prop_changed_method_name = format_ident!("{sk_member_name}_changed");
237            let prop_invalidate_method_name = format_ident!("{sk_member_name}_invalidate");
238
239            let p = p.or_insert_with(Property::new);
240            p.doc_comments.extend(doc_comments);
241            if has_inputs {
242                p.write = true;
243
244                let set_call = if is_result_output {
245                    quote!(self.#ident(val)#method_await)
246                } else if is_async {
247                    quote!(
248                            #zbus::export::futures_util::future::FutureExt::map(
249                                self.#ident(val),
250                                ::std::result::Result::Ok,
251                            )
252                            .await
253                    )
254                } else {
255                    quote!(::std::result::Result::Ok(self.#ident(val)))
256                };
257
258                // * For reference arg, we convert from `&Value` (so `TryFrom<&Value<'_>>` is
259                //   required).
260                //
261                // * For argument type with lifetimes, we convert from `Value` (so
262                //   `TryFrom<Value<'_>>` is required).
263                //
264                // * For all other arg types, we convert the passed value to `OwnedValue` first and
265                //   then pass it as `Value` (so `TryFrom<Value<'static>>` is required).
266                let value_to_owned = quote! {
267                    ::zbus::zvariant::Value::from(zbus::zvariant::Value::to_owned(value))
268                };
269                let value_arg = match &*typed_inputs
270                    .first()
271                    .ok_or_else(|| Error::new_spanned(&inputs, "Expected a value argument"))?
272                    .ty
273                {
274                    Type::Reference(_) => quote!(value),
275                    Type::Path(path) => path
276                        .path
277                        .segments
278                        .first()
279                        .map(|segment| match &segment.arguments {
280                            PathArguments::AngleBracketed(angled) => angled
281                                .args
282                                .first()
283                                .filter(|arg| matches!(arg, GenericArgument::Lifetime(_)))
284                                .map(|_| quote!(value.clone()))
285                                .unwrap_or_else(|| value_to_owned.clone()),
286                            _ => value_to_owned.clone(),
287                        })
288                        .unwrap_or_else(|| value_to_owned.clone()),
289                    _ => value_to_owned,
290                };
291                let do_set = quote!({
292                    let value = #value_arg;
293                    match ::std::convert::TryInto::try_into(value) {
294                        ::std::result::Result::Ok(val) => {
295                            match #set_call {
296                                ::std::result::Result::Ok(set_result) => {
297                                    self
298                                        .#prop_changed_method_name(&signal_context)
299                                        .await
300                                        .map(|_| set_result)
301                                        .map_err(Into::into)
302                                }
303                                e => e,
304                            }
305                        }
306                        ::std::result::Result::Err(e) => {
307                            ::std::result::Result::Err(
308                                ::std::convert::Into::into(#zbus::Error::Variant(::std::convert::Into::into(e))),
309                            )
310                        }
311                    }
312                });
313
314                if is_mut {
315                    let q = quote!(
316                        #member_name => {
317                            ::std::option::Option::Some(#do_set)
318                        }
319                    );
320                    set_mut_dispatch.extend(q);
321
322                    let q = quote!(
323                        #member_name => #zbus::DispatchResult::RequiresMut,
324                    );
325                    set_dispatch.extend(q);
326                } else {
327                    let q = quote!(
328                        #member_name => {
329                            #zbus::DispatchResult::Async(::std::boxed::Box::pin(async move {
330                                #do_set
331                            }))
332                        }
333                    );
334                    set_dispatch.extend(q);
335                }
336            } else {
337                let is_fallible_property = is_result_output;
338
339                p.ty = Some(get_property_type(output)?);
340                p.read = true;
341                let inner = if is_fallible_property {
342                    quote!(self.#ident()#method_await#handle_fallible_property)
343                } else {
344                    quote!(::std::result::Result::Ok(
345                        ::std::convert::Into::into(
346                            <#zbus::zvariant::Value as ::std::convert::From<_>>::from(
347                                self.#ident()#method_await,
348                            ),
349                        ),
350                    ))
351                };
352
353                let q = quote!(
354                    #member_name => {
355                        ::std::option::Option::Some(#inner)
356                    },
357                );
358                get_dispatch.extend(q);
359
360                let q = if is_fallible_property {
361                    quote!(if let Ok(prop) = self.#ident()#method_await {
362                        props.insert(
363                            ::std::string::ToString::to_string(#member_name),
364                            ::std::convert::Into::into(
365                                <#zbus::zvariant::Value as ::std::convert::From<_>>::from(
366                                    prop,
367                                ),
368                            ),
369                        );
370                    })
371                } else {
372                    quote!(props.insert(
373                        ::std::string::ToString::to_string(#member_name),
374                        ::std::convert::Into::into(
375                            <#zbus::zvariant::Value as ::std::convert::From<_>>::from(
376                                self.#ident()#method_await,
377                            ),
378                        ),
379                    );)
380                };
381
382                get_all.extend(q);
383
384                let prop_value_handled = if is_fallible_property {
385                    quote!(self.#ident()#method_await?)
386                } else {
387                    quote!(self.#ident()#method_await)
388                };
389
390                let prop_changed_method = quote!(
391                    pub async fn #prop_changed_method_name(
392                        &self,
393                        signal_context: &#zbus::SignalContext<'_>,
394                    ) -> #zbus::Result<()> {
395                        let mut changed = ::std::collections::HashMap::new();
396                        let value = <#zbus::zvariant::Value as ::std::convert::From<_>>::from(#prop_value_handled);
397                        changed.insert(#member_name, &value);
398                        #zbus::fdo::Properties::properties_changed(
399                            signal_context,
400                            #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name),
401                            &changed,
402                            &[],
403                        ).await
404                    }
405                );
406                generated_signals.extend(prop_changed_method);
407
408                let prop_invalidate_method = quote!(
409                    pub async fn #prop_invalidate_method_name(
410                        &self,
411                        signal_context: &#zbus::SignalContext<'_>,
412                    ) -> #zbus::Result<()> {
413                        #zbus::fdo::Properties::properties_changed(
414                            signal_context,
415                            #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name),
416                            &::std::collections::HashMap::new(),
417                            &[#member_name],
418                        ).await
419                    }
420                );
421                generated_signals.extend(prop_invalidate_method);
422            }
423        } else {
424            introspect.extend(doc_comments);
425            introspect.extend(introspect_method(&member_name, &intro_args));
426
427            let m = quote! {
428                #member_name => {
429                    let future = async move {
430                        #args_from_msg
431                        let reply = self.#ident(#args_names)#method_await;
432                        #reply
433                    };
434                    #zbus::DispatchResult::Async(::std::boxed::Box::pin(async move {
435                        future.await.map(|_seq: u32| ())
436                    }))
437                },
438            };
439
440            if is_mut {
441                call_dispatch.extend(quote! {
442                    #member_name => #zbus::DispatchResult::RequiresMut,
443                });
444                call_mut_dispatch.extend(m);
445            } else {
446                call_dispatch.extend(m);
447            }
448        }
449    }
450
451    introspect_properties(&mut introspect, properties)?;
452
453    let generics = &input.generics;
454    let where_clause = &generics.where_clause;
455
456    Ok(quote! {
457        #input
458
459        impl #generics #self_ty
460        #where_clause
461        {
462            #generated_signals
463        }
464
465        #[#zbus::export::async_trait::async_trait]
466        impl #generics #zbus::Interface for #self_ty
467        #where_clause
468        {
469            fn name() -> #zbus::names::InterfaceName<'static> {
470                #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name)
471            }
472
473            async fn get(
474                &self,
475                property_name: &str,
476            ) -> ::std::option::Option<#zbus::fdo::Result<#zbus::zvariant::OwnedValue>> {
477                match property_name {
478                    #get_dispatch
479                    _ => ::std::option::Option::None,
480                }
481            }
482
483            async fn get_all(
484                &self,
485            ) -> ::std::collections::HashMap<
486                ::std::string::String,
487                #zbus::zvariant::OwnedValue,
488            > {
489                let mut props: ::std::collections::HashMap<
490                    ::std::string::String,
491                    #zbus::zvariant::OwnedValue,
492                > = ::std::collections::HashMap::new();
493                #get_all
494                props
495            }
496
497            fn set<'call>(
498                &'call self,
499                property_name: &'call str,
500                value: &'call #zbus::zvariant::Value<'_>,
501                signal_context: &'call #zbus::SignalContext<'_>,
502            ) -> #zbus::DispatchResult<'call> {
503                match property_name {
504                    #set_dispatch
505                    _ => #zbus::DispatchResult::NotFound,
506                }
507            }
508
509            async fn set_mut(
510                &mut self,
511                property_name: &str,
512                value: &#zbus::zvariant::Value<'_>,
513                signal_context: &#zbus::SignalContext<'_>,
514            ) -> ::std::option::Option<#zbus::fdo::Result<()>> {
515                match property_name {
516                    #set_mut_dispatch
517                    _ => ::std::option::Option::None,
518                }
519            }
520
521            fn call<'call>(
522                &'call self,
523                s: &'call #zbus::ObjectServer,
524                c: &'call #zbus::Connection,
525                m: &'call #zbus::Message,
526                name: #zbus::names::MemberName<'call>,
527            ) -> #zbus::DispatchResult<'call> {
528                match name.as_str() {
529                    #call_dispatch
530                    _ => #zbus::DispatchResult::NotFound,
531                }
532            }
533
534            fn call_mut<'call>(
535                &'call mut self,
536                s: &'call #zbus::ObjectServer,
537                c: &'call #zbus::Connection,
538                m: &'call #zbus::Message,
539                name: #zbus::names::MemberName<'call>,
540            ) -> #zbus::DispatchResult<'call> {
541                match name.as_str() {
542                    #call_mut_dispatch
543                    _ => #zbus::DispatchResult::NotFound,
544                }
545            }
546
547            fn introspect_to_writer(&self, writer: &mut dyn ::std::fmt::Write, level: usize) {
548                ::std::writeln!(
549                    writer,
550                    r#"{:indent$}<interface name="{}">"#,
551                    "",
552                    <Self as #zbus::Interface>::name(),
553                    indent = level
554                ).unwrap();
555                {
556                    use #zbus::zvariant::Type;
557
558                    let level = level + 2;
559                    #introspect
560                }
561                ::std::writeln!(writer, r#"{:indent$}</interface>"#, "", indent = level).unwrap();
562            }
563        }
564    })
565}
566
567fn get_args_from_inputs(
568    inputs: &[PatType],
569    zbus: &TokenStream,
570) -> syn::Result<(TokenStream, TokenStream)> {
571    if inputs.is_empty() {
572        Ok((quote!(), quote!()))
573    } else {
574        let mut server_arg_decl = None;
575        let mut conn_arg_decl = None;
576        let mut header_arg_decl = None;
577        let mut signal_context_arg_decl = None;
578        let mut args_names = Vec::new();
579        let mut tys = Vec::new();
580
581        for input in inputs {
582            let attrs = ArgAttributes::parse(&input.attrs)?;
583
584            if attrs.object_server {
585                if server_arg_decl.is_some() {
586                    return Err(Error::new_spanned(
587                        input,
588                        "There can only be one object_server argument",
589                    ));
590                }
591
592                let server_arg = &input.pat;
593                server_arg_decl = Some(quote! { let #server_arg = &s; });
594            } else if attrs.connection {
595                if conn_arg_decl.is_some() {
596                    return Err(Error::new_spanned(
597                        input,
598                        "There can only be one connection argument",
599                    ));
600                }
601
602                let conn_arg = &input.pat;
603                conn_arg_decl = Some(quote! { let #conn_arg = &c; });
604            } else if attrs.header {
605                if header_arg_decl.is_some() {
606                    return Err(Error::new_spanned(
607                        input,
608                        "There can only be one header argument",
609                    ));
610                }
611
612                let header_arg = &input.pat;
613
614                header_arg_decl = Some(quote! {
615                    let #header_arg = m.header()?;
616                });
617            } else if attrs.signal_context {
618                if signal_context_arg_decl.is_some() {
619                    return Err(Error::new_spanned(
620                        input,
621                        "There can only be one `signal_context` argument",
622                    ));
623                }
624
625                let signal_context_arg = &input.pat;
626
627                signal_context_arg_decl = Some(quote! {
628                    let #signal_context_arg = match m.path() {
629                        ::std::option::Option::Some(p) => {
630                            #zbus::SignalContext::new(c, p).expect("Infallible conversion failed")
631                        }
632                        ::std::option::Option::None => {
633                            let hdr = m.header()?;
634                            let err = #zbus::fdo::Error::UnknownObject("Path Required".into());
635                            return c.reply_dbus_error(&hdr, err).await;
636                        }
637                    };
638                });
639            } else {
640                args_names.push(pat_ident(input).unwrap());
641                tys.push(&input.ty);
642            }
643        }
644
645        let args_from_msg = quote! {
646            #server_arg_decl
647
648            #conn_arg_decl
649
650            #header_arg_decl
651
652            #signal_context_arg_decl
653
654            let (#(#args_names),*): (#(#tys),*) =
655                match m.body() {
656                    ::std::result::Result::Ok(r) => r,
657                    ::std::result::Result::Err(e) => {
658                        let hdr = m.header()?;
659                        let err = <#zbus::fdo::Error as ::std::convert::From<_>>::from(e);
660                        return c.reply_dbus_error(&hdr, err).await;
661                    }
662                };
663        };
664
665        let all_args_names = inputs.iter().filter_map(pat_ident);
666        let all_args_names = quote! { #(#all_args_names,)* };
667
668        Ok((args_from_msg, all_args_names))
669    }
670}
671
672fn clean_input_args(inputs: &mut Punctuated<FnArg, Token![,]>) {
673    for input in inputs {
674        if let FnArg::Typed(t) = input {
675            t.attrs.retain(|attr| !attr.path.is_ident("zbus"));
676        }
677    }
678}
679
680fn introspect_signal(name: &str, args: &TokenStream) -> TokenStream {
681    quote!(
682        ::std::writeln!(writer, "{:indent$}<signal name=\"{}\">", "", #name, indent = level).unwrap();
683        {
684            let level = level + 2;
685            #args
686        }
687        ::std::writeln!(writer, "{:indent$}</signal>", "", indent = level).unwrap();
688    )
689}
690
691fn introspect_method(name: &str, args: &TokenStream) -> TokenStream {
692    quote!(
693        ::std::writeln!(writer, "{:indent$}<method name=\"{}\">", "", #name, indent = level).unwrap();
694        {
695            let level = level + 2;
696            #args
697        }
698        ::std::writeln!(writer, "{:indent$}</method>", "", indent = level).unwrap();
699    )
700}
701
702fn introspect_input_args(
703    inputs: &[PatType],
704    is_signal: bool,
705) -> impl Iterator<Item = TokenStream> + '_ {
706    inputs
707        .iter()
708        .filter_map(move |pat_type @ PatType { ty, attrs, .. }| {
709            let is_special_arg = attrs.iter().any(|attr| {
710                if !attr.path.is_ident("zbus") {
711                    return false;
712                }
713
714                let meta = match attr.parse_meta() {
715                    ::std::result::Result::Ok(meta) => meta,
716                    ::std::result::Result::Err(_) => return false,
717                };
718
719                let nested = match meta {
720                    Meta::List(MetaList { nested, .. }) => nested,
721                    _ => return false,
722                };
723
724                let res = nested.iter().any(|nested_meta| {
725                    matches!(
726                        nested_meta,
727                        NestedMeta::Meta(Meta::Path(path))
728                        if path.is_ident("object_server") || path.is_ident("connection") || path.is_ident("header") || path.is_ident("signal_context")
729                    )
730                });
731
732                res
733            });
734            if is_special_arg {
735                return None;
736            }
737
738            let ident = pat_ident(pat_type).unwrap();
739            let arg_name = quote!(#ident).to_string();
740            let dir = if is_signal { "" } else { " direction=\"in\"" };
741            Some(quote!(
742                ::std::writeln!(writer, "{:indent$}<arg name=\"{}\" type=\"{}\"{}/>", "",
743                         #arg_name, <#ty>::signature(), #dir, indent = level).unwrap();
744            ))
745        })
746}
747
748fn introspect_output_arg(ty: &Type, arg_name: Option<&String>) -> TokenStream {
749    let arg_name = match arg_name {
750        Some(name) => format!("name=\"{name}\" "),
751        None => String::from(""),
752    };
753
754    quote!(
755        ::std::writeln!(writer, "{:indent$}<arg {}type=\"{}\" direction=\"out\"/>", "",
756                 #arg_name, <#ty>::signature(), indent = level).unwrap();
757    )
758}
759
760fn get_result_type(p: &TypePath) -> syn::Result<&Type> {
761    if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &p
762        .path
763        .segments
764        .last()
765        .ok_or_else(|| Error::new_spanned(p, "unsupported result type"))?
766        .arguments
767    {
768        if let Some(syn::GenericArgument::Type(ty)) = args.first() {
769            return Ok(ty);
770        }
771    }
772
773    Err(Error::new_spanned(p, "unhandled Result return"))
774}
775
776fn introspect_add_output_args(
777    args: &mut TokenStream,
778    output: &ReturnType,
779    arg_names: Option<&[String]>,
780) -> syn::Result<bool> {
781    let mut is_result_output = false;
782
783    if let ReturnType::Type(_, ty) = output {
784        let mut ty = ty.as_ref();
785
786        if let Type::Path(p) = ty {
787            is_result_output = p
788                .path
789                .segments
790                .last()
791                .ok_or_else(|| Error::new_spanned(ty, "unsupported output type"))?
792                .ident
793                == "Result";
794            if is_result_output {
795                ty = get_result_type(p)?;
796            }
797        }
798
799        if let Type::Tuple(t) = ty {
800            if let Some(arg_names) = arg_names {
801                if t.elems.len() != arg_names.len() {
802                    // Turn into error
803                    panic!("Number of out arg names different from out args specified")
804                }
805            }
806            for i in 0..t.elems.len() {
807                let name = arg_names.map(|names| &names[i]);
808                args.extend(introspect_output_arg(&t.elems[i], name));
809            }
810        } else {
811            args.extend(introspect_output_arg(ty, None));
812        }
813    }
814
815    Ok(is_result_output)
816}
817
818fn get_property_type(output: &ReturnType) -> syn::Result<&Type> {
819    if let ReturnType::Type(_, ty) = output {
820        let ty = ty.as_ref();
821
822        if let Type::Path(p) = ty {
823            let is_result_output = p
824                .path
825                .segments
826                .last()
827                .ok_or_else(|| Error::new_spanned(ty, "unsupported property type"))?
828                .ident
829                == "Result";
830            if is_result_output {
831                return get_result_type(p);
832            }
833        }
834
835        Ok(ty)
836    } else {
837        Err(Error::new_spanned(output, "Invalid property getter"))
838    }
839}
840
841fn introspect_properties(
842    introspection: &mut TokenStream,
843    properties: BTreeMap<String, Property<'_>>,
844) -> syn::Result<()> {
845    for (name, prop) in properties {
846        let access = if prop.read && prop.write {
847            "readwrite"
848        } else if prop.read {
849            "read"
850        } else if prop.write {
851            "write"
852        } else {
853            return Err(Error::new_spanned(
854                name,
855                "property is neither readable nor writable",
856            ));
857        };
858        let ty = prop.ty.ok_or_else(|| {
859            Error::new_spanned(&name, "Write-only properties aren't supported yet")
860        })?;
861
862        let doc_comments = prop.doc_comments;
863        introspection.extend(quote!(
864            #doc_comments
865            ::std::writeln!(
866                writer,
867                "{:indent$}<property name=\"{}\" type=\"{}\" access=\"{}\"/>",
868                "", #name, <#ty>::signature(), #access, indent = level,
869            ).unwrap();
870        ));
871    }
872
873    Ok(())
874}
875
876pub fn to_xml_docs(lines: Vec<String>) -> TokenStream {
877    let mut docs = quote!();
878
879    let mut lines: Vec<&str> = lines
880        .iter()
881        .skip_while(|s| is_blank(s))
882        .flat_map(|s| s.split('\n'))
883        .collect();
884
885    while let Some(true) = lines.last().map(|s| is_blank(s)) {
886        lines.pop();
887    }
888
889    if lines.is_empty() {
890        return docs;
891    }
892
893    docs.extend(quote!(::std::writeln!(writer, "{:indent$}<!--", "", indent = level).unwrap();));
894    for line in lines {
895        if !line.is_empty() {
896            docs.extend(
897                quote!(::std::writeln!(writer, "{:indent$}{}", "", #line, indent = level).unwrap();),
898            );
899        } else {
900            docs.extend(quote!(::std::writeln!(writer, "").unwrap();));
901        }
902    }
903    docs.extend(quote!(::std::writeln!(writer, "{:indent$} -->", "", indent = level).unwrap();));
904
905    docs
906}