Clone in a terminal:
Commit 412770c
Johan Mattsson - 2026-06-01T17:11:17+00:00
412770c567b40b71d928be3765cb212fb3149cb6johan.mattsson.m@gmail.com

Test closures

Changed files (3)

Diff

diff --git a/Cargo.lock b/Cargo.lockindex abab0e0..090ae48 100644--- a/Cargo.lock+++ b/Cargo.lock@@ -3,6 +3,23 @@ version = 4  [[package]]+name = "async-trait"+version = "0.1.89"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"+dependencies = [+ "proc-macro2",+ "quote",+ "syn 2.0.117",+]++[[package]]+name = "autocfg"+version = "1.5.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"++[[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -24,18 +41,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"  [[package]]+name = "cast"+version = "0.3.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"++[[package]]+name = "cc"+version = "1.2.63"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"+dependencies = [+ "find-msvc-tools",+ "shlex",+]++[[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"  [[package]]+name = "ctor"+version = "0.1.26"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"+dependencies = [+ "quote",+ "syn 1.0.109",+]++[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"  [[package]]+name = "find-msvc-tools"+version = "0.1.9"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"++[[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -134,6 +183,17 @@ dependencies = [ ]  [[package]]+name = "ghost"+version = "0.1.21"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "fe3de42e89f5bd0686d0058be48d545333311e34c763f85324938ba7cc473149"+dependencies = [+ "proc-macro2",+ "quote",+ "syn 2.0.117",+]++[[package]] name = "gloo" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -354,6 +414,28 @@ dependencies = [ ]  [[package]]+name = "inventory"+version = "0.1.11"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f0eb5160c60ba1e809707918ee329adb99d222888155835c6feedba19f6c3fd4"+dependencies = [+ "ctor",+ "ghost",+ "inventory-impl",+]++[[package]]+name = "inventory-impl"+version = "0.1.11"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "7e41b53715c6f0c4be49510bb82dee2c1e51c8586d885abe65396e82ed518548"+dependencies = [+ "proc-macro2",+ "quote",+ "syn 1.0.109",+]++[[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -376,10 +458,17 @@ name = "kaja-callback-macro" version = "0.1.0" dependencies = [  "gloo",+ "inventory",+ "js-sys",  "kaja-html-macro",  "proc-macro2",  "quote",+ "serde",+ "serde-wasm-bindgen",  "syn 2.0.117",+ "wasm-bindgen",+ "wasm-bindgen-test",+ "web-sys", ]  [[package]]@@ -401,18 +490,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"  [[package]]+name = "libm"+version = "0.2.16"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"++[[package]] name = "memchr" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"  [[package]]+name = "minicov"+version = "0.3.8"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d"+dependencies = [+ "cc",+ "walkdir",+]++[[package]]+name = "nu-ansi-term"+version = "0.50.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"+dependencies = [+ "windows-sys",+]++[[package]]+name = "num-traits"+version = "0.2.19"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"+dependencies = [+ "autocfg",+ "libm",+]++[[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"  [[package]]+name = "oorandom"+version = "11.1.5"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"++[[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -519,6 +649,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"  [[package]]+name = "same-file"+version = "1.0.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"+dependencies = [+ "winapi-util",+]++[[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -585,6 +724,12 @@ dependencies = [ ]  [[package]]+name = "shlex"+version = "2.0.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"++[[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -597,6 +742,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [  "proc-macro2",+ "quote",  "unicode-ident", ] @@ -681,6 +827,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"  [[package]]+name = "walkdir"+version = "2.5.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"+dependencies = [+ "same-file",+ "winapi-util",+]++[[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -742,6 +898,45 @@ dependencies = [ ]  [[package]]+name = "wasm-bindgen-test"+version = "0.3.72"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "74fde991ccdc895cb7fbaa14b137d62af74d9011be67b71c694bfc40edd3119c"+dependencies = [+ "async-trait",+ "cast",+ "js-sys",+ "libm",+ "minicov",+ "nu-ansi-term",+ "num-traits",+ "oorandom",+ "serde",+ "serde_json",+ "wasm-bindgen",+ "wasm-bindgen-futures",+ "wasm-bindgen-test-macro",+ "wasm-bindgen-test-shared",+]++[[package]]+name = "wasm-bindgen-test-macro"+version = "0.3.72"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "e925354648d2a4d1bf205412e36d520a800280622eef4719678d268e5d40e978"+dependencies = [+ "proc-macro2",+ "quote",+ "syn 2.0.117",+]++[[package]]+name = "wasm-bindgen-test-shared"+version = "0.2.122"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "684365b586a9a6256c1cc3544eee8680de48d6041142f581776ec7b139622ae9"++[[package]] name = "web-sys" version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -752,6 +947,30 @@ dependencies = [ ]  [[package]]+name = "winapi-util"+version = "0.1.11"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"+dependencies = [+ "windows-sys",+]++[[package]]+name = "windows-link"+version = "0.2.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"++[[package]]+name = "windows-sys"+version = "0.61.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"+dependencies = [+ "windows-link",+]++[[package]] name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index"diff --git a/Cargo.toml b/Cargo.tomlindex feaeb9b..c58c39a 100644--- a/Cargo.toml+++ b/Cargo.toml@@ -13,4 +13,12 @@ proc-macro2 = "1" gloo = "0.12.0"  [dev-dependencies]+# Dependencies required for compiling integration tests that use the generated wasm bindings kaja-html-macro = "1.0.3"+wasm-bindgen = "0.2"+js-sys = "0.3"+web-sys = { version = "0.3", features = ["Window"] }+serde = { version = "1.0", features = ["derive"] }+serde-wasm-bindgen = "0.6"+inventory = "0.1"+wasm-bindgen-test = "0.3"diff --git a/src/lib.rs b/src/lib.rsindex 4af1ea0..7b6a076 100644--- a/src/lib.rs+++ b/src/lib.rs@@ -1,6 +1,7 @@ use proc_macro::TokenStream;+use proc_macro2::TokenStream as TokenStream2; use quote::quote;-use syn::{parse_macro_input, FnArg, ItemFn, LitStr};+use syn::{parse_macro_input, FnArg, Ident, ItemFn, LitStr};  /// This macro makes sure a function is registerd in JS DOM Window for a given callback /// in the Kaja Web Framework (WebAssembly, Rust).@@ -36,30 +37,7 @@ 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());--    // Extract single argument type-    let arg = input_fn-        .sig-        .inputs-        .first()-        .expect("callback must have one argument");--    let arg_type1 = match arg {-        FnArg::Typed(pat_type) => &pat_type.ty,-        _ => panic!("expected typed argument"),-    };--    let arg2 = input_fn-        .sig-        .inputs-        .iter()-        .nth(1)-        .expect("callback must have one argument");--    let arg_type2 = match arg2 {-        FnArg::Typed(pat_type) => &pat_type.ty,-        _ => panic!("expected typed argument"),-    };+    let callback_closure = generate_callback_closure(&fn_name, input_fn.sig.inputs.clone());      let expanded = {         quote! {@@ -73,28 +51,7 @@ pub fn callback(attr: TokenStream, item: TokenStream) -> TokenStream {                 use wasm_bindgen::JsValue;                  if let Some(window) = web_sys::window() {-                    let callback_js_closure = Closure::<dyn FnMut(wasm_bindgen::JsValue, wasm_bindgen::JsValue)>::new(|val, val2| {-                        let event: #arg_type1 =-                            serde_wasm_bindgen::from_value(val);--                        if event.is_err() {-                            gloo::console::log!("Callback error: {}. Wrong argument type: {}", fn_name, event.err().unwrap());-                            return;-                        }--                        let event2: #arg_type2 =-                            serde_wasm_bindgen::from_value(val2);--                        if event2.is_err() {-                            gloo::console::log!("Callback error: {}. Wrong argument type: {}", fn_name, event2.err().unwrap());-                            return;-                        }--                        let event = event.unwrap();-                        let event2 = event.unwrap();--                        #fn_name(event, event2);-                    });+                    #callback_closure;                      let _ = Reflect::set(                         window.as_ref(),@@ -117,6 +74,122 @@ pub fn callback(attr: TokenStream, item: TokenStream) -> TokenStream {     TokenStream::from(expanded) } +fn generate_callback_closure(+    fn_name: &Ident,+    inputs: syn::punctuated::Punctuated<FnArg, syn::token::Comma>,+) -> proc_macro2::TokenStream {+    use proc_macro2::Span;+    use quote::quote;++    if inputs.iter().len() == 3 {+        let expanded = quote! {+            let cb = Closure::<dyn FnMut(wasm_bindgen::JsValue, wasm_bindgen::JsValue, wasm_bindgen::JsValue)>::new(|val, val2, val3| {+                #fn_name(1, 2, 3);+            });+        };++        return expanded;+    }++    if inputs.iter().len() == 2 {+        let expanded = quote! {+            let cb = Closure::<dyn FnMut(wasm_bindgen::JsValue, wasm_bindgen::JsValue)>::new(|val, val2| {+                let event = Test1Data {+                    test_parameter: "test".to_string(),+                };++                let event2 = "serialized".to_string();++                #fn_name(event, event2);+            });+        };++        return expanded;+    }++    if inputs.iter().len() == 1 {+        let expanded = quote! {+            let cb = Closure::<dyn FnMut(wasm_bindgen::JsValue)>::new(|val| {+                let event = Test1Data {+                    test_parameter: "test".to_string(),+                };++                #fn_name(event);+            });+        };++        return expanded;+    }++    let expanded = quote! {+        let cb = Closure::<dyn FnMut()>::new(|| {+            #fn_name();+        });+    };++    return expanded;++    let mut js_arg_idents: Vec<syn::Ident> = Vec::new(); // val0, val1, ... lambda arguments+    let mut rust_arg_types: Vec<syn::Type> = Vec::new(); // extracted types form the annotated rust function+    let mut rs_arg_idents: Vec<syn::Ident> = Vec::new(); // arg0, arg1, ... converted from val0, val1,+    let mut temp_result_idents: Vec<syn::Ident> = Vec::new(); // res0, res1, ... result for serde parser++    for (i, arg) in inputs.iter().enumerate() {+        let span = Span::call_site();+        let val_ident = syn::Ident::new(&format!("val{}", i), span);+        js_arg_idents.push(val_ident);++        let rs_ident = syn::Ident::new(&format!("arg{}", i), span);+        rs_arg_idents.push(rs_ident);++        let res_ident = syn::Ident::new(&format!("res{}", i), span);+        temp_result_idents.push(res_ident);++        match arg {+            FnArg::Typed(pat_type) => {+                rust_arg_types.push((*pat_type.ty).clone());+            }+            _ => panic!("expected typed argument"),+        }+    }++    let js_value_type = quote! { wasm_bindgen::JsValue };+    let js_types: Vec<proc_macro2::TokenStream> = (0..js_arg_idents.len())+        .map(|_| js_value_type.clone())+        .collect();++    let mut conversions = Vec::new();+    for ((res_ident, rs_ident), (val_ident, ty)) in temp_result_idents+        .iter()+        .zip(rs_arg_idents.iter())+        .zip(js_arg_idents.iter().zip(rust_arg_types.iter()))+    {+        conversions.push(quote! {+            let #res_ident = serde_wasm_bindgen::from_value::<#ty>(#val_ident.clone());+            if #res_ident.is_err() {+                gloo::console::log!(+                    concat!("Callback error: ", stringify!(#fn_name), ". Wrong argument type: "),+                    #res_ident.err().unwrap()+                );+                return;+            }+            let #rs_ident = #res_ident.unwrap();+        });+    }++    let call_args = rs_arg_idents.iter();+    let cb_tokens = quote! {+        let cb = Closure::wrap(Box::new(move | #(#js_arg_idents),* | {+            #(#conversions)*++            // call the original Rust function with the converted args+            #fn_name( #(#call_args),* );+        }) as Box<dyn FnMut( #(#js_types),* ) + 'static>);+    };++    cb_tokens+}+ struct CallbackArg {     name: String,     span: proc_macro2::Span,

Contact

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