napi_derive/
lib.rs

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