1use proc_macro2::{Ident, Span, TokenStream};
2use quote::ToTokens;
3use syn::spanned::Spanned;
4
5use crate::{
6 codegen::{get_intermediate_ident, js_mod_to_token_stream},
7 BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, NapiFn, NapiFnArgKind, TryToTokens,
8 TYPEDARRAY_SLICE_TYPES,
9};
10
11impl TryToTokens for NapiFn {
12 fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
13 let name_str = self.name.to_string();
14 let intermediate_ident = get_intermediate_ident(&name_str);
15 let args_len = self.args.len();
16
17 let ArgConversions {
18 arg_conversions,
19 args: arg_names,
20 refs,
21 mut_ref_spans,
22 unsafe_,
23 } = self.gen_arg_conversions()?;
24 if self.is_async && !mut_ref_spans.is_empty() && !unsafe_ {
27 return Diagnostic::from_vec(
28 mut_ref_spans
29 .into_iter()
30 .map(|s| Diagnostic::span_error(s, "mutable reference is unsafe with async"))
31 .collect(),
32 );
33 }
34 if Some(FnSelf::MutRef) == self.fn_self && self.is_async && !self.unsafe_ {
35 return Err(Diagnostic::span_error(
36 self.name.span(),
37 "&mut self in async napi methods should be marked as unsafe",
38 ));
39 }
40 let arg_ref_count = refs.len();
41 let receiver = self.gen_fn_receiver();
42 let receiver_ret_name = Ident::new("_ret", Span::call_site());
43 let ret = self.gen_fn_return(&receiver_ret_name);
44 let register = self.gen_fn_register();
45 let attrs = &self.attrs;
46
47 let build_ref_container = if self.is_async {
48 quote! {
49 struct NapiRefContainer([napi::sys::napi_ref; #arg_ref_count]);
50 impl NapiRefContainer {
51 fn drop(self, env: napi::sys::napi_env) {
52 for r in self.0.into_iter() {
53 assert_eq!(
54 unsafe { napi::sys::napi_reference_unref(env, r, &mut 0) },
55 napi::sys::Status::napi_ok,
56 "failed to delete napi ref"
57 );
58 assert_eq!(
59 unsafe { napi::sys::napi_delete_reference(env, r) },
60 napi::sys::Status::napi_ok,
61 "failed to delete napi ref"
62 );
63 }
64 }
65 }
66 unsafe impl Send for NapiRefContainer {}
67 unsafe impl Sync for NapiRefContainer {}
68 let _make_ref = |a: ::std::ptr::NonNull<napi::bindgen_prelude::sys::napi_value__>| {
69 let mut node_ref = ::std::mem::MaybeUninit::uninit();
70 napi::bindgen_prelude::check_status!(unsafe {
71 napi::bindgen_prelude::sys::napi_create_reference(env, a.as_ptr(), 1, node_ref.as_mut_ptr())
72 },
73 "failed to create napi ref"
74 )?;
75 Ok::<napi::sys::napi_ref, napi::Error>(unsafe { node_ref.assume_init() })
76 };
77 let mut _args_array = [::std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_ref__>(); #arg_ref_count];
78 let mut _arg_write_index = 0;
79
80 #(#refs)*
81
82 #[cfg(debug_assertions)]
83 {
84 for a in &_args_array {
85 assert!(!a.is_null(), "failed to initialize napi ref");
86 }
87 }
88 let _args_ref = NapiRefContainer(_args_array);
89 }
90 } else {
91 quote! {}
92 };
93 let native_call = if !self.is_async {
94 quote! {
95 napi::bindgen_prelude::within_runtime_if_available(move || {
96 let #receiver_ret_name = {
97 #receiver(#(#arg_names),*)
98 };
99 #ret
100 })
101 }
102 } else {
103 let call = if self.is_ret_result {
104 quote! { #receiver(#(#arg_names),*).await }
105 } else {
106 quote! { Ok(#receiver(#(#arg_names),*).await) }
107 };
108 quote! {
109 napi::bindgen_prelude::execute_tokio_future(env, async move { #call }, move |env, #receiver_ret_name| {
110 _args_ref.drop(env);
111 #ret
112 })
113 }
114 };
115
116 let use_after_async = if self.is_async && self.parent.is_some() && self.fn_self.is_none() {
118 quote! { true }
119 } else {
120 quote! { false }
121 };
122
123 let function_call_inner = quote! {
124 napi::bindgen_prelude::CallbackInfo::<#args_len>::new(env, cb, None, #use_after_async).and_then(|mut cb| {
125 #build_ref_container
126 #(#arg_conversions)*
127 #native_call
128 })
129 };
130
131 let function_call = if args_len == 0
132 && self.fn_self.is_none()
133 && self.kind != FnKind::Constructor
134 && self.kind != FnKind::Factory
135 && !self.is_async
136 {
137 quote! { #native_call }
138 } else if self.kind == FnKind::Constructor {
139 let return_from_factory = if self.catch_unwind {
140 quote! { return Ok(std::ptr::null_mut()); }
141 } else {
142 quote! { return std::ptr::null_mut(); }
143 };
144 quote! {
145 if napi::__private::___CALL_FROM_FACTORY.with(|inner| inner.load(std::sync::atomic::Ordering::Relaxed)) {
148 #return_from_factory
149 }
150 #function_call_inner
151 }
152 } else {
153 function_call_inner
154 };
155
156 let function_call = if self.catch_unwind {
157 quote! {
158 {
159 std::panic::catch_unwind(|| { #function_call })
160 .map_err(|e| {
161 let message = {
162 if let Some(string) = e.downcast_ref::<String>() {
163 string.clone()
164 } else if let Some(string) = e.downcast_ref::<&str>() {
165 string.to_string()
166 } else {
167 format!("panic from Rust code: {:?}", e)
168 }
169 };
170 napi::Error::new(napi::Status::GenericFailure, message)
171 })
172 .and_then(|r| r)
173 }
174 }
175 } else {
176 quote! {
177 #function_call
178 }
179 };
180
181 (quote! {
182 #(#attrs)*
183 #[doc(hidden)]
184 #[allow(non_snake_case)]
185 #[allow(clippy::all)]
186 extern "C" fn #intermediate_ident(
187 env: napi::bindgen_prelude::sys::napi_env,
188 cb: napi::bindgen_prelude::sys::napi_callback_info
189 ) -> napi::bindgen_prelude::sys::napi_value {
190 unsafe {
191 #function_call.unwrap_or_else(|e| {
192 napi::bindgen_prelude::JsError::from(e).throw_into(env);
193 std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>()
194 })
195 }
196 }
197
198 #register
199 })
200 .to_tokens(tokens);
201
202 Ok(())
203 }
204}
205
206impl NapiFn {
207 fn gen_arg_conversions(&self) -> BindgenResult<ArgConversions> {
208 let mut arg_conversions = vec![];
209 let mut args = vec![];
210 let mut refs = vec![];
211 let mut mut_ref_spans = vec![];
212 let make_ref = |input| {
213 quote! {
214 _args_array[_arg_write_index] = _make_ref(
215 ::std::ptr::NonNull::new(#input)
216 .ok_or_else(|| napi::Error::new(napi::Status::InvalidArg, "referenced ptr is null".to_owned()))?
217 )?;
218 _arg_write_index += 1;
219 }
220 };
221
222 if let Some(parent) = &self.parent {
224 match self.fn_self {
225 Some(FnSelf::Ref) => {
226 refs.push(make_ref(quote! { cb.this }));
227 arg_conversions.push(quote! {
228 let this_ptr = unsafe { cb.unwrap_raw::<#parent>()? };
229 let this: &#parent = Box::leak(Box::from_raw(this_ptr));
230 });
231 }
232 Some(FnSelf::MutRef) => {
233 refs.push(make_ref(quote! { cb.this }));
234 arg_conversions.push(quote! {
235 let this_ptr = unsafe { cb.unwrap_raw::<#parent>()? };
236 let this: &mut #parent = Box::leak(Box::from_raw(this_ptr));
237 });
238 }
239 _ => {}
240 };
241 }
242
243 let mut skipped_arg_count = 0;
244 for (i, arg) in self.args.iter().enumerate() {
245 let i = i - skipped_arg_count;
246 let ident = Ident::new(&format!("arg{}", i), Span::call_site());
247
248 match &arg.kind {
249 NapiFnArgKind::PatType(path) => {
250 if &path.ty.to_token_stream().to_string() == "Env" {
251 args.push(quote! { napi::bindgen_prelude::Env::from(env) });
252 skipped_arg_count += 1;
253 } else {
254 let is_in_class = self.parent.is_some();
255 if let syn::Type::Path(path) = path.ty.as_ref() {
256 if let Some(p) = path.path.segments.last() {
257 if p.ident == "Reference" {
258 if !is_in_class {
259 bail_span!(p, "`Reference` is only allowed in class methods");
260 }
261 if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
262 args: angle_bracketed_args,
263 ..
264 }) = &p.arguments
265 {
266 if let Some(syn::GenericArgument::Type(syn::Type::Path(path))) =
267 angle_bracketed_args.first()
268 {
269 if let Some(p) = path.path.segments.first() {
270 if p.ident == *self.parent.as_ref().unwrap() {
271 args.push(quote! {
272 napi::bindgen_prelude::Reference::from_value_ptr(this_ptr.cast(), env)?
273 });
274 skipped_arg_count += 1;
275 continue;
276 }
277 }
278 }
279 }
280 } else if p.ident == "This" {
281 if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
282 args: angle_bracketed_args,
283 ..
284 }) = &p.arguments
285 {
286 if let Some(syn::GenericArgument::Type(generic_type)) =
287 angle_bracketed_args.first()
288 {
289 if let syn::Type::Path(syn::TypePath {
290 path: syn::Path { segments, .. },
291 ..
292 }) = generic_type
293 {
294 if let Some(syn::PathSegment { ident, .. }) = segments.first() {
295 if let Some((primitive_type, _)) =
296 crate::PRIMITIVE_TYPES.iter().find(|(p, _)| ident == *p)
297 {
298 bail_span!(
299 ident,
300 "This type must not be {} \nthis in JavaScript function must be `Object` type or `undefined`",
301 primitive_type
302 );
303 }
304 args.push(
305 quote! {
306 {
307 <#ident as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.this)?
308 }
309 },
310 );
311 skipped_arg_count += 1;
312 continue;
313 }
314 } else if let syn::Type::Reference(syn::TypeReference {
315 elem,
316 mutability,
317 ..
318 }) = generic_type
319 {
320 if let syn::Type::Path(syn::TypePath {
321 path: syn::Path { segments, .. },
322 ..
323 }) = elem.as_ref()
324 {
325 if let Some(syn::PathSegment { ident, .. }) = segments.first() {
326 refs.push(make_ref(quote! { cb.this }));
327 let token = if mutability.is_some() {
328 mut_ref_spans.push(generic_type.span());
329 quote! { <#ident as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.this)? }
330 } else {
331 quote! { <#ident as napi::bindgen_prelude::FromNapiRef>::from_napi_ref(env, cb.this)? }
332 };
333 args.push(token);
334 skipped_arg_count += 1;
335 continue;
336 }
337 }
338 }
339 }
340 }
341 refs.push(make_ref(quote! { cb.this }));
342 args.push(quote! { <napi::bindgen_prelude::This as napi::NapiValue>::from_raw_unchecked(env, cb.this) });
343 skipped_arg_count += 1;
344 continue;
345 }
346 }
347 }
348 let (arg_conversion, arg_type) = self.gen_ty_arg_conversion(&ident, i, path)?;
349 if NapiArgType::MutRef == arg_type {
350 mut_ref_spans.push(path.ty.span());
351 }
352 if arg_type.is_ref() {
353 refs.push(make_ref(quote! { cb.get_arg(#i) }));
354 }
355 arg_conversions.push(arg_conversion);
356 args.push(quote! { #ident });
357 }
358 }
359 NapiFnArgKind::Callback(cb) => {
360 arg_conversions.push(self.gen_cb_arg_conversion(&ident, i, cb));
361 args.push(quote! { #ident });
362 }
363 }
364 }
365
366 Ok(ArgConversions {
367 arg_conversions,
368 args,
369 refs,
370 mut_ref_spans,
371 unsafe_: self.unsafe_,
372 })
373 }
374
375 fn gen_ty_arg_conversion(
378 &self,
379 arg_name: &Ident,
380 index: usize,
381 path: &syn::PatType,
382 ) -> BindgenResult<(TokenStream, NapiArgType)> {
383 let ty = &*path.ty;
384 let type_check = if self.return_if_invalid {
385 quote! {
386 if let Ok(maybe_promise) = <#ty as napi::bindgen_prelude::ValidateNapiValue>::validate(env, cb.get_arg(#index)) {
387 if !maybe_promise.is_null() {
388 return Ok(maybe_promise);
389 }
390 } else {
391 return Ok(std::ptr::null_mut());
392 }
393 }
394 } else if self.strict {
395 quote! {
396 let maybe_promise = <#ty as napi::bindgen_prelude::ValidateNapiValue>::validate(env, cb.get_arg(#index))?;
397 if !maybe_promise.is_null() {
398 return Ok(maybe_promise);
399 }
400 }
401 } else {
402 quote! {}
403 };
404
405 match ty {
406 syn::Type::Reference(syn::TypeReference {
407 lifetime: Some(lifetime),
408 ..
409 }) => Err(Diagnostic::span_error(
410 lifetime.span(),
411 "lifetime is not allowed in napi function arguments",
412 )),
413 syn::Type::Reference(syn::TypeReference {
414 mutability: Some(_),
415 elem,
416 ..
417 }) => {
418 let q = quote! {
419 let #arg_name = {
420 #type_check
421 <#elem as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))?
422 };
423 };
424 Ok((q, NapiArgType::MutRef))
425 }
426 syn::Type::Reference(syn::TypeReference {
427 mutability, elem, ..
428 }) => {
429 if let syn::Type::Slice(slice) = &**elem {
430 if let syn::Type::Path(ele) = &*slice.elem {
431 if let Some(syn::PathSegment { ident, .. }) = ele.path.segments.first() {
432 if TYPEDARRAY_SLICE_TYPES.contains_key(&&*ident.to_string()) {
433 let q = quote! {
434 let #arg_name = {
435 #type_check
436 <&mut #elem as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.get_arg(#index))?
437 };
438 };
439 return Ok((q, NapiArgType::Ref));
440 }
441 }
442 }
443 }
444 let q = if mutability.is_some() {
445 quote! {
446 let #arg_name = {
447 #type_check
448 <#elem as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))?
449 }
450 }
451 } else {
452 quote! {
453 let #arg_name = {
454 #type_check
455 <#elem as napi::bindgen_prelude::FromNapiRef>::from_napi_ref(env, cb.get_arg(#index))?
456 };
457 }
458 };
459 Ok((
460 q,
461 if mutability.is_some() {
462 NapiArgType::MutRef
463 } else {
464 NapiArgType::Ref
465 },
466 ))
467 }
468 _ => {
469 let q = quote! {
470 let #arg_name = {
471 #type_check
472 <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.get_arg(#index))?
473 };
474 };
475 Ok((q, NapiArgType::Value))
476 }
477 }
478 }
479
480 fn gen_cb_arg_conversion(&self, arg_name: &Ident, index: usize, cb: &CallbackArg) -> TokenStream {
481 let mut inputs = vec![];
482 let mut arg_conversions = vec![];
483
484 for (i, ty) in cb.args.iter().enumerate() {
485 let cb_arg_ident = Ident::new(&format!("callback_arg_{}", i), Span::call_site());
486 inputs.push(quote! { #cb_arg_ident: #ty });
487 arg_conversions.push(
488 quote! { <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #cb_arg_ident)? },
489 );
490 }
491
492 let ret = match &cb.ret {
493 Some(ty) => {
494 quote! {
495 let ret = <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, ret_ptr)?;
496
497 Ok(ret)
498 }
499 }
500 None => quote! { Ok(()) },
501 };
502
503 quote! {
504 napi::bindgen_prelude::assert_type_of!(env, cb.get_arg(#index), napi::bindgen_prelude::ValueType::Function)?;
505 let #arg_name = |#(#inputs),*| {
506 let args = vec![
507 #(#arg_conversions),*
508 ];
509
510 let mut ret_ptr = std::ptr::null_mut();
511
512 napi::bindgen_prelude::check_pending_exception!(
513 env,
514 napi::bindgen_prelude::sys::napi_call_function(
515 env,
516 cb.this(),
517 cb.get_arg(#index),
518 args.len(),
519 args.as_ptr(),
520 &mut ret_ptr
521 )
522 )?;
523
524 #ret
525 };
526 }
527 }
528
529 fn gen_fn_receiver(&self) -> TokenStream {
530 let name = &self.name;
531
532 match self.fn_self {
533 Some(FnSelf::Value) => {
534 unreachable!();
536 }
537 Some(FnSelf::Ref) | Some(FnSelf::MutRef) => quote! { this.#name },
538 None => match &self.parent {
539 Some(class) => quote! { #class::#name },
540 None => quote! { #name },
541 },
542 }
543 }
544
545 fn gen_fn_return(&self, ret: &Ident) -> TokenStream {
546 let js_name = &self.js_name;
547
548 if let Some(ty) = &self.ret {
549 let ty_string = ty.into_token_stream().to_string();
550 let is_return_self = ty_string == "& Self" || ty_string == "&mut Self";
551 if self.kind == FnKind::Constructor {
552 let parent = self
553 .parent
554 .as_ref()
555 .expect("Parent must exist for constructor");
556 if self.is_ret_result {
557 if self.parent_is_generator {
558 quote! { cb.construct_generator::<false, #parent>(#js_name, #ret?) }
559 } else {
560 quote! {
561 match #ret {
562 Ok(value) => {
563 cb.construct::<false, #parent>(#js_name, value)
564 }
565 Err(err) => {
566 napi::bindgen_prelude::JsError::from(err).throw_into(env);
567 Ok(std::ptr::null_mut())
568 }
569 }
570 }
571 }
572 } else if self.parent_is_generator {
573 quote! { cb.construct_generator::<false, #parent>(#js_name, #ret) }
574 } else {
575 quote! { cb.construct::<false, #parent>(#js_name, #ret) }
576 }
577 } else if self.kind == FnKind::Factory {
578 if self.is_ret_result {
579 if self.parent_is_generator {
580 quote! { cb.generator_factory(#js_name, #ret?) }
581 } else if self.is_async {
582 quote! { cb.factory(#js_name, #ret) }
583 } else {
584 quote! {
585 match #ret {
586 Ok(value) => {
587 cb.factory(#js_name, value)
588 }
589 Err(err) => {
590 napi::bindgen_prelude::JsError::from(err).throw_into(env);
591 Ok(std::ptr::null_mut())
592 }
593 }
594 }
595 }
596 } else if self.parent_is_generator {
597 quote! { cb.generator_factory(#js_name, #ret) }
598 } else {
599 quote! { cb.factory(#js_name, #ret) }
600 }
601 } else if self.is_ret_result {
602 if self.is_async {
603 quote! {
604 <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #ret)
605 }
606 } else if is_return_self {
607 quote! { #ret.map(|_| cb.this) }
608 } else {
609 quote! {
610 match #ret {
611 Ok(value) => napi::bindgen_prelude::ToNapiValue::to_napi_value(env, value),
612 Err(err) => {
613 napi::bindgen_prelude::JsError::from(err).throw_into(env);
614 Ok(std::ptr::null_mut())
615 },
616 }
617 }
618 }
619 } else if is_return_self {
620 quote! { Ok(cb.this) }
621 } else {
622 quote! {
623 <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #ret)
624 }
625 }
626 } else {
627 quote! {
628 <() as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, ())
629 }
630 }
631 }
632
633 fn gen_fn_register(&self) -> TokenStream {
634 if self.parent.is_some() {
635 quote! {}
636 } else {
637 let name_str = self.name.to_string();
638 let js_name = format!("{}\0", &self.js_name);
639 let name_len = self.js_name.len();
640 let module_register_name = &self.register_name;
641 let intermediate_ident = get_intermediate_ident(&name_str);
642 let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
643 let cb_name = Ident::new(&format!("{}_js_function", name_str), Span::call_site());
644
645 quote! {
646 #[allow(non_snake_case)]
647 #[allow(clippy::all)]
648 unsafe fn #cb_name(env: napi::bindgen_prelude::sys::napi_env) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
649 let mut fn_ptr = std::ptr::null_mut();
650
651 napi::bindgen_prelude::check_status!(
652 napi::bindgen_prelude::sys::napi_create_function(
653 env,
654 #js_name.as_ptr().cast(),
655 #name_len,
656 Some(#intermediate_ident),
657 std::ptr::null_mut(),
658 &mut fn_ptr,
659 ),
660 "Failed to register function `{}`",
661 #name_str,
662 )?;
663 napi::bindgen_prelude::register_js_function(#js_name, #cb_name, Some(#intermediate_ident));
664 Ok(fn_ptr)
665 }
666
667 #[allow(clippy::all)]
668 #[allow(non_snake_case)]
669 #[cfg(all(not(test), not(target_family = "wasm")))]
670 #[napi::bindgen_prelude::ctor]
671 fn #module_register_name() {
672 napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name);
673 }
674
675 #[allow(clippy::all)]
676 #[allow(non_snake_case)]
677 #[cfg(all(not(test), target_family = "wasm"))]
678 #[no_mangle]
679 extern "C" fn #module_register_name() {
680 napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name);
681 }
682 }
683 }
684 }
685}
686
687struct ArgConversions {
688 pub args: Vec<TokenStream>,
689 pub arg_conversions: Vec<TokenStream>,
690 pub refs: Vec<TokenStream>,
691 pub mut_ref_spans: Vec<Span>,
692 pub unsafe_: bool,
693}
694
695#[derive(Debug, PartialEq, Eq)]
696enum NapiArgType {
697 Ref,
698 MutRef,
699 Value,
700}
701
702impl NapiArgType {
703 fn is_ref(&self) -> bool {
704 matches!(self, NapiArgType::Ref | NapiArgType::MutRef)
705 }
706}