1#[macro_use]
2pub mod attrs;
3
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::str::Chars;
7use std::sync::atomic::AtomicUsize;
8
9use attrs::BindgenAttrs;
10
11use convert_case::{Case, Casing};
12use napi_derive_backend::{
13 BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiConst, NapiEnum, NapiEnumValue,
14 NapiEnumVariant, NapiFn, NapiFnArg, NapiFnArgKind, NapiImpl, NapiItem, NapiStruct,
15 NapiStructField, NapiStructKind,
16};
17use proc_macro2::{Ident, Span, TokenStream};
18use quote::ToTokens;
19use syn::ext::IdentExt;
20use syn::parse::{Parse, ParseStream, Result as SynResult};
21use syn::spanned::Spanned;
22use syn::{Attribute, ExprLit, Meta, PatType, PathSegment, Signature, Type, Visibility};
23
24use crate::parser::attrs::{check_recorded_struct_for_impl, record_struct};
25
26thread_local! {
27 static GENERATOR_STRUCT: RefCell<HashMap<String, bool>> = Default::default();
28}
29
30static REGISTER_INDEX: AtomicUsize = AtomicUsize::new(0);
31
32fn get_register_ident(name: &str) -> Ident {
33 let new_name: String = format!(
34 "__napi_register__{}_{}",
35 name,
36 REGISTER_INDEX.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
37 );
38 Ident::new(&new_name, Span::call_site())
39}
40
41struct AnyIdent(Ident);
42
43impl Parse for AnyIdent {
44 fn parse(input: ParseStream) -> SynResult<Self> {
45 input.step(|cursor: StepCursor<'_, '_>| match cursor.ident() {
46 Some((ident: Ident, remaining: Cursor<'_>)) => Ok((AnyIdent(ident), remaining)),
47 None => Err(cursor.error(message:"expected an identifier")),
48 })
49 }
50}
51
52pub trait ConvertToAST {
53 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi>;
54}
55
56pub trait ParseNapi {
57 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi>;
58}
59
60/// This function does a few things:
61/// - parses the tokens for the given argument `p` to find the `#[napi(ts_arg_type = "MyType")]`
62/// attribute and return the manually overridden type.
63/// - If both the `ts_args_type` override and the `ts_arg_type` override are present, bail
64/// since it should only allow one at a time.
65/// - Bails if it finds the `#[napi...]` attribute but it has the wrong data.
66/// - Removes the attribute from the output token stream so this
67/// `pub fn add(u: u32, #[napi(ts_arg_type = "MyType")] f: String)`
68/// turns into
69/// `pub fn add(u: u32, f: String)`
70/// otherwise it won't compile
71fn find_ts_arg_type_and_remove_attribute(
72 p: &mut PatType,
73 ts_args_type: Option<&(&str, Span)>,
74) -> BindgenResult<Option<String>> {
75 let mut ts_type_attr: Option<(usize, String)> = None;
76 for (idx, attr) in p.attrs.iter().enumerate() {
77 if attr.path().is_ident("napi") {
78 if let Some((ts_args_type, _)) = ts_args_type {
79 bail_span!(
80 attr,
81 "Found a 'ts_args_type'=\"{}\" override. Cannot use 'ts_arg_type' at the same time since they are mutually exclusive.",
82 ts_args_type
83 );
84 }
85
86 match &attr.meta {
87 syn::Meta::Path(_) | syn::Meta::NameValue(_) => {
88 bail_span!(
89 attr,
90 "Expects an assignment #[napi(ts_arg_type = \"MyType\")]"
91 )
92 }
93 syn::Meta::List(list) => {
94 let mut found = false;
95 list
96 .parse_args_with(|tokens: &syn::parse::ParseBuffer<'_>| {
97 // tokens:
98 // #[napi(xxx, xxx=xxx)]
99 // ^^^^^^^^^^^^
100 let list = tokens.parse_terminated(Meta::parse, Token![,])?;
101
102 for meta in list {
103 if meta.path().is_ident("ts_arg_type") {
104 match meta {
105 Meta::Path(_) | Meta::List(_) => {
106 return Err(syn::Error::new(
107 meta.path().span(),
108 "Expects an assignment (ts_arg_type = \"MyType\")",
109 ))
110 }
111 Meta::NameValue(name_value) => match name_value.value {
112 syn::Expr::Lit(syn::ExprLit {
113 lit: syn::Lit::Str(str),
114 ..
115 }) => {
116 let value = str.value();
117 found = true;
118 ts_type_attr = Some((idx, value));
119 }
120 _ => {
121 return Err(syn::Error::new(
122 name_value.value.span(),
123 "Expects a string literal",
124 ))
125 }
126 },
127 }
128 }
129 }
130
131 Ok(())
132 })
133 .map_err(Diagnostic::from)?;
134
135 if !found {
136 bail_span!(attr, "Expects a 'ts_arg_type'");
137 }
138 }
139 }
140 }
141 }
142
143 if let Some((idx, value)) = ts_type_attr {
144 p.attrs.remove(idx);
145 Ok(Some(value))
146 } else {
147 Ok(None)
148 }
149}
150
151fn get_ty(mut ty: &syn::Type) -> &syn::Type {
152 while let syn::Type::Group(g: &TypeGroup) = ty {
153 ty = &g.elem;
154 }
155
156 ty
157}
158
159fn replace_self(ty: syn::Type, self_ty: Option<&Ident>) -> syn::Type {
160 let self_ty: &Ident = match self_ty {
161 Some(i: &Ident) => i,
162 None => return ty,
163 };
164 let path: Path = match get_ty(&ty) {
165 syn::Type::Path(syn::TypePath { qself: None, path: &Path }) => path.clone(),
166 other: &Type => return other.clone(),
167 };
168 let new_path: Path = if path.segments.len() == 1 && path.segments[0].ident == "Self" {
169 self_ty.clone().into()
170 } else {
171 path
172 };
173 syn::Type::Path(syn::TypePath {
174 qself: None,
175 path: new_path,
176 })
177}
178
179/// Extracts the last ident from the path
180fn extract_path_ident(path: &syn::Path) -> BindgenResult<Ident> {
181 for segment: &PathSegment in path.segments.iter() {
182 match segment.arguments {
183 syn::PathArguments::None => {}
184 _ => bail_span!(path, "paths with type parameters are not supported yet"),
185 }
186 }
187
188 match path.segments.last() {
189 Some(value: &PathSegment) => Ok(value.ident.clone()),
190 None => {
191 bail_span!(path, "empty idents are not supported");
192 }
193 }
194}
195
196fn extract_callback_trait_types(
197 arguments: &syn::PathArguments,
198) -> BindgenResult<(Vec<syn::Type>, Option<syn::Type>)> {
199 match arguments {
200 // <T: Fn>
201 syn::PathArguments::None => Ok((vec![], None)),
202 syn::PathArguments::AngleBracketed(_) => {
203 bail_span!(arguments, "use parentheses for napi callback trait")
204 }
205 syn::PathArguments::Parenthesized(arguments) => {
206 let args = arguments.inputs.iter().cloned().collect::<Vec<_>>();
207
208 let ret = match &arguments.output {
209 syn::ReturnType::Type(_, ret_ty) => {
210 let ret_ty = &**ret_ty;
211 if let Some(ty_of_result) = extract_result_ty(ret_ty)? {
212 if ty_of_result.to_token_stream().to_string() == "()" {
213 None
214 } else {
215 Some(ty_of_result)
216 }
217 } else {
218 bail_span!(ret_ty, "The return type of callback can only be `Result`");
219 }
220 }
221 _ => {
222 bail_span!(
223 arguments,
224 "The return type of callback can only be `Result`. Try with `Result<()>`"
225 );
226 }
227 };
228
229 Ok((args, ret))
230 }
231 }
232}
233
234fn extract_result_ty(ty: &syn::Type) -> BindgenResult<Option<syn::Type>> {
235 match ty {
236 syn::Type::Path(syn::TypePath { qself: None, path }) => {
237 let segment = path.segments.last().unwrap();
238 if segment.ident != "Result" {
239 Ok(None)
240 } else {
241 match &segment.arguments {
242 syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
243 args, ..
244 }) => {
245 let ok_arg = args.first().unwrap();
246 match ok_arg {
247 syn::GenericArgument::Type(ty) => Ok(Some(ty.clone())),
248 _ => bail_span!(ok_arg, "unsupported generic type"),
249 }
250 }
251 _ => {
252 bail_span!(segment, "unsupported generic type")
253 }
254 }
255 }
256 }
257 _ => Ok(None),
258 }
259}
260
261fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
262 while let syn::Expr::Group(g: &ExprGroup) = expr {
263 expr = &g.expr;
264 }
265
266 expr
267}
268
269/// Extract the documentation comments from a Vec of attributes
270fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
271 attrs
272 .iter()
273 .filter_map(|a| {
274 // if the path segments include an ident of "doc" we know this
275 // this is a doc comment
276 let name_value = a.meta.require_name_value();
277 if let Ok(name) = name_value {
278 if a.path().is_ident("doc") {
279 Some(
280 // We want to filter out any Puncts so just grab the Literals
281 match &name.value {
282 syn::Expr::Lit(ExprLit {
283 lit: syn::Lit::Str(str),
284 ..
285 }) => {
286 let quoted = str.token().to_string();
287 Some(try_unescape(&quoted).unwrap_or(quoted))
288 }
289 _ => None,
290 },
291 )
292 } else {
293 None
294 }
295 } else {
296 None
297 }
298 })
299 //Fold up the [[String]] iter we created into Vec<String>
300 .fold(vec![], |mut acc, a| {
301 acc.extend(a);
302 acc
303 })
304}
305
306// Unescaped a quoted string. char::escape_debug() was used to escape the text.
307fn try_unescape(s: &str) -> Option<String> {
308 if s.is_empty() {
309 return Some(String::new());
310 }
311 let mut result = String::with_capacity(s.len());
312 let mut chars = s.chars();
313 for i in 0.. {
314 let c = match chars.next() {
315 Some(c) => c,
316 None => {
317 if result.ends_with('"') {
318 result.pop();
319 }
320 return Some(result);
321 }
322 };
323 if i == 0 && c == '"' {
324 // ignore it
325 } else if c == '\\' {
326 let c = chars.next()?;
327 match c {
328 't' => result.push('\t'),
329 'r' => result.push('\r'),
330 'n' => result.push('\n'),
331 '\\' | '\'' | '"' => result.push(c),
332 'u' => {
333 if chars.next() != Some('{') {
334 return None;
335 }
336 let (c, next) = unescape_unicode(&mut chars)?;
337 result.push(c);
338 if next != '}' {
339 return None;
340 }
341 }
342 _ => return None,
343 }
344 } else {
345 result.push(c);
346 }
347 }
348 None
349}
350
351fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
352 let mut value = 0;
353 for i in 0..7 {
354 let c = chars.next()?;
355 let num = match c {
356 '0'..='9' => c as u32 - '0' as u32,
357 'a'..='f' => c as u32 - 'a' as u32,
358 'A'..='F' => c as u32 - 'A' as u32,
359 _ => {
360 if i == 0 {
361 return None;
362 }
363
364 if i == 0 {
365 return None;
366 }
367 let decoded = char::from_u32(value)?;
368 return Some((decoded, c));
369 }
370 };
371
372 if i >= 6 {
373 return None;
374 }
375 value = (value << 4) | num;
376 }
377 None
378}
379
380fn extract_fn_closure_generics(
381 generics: &syn::Generics,
382) -> BindgenResult<HashMap<String, syn::PathArguments>> {
383 let mut errors = vec![];
384
385 let mut map = HashMap::default();
386 if generics.params.is_empty() {
387 return Ok(map);
388 }
389
390 if let Some(where_clause) = &generics.where_clause {
391 for prediction in where_clause.predicates.iter() {
392 match prediction {
393 syn::WherePredicate::Type(syn::PredicateType {
394 bounded_ty, bounds, ..
395 }) => {
396 for bound in bounds {
397 match bound {
398 syn::TypeParamBound::Trait(t) => {
399 for segment in t.path.segments.iter() {
400 match segment.ident.to_string().as_str() {
401 "Fn" | "FnOnce" | "FnMut" => {
402 map.insert(
403 bounded_ty.to_token_stream().to_string(),
404 segment.arguments.clone(),
405 );
406 }
407 _ => {}
408 };
409 }
410 }
411 syn::TypeParamBound::Lifetime(lifetime) => {
412 if lifetime.ident != "static" {
413 errors.push(err_span!(
414 bound,
415 "only 'static is supported in lifetime bound for fn arguments"
416 ));
417 }
418 }
419 _ => errors.push(err_span! {
420 bound,
421 "unsupported bound in napi"
422 }),
423 }
424 }
425 }
426 _ => errors.push(err_span! {
427 prediction,
428 "unsupported where clause prediction in napi"
429 }),
430 };
431 }
432 }
433
434 for param in generics.params.iter() {
435 match param {
436 syn::GenericParam::Type(syn::TypeParam { ident, bounds, .. }) => {
437 for bound in bounds {
438 match bound {
439 syn::TypeParamBound::Trait(t) => {
440 for segment in t.path.segments.iter() {
441 match segment.ident.to_string().as_str() {
442 "Fn" | "FnOnce" | "FnMut" => {
443 map.insert(ident.to_string(), segment.arguments.clone());
444 }
445 _ => {}
446 };
447 }
448 }
449 syn::TypeParamBound::Lifetime(lifetime) => {
450 if lifetime.ident != "static" {
451 errors.push(err_span!(
452 bound,
453 "only 'static is supported in lifetime bound for fn arguments"
454 ));
455 }
456 }
457 _ => errors.push(err_span! {
458 bound,
459 "unsupported bound in napi"
460 }),
461 }
462 }
463 }
464 _ => {
465 errors.push(err_span!(param, "unsupported napi generic param for fn"));
466 }
467 }
468 }
469
470 Diagnostic::from_vec(errors).and(Ok(map))
471}
472
473fn napi_fn_from_decl(
474 sig: &mut Signature,
475 opts: &BindgenAttrs,
476 attrs: Vec<Attribute>,
477 vis: Visibility,
478 parent: Option<&Ident>,
479) -> BindgenResult<NapiFn> {
480 let mut errors = vec![];
481
482 let syn::Signature {
483 ident,
484 asyncness,
485 output,
486 generics,
487 ..
488 } = sig.clone();
489
490 let mut fn_self = None;
491 let callback_traits = extract_fn_closure_generics(&generics)?;
492
493 let args = sig
494 .inputs
495 .iter_mut()
496 .filter_map(|arg| match arg {
497 syn::FnArg::Typed(ref mut p) => {
498 let ts_arg_type = find_ts_arg_type_and_remove_attribute(p, opts.ts_args_type().as_ref())
499 .unwrap_or_else(|e| {
500 errors.push(e);
501 None
502 });
503
504 let ty_str = p.ty.to_token_stream().to_string();
505 if let Some(path_arguments) = callback_traits.get(&ty_str) {
506 match extract_callback_trait_types(path_arguments) {
507 Ok((fn_args, fn_ret)) => Some(NapiFnArg {
508 kind: NapiFnArgKind::Callback(Box::new(CallbackArg {
509 pat: p.pat.clone(),
510 args: fn_args,
511 ret: fn_ret,
512 })),
513 ts_arg_type,
514 }),
515 Err(e) => {
516 errors.push(e);
517 None
518 }
519 }
520 } else {
521 let ty = replace_self(p.ty.as_ref().clone(), parent);
522 p.ty = Box::new(ty);
523 Some(NapiFnArg {
524 kind: NapiFnArgKind::PatType(Box::new(p.clone())),
525 ts_arg_type,
526 })
527 }
528 }
529 syn::FnArg::Receiver(r) => {
530 if parent.is_some() {
531 assert!(fn_self.is_none());
532 if r.reference.is_none() {
533 errors.push(err_span!(
534 r,
535 "The native methods can't move values from napi. Try `&self` or `&mut self` instead."
536 ));
537 } else if r.mutability.is_some() {
538 fn_self = Some(FnSelf::MutRef);
539 } else {
540 fn_self = Some(FnSelf::Ref);
541 }
542 } else {
543 errors.push(err_span!(r, "arguments cannot be `self`"));
544 }
545 None
546 }
547 })
548 .collect::<Vec<_>>();
549
550 let (ret, is_ret_result) = match output {
551 syn::ReturnType::Default => (None, false),
552 syn::ReturnType::Type(_, ty) => {
553 let result_ty = extract_result_ty(&ty)?;
554 if result_ty.is_some() {
555 (result_ty, true)
556 } else {
557 (Some(replace_self(*ty, parent)), false)
558 }
559 }
560 };
561
562 Diagnostic::from_vec(errors).map(|_| {
563 let js_name = if let Some(prop_name) = opts.getter() {
564 opts.js_name().map_or_else(
565 || {
566 if let Some(ident) = prop_name {
567 ident.to_string()
568 } else {
569 ident
570 .to_string()
571 .trim_start_matches("get_")
572 .to_case(Case::Camel)
573 }
574 },
575 |(js_name, _)| js_name.to_owned(),
576 )
577 } else if let Some(prop_name) = opts.setter() {
578 opts.js_name().map_or_else(
579 || {
580 if let Some(ident) = prop_name {
581 ident.to_string()
582 } else {
583 ident
584 .to_string()
585 .trim_start_matches("set_")
586 .to_case(Case::Camel)
587 }
588 },
589 |(js_name, _)| js_name.to_owned(),
590 )
591 } else if opts.constructor().is_some() {
592 "constructor".to_owned()
593 } else {
594 opts.js_name().map_or_else(
595 || ident.to_string().to_case(Case::Camel),
596 |(js_name, _)| js_name.to_owned(),
597 )
598 };
599
600 let namespace = opts.namespace().map(|(m, _)| m.to_owned());
601 let parent_is_generator = if let Some(p) = parent {
602 GENERATOR_STRUCT.with(|inner| {
603 let inner = inner.borrow();
604 let key = namespace
605 .as_ref()
606 .map(|n| format!("{}::{}", n, p))
607 .unwrap_or_else(|| p.to_string());
608 *inner.get(&key).unwrap_or(&false)
609 })
610 } else {
611 false
612 };
613
614 NapiFn {
615 name: ident.clone(),
616 js_name,
617 args,
618 ret,
619 is_ret_result,
620 is_async: asyncness.is_some(),
621 vis,
622 kind: fn_kind(opts),
623 fn_self,
624 parent: parent.cloned(),
625 comments: extract_doc_comments(&attrs),
626 attrs,
627 strict: opts.strict().is_some(),
628 return_if_invalid: opts.return_if_invalid().is_some(),
629 js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
630 ts_generic_types: opts.ts_generic_types().map(|(m, _)| m.to_owned()),
631 ts_args_type: opts.ts_args_type().map(|(m, _)| m.to_owned()),
632 ts_return_type: opts.ts_return_type().map(|(m, _)| m.to_owned()),
633 skip_typescript: opts.skip_typescript().is_some(),
634 parent_is_generator,
635 writable: opts.writable(),
636 enumerable: opts.enumerable(),
637 configurable: opts.configurable(),
638 catch_unwind: opts.catch_unwind().is_some(),
639 unsafe_: sig.unsafety.is_some(),
640 register_name: get_register_ident(ident.to_string().as_str()),
641 }
642 })
643}
644
645impl ParseNapi for syn::Item {
646 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
647 match self {
648 syn::Item::Fn(f: &mut ItemFn) => f.parse_napi(tokens, opts),
649 syn::Item::Struct(s: &mut ItemStruct) => s.parse_napi(tokens, opts),
650 syn::Item::Impl(i: &mut ItemImpl) => i.parse_napi(tokens, opts),
651 syn::Item::Enum(e: &mut ItemEnum) => e.parse_napi(tokens, opts),
652 syn::Item::Const(c: &mut ItemConst) => c.parse_napi(tokens, opts),
653 _ => bail_span!(
654 self,
655 "#[napi] can only be applied to a function, struct, enum, const, mod or impl."
656 ),
657 }
658 }
659}
660
661impl ParseNapi for syn::ItemFn {
662 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
663 if opts.ts_type().is_some() {
664 bail_span!(
665 self,
666 "#[napi] can't be applied to a function with #[napi(ts_type)]"
667 );
668 }
669 if opts.return_if_invalid().is_some() && opts.strict().is_some() {
670 bail_span!(
671 self,
672 "#[napi(return_if_invalid)] can't be used with #[napi(strict)]"
673 );
674 }
675 let napi: Result = self.convert_to_ast(opts);
676 self.to_tokens(tokens);
677
678 napi
679 }
680}
681impl ParseNapi for syn::ItemStruct {
682 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
683 if opts.ts_args_type().is_some()
684 || opts.ts_return_type().is_some()
685 || opts.skip_typescript().is_some()
686 || opts.ts_type().is_some()
687 {
688 bail_span!(
689 self,
690 "#[napi] can't be applied to a struct with #[napi(ts_args_type)], #[napi(ts_return_type)], #[napi(skip_typescript)] or #[napi(ts_type)]"
691 );
692 }
693 if opts.return_if_invalid().is_some() {
694 bail_span!(
695 self,
696 "#[napi(return_if_invalid)] can only be applied to a function or method."
697 );
698 }
699 if opts.catch_unwind().is_some() {
700 bail_span!(
701 self,
702 "#[napi(catch_unwind)] can only be applied to a function or method."
703 );
704 }
705 if opts.object().is_some() && opts.custom_finalize().is_some() {
706 bail_span!(self, "Custom finalize is not supported for #[napi(object)]");
707 }
708 let napi = self.convert_to_ast(opts);
709 self.to_tokens(tokens);
710
711 napi
712 }
713}
714
715impl ParseNapi for syn::ItemImpl {
716 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
717 if opts.ts_args_type().is_some()
718 || opts.ts_return_type().is_some()
719 || opts.skip_typescript().is_some()
720 || opts.ts_type().is_some()
721 || opts.custom_finalize().is_some()
722 {
723 bail_span!(
724 self,
725 "#[napi] can't be applied to impl with #[napi(ts_args_type)], #[napi(ts_return_type)], #[napi(skip_typescript)] or #[napi(ts_type)] or #[napi(custom_finalize)]"
726 );
727 }
728 if opts.return_if_invalid().is_some() {
729 bail_span!(
730 self,
731 "#[napi(return_if_invalid)] can only be applied to a function or method."
732 );
733 }
734 if opts.catch_unwind().is_some() {
735 bail_span!(
736 self,
737 "#[napi(catch_unwind)] can only be applied to a function or method."
738 );
739 }
740 // #[napi] macro will be remove from impl items after converted to ast
741 let napi = self.convert_to_ast(opts);
742 self.to_tokens(tokens);
743
744 napi
745 }
746}
747
748impl ParseNapi for syn::ItemEnum {
749 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
750 if opts.ts_args_type().is_some()
751 || opts.ts_return_type().is_some()
752 || opts.ts_type().is_some()
753 || opts.custom_finalize().is_some()
754 {
755 bail_span!(
756 self,
757 "#[napi] can't be applied to a enum with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)] or #[napi(custom_finalize)]"
758 );
759 }
760 if opts.return_if_invalid().is_some() {
761 bail_span!(
762 self,
763 "#[napi(return_if_invalid)] can only be applied to a function or method."
764 );
765 }
766 if opts.catch_unwind().is_some() {
767 bail_span!(
768 self,
769 "#[napi(catch_unwind)] can only be applied to a function or method."
770 );
771 }
772 let napi = self.convert_to_ast(opts);
773 self.to_tokens(tokens);
774
775 napi
776 }
777}
778impl ParseNapi for syn::ItemConst {
779 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
780 if opts.ts_args_type().is_some()
781 || opts.ts_return_type().is_some()
782 || opts.ts_type().is_some()
783 || opts.custom_finalize().is_some()
784 {
785 bail_span!(
786 self,
787 "#[napi] can't be applied to a const with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)] or #[napi(custom_finalize)]"
788 );
789 }
790 if opts.return_if_invalid().is_some() {
791 bail_span!(
792 self,
793 "#[napi(return_if_invalid)] can only be applied to a function or method."
794 );
795 }
796 if opts.catch_unwind().is_some() {
797 bail_span!(
798 self,
799 "#[napi(catch_unwind)] can only be applied to a function or method."
800 );
801 }
802 let napi = self.convert_to_ast(opts);
803 self.to_tokens(tokens);
804 napi
805 }
806}
807
808fn fn_kind(opts: &BindgenAttrs) -> FnKind {
809 let mut kind: FnKind = FnKind::Normal;
810
811 if opts.getter().is_some() {
812 kind = FnKind::Getter;
813 }
814
815 if opts.setter().is_some() {
816 kind = FnKind::Setter;
817 }
818
819 if opts.constructor().is_some() {
820 kind = FnKind::Constructor;
821 }
822
823 if opts.factory().is_some() {
824 kind = FnKind::Factory;
825 }
826
827 kind
828}
829
830impl ConvertToAST for syn::ItemFn {
831 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi> {
832 let func: NapiFn = napi_fn_from_decl(
833 &mut self.sig,
834 opts,
835 self.attrs.clone(),
836 self.vis.clone(),
837 parent:None,
838 )?;
839
840 Ok(Napi {
841 item: NapiItem::Fn(func),
842 })
843 }
844}
845
846impl ConvertToAST for syn::ItemStruct {
847 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi> {
848 let mut errors = vec![];
849
850 let vis = self.vis.clone();
851 let struct_name = self.ident.clone();
852 let js_name = opts.js_name().map_or_else(
853 || self.ident.to_string().to_case(Case::Pascal),
854 |(js_name, _)| js_name.to_owned(),
855 );
856 let mut fields = vec![];
857 let mut is_tuple = false;
858 let struct_kind = if opts.constructor().is_some() {
859 NapiStructKind::Constructor
860 } else if opts.object().is_some() {
861 NapiStructKind::Object
862 } else {
863 NapiStructKind::None
864 };
865 let use_nullable = opts.use_nullable();
866
867 for (i, field) in self.fields.iter_mut().enumerate() {
868 match field.vis {
869 syn::Visibility::Public(..) => {}
870 _ => {
871 if struct_kind != NapiStructKind::None {
872 errors.push(err_span!(
873 field,
874 "#[napi] requires all struct fields to be public to mark struct as constructor or object shape\nthis field is not public."
875 ));
876 }
877 continue;
878 }
879 }
880
881 let field_opts = BindgenAttrs::find(&mut field.attrs)?;
882
883 let (js_name, name) = match &field.ident {
884 Some(ident) => (
885 field_opts.js_name().map_or_else(
886 || ident.unraw().to_string().to_case(Case::Camel),
887 |(js_name, _)| js_name.to_owned(),
888 ),
889 syn::Member::Named(ident.clone()),
890 ),
891 None => {
892 is_tuple = true;
893 (format!("field{}", i), syn::Member::Unnamed(i.into()))
894 }
895 };
896
897 let ignored = field_opts.skip().is_some();
898 let readonly = field_opts.readonly().is_some();
899 let writable = field_opts.writable();
900 let enumerable = field_opts.enumerable();
901 let configurable = field_opts.configurable();
902 let skip_typescript = field_opts.skip_typescript().is_some();
903 let ts_type = field_opts.ts_type().map(|e| e.0.to_string());
904
905 fields.push(NapiStructField {
906 name,
907 js_name,
908 ty: field.ty.clone(),
909 getter: !ignored,
910 setter: !(ignored || readonly),
911 writable,
912 enumerable,
913 configurable,
914 comments: extract_doc_comments(&field.attrs),
915 skip_typescript,
916 ts_type,
917 })
918 }
919
920 record_struct(&struct_name, js_name.clone(), opts);
921 let namespace = opts.namespace().map(|(m, _)| m.to_owned());
922 let implement_iterator = opts.iterator().is_some();
923 GENERATOR_STRUCT.with(|inner| {
924 let mut inner = inner.borrow_mut();
925 let key = namespace
926 .as_ref()
927 .map(|n| format!("{}::{}", n, struct_name))
928 .unwrap_or_else(|| struct_name.to_string());
929 inner.insert(key, implement_iterator);
930 });
931
932 Diagnostic::from_vec(errors).map(|()| Napi {
933 item: NapiItem::Struct(NapiStruct {
934 js_name,
935 name: struct_name.clone(),
936 vis,
937 fields,
938 is_tuple,
939 kind: struct_kind,
940 object_from_js: opts.object_from_js(),
941 object_to_js: opts.object_to_js(),
942 js_mod: namespace,
943 comments: extract_doc_comments(&self.attrs),
944 implement_iterator,
945 use_custom_finalize: opts.custom_finalize().is_some(),
946 register_name: get_register_ident(format!("{struct_name}_struct").as_str()),
947 use_nullable,
948 }),
949 })
950 }
951}
952
953impl ConvertToAST for syn::ItemImpl {
954 fn convert_to_ast(&mut self, impl_opts: &BindgenAttrs) -> BindgenResult<Napi> {
955 let struct_name = match get_ty(&self.self_ty) {
956 syn::Type::Path(syn::TypePath {
957 ref path,
958 qself: None,
959 }) => path,
960 _ => {
961 bail_span!(self.self_ty, "unsupported self type in #[napi] impl")
962 }
963 };
964
965 let struct_name = extract_path_ident(struct_name)?;
966
967 let mut struct_js_name = struct_name.to_string().to_case(Case::UpperCamel);
968 let mut items = vec![];
969 let mut task_output_type = None;
970 let mut iterator_yield_type = None;
971 let mut iterator_next_type = None;
972 let mut iterator_return_type = None;
973 for item in self.items.iter_mut() {
974 if let Some(method) = match item {
975 syn::ImplItem::Fn(m) => Some(m),
976 syn::ImplItem::Type(m) => {
977 if let Some((_, t, _)) = &self.trait_ {
978 if let Some(PathSegment { ident, .. }) = t.segments.last() {
979 if ident == "Task" && m.ident == "JsValue" {
980 task_output_type = Some(m.ty.clone());
981 } else if ident == "Generator" {
982 if let Type::Path(_) = &m.ty {
983 if m.ident == "Yield" {
984 iterator_yield_type = Some(m.ty.clone());
985 } else if m.ident == "Next" {
986 iterator_next_type = Some(m.ty.clone());
987 } else if m.ident == "Return" {
988 iterator_return_type = Some(m.ty.clone());
989 }
990 }
991 }
992 }
993 }
994 None
995 }
996 _ => {
997 bail_span!(item, "unsupported impl item in #[napi]")
998 }
999 } {
1000 let opts = BindgenAttrs::find(&mut method.attrs)?;
1001
1002 // it'd better only care methods decorated with `#[napi]` attribute
1003 if !opts.exists {
1004 continue;
1005 }
1006
1007 if opts.constructor().is_some() || opts.factory().is_some() {
1008 struct_js_name = check_recorded_struct_for_impl(&struct_name, &opts)?;
1009 }
1010
1011 let vis = method.vis.clone();
1012
1013 match &vis {
1014 Visibility::Public(_) => {}
1015 _ => {
1016 bail_span!(method.sig.ident, "only pub method supported by #[napi].",);
1017 }
1018 }
1019
1020 let func = napi_fn_from_decl(
1021 &mut method.sig,
1022 &opts,
1023 method.attrs.clone(),
1024 vis,
1025 Some(&struct_name),
1026 )?;
1027
1028 items.push(func);
1029 }
1030 }
1031
1032 let namespace = impl_opts.namespace().map(|(m, _)| m.to_owned());
1033
1034 Ok(Napi {
1035 item: NapiItem::Impl(NapiImpl {
1036 name: struct_name.clone(),
1037 js_name: struct_js_name,
1038 items,
1039 task_output_type,
1040 iterator_yield_type,
1041 iterator_next_type,
1042 iterator_return_type,
1043 js_mod: namespace,
1044 comments: extract_doc_comments(&self.attrs),
1045 register_name: get_register_ident(format!("{struct_name}_impl").as_str()),
1046 }),
1047 })
1048 }
1049}
1050
1051impl ConvertToAST for syn::ItemEnum {
1052 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi> {
1053 match self.vis {
1054 Visibility::Public(_) => {}
1055 _ => bail_span!(self, "only public enum allowed"),
1056 }
1057
1058 self.attrs.push(parse_quote!(#[derive(Copy, Clone)]));
1059
1060 let js_name = opts
1061 .js_name()
1062 .map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string());
1063
1064 let variants = match opts.string_enum() {
1065 Some(_) => self
1066 .variants
1067 .iter()
1068 .map(|v| {
1069 if !matches!(v.fields, syn::Fields::Unit) {
1070 bail_span!(v.fields, "Structured enum is not supported in #[napi]")
1071 }
1072 if matches!(&v.discriminant, Some((_, _))) {
1073 bail_span!(
1074 v.fields,
1075 "Literal values are not supported with string enum in #[napi]"
1076 )
1077 }
1078 Ok(NapiEnumVariant {
1079 name: v.ident.clone(),
1080 val: NapiEnumValue::String(v.ident.to_string()),
1081 comments: extract_doc_comments(&v.attrs),
1082 })
1083 })
1084 .collect::<BindgenResult<Vec<NapiEnumVariant>>>()?,
1085 None => {
1086 let mut last_variant_val: i32 = -1;
1087
1088 self
1089 .variants
1090 .iter()
1091 .map(|v| {
1092 if !matches!(v.fields, syn::Fields::Unit) {
1093 bail_span!(v.fields, "Structured enum is not supported in #[napi]")
1094 }
1095
1096 let val = match &v.discriminant {
1097 Some((_, expr)) => {
1098 let mut symbol = 1;
1099 let mut inner_expr = get_expr(expr);
1100 if let syn::Expr::Unary(syn::ExprUnary {
1101 attrs: _,
1102 op: syn::UnOp::Neg(_),
1103 expr,
1104 }) = inner_expr
1105 {
1106 symbol = -1;
1107 inner_expr = expr;
1108 }
1109
1110 match inner_expr {
1111 syn::Expr::Lit(syn::ExprLit {
1112 attrs: _,
1113 lit: syn::Lit::Int(int_lit),
1114 }) => match int_lit.base10_digits().parse::<i32>() {
1115 Ok(v) => symbol * v,
1116 Err(_) => {
1117 bail_span!(
1118 int_lit,
1119 "enums with #[wasm_bindgen] can only support \
1120 numbers that can be represented as i32",
1121 );
1122 }
1123 },
1124 _ => bail_span!(
1125 expr,
1126 "enums with #[wasm_bindgen] may only have \
1127 number literal values",
1128 ),
1129 }
1130 }
1131 None => last_variant_val + 1,
1132 };
1133
1134 last_variant_val = val;
1135
1136 Ok(NapiEnumVariant {
1137 name: v.ident.clone(),
1138 val: NapiEnumValue::Number(val),
1139 comments: extract_doc_comments(&v.attrs),
1140 })
1141 })
1142 .collect::<BindgenResult<Vec<NapiEnumVariant>>>()?
1143 }
1144 };
1145
1146 Ok(Napi {
1147 item: NapiItem::Enum(NapiEnum {
1148 name: self.ident.clone(),
1149 js_name,
1150 variants,
1151 js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
1152 comments: extract_doc_comments(&self.attrs),
1153 skip_typescript: opts.skip_typescript().is_some(),
1154 register_name: get_register_ident(self.ident.to_string().as_str()),
1155 }),
1156 })
1157 }
1158}
1159
1160impl ConvertToAST for syn::ItemConst {
1161 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi> {
1162 match self.vis {
1163 Visibility::Public(_) => Ok(Napi {
1164 item: NapiItem::Const(NapiConst {
1165 name: self.ident.clone(),
1166 js_name: opts
1167 .js_name()
1168 .map_or_else(|| self.ident.to_string(), |(s: &str, _)| s.to_string()),
1169 type_name: *self.ty.clone(),
1170 value: *self.expr.clone(),
1171 js_mod: opts.namespace().map(|(m: &str, _)| m.to_owned()),
1172 comments: extract_doc_comments(&self.attrs),
1173 skip_typescript: opts.skip_typescript().is_some(),
1174 register_name: get_register_ident(self.ident.to_string().as_str()),
1175 }),
1176 }),
1177 _ => bail_span!(self, "only public const allowed"),
1178 }
1179 }
1180}
1181