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(¶ms);
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 ¶ms {
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 ¶ms,
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}