Skip to main content

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