wrapper.rs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use proc_macro::TokenStream;
  5. use proc_macro2::TokenStream as TokenStream2;
  6. use quote::quote;
  7. use syn::{
  8. parse::{Parse, ParseBuffer},
  9. parse_macro_input,
  10. spanned::Spanned,
  11. FnArg, Ident, ItemFn, Pat, Token, Visibility,
  12. };
  13. /// The execution context of the command.
  14. enum ExecutionContext {
  15. Async,
  16. Blocking,
  17. }
  18. impl Parse for ExecutionContext {
  19. fn parse(input: &ParseBuffer) -> syn::Result<Self> {
  20. if input.is_empty() {
  21. return Ok(Self::Blocking);
  22. }
  23. input
  24. .parse::<Token![async]>()
  25. .map(|_| Self::Async)
  26. .map_err(|_| {
  27. syn::Error::new(
  28. input.span(),
  29. "only a single item `async` is currently allowed",
  30. )
  31. })
  32. }
  33. }
  34. /// Create a new [`Wrapper`] from the function and the generated code parsed from the function.
  35. pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
  36. let function = parse_macro_input!(item as ItemFn);
  37. let wrapper = super::format_command_wrapper(&function.sig.ident);
  38. let visibility = &function.vis;
  39. // macros used with `pub use my_macro;` need to be exported with `#[macro_export]`
  40. let maybe_macro_export = match &function.vis {
  41. Visibility::Public(_) => quote!(#[macro_export]),
  42. _ => Default::default(),
  43. };
  44. // body to the command wrapper or a `compile_error!` of an error occurred while parsing it.
  45. let body = syn::parse::<ExecutionContext>(attributes)
  46. .map(|context| match function.sig.asyncness {
  47. Some(_) => ExecutionContext::Async,
  48. None => context,
  49. })
  50. .and_then(|context| match context {
  51. ExecutionContext::Async => body_async(&function),
  52. ExecutionContext::Blocking => body_blocking(&function),
  53. })
  54. .unwrap_or_else(syn::Error::into_compile_error);
  55. // Rely on rust 2018 edition to allow importing a macro from a path.
  56. quote!(
  57. #function
  58. #maybe_macro_export
  59. macro_rules! #wrapper {
  60. // double braces because the item is expected to be a block expression
  61. ($path:path, $invoke:ident) => {{
  62. // import all the autoref specialization items
  63. #[allow(unused_imports)]
  64. use ::tauri::command::private::*;
  65. // prevent warnings when the body is a `compile_error!` or if the command has no arguments
  66. #[allow(unused_variables)]
  67. let ::tauri::Invoke { message, resolver } = $invoke;
  68. #body
  69. }};
  70. }
  71. // allow the macro to be resolved with the same path as the command function
  72. #[allow(unused_imports)]
  73. #visibility use #wrapper;
  74. )
  75. .into()
  76. }
  77. /// Generates an asynchronous command response from the arguments and return value of a function.
  78. ///
  79. /// See the [`tauri::command`] module for all the items and traits that make this possible.
  80. ///
  81. /// * Requires binding `message` and `resolver`.
  82. /// * Requires all the traits from `tauri::command::private` to be in scope.
  83. ///
  84. /// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html
  85. fn body_async(function: &ItemFn) -> syn::Result<TokenStream2> {
  86. parse_args(function).map(|args| {
  87. quote! {
  88. resolver.respond_async_serialized(async move {
  89. let result = $path(#(#args?),*);
  90. (&result).async_kind().future(result).await
  91. })
  92. }
  93. })
  94. }
  95. /// Generates a blocking command response from the arguments and return value of a function.
  96. ///
  97. /// See the [`tauri::command`] module for all the items and traits that make this possible.
  98. ///
  99. /// * Requires binding `message` and `resolver`.
  100. /// * Requires all the traits from `tauri::command::private` to be in scope.
  101. ///
  102. /// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html
  103. fn body_blocking(function: &ItemFn) -> syn::Result<TokenStream2> {
  104. let args = parse_args(function)?;
  105. // the body of a `match` to early return any argument that wasn't successful in parsing.
  106. let match_body = quote!({
  107. Ok(arg) => arg,
  108. Err(err) => return resolver.invoke_error(err),
  109. });
  110. Ok(quote! {
  111. let result = $path(#(match #args #match_body),*);
  112. (&result).blocking_kind().block(result, resolver);
  113. })
  114. }
  115. /// Parse all arguments for the command wrapper to use from the signature of the command function.
  116. fn parse_args(function: &ItemFn) -> syn::Result<Vec<TokenStream2>> {
  117. function
  118. .sig
  119. .inputs
  120. .iter()
  121. .map(|arg| parse_arg(&function.sig.ident, arg))
  122. .collect()
  123. }
  124. /// Transform a [`FnArg`] into a command argument.
  125. ///
  126. /// * Requires binding `message`.
  127. fn parse_arg(command: &Ident, arg: &FnArg) -> syn::Result<TokenStream2> {
  128. // we have no use for self arguments
  129. let mut arg = match arg {
  130. FnArg::Typed(arg) => arg.pat.as_ref().clone(),
  131. FnArg::Receiver(arg) => {
  132. return Err(syn::Error::new(
  133. arg.span(),
  134. "unable to use self as a command function parameter",
  135. ))
  136. }
  137. };
  138. // we only support patterns that allow us to extract some sort of keyed identifier.
  139. let key = match &mut arg {
  140. Pat::Ident(arg) => arg.ident.to_string(),
  141. Pat::Wild(_) => "_".into(),
  142. Pat::Struct(s) => super::path_to_command(&mut s.path).ident.to_string(),
  143. Pat::TupleStruct(s) => super::path_to_command(&mut s.path).ident.to_string(),
  144. err => {
  145. return Err(syn::Error::new(
  146. err.span(),
  147. "only named, wildcard, struct, and tuple struct arguments allowed",
  148. ))
  149. }
  150. };
  151. // also catch self arguments that use FnArg::Typed syntax
  152. if key == "self" {
  153. return Err(syn::Error::new(
  154. key.span(),
  155. "unable to use self as a command function parameter",
  156. ));
  157. }
  158. Ok(quote!(::tauri::command::CommandArg::from_command(
  159. ::tauri::command::CommandItem {
  160. name: stringify!(#command),
  161. key: #key,
  162. message: &message,
  163. }
  164. )))
  165. }