22// SPDX-License-Identifier: Apache-2.0
33// SPDX-License-Identifier: MIT
44
5- use proc_macro2:: TokenStream ;
6- use quote:: { quote, ToTokens , TokenStreamExt } ;
7- use std:: convert:: TryFrom ;
8- use syn:: { spanned:: Spanned , FnArg , Ident , ItemFn , Pat , ReturnType , Type , Visibility } ;
9-
10- /// The command wrapper created for a function marked with `#[command]`.
11- pub struct Wrapper {
12- function : ItemFn ,
13- visibility : Visibility ,
14- maybe_export : TokenStream ,
15- wrapper : Ident ,
16- body : syn:: Result < WrapperBody > ,
5+ use proc_macro:: TokenStream ;
6+ use proc_macro2:: TokenStream as TokenStream2 ;
7+ use quote:: quote;
8+ use syn:: {
9+ parse:: { Parse , ParseBuffer } ,
10+ parse_macro_input,
11+ spanned:: Spanned ,
12+ FnArg , Ident , ItemFn , Pat , Token , Visibility ,
13+ } ;
14+
15+ /// The execution context of the command.
16+ enum ExecutionContext {
17+ Async ,
18+ Blocking ,
1719}
1820
19- impl Wrapper {
20- /// Create a new [`Wrapper`] from the function and the generated code parsed from the function.
21- pub fn new ( function : ItemFn , body : syn:: Result < WrapperBody > ) -> Self {
22- // macros used with `pub use my_macro;` need to be exported with `#[macro_export]`
23- let maybe_export = match & function. vis {
24- Visibility :: Public ( _) => quote ! ( #[ macro_export] ) ,
25- _ => Default :: default ( ) ,
26- } ;
27-
28- let visibility = function. vis . clone ( ) ;
29- let wrapper = super :: format_command_wrapper ( & function. sig . ident ) ;
30-
31- Self {
32- function,
33- visibility,
34- maybe_export,
35- wrapper,
36- body,
21+ impl Parse for ExecutionContext {
22+ fn parse ( input : & ParseBuffer ) -> syn:: Result < Self > {
23+ if input. is_empty ( ) {
24+ return Ok ( Self :: Blocking ) ;
3725 }
26+
27+ input
28+ . parse :: < Token ! [ async ] > ( )
29+ . map ( |_| Self :: Async )
30+ . map_err ( |_| {
31+ syn:: Error :: new (
32+ input. span ( ) ,
33+ "only a single item `async` is currently allowed" ,
34+ )
35+ } )
3836 }
3937}
4038
41- impl From < Wrapper > for proc_macro:: TokenStream {
42- fn from (
43- Wrapper {
44- function,
45- maybe_export,
46- wrapper,
47- body,
48- visibility,
49- } : Wrapper ,
50- ) -> Self {
51- // either use the successful body or a `compile_error!` of the error occurred while parsing it.
52- let body = body
53- . as_ref ( )
54- . map ( ToTokens :: to_token_stream)
55- . unwrap_or_else ( syn:: Error :: to_compile_error) ;
56-
57- // we `use` the macro so that other modules can resolve the with the same path as the function.
58- // this is dependent on rust 2018 edition.
59- quote ! (
60- #function
61- #maybe_export
62- macro_rules! #wrapper { ( $path: path, $invoke: ident) => { { #body } } ; }
63- #visibility use #wrapper;
64- )
65- . into ( )
66- }
39+ /// Create a new [`Wrapper`] from the function and the generated code parsed from the function.
40+ pub fn wrapper ( attributes : TokenStream , item : TokenStream ) -> TokenStream {
41+ let function = parse_macro_input ! ( item as ItemFn ) ;
42+ let wrapper = super :: format_command_wrapper ( & function. sig . ident ) ;
43+ let visibility = & function. vis ;
44+
45+ // macros used with `pub use my_macro;` need to be exported with `#[macro_export]`
46+ let maybe_macro_export = match & function. vis {
47+ Visibility :: Public ( _) => quote ! ( #[ macro_export] ) ,
48+ _ => Default :: default ( ) ,
49+ } ;
50+
51+ // body to the command wrapper or a `compile_error!` of an error occurred while parsing it.
52+ let body = syn:: parse :: < ExecutionContext > ( attributes)
53+ . map ( |context| match function. sig . asyncness {
54+ Some ( _) => ExecutionContext :: Async ,
55+ None => context,
56+ } )
57+ . and_then ( |context| match context {
58+ ExecutionContext :: Async => body_async ( & function) ,
59+ ExecutionContext :: Blocking => body_blocking ( & function) ,
60+ } )
61+ . unwrap_or_else ( syn:: Error :: into_compile_error) ;
62+
63+ // Rely on rust 2018 edition to allow importing a macro from a path.
64+ quote ! (
65+ #function
66+
67+ #maybe_macro_export
68+ macro_rules! #wrapper {
69+ // double braces because the item is expected to be a block expression
70+ ( $path: path, $invoke: ident) => { {
71+ // import all the autoref specialization items
72+ #[ allow( unused_imports) ]
73+ use :: tauri:: command:: private:: * ;
74+
75+ // prevent warnings when the body is a `compile_error!` or if the command has no arguments
76+ #[ allow( unused_variables) ]
77+ let :: tauri:: Invoke { message, resolver } = $invoke;
78+
79+ #body
80+ } } ;
81+ }
82+
83+ // allow the macro to be resolved with the same path as the command function
84+ #[ allow( unused_imports) ]
85+ #visibility use #wrapper;
86+ )
87+ . into ( )
6788}
6889
69- /// Body of the wrapper that maps the command parameters into callable arguments from [`Invoke`] .
90+ /// Generates an asynchronous command response from the arguments and return value of a function .
7091///
71- /// This is possible because we require the command parameters to be [`CommandArg`] and use type
72- /// inference to put values generated from that trait into the arguments of the called command.
92+ /// See the [`tauri::command`] module for all the items and traits that make this possible.
7393///
74- /// [`CommandArg`]: https://docs.rs/tauri/*/tauri/command/trait.CommandArg.html
75- /// [`Invoke`]: https://docs.rs/tauri/*/tauri/struct.Invoke.html
76- pub struct WrapperBody ( TokenStream ) ;
77-
78- impl TryFrom < & ItemFn > for WrapperBody {
79- type Error = syn:: Error ;
80-
81- fn try_from ( function : & ItemFn ) -> syn:: Result < Self > {
82- // the name of the #[command] function is the name of the command to handle
83- let command = function. sig . ident . clone ( ) ;
84-
85- // automatically append await when the #[command] function is async
86- let maybe_await = match function. sig . asyncness {
87- Some ( _) => quote ! ( . await ) ,
88- None => Default :: default ( ) ,
89- } ;
90-
91- // todo: detect command return types automatically like params, removes parsing type name
92- let returns_result = match function. sig . output {
93- ReturnType :: Type ( _, ref ty) => match & * * ty {
94- Type :: Path ( type_path) => type_path
95- . path
96- . segments
97- . first ( )
98- . map ( |seg| seg. ident == "Result" )
99- . unwrap_or_default ( ) ,
100- _ => false ,
101- } ,
102- ReturnType :: Default => false ,
103- } ;
104-
105- let mut args = Vec :: new ( ) ;
106- for param in & function. sig . inputs {
107- args. push ( parse_arg ( & command, param) ?) ;
108- }
109-
110- // todo: change this to automatically detect result returns (see above result todo)
111- // if the command handler returns a Result,
112- // we just map the values to the ones expected by Tauri
113- // otherwise we wrap it with an `Ok()`, converting the return value to tauri::InvokeResponse
114- // note that all types must implement `serde::Serialize`.
115- let result = if returns_result {
116- quote ! {
117- let result = $path( #( #args?) , * ) ;
118- :: core:: result:: Result :: Ok ( result #maybe_await?)
119- }
120- } else {
121- quote ! {
94+ /// * Requires binding `message` and `resolver`.
95+ /// * Requires all the traits from `tauri::command::private` to be in scope.
96+ ///
97+ /// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html
98+ fn body_async ( function : & ItemFn ) -> syn:: Result < TokenStream2 > {
99+ parse_args ( function) . map ( |args| {
100+ quote ! {
101+ resolver. respond_async_serialized( async move {
122102 let result = $path( #( #args?) , * ) ;
123- :: core:: result:: Result :: <_, :: tauri:: InvokeError >:: Ok ( result #maybe_await)
124- }
125- } ;
126-
127- Ok ( Self ( result) )
128- }
103+ ( & result) . async_kind( ) . future( result) . await
104+ } )
105+ }
106+ } )
129107}
130108
131- impl ToTokens for WrapperBody {
132- fn to_tokens ( & self , tokens : & mut TokenStream ) {
133- let body = & self . 0 ;
109+ /// Generates a blocking command response from the arguments and return value of a function.
110+ ///
111+ /// See the [`tauri::command`] module for all the items and traits that make this possible.
112+ ///
113+ /// * Requires binding `message` and `resolver`.
114+ /// * Requires all the traits from `tauri::command::private` to be in scope.
115+ ///
116+ /// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html
117+ fn body_blocking ( function : & ItemFn ) -> syn:: Result < TokenStream2 > {
118+ let args = parse_args ( function) ?;
119+
120+ // the body of a `match` to early return any argument that wasn't successful in parsing.
121+ let match_body = quote ! ( {
122+ Ok ( arg) => arg,
123+ Err ( err) => return resolver. invoke_error( err) ,
124+ } ) ;
125+
126+ Ok ( quote ! {
127+ let result = $path( #( match #args #match_body) , * ) ;
128+ ( & result) . blocking_kind( ) . block( result, resolver) ;
129+ } )
130+ }
134131
135- // we #[allow(unused_variables)] because a command with no arguments will not use message.
136- tokens. append_all ( quote ! (
137- #[ allow( unused_variables) ]
138- let :: tauri:: Invoke { message, resolver } = $invoke;
139- resolver. respond_async( async move { #body } ) ;
140- ) )
141- }
132+ /// Parse all arguments for the command wrapper to use from the signature of the command function.
133+ fn parse_args ( function : & ItemFn ) -> syn:: Result < Vec < TokenStream2 > > {
134+ function
135+ . sig
136+ . inputs
137+ . iter ( )
138+ . map ( |arg| parse_arg ( & function. sig . ident , arg) )
139+ . collect ( )
142140}
143141
144- /// Transform a [`FnArg`] into a command argument. Expects borrowable binding `message` to exist.
145- fn parse_arg ( command : & Ident , arg : & FnArg ) -> syn:: Result < TokenStream > {
142+ /// Transform a [`FnArg`] into a command argument.
143+ ///
144+ /// * Requires binding `message`.
145+ fn parse_arg ( command : & Ident , arg : & FnArg ) -> syn:: Result < TokenStream2 > {
146146 // we have no use for self arguments
147147 let mut arg = match arg {
148148 FnArg :: Typed ( arg) => arg. pat . as_ref ( ) . clone ( ) ,
@@ -154,7 +154,7 @@ fn parse_arg(command: &Ident, arg: &FnArg) -> syn::Result<TokenStream> {
154154 }
155155 } ;
156156
157- // we only support patterns supported as arguments to a `ItemFn` .
157+ // we only support patterns that allow us to extract some sort of keyed identifier .
158158 let key = match & mut arg {
159159 Pat :: Ident ( arg) => arg. ident . to_string ( ) ,
160160 Pat :: Wild ( _) => "_" . into ( ) ,
0 commit comments