1 | #[macro_use ] |
2 | pub mod attrs; |
3 | |
4 | use std::cell::RefCell; |
5 | use std::collections::HashMap; |
6 | use std::str::Chars; |
7 | use std::sync::atomic::AtomicUsize; |
8 | |
9 | use attrs::BindgenAttrs; |
10 | |
11 | use convert_case::{Case, Casing}; |
12 | use 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 | }; |
17 | use proc_macro2::{Ident, Span, TokenStream}; |
18 | use quote::ToTokens; |
19 | use syn::ext::IdentExt; |
20 | use syn::parse::{Parse, ParseStream, Result as SynResult}; |
21 | use syn::spanned::Spanned; |
22 | use syn::{Attribute, ExprLit, Meta, PatType, PathSegment, Signature, Type, Visibility}; |
23 | |
24 | use crate::parser::attrs::{check_recorded_struct_for_impl, record_struct}; |
25 | |
26 | thread_local! { |
27 | static GENERATOR_STRUCT: RefCell<HashMap<String, bool>> = Default::default(); |
28 | } |
29 | |
30 | static REGISTER_INDEX: AtomicUsize = AtomicUsize::new(0); |
31 | |
32 | fn 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 | |
41 | struct AnyIdent(Ident); |
42 | |
43 | impl 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 | |
52 | pub trait ConvertToAST { |
53 | fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi>; |
54 | } |
55 | |
56 | pub 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 |
71 | fn 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 | |
151 | fn 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 | |
159 | fn 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 |
180 | fn 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 | |
196 | fn 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 | |
234 | fn 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 | |
261 | fn 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 |
270 | fn 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("ed).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. |
307 | fn 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 | |
351 | fn 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 | |
380 | fn 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 | |
473 | fn 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 | |
645 | impl 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 | |
661 | impl 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 | } |
681 | impl 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 | |
715 | impl 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 | |
748 | impl 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 | } |
778 | impl 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 | |
808 | fn 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 | |
830 | impl 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 | |
846 | impl 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 | |
953 | impl 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 | |
1051 | impl 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 | |
1160 | impl 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 | |