Skip to main content

glue_v8/
lib.rs

1//! V8 Glue - Rust to V8 binding macros for OpenWorkers
2//!
3//! Generate V8 callback boilerplate from Rust functions.
4//!
5//! ## Functions with state
6//!
7//! State is passed via FunctionTemplate data (External containing Rc<T>).
8//! Use `{fn_name}_v8_template(scope, &state)` to register the function.
9//!
10//! ```ignore
11//! #[glue_v8::method(state = Rc<TimerState>)]
12//! fn schedule_timeout(state: &Rc<TimerState>, id: u64, delay: u64) {
13//!     let _ = state.scheduler_tx.send(ScheduleTimeout(id, delay));
14//! }
15//!
16//! // Registration:
17//! let state = Rc::new(TimerState { ... });
18//! let func = schedule_timeout_v8_template(scope, &state)
19//!     .get_function(scope).unwrap();
20//! ```
21//!
22//! ## Fast API Support
23//!
24//! The `fast` attribute enables V8 Fast API for functions with primitive arguments.
25//! Fast API can be ~10x faster for hot functions.
26//!
27//! ```ignore
28//! #[glue_v8::method(fast)]
29//! fn add(a: i32, b: i32) -> i32 {
30//!     a + b
31//! }
32//!
33//! // Registration:
34//! let template = add_v8_template(scope, None);
35//! let func = template.get_function(scope).unwrap();
36//! ```
37//!
38//! Requirements for Fast API:
39//! - Only primitive types: bool, i32, u32, i64, u64, f32, f64
40//! - No scope parameter (cannot use V8 APIs in fast path)
41//! - Return type must be a primitive or void
42
43mod codegen;
44mod fast;
45mod parse;
46mod types;
47
48use proc_macro::TokenStream;
49use quote::quote;
50use syn::{FnArg, ItemFn, Pat, ReturnType, parse_macro_input};
51
52use codegen::{
53    generate_arg_extractions, generate_call_and_return, generate_state_extraction,
54    generate_state_template,
55};
56use fast::generate_fast_api_code;
57use parse::MethodAttrs;
58use types::is_result_type;
59
60/// Generate a V8 callback wrapper for a Rust function.
61///
62/// # Examples
63///
64/// Basic function (no state):
65/// ```ignore
66/// #[glue_v8::method]
67/// fn add(scope: &mut v8::PinScope, a: f64, b: f64) -> f64 {
68///     a + b
69/// }
70/// ```
71///
72/// With state (passed via FunctionTemplate data):
73/// ```ignore
74/// #[glue_v8::method(state = Rc<TimerState>)]
75/// fn schedule_timeout(scope: &mut v8::PinScope, state: &Rc<TimerState>, id: u64, delay: u64) {
76///     let _ = state.scheduler_tx.send(SchedulerMessage::ScheduleTimeout(id, delay));
77/// }
78///
79/// // Registration:
80/// let state = Rc::new(TimerState { ... });
81/// let func = schedule_timeout_v8_template(scope, &state).get_function(scope).unwrap();
82/// ```
83///
84/// With custom JS name:
85/// ```ignore
86/// #[glue_v8::method(name = "setTimeout")]
87/// fn set_timeout(scope: &mut v8::PinScope, delay: f64) -> u64 { ... }
88/// ```
89///
90/// With Result return type (throws on Err):
91/// ```ignore
92/// #[glue_v8::method]
93/// fn parse_json(scope: &mut v8::PinScope, input: String) -> Result<serde_json::Value, String> {
94///     serde_json::from_str(&input).map_err(|e| e.to_string())
95/// }
96/// ```
97///
98/// With Promise (returns JS Promise):
99/// ```ignore
100/// #[glue_v8::method(promise)]
101/// fn fetch_data(scope: &mut v8::PinScope, url: String) -> Result<String, String> {
102///     // Ok(value) → Promise.resolve(value)
103///     // Err(msg)  → Promise.reject(new Error(msg))
104///     Ok("data".to_string())
105/// }
106/// ```
107///
108/// With optional parameters:
109/// ```ignore
110/// #[glue_v8::method]
111/// fn greet(scope: &mut v8::PinScope, name: String, title: Option<String>) -> String {
112///     // greet("Alice") → title is None
113///     // greet("Alice", "Dr.") → title is Some("Dr.")
114///     match title {
115///         Some(t) => format!("{} {}", t, name),
116///         None => name,
117///     }
118/// }
119/// ```
120///
121/// With Fast API (for hot paths with primitive types):
122/// ```ignore
123/// #[glue_v8::method(fast)]
124/// fn add(a: i32, b: i32) -> i32 {
125///     a + b
126/// }
127///
128/// // With state - state is passed via FunctionTemplate data
129/// #[glue_v8::method(fast, state = Rc<TimerState>)]
130/// fn schedule_timeout(state: &Rc<TimerState>, id: u64, delay: u64) {
131///     let _ = state.scheduler_tx.send(SchedulerMessage::ScheduleTimeout(id, delay));
132/// }
133/// ```
134///
135/// Note: Fast API functions generate both slow and fast paths.
136/// Use `{fn_name}_v8_template(scope, state_external)` to register with FunctionTemplate.
137#[proc_macro_attribute]
138pub fn method(attr: TokenStream, item: TokenStream) -> TokenStream {
139    let attrs = MethodAttrs::parse(attr);
140    let input_fn = parse_macro_input!(item as ItemFn);
141    let fn_name = &input_fn.sig.ident;
142    let _js_name = attrs.js_name.unwrap_or_else(|| fn_name.to_string());
143    let wrapper_name = syn::Ident::new(&format!("{}_v8", fn_name), fn_name.span());
144
145    // Extract parameters, tracking which are special (scope, state)
146    let mut has_scope = false;
147    let mut has_state = false;
148    let params: Vec<_> = input_fn
149        .sig
150        .inputs
151        .iter()
152        .filter_map(|arg| {
153            if let FnArg::Typed(pat_type) = arg {
154                if let Pat::Ident(pat_ident) = &*pat_type.pat {
155                    let name = &pat_ident.ident;
156                    let ty = &pat_type.ty;
157
158                    // Skip 'scope' or '_scope' - provided by V8 callback
159                    let name_str = name.to_string();
160                    if name_str == "scope" || name_str == "_scope" {
161                        has_scope = true;
162                        return None;
163                    }
164
165                    // Skip 'state' - will be extracted from FunctionTemplate data
166                    if name_str == "state" {
167                        has_state = true;
168                        return None;
169                    }
170
171                    return Some((name.clone(), ty.clone()));
172                }
173            }
174            None
175        })
176        .collect();
177
178    // Generate argument extraction code
179    let arg_extractions = generate_arg_extractions(&params);
180
181    // Generate state extraction if needed
182    let state_extraction = generate_state_extraction(has_state, &attrs.state_type);
183
184    // Generate function call arguments
185    let call_args: Vec<_> = {
186        let mut args = Vec::new();
187
188        if has_scope {
189            args.push(quote! { scope });
190        }
191
192        if has_state {
193            args.push(quote! { &state });
194        }
195
196        for (name, _) in &params {
197            args.push(quote! { #name });
198        }
199
200        args
201    };
202
203    // Check if function has a return type and if it's a Result
204    let has_return = !matches!(input_fn.sig.output, ReturnType::Default);
205    let returns_result = if let ReturnType::Type(_, ty) = &input_fn.sig.output {
206        is_result_type(ty)
207    } else {
208        false
209    };
210
211    let call_and_return = generate_call_and_return(
212        fn_name,
213        &call_args,
214        has_return,
215        returns_result,
216        attrs.promise,
217    );
218
219    // Generate template function name for stateful functions
220    let template_fn_name = syn::Ident::new(&format!("{}_v8_template", fn_name), fn_name.span());
221
222    // Generate the expanded code
223    let expanded = if attrs.fast {
224        // Fast API mode: generate both slow and fast paths
225        generate_fast_api_code(
226            &input_fn,
227            fn_name,
228            &wrapper_name,
229            &params,
230            has_scope,
231            has_state,
232            &attrs.state_type,
233            &state_extraction,
234            &arg_extractions,
235            &call_and_return,
236        )
237    } else if has_state {
238        // Non-fast with state: generate wrapper + template function
239        let state_type = attrs
240            .state_type
241            .as_ref()
242            .expect("Function has 'state' parameter but no state type specified");
243        let template_fn = generate_state_template(&wrapper_name, &template_fn_name, state_type);
244
245        quote! {
246            #input_fn
247
248            /// V8 callback wrapper - auto-generated by glue_v8::method
249            pub fn #wrapper_name(
250                scope: &mut v8::PinScope,
251                args: v8::FunctionCallbackArguments,
252                mut rv: v8::ReturnValue,
253            ) {
254                #state_extraction
255                #(#arg_extractions)*
256                #call_and_return
257            }
258
259            #template_fn
260        }
261    } else {
262        // Standard mode: only slow path, no state
263        quote! {
264            #input_fn
265
266            /// V8 callback wrapper - auto-generated by glue_v8::method
267            pub fn #wrapper_name(
268                scope: &mut v8::PinScope,
269                args: v8::FunctionCallbackArguments,
270                mut rv: v8::ReturnValue,
271            ) {
272                #state_extraction
273                #(#arg_extractions)*
274                #call_and_return
275            }
276        }
277    };
278
279    TokenStream::from(expanded)
280}