napi_derive/
lib.rs

1#[cfg(feature = "compat-mode")]
2mod compat_macro;
3mod expand;
4#[cfg(not(feature = "noop"))]
5mod parser;
6
7#[cfg(not(feature = "noop"))]
8#[macro_use]
9extern crate napi_derive_backend;
10#[macro_use]
11extern crate quote;
12
13use std::env;
14
15use proc_macro::TokenStream;
16#[cfg(feature = "compat-mode")]
17use syn::fold::Fold;
18use syn::{parse_macro_input, ItemFn};
19
20/// ```ignore
21/// #[napi]
22/// fn test(name: String) {
23///   "hello" + name
24/// }
25/// ```
26#[proc_macro_attribute]
27pub fn napi(attr: TokenStream, input: TokenStream) -> TokenStream {
28  match expand::expand(attr.into(), input.into()) {
29    Ok(tokens) => {
30      if env::var("NAPI_DEBUG_GENERATED_CODE").is_ok() {
31        println!("{tokens}");
32      }
33      tokens.into()
34    }
35    Err(diagnostic) => {
36      println!("`napi` macro expand failed.");
37
38      (quote! { #diagnostic }).into()
39    }
40  }
41}
42
43#[cfg(feature = "compat-mode")]
44#[proc_macro_attribute]
45pub fn contextless_function(_attr: TokenStream, input: TokenStream) -> TokenStream {
46  let input = parse_macro_input!(input as ItemFn);
47  let mut js_fn = compat_macro::JsFunction::new();
48  js_fn.fold_item_fn(input);
49  let fn_name = js_fn.name.unwrap();
50  let fn_block = js_fn.block;
51  let signature = js_fn.signature.unwrap();
52  let visibility = js_fn.visibility;
53  let new_fn_name = signature.ident.clone();
54  let execute_js_function =
55    compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::Contextless);
56
57  let expanded = quote! {
58    #[inline(always)]
59    #signature #(#fn_block)*
60
61    #visibility extern "C" fn #fn_name(
62      raw_env: napi::sys::napi_env,
63      cb_info: napi::sys::napi_callback_info,
64    ) -> napi::sys::napi_value {
65      use std::ptr;
66      use std::panic::{self, AssertUnwindSafe};
67      use std::ffi::CString;
68      use napi::{Env, NapiValue, NapiRaw, Error, Status};
69
70      let ctx = unsafe { Env::from_raw(raw_env) };
71      #execute_js_function
72    }
73  };
74  // Hand the output tokens back to the compiler
75  TokenStream::from(expanded)
76}
77
78#[cfg(feature = "compat-mode")]
79#[proc_macro_attribute]
80pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
81  let arg_len = parse_macro_input!(attr as compat_macro::ArgLength);
82  let arg_len_span = arg_len.length;
83  let input = parse_macro_input!(input as ItemFn);
84  let mut js_fn = compat_macro::JsFunction::new();
85  js_fn.fold_item_fn(input);
86  let fn_name = js_fn.name.unwrap();
87  let fn_block = js_fn.block;
88  let signature = js_fn.signature.unwrap();
89  let visibility = js_fn.visibility;
90  let new_fn_name = signature.ident.clone();
91  let execute_js_function =
92    compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::JsFunction);
93  let expanded = quote! {
94    #[inline(always)]
95    #signature #(#fn_block)*
96
97    #visibility extern "C" fn #fn_name(
98      raw_env: napi::sys::napi_env,
99      cb_info: napi::sys::napi_callback_info,
100    ) -> napi::sys::napi_value {
101      use std::ptr;
102      use std::panic::{self, AssertUnwindSafe};
103      use std::ffi::CString;
104      use napi::{Env, Error, Status, NapiValue, NapiRaw, CallContext};
105      let mut argc = #arg_len_span as usize;
106      #[cfg(all(target_os = "windows", target_arch = "x86"))]
107      let mut raw_args = vec![ptr::null_mut(); #arg_len_span];
108      #[cfg(not(all(target_os = "windows", target_arch = "x86")))]
109      let mut raw_args = [ptr::null_mut(); #arg_len_span];
110      let mut raw_this = ptr::null_mut();
111
112      unsafe {
113        let status = napi::sys::napi_get_cb_info(
114          raw_env,
115          cb_info,
116          &mut argc,
117          raw_args.as_mut_ptr(),
118          &mut raw_this,
119          ptr::null_mut(),
120        );
121        debug_assert!(Status::from(status) == Status::Ok, "napi_get_cb_info failed");
122      }
123
124      let mut env = unsafe { Env::from_raw(raw_env) };
125      #[cfg(all(target_os = "windows", target_arch = "x86"))]
126      let ctx = CallContext::new(&mut env, cb_info, raw_this, raw_args.as_slice(), argc);
127      #[cfg(not(all(target_os = "windows", target_arch = "x86")))]
128      let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, argc);
129      #execute_js_function
130    }
131  };
132  // Hand the output tokens back to the compiler
133  TokenStream::from(expanded)
134}
135
136#[cfg(feature = "compat-mode")]
137#[proc_macro_attribute]
138pub fn module_exports(_attr: TokenStream, input: TokenStream) -> TokenStream {
139  let input = parse_macro_input!(input as ItemFn);
140  let mut js_fn = compat_macro::JsFunction::new();
141  js_fn.fold_item_fn(input);
142  let fn_block = js_fn.block;
143  let fn_name = js_fn.name.unwrap();
144  let signature = js_fn.signature_raw.unwrap();
145  let args_len = js_fn.args.len();
146  let call_expr = if args_len == 1 {
147    quote! { #fn_name(exports) }
148  } else if args_len == 2 {
149    quote! { #fn_name(exports, env) }
150  } else {
151    panic!("Arguments length of #[module_exports] function must be 1 or 2");
152  };
153
154  let register = quote! {
155    #[cfg_attr(not(target_family = "wasm"), napi::ctor::ctor(crate_path=napi::ctor))]
156    fn __napi_explicit_module_register() {
157      unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> {
158        use napi::{Env, JsObject, NapiValue};
159
160        let env = Env::from_raw(raw_env);
161        let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);
162
163        #call_expr
164      }
165
166      napi::bindgen_prelude::register_module_exports(register)
167    }
168  };
169
170  (quote! {
171    #[inline]
172    #signature #(#fn_block)*
173
174    #register
175  })
176  .into()
177}
178
179#[proc_macro_attribute]
180pub fn module_init(_: TokenStream, input: TokenStream) -> TokenStream {
181  let input = parse_macro_input!(input as ItemFn);
182  quote! {
183    #[napi::ctor::ctor(crate_path=napi::ctor)]
184    #input
185  }
186  .into()
187}