1 | use proc_macro2::TokenStream; |
2 | use quote::{format_ident, quote}; |
3 | use std::collections::BTreeMap; |
4 | use 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 | }; |
16 | use zvariant_utils::{case, def_attrs, macros::AttrParse, old_new}; |
17 | |
18 | use crate::utils::*; |
19 | |
20 | pub 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 | |
44 | def_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 | |
97 | old_new!(ImplAttrs, old::ImplAttributes, ImplAttributes); |
98 | old_new!(MethodAttrs, old::MethodAttributes, MethodAttributes); |
99 | |
100 | #[derive (Debug)] |
101 | struct Property<'a> { |
102 | read: bool, |
103 | write: bool, |
104 | emits_changed_signal: PropertyEmitsChangedSignal, |
105 | ty: Option<&'a Type>, |
106 | doc_comments: TokenStream, |
107 | } |
108 | |
109 | impl<'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)] |
122 | enum MethodType { |
123 | Signal, |
124 | Property(PropertyType), |
125 | Other, |
126 | } |
127 | |
128 | #[derive (Debug, PartialEq, Copy, Clone)] |
129 | enum PropertyType { |
130 | Inputs, |
131 | NoInputs, |
132 | } |
133 | |
134 | #[derive (Debug, Clone)] |
135 | struct 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 | |
176 | impl 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 | |
330 | pub 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 | |
917 | fn 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. |
1029 | fn clear_input_arg_attrs(inputs: &mut Punctuated<FnArg, Token![,]>) { |
1030 | for input: &mut FnArg in inputs { |
1031 | if let FnArg::Typed(t: &mut PatType) = input { |
1032 | t.attrs.retain(|attr: &Attribute| { |
1033 | !attr.path().is_ident("zbus" ) && !attr.path().is_ident("dbus_interface" ) |
1034 | }); |
1035 | } |
1036 | } |
1037 | } |
1038 | |
1039 | fn 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 | |
1050 | fn 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 | |
1061 | fn 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 | |
1106 | fn introspect_output_arg( |
1107 | ty: &Type, |
1108 | arg_name: Option<&String>, |
1109 | cfg_attrs: &[&syn::Attribute], |
1110 | ) -> TokenStream { |
1111 | let arg_name: String = match arg_name { |
1112 | Some(name: &String) => 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 | |
1123 | fn get_result_inner_type(p: &TypePath) -> syn::Result<&Type> { |
1124 | if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args: &Punctuated, .. }) = &p&PathSegment |
1125 | .path |
1126 | .segments |
1127 | .last() |
1128 | .ok_or_else(|| Error::new_spanned(tokens:p, message:"unsupported result type" ))? |
1129 | .arguments |
1130 | { |
1131 | if let Some(syn::GenericArgument::Type(ty: &Type)) = args.first() { |
1132 | return Ok(ty); |
1133 | } |
1134 | } |
1135 | |
1136 | Err(Error::new_spanned(tokens:p, message:"unhandled Result return" )) |
1137 | } |
1138 | |
1139 | fn 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 | |
1182 | fn get_return_type(output: &ReturnType) -> syn::Result<&Type> { |
1183 | if let ReturnType::Type(_, ty: &Box) = output { |
1184 | let ty: &Type = ty.as_ref(); |
1185 | |
1186 | if let Type::Path(p: &TypePath) = ty { |
1187 | let is_result_output: bool = p&PathSegment |
1188 | .path |
1189 | .segments |
1190 | .last() |
1191 | .ok_or_else(|| Error::new_spanned(tokens:ty, message:"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(tokens:output, message:"Invalid return type" )) |
1202 | } |
1203 | } |
1204 | |
1205 | fn 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 | |
1261 | pub 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 |
1294 | struct ImplItemSignal { |
1295 | attrs: Vec<Attribute>, |
1296 | vis: Visibility, |
1297 | sig: Signature, |
1298 | } |
1299 | |
1300 | impl Parse for ImplItemSignal { |
1301 | fn parse(input: ParseStream<'_>) -> syn::Result<Self> { |
1302 | let attrs: Vec = input.call(function:Attribute::parse_outer)?; |
1303 | let vis: Visibility = input.parse()?; |
1304 | let sig: Signature = input.parse()?; |
1305 | let _: Token![;] = input.parse()?; |
1306 | |
1307 | Ok(ImplItemSignal { attrs, vis, sig }) |
1308 | } |
1309 | } |
1310 | |
1311 | #[derive (Debug)] |
1312 | struct 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 | |
1327 | impl 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 | } |
1493 | |