napi_derive/parser/
attrs.rs

1use std::cell::Cell;
2use std::collections::HashMap;
3use std::sync::{
4  atomic::{AtomicUsize, Ordering},
5  Mutex, OnceLock,
6};
7
8use napi_derive_backend::{bail_span, BindgenResult, Diagnostic};
9use proc_macro2::{Delimiter, Ident, Span, TokenTree};
10use quote::ToTokens;
11use syn::parse::{Parse, ParseStream};
12use syn::spanned::Spanned;
13use syn::Attribute;
14
15use crate::parser::AnyIdent;
16
17static ATTRS: OnceLock<AttributeParseState> = OnceLock::new();
18static STRUCTS: OnceLock<StructParseState> = OnceLock::new();
19
20#[derive(Default)]
21struct StructParseState {
22  parsed: Mutex<HashMap<String, ParsedStruct>>,
23}
24
25struct ParsedStruct {
26  js_name: String,
27  ctor_defined: bool,
28}
29
30#[derive(Default)]
31struct AttributeParseState {
32  parsed: AtomicUsize,
33  #[allow(unused)]
34  checks: AtomicUsize,
35}
36
37#[derive(Debug)]
38/// Parsed attributes from a `#[napi(..)]`.
39pub struct BindgenAttrs {
40  /// Whether `#[napi]` attribute exists
41  pub exists: bool,
42  /// List of parsed attributes
43  pub attrs: Vec<(Cell<bool>, BindgenAttr)>,
44  /// Span of original attribute
45  pub span: Span,
46}
47
48// NOTE: borrowed from wasm-bindgen
49// some of them may useless in #[napi] macro
50macro_rules! attrgen {
51  ($mac:ident) => {
52    $mac! {
53      (catch_unwind, CatchUnwind(Span)),
54      (async_runtime, AsyncRuntime(Span)),
55      (module_exports, ModuleExports(Span)),
56      (js_name, JsName(Span, String, Span)),
57      (constructor, Constructor(Span)),
58      (factory, Factory(Span)),
59      (getter, Getter(Span, Option<Ident>)),
60      (setter, Setter(Span, Option<Ident>)),
61      (readonly, Readonly(Span)),
62      (enumerable, Enumerable(Span, Option<bool>), true),
63      (writable, Writable(Span, Option<bool>), true),
64      (configurable, Configurable(Span, Option<bool>), true),
65      (skip, Skip(Span)),
66      (strict, Strict(Span)),
67      (return_if_invalid, ReturnIfInvalid(Span)),
68      (object, Object(Span)),
69      (object_from_js, ObjectFromJs(Span, Option<bool>), true),
70      (object_to_js, ObjectToJs(Span, Option<bool>), true),
71      (custom_finalize, CustomFinalize(Span)),
72      (namespace, Namespace(Span, String, Span)),
73      (iterator, Iterator(Span)),
74      (ts_args_type, TsArgsType(Span, String, Span)),
75      (ts_return_type, TsReturnType(Span, String, Span)),
76      (ts_type, TsType(Span, String, Span)),
77      (ts_generic_types, TsGenericTypes(Span, String, Span)),
78      (string_enum, StringEnum(Span, Option<(String, Span)>)),
79      (use_nullable, UseNullable(Span, Option<bool>), false),
80      (discriminant, Discriminant(Span, String, Span)),
81      (discriminant_case, DiscriminantCase(Span, String, Span)),
82      (transparent, Transparent(Span)),
83      (array, Array(Span)),
84      (no_export, NoExport(Span)),
85
86      // impl later
87      // (inspectable, Inspectable(Span)),
88      // (typescript_custom_section, TypescriptCustomSection(Span)),
89      (skip_typescript, SkipTypescript(Span)),
90      // (getter_with_clone, GetterWithClone(Span)),
91
92      // For testing purposes only.
93      // (assert_no_shim, AssertNoShim(Span)),
94    }
95  };
96}
97
98macro_rules! methods {
99  ($(($name:ident, $variant:ident($($contents:tt)*) $($extra_tokens:tt)*),)*) => {
100    $(methods!(@method $name, $variant($($contents)*) $($extra_tokens)*);)*
101
102    #[cfg(feature = "strict")]
103    #[allow(unused)]
104    pub fn check_used(&self) -> Result<(), Diagnostic> {
105      // Account for the fact this method was called
106      let attrs = ATTRS.get_or_init(|| AttributeParseState::default());
107      attrs.checks.fetch_add(1, Ordering::SeqCst);
108
109      let mut errors = Vec::new();
110      for (used, attr) in self.attrs.iter() {
111        if used.get() {
112            continue
113        }
114        let span = match attr {
115          $(BindgenAttr::$variant(span, ..) => span,)*
116        };
117        errors.push(Diagnostic::span_error(*span, "unused #[napi] attribute"));
118      }
119      Diagnostic::from_vec(errors)
120    }
121
122    #[cfg(not(feature = "strict"))]
123    #[allow(unused)]
124    pub fn check_used(&self) -> Result<(), Diagnostic> {
125        // Account for the fact this method was called
126      let attrs = ATTRS.get_or_init(AttributeParseState::default);
127      attrs.checks.fetch_add(1, Ordering::SeqCst);
128      Ok(())
129    }
130  };
131
132  (@method $name:ident, $variant:ident(Span, String, Span)) => {
133    #[allow(unused)]
134    pub fn $name(&self) -> Option<(&str, Span)> {
135      self.attrs
136        .iter()
137        .filter_map(|a| match &a.1 {
138          BindgenAttr::$variant(_, s, span) => {
139            a.0.set(true);
140            Some((&s[..], *span))
141          }
142          _ => None,
143        })
144        .next()
145    }
146  };
147
148  (@method $name:ident, $variant:ident(Span, Option<(String, Span)>)) => {
149    #[allow(unused)]
150    pub fn $name(&self) -> Option<Option<&(String, Span)>> {
151      self.attrs
152        .iter()
153        .filter_map(|a| match &a.1 {
154          BindgenAttr::$variant(_, s) => {
155            a.0.set(true);
156            Some(s.as_ref())
157          }
158          _ => None,
159        })
160        .next()
161    }
162  };
163
164  (@method $name:ident, $variant:ident(Span, Option<bool>), $default_value:literal) => {
165    #[allow(unused)]
166    pub fn $name(&self) -> bool {
167      self.attrs
168        .iter()
169        .filter_map(|a| match &a.1 {
170          BindgenAttr::$variant(_, s) => {
171            a.0.set(true);
172            *s
173          }
174          _ => None,
175        })
176        .next()
177        .unwrap_or($default_value)
178    }
179  };
180
181  (@method $name:ident, $variant:ident(Span, Vec<String>, Vec<Span>)) => {
182    #[allow(unused)]
183    pub fn $name(&self) -> Option<(&[String], &[Span])> {
184      self.attrs
185        .iter()
186        .filter_map(|a| match &a.1 {
187          BindgenAttr::$variant(_, ss, spans) => {
188            a.0.set(true);
189            Some((&ss[..], &spans[..]))
190          }
191          _ => None,
192        })
193        .next()
194      }
195  };
196
197  (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
198    #[allow(unused)]
199    pub fn $name(&self) -> Option<&$($other)*> {
200      self.attrs
201        .iter()
202        .filter_map(|a| match &a.1 {
203          BindgenAttr::$variant(_, s) => {
204            a.0.set(true);
205            Some(s)
206          }
207          _ => None,
208        })
209        .next()
210      }
211  };
212
213  (@method $name:ident, $variant:ident($($other:tt)*)) => {
214    #[allow(unused)]
215    pub fn $name(&self) -> Option<&$($other)*> {
216      self.attrs
217        .iter()
218        .filter_map(|a| match &a.1 {
219          BindgenAttr::$variant(s) => {
220            a.0.set(true);
221            Some(s)
222          }
223          _ => None,
224        })
225        .next()
226    }
227  };
228}
229
230impl BindgenAttrs {
231  /// Find and parse the napi attributes.
232  pub fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
233    for (index, attr) in attrs.iter().enumerate() {
234      let attr = BindgenAttrs::try_from(attr)?;
235      if attr.exists {
236        attrs.remove(index);
237
238        return Ok(attr);
239      }
240    }
241
242    Ok(BindgenAttrs::default())
243  }
244
245  attrgen!(methods);
246}
247
248impl TryFrom<&Attribute> for BindgenAttrs {
249  type Error = Diagnostic;
250
251  fn try_from(attr: &Attribute) -> Result<Self, Self::Error> {
252    let mut ret = BindgenAttrs {
253      exists: false,
254      attrs: vec![],
255      span: Span::call_site(),
256    };
257
258    let is_napi =
259      attr.path().segments.last().map(|s| s.ident.to_string()) == Some("napi".to_string());
260    let is_cfg_attr = attr
261      .meta
262      .path()
263      .segments
264      .first()
265      .map(|s| s.ident.to_string())
266      == Some("cfg_attr".to_string());
267
268    if is_napi {
269      ret.exists = true;
270      ret.span = attr.span();
271
272      let tts = attr.meta.to_token_stream().into_iter();
273      let group = match tts.last() {
274        // #[napi(xxx)]
275        //   ^^^^^^^^^
276        Some(TokenTree::Group(d)) => d,
277        // #[napi]
278        //   ^^^^
279        Some(TokenTree::Ident(_)) => parse_quote!(()),
280        _ => bail_span!(attr, "invalid #[napi] attribute"),
281      };
282
283      if group.delimiter() != Delimiter::Parenthesis {
284        bail_span!(attr, "malformed #[napi] attribute");
285      }
286
287      let mut attrs: BindgenAttrs = syn::parse2(group.stream())?;
288      ret.attrs.append(&mut attrs.attrs);
289    }
290
291    if is_cfg_attr {
292      let cfg_attr_list = attr.meta.require_list()?;
293      // #[cfg_attr(condition, attr_to_apply)]
294      // We parse the arguments of cfg_attr.
295      let mut args_iter = cfg_attr_list
296        .parse_args_with(syn::punctuated::Punctuated::<syn::Meta, Token![,]>::parse_terminated)?
297        .into_iter();
298      if let Some(arg) = args_iter.next_back() {
299        if arg.path().segments.last().map(|s| s.ident.to_string()) == Some("napi".to_string()) {
300          ret.exists = true;
301          ret.span = arg.span();
302          let tts = arg.to_token_stream().into_iter();
303          let group = match tts.last() {
304            // #[napi(xxx)]
305            //   ^^^^^^^^^
306            Some(TokenTree::Group(d)) => d,
307            // #[napi]
308            //   ^^^^
309            Some(TokenTree::Ident(_)) => parse_quote!(()),
310            _ => bail_span!(attr, "invalid #[napi] attribute"),
311          };
312
313          if group.delimiter() != Delimiter::Parenthesis {
314            bail_span!(attr, "malformed #[napi] attribute");
315          }
316
317          let mut attrs: BindgenAttrs = syn::parse2(group.stream())?;
318          ret.attrs.append(&mut attrs.attrs);
319        }
320      }
321    }
322
323    Ok(ret)
324  }
325}
326
327impl Default for BindgenAttrs {
328  fn default() -> BindgenAttrs {
329    // Add 1 to the list of parsed attribute sets. We'll use this counter to
330    // sanity check that we call `check_used` an appropriate number of
331    // times.
332    let attrs = ATTRS.get_or_init(AttributeParseState::default);
333    attrs.parsed.fetch_add(1, Ordering::SeqCst);
334    BindgenAttrs {
335      span: Span::call_site(),
336      attrs: Vec::new(),
337      exists: false,
338    }
339  }
340}
341
342macro_rules! gen_bindgen_attr {
343  ($( ($method:ident, $variant:ident($($associated_data:tt)*) $($extra_tokens:tt)*) ,)*) => {
344    /// The possible attributes in the `#[napi]`.
345    #[derive(Debug)]
346    #[allow(unused)]
347    pub enum BindgenAttr {
348      $($variant($($associated_data)*)),*
349    }
350  }
351}
352
353attrgen!(gen_bindgen_attr);
354
355pub fn record_struct(ident: &Ident, js_name: String, opts: &BindgenAttrs) {
356  let state = STRUCTS.get_or_init(StructParseState::default);
357  let mut map = state.parsed.lock().unwrap();
358  let struct_name = ident.to_string();
359
360  map.insert(
361    struct_name,
362    ParsedStruct {
363      js_name,
364      ctor_defined: opts.constructor().is_some(),
365    },
366  );
367}
368
369pub fn check_recorded_struct_for_impl(ident: &Ident, opts: &BindgenAttrs) -> BindgenResult<String> {
370  let state = STRUCTS.get_or_init(StructParseState::default);
371  let mut map = state.parsed.lock().unwrap();
372  let struct_name = ident.to_string();
373  if let Some(parsed) = map.get_mut(&struct_name) {
374    if opts.constructor().is_some() && !cfg!(debug_assertions) {
375      if parsed.ctor_defined {
376        bail_span!(
377          ident,
378          "Constructor has already been defined for struct `{}`",
379          &struct_name
380        );
381      } else {
382        parsed.ctor_defined = true;
383      }
384    }
385
386    Ok(parsed.js_name.clone())
387  } else {
388    bail_span!(
389      ident,
390      "Did not find struct `{}` parsed before expand #[napi] for impl",
391      &struct_name,
392    )
393  }
394}
395
396impl Parse for BindgenAttrs {
397  fn parse(input: ParseStream) -> syn::Result<Self> {
398    let mut attrs = BindgenAttrs::default();
399    if input.is_empty() {
400      return Ok(attrs);
401    }
402
403    let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
404    attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect();
405    Ok(attrs)
406  }
407}
408
409impl Parse for BindgenAttr {
410  fn parse(input: ParseStream) -> syn::Result<Self> {
411    let original = input.fork();
412    let attr: AnyIdent = input.parse()?;
413    let attr = attr.0;
414    let attr_span = attr.span();
415    let attr_string = attr.to_string();
416    let raw_attr_string = format!("r#{attr_string}");
417
418    macro_rules! parsers {
419      ($(($name:ident, $($contents:tt)*),)*) => {
420        $(
421          if attr_string == stringify!($name) || raw_attr_string == stringify!($name) {
422            parsers!(
423              @parser
424              $($contents)*
425            );
426          }
427        )*
428      };
429
430      (@parser $variant:ident(Span)) => ({
431        return Ok(BindgenAttr::$variant(attr_span));
432      });
433
434      (@parser $variant:ident(Span, Ident)) => ({
435        input.parse::<Token![=]>()?;
436        let ident = input.parse::<AnyIdent>()?.0;
437        return Ok(BindgenAttr::$variant(attr_span, ident))
438      });
439
440      (@parser $variant:ident(Span, Option<Ident>)) => ({
441        if input.parse::<Token![=]>().is_ok() {
442          let ident = input.parse::<AnyIdent>()?.0;
443          return Ok(BindgenAttr::$variant(attr_span, Some(ident)))
444        } else {
445          return Ok(BindgenAttr::$variant(attr_span, None));
446        }
447      });
448
449        (@parser $variant:ident(Span, syn::Path)) => ({
450            input.parse::<Token![=]>()?;
451            return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
452        });
453
454        (@parser $variant:ident(Span, syn::Expr)) => ({
455            input.parse::<Token![=]>()?;
456            return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
457        });
458
459        (@parser $variant:ident(Span, String, Span)) => ({
460          input.parse::<Token![=]>()?;
461          let (val, span) = match input.parse::<syn::LitStr>() {
462            Ok(str) => (str.value(), str.span()),
463            Err(_) => {
464              let ident = input.parse::<AnyIdent>()?.0;
465              (ident.to_string(), ident.span())
466            }
467          };
468          return Ok(BindgenAttr::$variant(attr_span, val, span))
469        });
470
471        (@parser $variant:ident(Span, Option<(String, Span)>)) => ({
472          if let Ok(_) = input.parse::<Token![=]>() {
473            let val = match input.parse::<syn::LitStr>() {
474              Ok(str) => Some((str.value(), str.span())),
475              Err(_) => {
476                let ident = input.parse::<AnyIdent>()?.0;
477                Some((ident.to_string(), ident.span()))
478              }
479            };
480            return Ok(BindgenAttr::$variant(attr_span, val))
481          } else {
482            return Ok(BindgenAttr::$variant(attr_span, None))
483          }
484        });
485
486        (@parser $variant:ident(Span, Option<bool>), $default_value:literal) => ({
487          if let Ok(_) = input.parse::<Token![=]>() {
488            let (val, _) = match input.parse::<syn::LitBool>() {
489              Ok(str) => (str.value(), str.span()),
490              Err(_) => {
491                let ident = input.parse::<AnyIdent>()?.0;
492                (true, ident.span())
493              }
494            };
495            return Ok::<BindgenAttr, syn::Error>(BindgenAttr::$variant(attr_span, Some(val)))
496          } else {
497            return Ok(BindgenAttr::$variant(attr_span, Some($default_value)))
498          }
499        });
500
501        (@parser $variant:ident(Span, Vec<String>, Vec<Span>)) => ({
502          input.parse::<Token![=]>()?;
503          let (vals, spans) = match input.parse::<syn::ExprArray>() {
504            Ok(exprs) => {
505              let mut vals = vec![];
506              let mut spans = vec![];
507
508              for expr in exprs.elems.iter() {
509                if let syn::Expr::Lit(syn::ExprLit {
510                  lit: syn::Lit::Str(ref str),
511                  ..
512                }) = expr {
513                  vals.push(str.value());
514                  spans.push(str.span());
515                } else {
516                  return Err(syn::Error::new(expr.span(), "expected string literals"));
517                }
518              }
519
520              (vals, spans)
521            },
522            Err(_) => {
523              let ident = input.parse::<AnyIdent>()?.0;
524              (vec![ident.to_string()], vec![ident.span()])
525            }
526          };
527          return Ok(BindgenAttr::$variant(attr_span, vals, spans))
528        });
529      }
530
531    attrgen!(parsers);
532
533    Err(original.error("unknown attribute"))
534  }
535}