Clone in a terminal:
Commit 11c9583
Johan Mattsson - 2026-06-28T09:42:46+00:00
11c958325e6a6ea8bf3434c6c68f5608755a5636johan.mattsson.m@gmail.com

Add return type to callback closure

Changed files (1)

Diff

diff --git a/src/lib.rs b/src/lib.rsindex 4d58cd9..affd664 100644--- a/src/lib.rs+++ b/src/lib.rs@@ -47,8 +47,12 @@ pub fn callback(attr: TokenStream, item: TokenStream) -> TokenStream {     let register_fn_name = syn::Ident::new(&format!("{}_register", fn_name), fn_name.span());     let register_fn_name_lit = LitStr::new(&register_fn_name.to_string(), register_fn_name.span());     let is_async = input_fn.sig.asyncness.is_some();-    let callback_closure =-        generate_callback_closure(&fn_name, input_fn.sig.inputs.clone(), is_async);+    let callback_closure = generate_callback_closure(+        &fn_name,+        input_fn.sig.inputs.clone(),+        is_async,+        &input_fn.sig.output,+    );      let expanded = {         quote! {@@ -127,6 +131,7 @@ fn generate_callback_closure(     fn_name: &Ident,     inputs: syn::punctuated::Punctuated<FnArg, syn::token::Comma>,     is_async: bool,+    return_type: &syn::ReturnType, ) -> proc_macro2::TokenStream {     use proc_macro2::Span;     use quote::quote;@@ -188,7 +193,7 @@ fn generate_callback_closure(                         concat!("Callback error: ", stringify!(#fn_name), ". Wrong argument"),                         #res_ident.err().unwrap()                     );-                    return;+                    return JsValue::UNDEFINED;                 }                 let #rs_ident = #res_ident.unwrap();             }@@ -200,7 +205,7 @@ fn generate_callback_closure(                         concat!("Callback error: ", stringify!(#fn_name), ". Wrong argument"),                         #res_ident.err().unwrap()                     );-                    return;+                    return JsValue::UNDEFINED;                 }                 let #rs_ident = #res_ident.unwrap();             }@@ -213,25 +218,105 @@ fn generate_callback_closure(         .map(|_| quote! { wasm_bindgen::JsValue })         .collect(); -    let call_block = if is_async {+    // Build the call + conversion to JsValue for the function's return value.+    let ret_handling = match return_type {+        syn::ReturnType::Default => {+            // no return type -> return undefined+            quote! {+                #fn_name( #(#rs_arg_idents),* );+                JsValue::UNDEFINED+            }+        }+        syn::ReturnType::Type(_, ty) => {+            let ret_ty_tokens = quote! { #ty }.to_string();++            if ret_ty_tokens.contains("JsValue") {+                // function returns a JsValue directly+                quote! {+                    let res = #fn_name( #(#rs_arg_idents),* );+                    res+                }+            } else if ret_ty_tokens.contains("web_sys")+                || ret_ty_tokens.contains("js_sys")+                || ret_ty_tokens.contains("wasm_bindgen")+                || ret_ty_tokens.contains("HtmlElement")+            {+                // web_sys/js_sys types -> convert to JsValue via Into+                quote! {+                    let res = #fn_name( #(#rs_arg_idents),* );+                    wasm_bindgen::JsValue::from(res)+                }+            } else {+                // fallback: serialize with serde_wasm_bindgen+                quote! {+                    let res = #fn_name( #(#rs_arg_idents),* );+                    match serde_wasm_bindgen::to_value(&res) {+                        Ok(v) => v,+                        Err(e) => {+                            ::gloo::console::error!(+                                concat!("Callback error: ", stringify!(#fn_name), ". Serialize error"),+                                e+                            );+                            JsValue::UNDEFINED+                        }+                    }+                }+            }+        }+    };++    // Build async conversion logic (returns Result<JsValue, JsValue> inside the future)+    let async_conv = match return_type {+        syn::ReturnType::Default => quote! { Ok(JsValue::UNDEFINED) },+        syn::ReturnType::Type(_, ty) => {+            let ret_ty_tokens = quote! { #ty }.to_string();+            if ret_ty_tokens.contains("JsValue") {+                quote! { Ok(ret) }+            } else if ret_ty_tokens.contains("web_sys")+                || ret_ty_tokens.contains("js_sys")+                || ret_ty_tokens.contains("wasm_bindgen")+                || ret_ty_tokens.contains("HtmlElement")+            {+                quote! { Ok(wasm_bindgen::JsValue::from(ret)) }+            } else {+                quote! {+                    match serde_wasm_bindgen::to_value(&ret) {+                        Ok(v) => Ok(v),+                        Err(e) => {+                            ::gloo::console::error!(+                                concat!("Callback error: ", stringify!(#fn_name), ". Serialize error"),+                                e+                            );+                            Ok(JsValue::UNDEFINED)+                        }+                    }+                }+            }+        }+    };++    let expanded = if is_async {+        // For async functions return a Promise (JsValue) by using future_to_promise         quote! {-            ::wasm_bindgen_futures::spawn_local(async move {-                #fn_name( #(#rs_arg_idents),* ).await;-            });+            let callback_closure = Closure::wrap(Box::new(move | #( #js_arg_idents : #js_types ),* | -> wasm_bindgen::JsValue {+                #(#conversions)*+                let promise = ::wasm_bindgen_futures::future_to_promise(async move {+                    let ret = #fn_name( #(#rs_arg_idents),* ).await;+                    #async_conv+                });++                promise.into()+            }) as Box<dyn FnMut( #(#js_types),* ) -> wasm_bindgen::JsValue + 'static>);         }     } else {         quote! {-            #fn_name( #(#rs_arg_idents),* );+            let callback_closure = Closure::wrap(Box::new(move | #( #js_arg_idents : #js_types ),* | -> wasm_bindgen::JsValue {+                #(#conversions)*+                #ret_handling+            }) as Box<dyn FnMut( #(#js_types),* ) -> wasm_bindgen::JsValue + 'static>);         }     }; -    let expanded = quote! {-        let callback_closure = Closure::wrap(Box::new(move | #( #js_arg_idents : #js_types ),* | {-            #(#conversions)*-            #call_block-        }) as Box<dyn FnMut( #(#js_types),* ) + 'static>);-    };-     return expanded; } 

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112