napi_derive/
lib.rs

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