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: &mut FnArg in inputs {
674 if let FnArg::Typed(t: &mut PatType) = input {
675 t.attrs.retain(|attr: &Attribute| !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: String = match arg_name {
750 Some(name: &String) => 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: &Punctuated, .. }) = &p&PathSegment
762 .path
763 .segments
764 .last()
765 .ok_or_else(|| Error::new_spanned(tokens:p, message:"unsupported result type"))?
766 .arguments
767 {
768 if let Some(syn::GenericArgument::Type(ty: &Type)) = args.first() {
769 return Ok(ty);
770 }
771 }
772
773 Err(Error::new_spanned(tokens:p, message:"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: &Box) = output {
820 let ty: &Type = ty.as_ref();
821
822 if let Type::Path(p: &TypePath) = ty {
823 let is_result_output: bool = p&PathSegment
824 .path
825 .segments
826 .last()
827 .ok_or_else(|| Error::new_spanned(tokens:ty, message:"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(tokens:output, message:"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}
907