rust_jsc_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{parse_macro_input, FnArg, ItemFn, PatType, Type, TypePath};
4
5#[proc_macro_attribute]
6pub fn callback(_attr: TokenStream, item: TokenStream) -> TokenStream {
7    let input = parse_macro_input!(item as ItemFn);
8    let fn_name = &input.sig.ident;
9    let visibility = &input.vis;
10    let generics = &input.sig.generics;
11    let generic_params = &generics.params;
12    let where_clause = &generics.where_clause;
13
14    // Collect typed params (excluding the first three)
15    // e.g. ctx, func, this -> skip them
16    let params: Vec<_> = input.sig.inputs.iter().skip(3).collect();
17
18    // Check if using raw arguments slice
19    if params.len() == 1 {
20        if let FnArg::Typed(PatType { ty, .. }) = &params[0] {
21            if let Type::Reference(_) = &**ty {
22                // Handle old-style with raw arguments slice
23                return generate_legacy_callback(&input, fn_name, visibility, generics);
24            }
25        }
26    }
27
28    // Generate argument parsing code
29    let mut parse_stmts = Vec::new();
30    for (i, param) in params.iter().enumerate() {
31        if let FnArg::Typed(PatType { pat, ty, .. }) = param {
32            let idx = syn::Index::from(i);
33            let var_ident = format_ident!("arg_{}", i);
34            let param_name = quote!(#pat).to_string();
35
36            parse_stmts.push(match &**ty {
37                Type::Path(TypePath { path, .. }) => {
38                    let is_optional = path
39                        .segments
40                        .last()
41                        .map(|s| s.ident == "Option")
42                        .unwrap_or(false);
43
44                    if is_optional {
45                        generate_optional_param_parsing(idx, var_ident)
46                    } else {
47                        generate_required_param_parsing(
48                            idx,
49                            var_ident,
50                            fn_name.to_string().as_str(),
51                            param_name.as_str(),
52                        )
53                    }
54                }
55                _ => quote! {
56                    panic!("[callback] Unsupported parameter type for {}", #param_name);
57                },
58            });
59        }
60    }
61
62    let call_args: Vec<_> = params
63        .iter()
64        .enumerate()
65        .map(|(i, _)| {
66            let var_ident = format_ident!("arg_{}", i);
67            quote!(#var_ident)
68        })
69        .collect();
70
71    let func_call = quote! {
72        #fn_name ::<#generic_params>(ctx, function, this_object, #(#call_args),*)
73    };
74
75    let expanded = quote! {
76        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
77            __ctx_ref: rust_jsc::internal::JSContextRef,
78            __function: rust_jsc::internal::JSObjectRef,
79            __this_object: rust_jsc::internal::JSObjectRef,
80            __argument_count: usize,
81            __arguments: *const rust_jsc::internal::JSValueRef,
82            __exception: *mut rust_jsc::internal::JSValueRef,
83        ) -> *const rust_jsc::internal::OpaqueJSValue
84        #where_clause {
85            let ctx = rust_jsc::JSContext::from(__ctx_ref);
86            let function = rust_jsc::JSObject::from_ref(__function, __ctx_ref);
87            let this_object = rust_jsc::JSObject::from_ref(__this_object, __ctx_ref);
88            let arguments = if __arguments.is_null() || __argument_count == 0 {
89                vec![]
90            } else {
91                unsafe { std::slice::from_raw_parts(__arguments, __argument_count) }
92                    .iter()
93                    .map(|__inner_value| rust_jsc::JSValue::new(*__inner_value, __ctx_ref))
94                    .collect::<Vec<_>>()
95            };
96
97            #(#parse_stmts)*
98
99            let result = (|| {
100                #input
101                #func_call
102            })();
103
104            match result {
105                Ok(value) => {
106                    *__exception = std::ptr::null_mut();
107                    value.into()
108                }
109                Err(exception) => {
110                    *__exception = rust_jsc::internal::JSValueRef::from(exception) as *mut _;
111                    std::ptr::null_mut()
112                }
113            }
114        }
115    };
116
117    TokenStream::from(expanded)
118}
119
120fn generate_optional_param_parsing(
121    idx: syn::Index,
122    var_ident: syn::Ident,
123) -> proc_macro2::TokenStream {
124    quote! {
125        let #var_ident = match arguments.get(#idx).map(|value| value.try_into()) {
126            Some(Ok(value)) => Some(value),
127            Some(Err(err)) => {
128                *__exception = rust_jsc::internal::JSValueRef::from(err) as *mut _;
129                return std::ptr::null_mut();
130            },
131            None => None,
132        };
133    }
134}
135
136fn generate_required_param_parsing(
137    idx: syn::Index,
138    var_ident: syn::Ident,
139    fn_name: &str,
140    param_name: &str,
141) -> proc_macro2::TokenStream {
142    quote! {
143        let #var_ident = match arguments.get(#idx).map(|value| value.try_into()) {
144            Some(Ok(value)) => value,
145            Some(Err(err)) => {
146                *__exception = rust_jsc::internal::JSValueRef::from(err) as *mut _;
147                return std::ptr::null_mut();
148            },
149            None => {
150                *__exception = rust_jsc::JSError::new_typ_raw(&ctx, format!("[{}] Missing argument {}", #fn_name, #param_name)) as *mut _;
151                return std::ptr::null_mut();
152            },
153        };
154    }
155}
156
157fn generate_legacy_callback(
158    input: &ItemFn,
159    fn_name: &syn::Ident,
160    visibility: &syn::Visibility,
161    generics: &syn::Generics,
162) -> TokenStream {
163    let generic_params = &generics.params;
164    let where_clause = &generics.where_clause;
165
166    let expanded = quote! {
167        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
168            __ctx_ref: rust_jsc::internal::JSContextRef,
169            __function: rust_jsc::internal::JSObjectRef,
170            __this_object: rust_jsc::internal::JSObjectRef,
171            __argument_count: usize,
172            __arguments: *const rust_jsc::internal::JSValueRef,
173            __exception: *mut rust_jsc::internal::JSValueRef,
174        ) -> *const rust_jsc::internal::OpaqueJSValue
175        #where_clause {
176            let ctx = rust_jsc::JSContext::from(__ctx_ref);
177            let function = rust_jsc::JSObject::from_ref(__function, __ctx_ref);
178            let this_object = rust_jsc::JSObject::from_ref(__this_object, __ctx_ref);
179            let arguments = if __arguments.is_null() || __argument_count == 0 {
180                vec![]
181            } else {
182                unsafe { std::slice::from_raw_parts(__arguments, __argument_count) }
183                    .iter()
184                    .map(|__inner_value| rust_jsc::JSValue::new(*__inner_value, __ctx_ref))
185                    .collect::<Vec<_>>()
186            };
187
188            let func: fn(
189                rust_jsc::JSContext,
190                rust_jsc::JSObject,
191                rust_jsc::JSObject,
192                &[rust_jsc::JSValue],
193            ) -> rust_jsc::JSResult<rust_jsc::JSValue> = {
194                #input
195
196                #fn_name ::<#generic_params>
197            };
198
199            let result = func(ctx, function, this_object, arguments.as_slice());
200
201            match result {
202                Ok(value) => {
203                    *__exception = std::ptr::null_mut();
204                    value.into()
205                }
206                Err(exception) => {
207                    *__exception = rust_jsc::internal::JSValueRef::from(exception) as *mut _;
208                    std::ptr::null_mut()
209                }
210            }
211        }
212    };
213
214    TokenStream::from(expanded)
215}
216
217#[proc_macro_attribute]
218pub fn constructor(_attr: TokenStream, item: TokenStream) -> TokenStream {
219    let input = parse_macro_input!(item as ItemFn);
220    let fn_name = &input.sig.ident;
221    let visibility = &input.vis;
222    let generics = &input.sig.generics;
223    let generic_params = &generics.params;
224    let where_clause = &generics.where_clause;
225
226    let expanded = quote! {
227        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
228            __ctx_ref: rust_jsc::internal::JSContextRef,
229            __constructor: rust_jsc::internal::JSObjectRef,
230            __argument_count: usize,
231            __arguments: *const rust_jsc::internal::JSValueRef,
232            __exception: *mut rust_jsc::internal::JSValueRef,
233        ) -> *mut rust_jsc::internal::OpaqueJSValue
234        #where_clause {
235            let ctx = rust_jsc::JSContext::from(__ctx_ref);
236            let constructor = rust_jsc::JSObject::from_ref(__constructor, __ctx_ref);
237            let arguments = if __arguments.is_null() || __argument_count == 0 {
238                vec![]
239            } else {
240                unsafe { std::slice::from_raw_parts(__arguments, __argument_count) }
241                    .iter()
242                    .map(|__inner_value| rust_jsc::JSValue::new(*__inner_value, __ctx_ref))
243                    .collect::<Vec<_>>()
244            };
245
246            let func: fn(
247                rust_jsc::JSContext,
248                rust_jsc::JSObject,
249                &[rust_jsc::JSValue],
250            ) -> rust_jsc::JSResult<rust_jsc::JSValue> = {
251                #input
252
253                #fn_name ::<#generic_params>
254            };
255
256            let result = func(ctx, constructor, arguments.as_slice());
257
258            match result {
259                Ok(value) => {
260                    *__exception = std::ptr::null_mut();
261                    value.into()
262                }
263                Err(exception) => {
264                    *__exception = rust_jsc::internal::JSValueRef::from(exception) as *mut _;
265                    std::ptr::null_mut()
266                }
267            }
268        }
269    };
270
271    TokenStream::from(expanded)
272}
273
274#[proc_macro_attribute]
275pub fn initialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
276    let input = parse_macro_input!(item as ItemFn);
277    let fn_name = &input.sig.ident;
278    let visibility = &input.vis;
279    let generics = &input.sig.generics;
280    let generic_params = &generics.params;
281    let where_clause = &generics.where_clause;
282
283    let expanded = quote! {
284        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
285            __ctx_ref: rust_jsc::internal::JSContextRef,
286            __object: rust_jsc::internal::JSObjectRef,
287        )
288        #where_clause {
289            let ctx = rust_jsc::JSContext::from(__ctx_ref);
290            let object = rust_jsc::JSObject::from_ref(__object, __ctx_ref);
291
292            let func: fn(
293                rust_jsc::JSContext,
294                rust_jsc::JSObject,
295            ) = {
296                #input
297
298                #fn_name ::<#generic_params>
299            };
300
301            func(ctx, object);
302        }
303    };
304
305    TokenStream::from(expanded)
306}
307
308#[proc_macro_attribute]
309pub fn finalize(_attr: TokenStream, item: TokenStream) -> TokenStream {
310    let input = parse_macro_input!(item as ItemFn);
311    let fn_name = &input.sig.ident;
312    let visibility = &input.vis;
313    let generics = &input.sig.generics;
314    let generic_params = &generics.params;
315    let where_clause = &generics.where_clause;
316
317    let expanded = quote! {
318        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
319            __object: rust_jsc::internal::JSObjectRef,
320        )
321        #where_clause {
322            let data_ptr = rust_jsc::internal::JSObjectGetPrivate(__object);
323
324            let func: fn(
325                rust_jsc::PrivateData
326            ) = {
327                #input
328
329                #fn_name ::<#generic_params>
330            };
331
332            func(data_ptr);
333        }
334    };
335
336    TokenStream::from(expanded)
337}
338
339#[proc_macro_attribute]
340pub fn has_instance(_attr: TokenStream, item: TokenStream) -> TokenStream {
341    let input = parse_macro_input!(item as ItemFn);
342    let fn_name = &input.sig.ident;
343    let visibility = &input.vis;
344    let generics = &input.sig.generics;
345    let generic_params = &generics.params;
346    let where_clause = &generics.where_clause;
347
348    let expanded = quote! {
349        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
350            __ctx_ref: rust_jsc::internal::JSContextRef,
351            __constructor: rust_jsc::internal::JSObjectRef,
352            __possible_instance: rust_jsc::internal::JSValueRef,
353            __exception: *mut rust_jsc::internal::JSValueRef,
354        ) -> bool
355        #where_clause {
356            let ctx = rust_jsc::JSContext::from(__ctx_ref);
357            let constructor = rust_jsc::JSObject::from_ref(__constructor, __ctx_ref);
358            let possible_instance = rust_jsc::JSValue::new(__possible_instance, __ctx_ref);
359
360            let func: fn(
361                rust_jsc::JSContext,
362                rust_jsc::JSObject,
363                rust_jsc::JSValue,
364            ) -> rust_jsc::JSResult<bool> = {
365                #input
366
367                #fn_name ::<#generic_params>
368            };
369
370            let result = func(ctx, constructor, possible_instance);
371
372            match result {
373                Ok(value) => {
374                    *__exception = std::ptr::null_mut();
375                    value
376                }
377                Err(exception) => {
378                    *__exception = rust_jsc::internal::JSValueRef::from(exception) as *mut _;
379                    false
380                }
381            }
382        }
383    };
384
385    TokenStream::from(expanded)
386}
387
388#[proc_macro_attribute]
389pub fn module_resolve(_attr: TokenStream, item: TokenStream) -> TokenStream {
390    let input = parse_macro_input!(item as ItemFn);
391    let fn_name = &input.sig.ident;
392    let visibility = &input.vis;
393    let generics = &input.sig.generics;
394    let generic_params = &generics.params;
395    let where_clause = &generics.where_clause;
396
397    let expanded = quote! {
398        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
399            __ctx_ref: rust_jsc::internal::JSContextRef,
400            __key_value: rust_jsc::internal::JSValueRef,
401            __referrer: rust_jsc::internal::JSValueRef,
402            __script_fetcher: rust_jsc::internal::JSValueRef,
403        ) -> *mut rust_jsc::internal::OpaqueJSString
404        #where_clause {
405            let ctx = rust_jsc::JSContext::from(__ctx_ref);
406            let key_value = rust_jsc::JSValue::new(__key_value, __ctx_ref);
407            let referrer = rust_jsc::JSValue::new(__referrer, __ctx_ref);
408            let script_fetcher = rust_jsc::JSValue::new(__script_fetcher, __ctx_ref);
409
410            let func: fn(
411                rust_jsc::JSContext,
412                rust_jsc::JSValue,
413                rust_jsc::JSValue,
414                rust_jsc::JSValue,
415            ) -> rust_jsc::JSStringProctected = {
416                #input
417
418                #fn_name ::<#generic_params>
419            };
420
421            let result = func(ctx, key_value, referrer, script_fetcher);
422            rust_jsc::internal::JSStringRef::from(result)
423        }
424    };
425
426    TokenStream::from(expanded)
427}
428
429#[proc_macro_attribute]
430pub fn module_evaluate(_attr: TokenStream, item: TokenStream) -> TokenStream {
431    let input = parse_macro_input!(item as ItemFn);
432    let fn_name = &input.sig.ident;
433    let visibility = &input.vis;
434    let generics = &input.sig.generics;
435    let generic_params = &generics.params;
436    let where_clause = &generics.where_clause;
437
438    let expanded = quote! {
439        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
440            __ctx_ref: rust_jsc::internal::JSContextRef,
441            __key_value: rust_jsc::internal::JSValueRef,
442        ) -> *const rust_jsc::internal::OpaqueJSValue
443        #where_clause {
444            let ctx = rust_jsc::JSContext::from(__ctx_ref);
445            let key_value = rust_jsc::JSValue::new(__key_value, __ctx_ref);
446
447            let func: fn(
448                rust_jsc::JSContext,
449                rust_jsc::JSValue,
450            ) -> rust_jsc::JSValue = {
451                #input
452
453                #fn_name ::<#generic_params>
454            };
455
456            let result = func(ctx, key_value);
457            result.into()
458        }
459    };
460
461    TokenStream::from(expanded)
462}
463
464#[proc_macro_attribute]
465pub fn module_fetch(_attr: TokenStream, item: TokenStream) -> TokenStream {
466    let input = parse_macro_input!(item as ItemFn);
467    let fn_name = &input.sig.ident;
468    let visibility = &input.vis;
469    let generics = &input.sig.generics;
470    let generic_params = &generics.params;
471    let where_clause = &generics.where_clause;
472
473    let expanded = quote! {
474        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
475            __ctx_ref: rust_jsc::internal::JSContextRef,
476            __key_value: rust_jsc::internal::JSValueRef,
477            __attributes_value: rust_jsc::internal::JSValueRef,
478            __script_fetcher: rust_jsc::internal::JSValueRef,
479        ) -> *mut rust_jsc::internal::OpaqueJSString
480        #where_clause {
481            let ctx = rust_jsc::JSContext::from(__ctx_ref);
482            let key_value = rust_jsc::JSValue::new(__key_value, __ctx_ref);
483            let attributes_value = rust_jsc::JSValue::new(__attributes_value, __ctx_ref);
484            let script_fetcher = rust_jsc::JSValue::new(__script_fetcher, __ctx_ref);
485
486            let func: fn(
487                rust_jsc::JSContext,
488                rust_jsc::JSValue,
489                rust_jsc::JSValue,
490                rust_jsc::JSValue,
491            ) -> rust_jsc::JSStringProctected = {
492                #input
493
494                #fn_name ::<#generic_params>
495            };
496
497            let result = func(ctx, key_value, attributes_value, script_fetcher);
498            rust_jsc::internal::JSStringRef::from(result)
499        }
500    };
501
502    TokenStream::from(expanded)
503}
504
505#[proc_macro_attribute]
506pub fn module_import_meta(_attr: TokenStream, item: TokenStream) -> TokenStream {
507    let input = parse_macro_input!(item as ItemFn);
508    let fn_name = &input.sig.ident;
509    let visibility = &input.vis;
510    let generics = &input.sig.generics;
511    let generic_params = &generics.params;
512    let where_clause = &generics.where_clause;
513
514    let expanded = quote! {
515        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
516            __ctx_ref: rust_jsc::internal::JSContextRef,
517            __key_value: rust_jsc::internal::JSValueRef,
518            __script_fetcher: rust_jsc::internal::JSValueRef,
519        ) -> *mut rust_jsc::internal::OpaqueJSValue
520        #where_clause {
521            let ctx = rust_jsc::JSContext::from(__ctx_ref);
522            let key_value = rust_jsc::JSValue::new(__key_value, __ctx_ref);
523            let script_fetcher = rust_jsc::JSValue::new(__script_fetcher, __ctx_ref);
524
525            let func: fn(
526                rust_jsc::JSContext,
527                rust_jsc::JSValue,
528                rust_jsc::JSValue,
529            ) -> rust_jsc::JSObject = {
530                #input
531
532                #fn_name ::<#generic_params>
533            };
534
535            let result = func(ctx, key_value, script_fetcher);
536            rust_jsc::internal::JSObjectRef::from(result)
537        }
538    };
539
540    TokenStream::from(expanded)
541}
542
543#[proc_macro_attribute]
544pub fn uncaught_exception(_attr: TokenStream, item: TokenStream) -> TokenStream {
545    let input = parse_macro_input!(item as ItemFn);
546    let fn_name = &input.sig.ident;
547    let visibility = &input.vis;
548    let generics = &input.sig.generics;
549    let generic_params = &generics.params;
550    let where_clause = &generics.where_clause;
551
552    let expanded = quote! {
553        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
554            __ctx_ref: rust_jsc::internal::JSContextRef,
555            __filename: rust_jsc::internal::JSStringRef,
556            __exception: rust_jsc::internal::JSValueRef,
557        ) #where_clause {
558            let ctx = rust_jsc::JSContext::from(__ctx_ref);
559            let filename = rust_jsc::JSString::from(__filename);
560            let exception = rust_jsc::JSValue::new(__exception, __ctx_ref);
561
562            let func: fn(
563                rust_jsc::JSContext,
564                rust_jsc::JSString,
565                rust_jsc::JSValue,
566            ) = {
567                #input
568
569                #fn_name ::<#generic_params>
570            };
571
572            func(ctx, filename, exception);
573        }
574    };
575
576    TokenStream::from(expanded)
577}
578
579#[proc_macro_attribute]
580pub fn uncaught_exception_event_loop(
581    _attr: TokenStream,
582    item: TokenStream,
583) -> TokenStream {
584    let input = parse_macro_input!(item as ItemFn);
585    let fn_name = &input.sig.ident;
586    let visibility = &input.vis;
587    let generics = &input.sig.generics;
588    let generic_params = &generics.params;
589    let where_clause = &generics.where_clause;
590
591    let expanded = quote! {
592        #visibility unsafe extern "C" fn #fn_name <#generic_params> (
593            __ctx_ref: rust_jsc::internal::JSContextRef,
594            __exception: rust_jsc::internal::JSValueRef,
595        ) #where_clause {
596            let ctx = rust_jsc::JSContext::from(__ctx_ref);
597            let exception = rust_jsc::JSValue::new(__exception, __ctx_ref);
598
599            let func: fn(
600                rust_jsc::JSContext,
601                rust_jsc::JSValue,
602            ) = {
603                #input
604
605                #fn_name ::<#generic_params>
606            };
607
608            func(ctx, exception);
609        }
610    };
611
612    TokenStream::from(expanded)
613}