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