55use heck:: { ToLowerCamelCase , ToSnakeCase } ;
66use proc_macro:: TokenStream ;
77use proc_macro2:: TokenStream as TokenStream2 ;
8- use quote:: { format_ident, quote} ;
8+ use quote:: { format_ident, quote, quote_spanned } ;
99use syn:: {
1010 ext:: IdentExt ,
1111 parse:: { Parse , ParseStream } ,
@@ -103,6 +103,63 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
103103 resolver : format_ident ! ( "__tauri_resolver__" ) ,
104104 } ;
105105
106+ // Tauri currently doesn't support async commands that take a reference as input and don't return
107+ // a result. See: https://github.com/tauri-apps/tauri/issues/2533
108+ //
109+ // For now, we provide an informative error message to the user in that case. Once #2533 is
110+ // resolved, this check can be removed.
111+ let mut async_command_check = TokenStream2 :: new ( ) ;
112+ if function. sig . asyncness . is_some ( ) {
113+ // This check won't catch all possible problems but it should catch the most common ones.
114+ let mut ref_argument_span = None ;
115+
116+ for arg in & function. sig . inputs {
117+ if let syn:: FnArg :: Typed ( pat) = arg {
118+ match & * pat. ty {
119+ syn:: Type :: Reference ( _) => {
120+ ref_argument_span = Some ( pat. span ( ) ) ;
121+ }
122+ syn:: Type :: Path ( path) => {
123+ // Check if the type contains a lifetime argument
124+ let last = path. path . segments . last ( ) . unwrap ( ) ;
125+ if let syn:: PathArguments :: AngleBracketed ( args) = & last. arguments {
126+ if args
127+ . args
128+ . iter ( )
129+ . any ( |arg| matches ! ( arg, syn:: GenericArgument :: Lifetime ( _) ) )
130+ {
131+ ref_argument_span = Some ( pat. span ( ) ) ;
132+ }
133+ }
134+ }
135+ _ => { }
136+ }
137+
138+ if let Some ( span) = ref_argument_span {
139+ if let syn:: ReturnType :: Type ( _, return_type) = & function. sig . output {
140+ // To check if the return type is `Result` we require it to check a trait that is
141+ // only implemented by `Result`. That way we don't exclude renamed result types
142+ // which we wouldn't otherwise be able to detect purely from the token stream.
143+ // The "error message" displayed to the user is simply the trait name.
144+ async_command_check = quote_spanned ! { return_type. span( ) =>
145+ #[ allow( unreachable_code, clippy:: diverging_sub_expression) ]
146+ const _: ( ) = if false {
147+ trait AsyncCommandMustReturnResult { }
148+ impl <A , B > AsyncCommandMustReturnResult for Result <A , B > { }
149+ let _check: #return_type = unreachable!( ) ;
150+ let _: & dyn AsyncCommandMustReturnResult = & _check;
151+ } ;
152+ } ;
153+ } else {
154+ return quote_spanned ! {
155+ span => compile_error!( "async commands that contain references as inputs must return a `Result`" ) ;
156+ } . into ( ) ;
157+ }
158+ }
159+ }
160+ }
161+ }
162+
106163 // body to the command wrapper or a `compile_error!` of an error occurred while parsing it.
107164 let body = syn:: parse :: < WrapperAttributes > ( attributes)
108165 . map ( |mut attrs| {
@@ -121,6 +178,8 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
121178
122179 // Rely on rust 2018 edition to allow importing a macro from a path.
123180 quote ! (
181+ #async_command_check
182+
124183 #function
125184
126185 #maybe_macro_export
0 commit comments