wasm_bindgen_macro_support/
parser.rs

1use std::cell::{Cell, RefCell};
2use std::char;
3use std::str::Chars;
4
5use ast::OperationKind;
6use backend::ast;
7use backend::util::{ident_ty, ShortHash};
8use backend::Diagnostic;
9use proc_macro2::{Ident, Span, TokenStream, TokenTree};
10use quote::ToTokens;
11use syn::ext::IdentExt;
12use syn::parse::{Parse, ParseStream, Result as SynResult};
13use syn::spanned::Spanned;
14use syn::{ItemFn, Lit, MacroDelimiter, ReturnType};
15
16use crate::ClassMarker;
17
18thread_local!(static ATTRS: AttributeParseState = Default::default());
19
20/// Javascript keywords which are not keywords in Rust.
21const JS_KEYWORDS: [&str; 20] = [
22    "class",
23    "case",
24    "catch",
25    "debugger",
26    "default",
27    "delete",
28    "export",
29    "extends",
30    "finally",
31    "function",
32    "import",
33    "instanceof",
34    "new",
35    "null",
36    "switch",
37    "this",
38    "throw",
39    "var",
40    "void",
41    "with",
42];
43#[derive(Default)]
44struct AttributeParseState {
45    parsed: Cell<usize>,
46    checks: Cell<usize>,
47    unused_attrs: RefCell<Vec<Ident>>,
48}
49
50/// Parsed attributes from a `#[wasm_bindgen(..)]`.
51#[cfg_attr(feature = "extra-traits", derive(Debug))]
52pub struct BindgenAttrs {
53    /// List of parsed attributes
54    pub attrs: Vec<(Cell<bool>, BindgenAttr)>,
55}
56
57macro_rules! attrgen {
58    ($mac:ident) => {
59        $mac! {
60            (catch, Catch(Span)),
61            (constructor, Constructor(Span)),
62            (method, Method(Span)),
63            (static_method_of, StaticMethodOf(Span, Ident)),
64            (js_namespace, JsNamespace(Span, Vec<String>, Vec<Span>)),
65            (module, Module(Span, String, Span)),
66            (raw_module, RawModule(Span, String, Span)),
67            (inline_js, InlineJs(Span, String, Span)),
68            (getter, Getter(Span, Option<String>)),
69            (setter, Setter(Span, Option<String>)),
70            (indexing_getter, IndexingGetter(Span)),
71            (indexing_setter, IndexingSetter(Span)),
72            (indexing_deleter, IndexingDeleter(Span)),
73            (structural, Structural(Span)),
74            (r#final, Final(Span)),
75            (readonly, Readonly(Span)),
76            (js_name, JsName(Span, String, Span)),
77            (js_class, JsClass(Span, String, Span)),
78            (inspectable, Inspectable(Span)),
79            (is_type_of, IsTypeOf(Span, syn::Expr)),
80            (extends, Extends(Span, syn::Path)),
81            (no_deref, NoDeref(Span)),
82            (vendor_prefix, VendorPrefix(Span, Ident)),
83            (variadic, Variadic(Span)),
84            (typescript_custom_section, TypescriptCustomSection(Span)),
85            (skip_typescript, SkipTypescript(Span)),
86            (skip_jsdoc, SkipJsDoc(Span)),
87            (main, Main(Span)),
88            (start, Start(Span)),
89            (wasm_bindgen, WasmBindgen(Span, syn::Path)),
90            (js_sys, JsSys(Span, syn::Path)),
91            (wasm_bindgen_futures, WasmBindgenFutures(Span, syn::Path)),
92            (skip, Skip(Span)),
93            (typescript_type, TypeScriptType(Span, String, Span)),
94            (getter_with_clone, GetterWithClone(Span)),
95            (static_string, StaticString(Span)),
96            (thread_local, ThreadLocal(Span)),
97
98            // For testing purposes only.
99            (assert_no_shim, AssertNoShim(Span)),
100        }
101    };
102}
103
104macro_rules! methods {
105    ($(($name:ident, $variant:ident($($contents:tt)*)),)*) => {
106        $(methods!(@method $name, $variant($($contents)*));)*
107
108        fn enforce_used(self) -> Result<(), Diagnostic> {
109            // Account for the fact this method was called
110            ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
111
112            let mut errors = Vec::new();
113            for (used, attr) in self.attrs.iter() {
114                if used.get() {
115                    continue
116                }
117                let span = match attr {
118                    $(BindgenAttr::$variant(span, ..) => span,)*
119                };
120                errors.push(Diagnostic::span_error(*span, "unused wasm_bindgen attribute"));
121            }
122            Diagnostic::from_vec(errors)
123        }
124
125        fn check_used(self) {
126            // Account for the fact this method was called
127            ATTRS.with(|state| {
128                state.checks.set(state.checks.get() + 1);
129
130                state.unused_attrs.borrow_mut().extend(
131                    self.attrs
132                    .iter()
133                    .filter_map(|(used, attr)| if used.get() { None } else { Some(attr) })
134                    .map(|attr| {
135                        match attr {
136                            $(BindgenAttr::$variant(span, ..) => {
137                                syn::parse_quote_spanned!(*span => $name)
138                            })*
139                        }
140                    })
141                );
142            });
143        }
144    };
145
146    (@method $name:ident, $variant:ident(Span, String, Span)) => {
147        fn $name(&self) -> Option<(&str, Span)> {
148            self.attrs
149                .iter()
150                .find_map(|a| match &a.1 {
151                    BindgenAttr::$variant(_, s, span) => {
152                        a.0.set(true);
153                        Some((&s[..], *span))
154                    }
155                    _ => None,
156                })
157        }
158    };
159
160    (@method $name:ident, $variant:ident(Span, Vec<String>, Vec<Span>)) => {
161        fn $name(&self) -> Option<(&[String], &[Span])> {
162            self.attrs
163                .iter()
164                .find_map(|a| match &a.1 {
165                    BindgenAttr::$variant(_, ss, spans) => {
166                        a.0.set(true);
167                        Some((&ss[..], &spans[..]))
168                    }
169                    _ => None,
170                })
171        }
172    };
173
174    (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
175        #[allow(unused)]
176        fn $name(&self) -> Option<&$($other)*> {
177            self.attrs
178                .iter()
179                .find_map(|a| match &a.1 {
180                    BindgenAttr::$variant(_, s) => {
181                        a.0.set(true);
182                        Some(s)
183                    }
184                    _ => None,
185                })
186        }
187    };
188
189    (@method $name:ident, $variant:ident($($other:tt)*)) => {
190        #[allow(unused)]
191        fn $name(&self) -> Option<&$($other)*> {
192            self.attrs
193                .iter()
194                .find_map(|a| match &a.1 {
195                    BindgenAttr::$variant(s) => {
196                        a.0.set(true);
197                        Some(s)
198                    }
199                    _ => None,
200                })
201        }
202    };
203}
204
205impl BindgenAttrs {
206    /// Find and parse the wasm_bindgen attributes.
207    fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
208        let mut ret = BindgenAttrs::default();
209        loop {
210            let pos = attrs
211                .iter()
212                .enumerate()
213                .find(|&(_, m)| m.path().segments[0].ident == "wasm_bindgen")
214                .map(|a| a.0);
215            let pos = match pos {
216                Some(i) => i,
217                None => return Ok(ret),
218            };
219            let attr = attrs.remove(pos);
220            let tokens = match attr.meta {
221                syn::Meta::Path(_) => continue,
222                syn::Meta::List(syn::MetaList {
223                    delimiter: MacroDelimiter::Paren(_),
224                    tokens,
225                    ..
226                }) => tokens,
227                syn::Meta::List(_) | syn::Meta::NameValue(_) => {
228                    bail_span!(attr, "malformed #[wasm_bindgen] attribute")
229                }
230            };
231            let mut attrs: BindgenAttrs = syn::parse2(tokens)?;
232            ret.attrs.append(&mut attrs.attrs);
233            attrs.check_used();
234        }
235    }
236
237    attrgen!(methods);
238}
239
240impl Default for BindgenAttrs {
241    fn default() -> BindgenAttrs {
242        // Add 1 to the list of parsed attribute sets. We'll use this counter to
243        // sanity check that we call `check_used` an appropriate number of
244        // times.
245        ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1));
246        BindgenAttrs { attrs: Vec::new() }
247    }
248}
249
250impl Parse for BindgenAttrs {
251    fn parse(input: ParseStream) -> SynResult<Self> {
252        let mut attrs = BindgenAttrs::default();
253        if input.is_empty() {
254            return Ok(attrs);
255        }
256
257        let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
258        attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect();
259        Ok(attrs)
260    }
261}
262
263macro_rules! gen_bindgen_attr {
264    ($(($method:ident, $($variants:tt)*),)*) => {
265        /// The possible attributes in the `#[wasm_bindgen]`.
266        #[cfg_attr(feature = "extra-traits", derive(Debug))]
267        pub enum BindgenAttr {
268            $($($variants)*,)*
269        }
270    }
271}
272attrgen!(gen_bindgen_attr);
273
274impl Parse for BindgenAttr {
275    fn parse(input: ParseStream) -> SynResult<Self> {
276        let original = input.fork();
277        let attr: AnyIdent = input.parse()?;
278        let attr = attr.0;
279        let attr_span = attr.span();
280        let attr_string = attr.to_string();
281        let raw_attr_string = format!("r#{}", attr_string);
282
283        macro_rules! parsers {
284            ($(($name:ident, $($contents:tt)*),)*) => {
285                $(
286                    if attr_string == stringify!($name) || raw_attr_string == stringify!($name) {
287                        parsers!(
288                            @parser
289                            $($contents)*
290                        );
291                    }
292                )*
293            };
294
295            (@parser $variant:ident(Span)) => ({
296                return Ok(BindgenAttr::$variant(attr_span));
297            });
298
299            (@parser $variant:ident(Span, Ident)) => ({
300                input.parse::<Token![=]>()?;
301                let ident = input.parse::<AnyIdent>()?.0;
302                return Ok(BindgenAttr::$variant(attr_span, ident))
303            });
304
305            (@parser $variant:ident(Span, Option<String>)) => ({
306                if input.parse::<Token![=]>().is_ok() {
307                    if input.peek(syn::LitStr) {
308                        let litstr = input.parse::<syn::LitStr>()?;
309                        return Ok(BindgenAttr::$variant(attr_span, Some(litstr.value())))
310                    }
311
312                    let ident = input.parse::<AnyIdent>()?.0;
313                    return Ok(BindgenAttr::$variant(attr_span, Some(ident.to_string())))
314                } else {
315                    return Ok(BindgenAttr::$variant(attr_span, None));
316                }
317            });
318
319            (@parser $variant:ident(Span, syn::Path)) => ({
320                input.parse::<Token![=]>()?;
321                return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
322            });
323
324            (@parser $variant:ident(Span, syn::Expr)) => ({
325                input.parse::<Token![=]>()?;
326                return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
327            });
328
329            (@parser $variant:ident(Span, String, Span)) => ({
330                input.parse::<Token![=]>()?;
331                let (val, span) = match input.parse::<syn::LitStr>() {
332                    Ok(str) => (str.value(), str.span()),
333                    Err(_) => {
334                        let ident = input.parse::<AnyIdent>()?.0;
335                        (ident.to_string(), ident.span())
336                    }
337                };
338                return Ok(BindgenAttr::$variant(attr_span, val, span))
339            });
340
341            (@parser $variant:ident(Span, Vec<String>, Vec<Span>)) => ({
342                input.parse::<Token![=]>()?;
343                let (vals, spans) = match input.parse::<syn::ExprArray>() {
344                    Ok(exprs) => {
345                        let mut vals = vec![];
346                        let mut spans = vec![];
347
348                        for expr in exprs.elems.iter() {
349                            if let syn::Expr::Lit(syn::ExprLit {
350                                lit: syn::Lit::Str(ref str),
351                                ..
352                            }) = expr {
353                                vals.push(str.value());
354                                spans.push(str.span());
355                            } else {
356                                return Err(syn::Error::new(expr.span(), "expected string literals"));
357                            }
358                        }
359
360                        (vals, spans)
361                    },
362                    Err(_) => {
363                        let ident = input.parse::<AnyIdent>()?.0;
364                        (vec![ident.to_string()], vec![ident.span()])
365                    }
366                };
367                return Ok(BindgenAttr::$variant(attr_span, vals, spans))
368            });
369        }
370
371        attrgen!(parsers);
372
373        Err(original.error(if attr_string.starts_with('_') {
374            "unknown attribute: it's safe to remove unused attributes entirely."
375        } else {
376            "unknown attribute"
377        }))
378    }
379}
380
381struct AnyIdent(Ident);
382
383impl Parse for AnyIdent {
384    fn parse(input: ParseStream) -> SynResult<Self> {
385        input.step(|cursor| match cursor.ident() {
386            Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)),
387            None => Err(cursor.error("expected an identifier")),
388        })
389    }
390}
391
392/// Conversion trait with context.
393///
394/// Used to convert syn tokens into an AST, that we can then use to generate glue code. The context
395/// (`Ctx`) is used to pass in the attributes from the `#[wasm_bindgen]`, if needed.
396trait ConvertToAst<Ctx> {
397    /// What we are converting to.
398    type Target;
399    /// Convert into our target.
400    ///
401    /// Since this is used in a procedural macro, use panic to fail.
402    fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
403}
404
405impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs)> for &'a mut syn::ItemStruct {
406    type Target = ast::Struct;
407
408    fn convert(
409        self,
410        (program, attrs): (&ast::Program, BindgenAttrs),
411    ) -> Result<Self::Target, Diagnostic> {
412        if !self.generics.params.is_empty() {
413            bail_span!(
414                self.generics,
415                "structs with #[wasm_bindgen] cannot have lifetime or \
416                 type parameters currently"
417            );
418        }
419        let mut fields = Vec::new();
420        let js_name = attrs
421            .js_name()
422            .map(|s| s.0.to_string())
423            .unwrap_or(self.ident.unraw().to_string());
424        let is_inspectable = attrs.inspectable().is_some();
425        let getter_with_clone = attrs.getter_with_clone();
426        for (i, field) in self.fields.iter_mut().enumerate() {
427            match field.vis {
428                syn::Visibility::Public(..) => {}
429                _ => continue,
430            }
431            let (js_field_name, member) = match &field.ident {
432                Some(ident) => (ident.unraw().to_string(), syn::Member::Named(ident.clone())),
433                None => (i.to_string(), syn::Member::Unnamed(i.into())),
434            };
435
436            let attrs = BindgenAttrs::find(&mut field.attrs)?;
437            if attrs.skip().is_some() {
438                attrs.check_used();
439                continue;
440            }
441
442            let js_field_name = match attrs.js_name() {
443                Some((name, _)) => name.to_string(),
444                None => js_field_name,
445            };
446
447            let comments = extract_doc_comments(&field.attrs);
448            let getter = shared::struct_field_get(&js_name, &js_field_name);
449            let setter = shared::struct_field_set(&js_name, &js_field_name);
450
451            fields.push(ast::StructField {
452                rust_name: member,
453                js_name: js_field_name,
454                struct_name: self.ident.clone(),
455                readonly: attrs.readonly().is_some(),
456                ty: field.ty.clone(),
457                getter: Ident::new(&getter, Span::call_site()),
458                setter: Ident::new(&setter, Span::call_site()),
459                comments,
460                generate_typescript: attrs.skip_typescript().is_none(),
461                generate_jsdoc: attrs.skip_jsdoc().is_none(),
462                getter_with_clone: attrs.getter_with_clone().or(getter_with_clone).copied(),
463                wasm_bindgen: program.wasm_bindgen.clone(),
464            });
465            attrs.check_used();
466        }
467        let generate_typescript = attrs.skip_typescript().is_none();
468        let comments: Vec<String> = extract_doc_comments(&self.attrs);
469        attrs.check_used();
470        Ok(ast::Struct {
471            rust_name: self.ident.clone(),
472            js_name,
473            fields,
474            comments,
475            is_inspectable,
476            generate_typescript,
477            wasm_bindgen: program.wasm_bindgen.clone(),
478        })
479    }
480}
481
482fn get_ty(mut ty: &syn::Type) -> &syn::Type {
483    while let syn::Type::Group(g) = ty {
484        ty = &g.elem;
485    }
486
487    ty
488}
489
490fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
491    while let syn::Expr::Group(g) = expr {
492        expr = &g.expr;
493    }
494
495    expr
496}
497
498impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
499    for syn::ForeignItemFn
500{
501    type Target = ast::ImportKind;
502
503    fn convert(
504        self,
505        (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
506    ) -> Result<Self::Target, Diagnostic> {
507        let mut wasm = function_from_decl(
508            &self.sig.ident,
509            &opts,
510            self.sig.clone(),
511            self.attrs.clone(),
512            self.vis.clone(),
513            false,
514            None,
515            false,
516            Some(&["default"]),
517        )?
518        .0;
519        let catch = opts.catch().is_some();
520        let variadic = opts.variadic().is_some();
521        let js_ret = if catch {
522            // TODO: this assumes a whole bunch:
523            //
524            // * The outer type is actually a `Result`
525            // * The error type is a `JsValue`
526            // * The actual type is the first type parameter
527            //
528            // should probably fix this one day...
529            extract_first_ty_param(wasm.ret.as_ref())?
530        } else {
531            wasm.ret.clone()
532        };
533
534        let operation_kind = operation_kind(&opts);
535
536        let kind = if opts.method().is_some() {
537            let class = wasm.arguments.first().ok_or_else(|| {
538                err_span!(self, "imported methods must have at least one argument")
539            })?;
540            let class = match get_ty(&class.ty) {
541                syn::Type::Reference(syn::TypeReference {
542                    mutability: None,
543                    elem,
544                    ..
545                }) => &**elem,
546                _ => bail_span!(
547                    class.ty,
548                    "first argument of method must be a shared reference"
549                ),
550            };
551            let class_name = match get_ty(class) {
552                syn::Type::Path(syn::TypePath {
553                    qself: None,
554                    ref path,
555                }) => path,
556                _ => bail_span!(class, "first argument of method must be a path"),
557            };
558            let class_name = extract_path_ident(class_name)?;
559            let class_name = opts
560                .js_class()
561                .map(|p| p.0.into())
562                .unwrap_or_else(|| class_name.to_string());
563
564            let kind = ast::MethodKind::Operation(ast::Operation {
565                is_static: false,
566                kind: operation_kind,
567            });
568
569            ast::ImportFunctionKind::Method {
570                class: class_name,
571                ty: class.clone(),
572                kind,
573            }
574        } else if let Some(cls) = opts.static_method_of() {
575            let class = opts
576                .js_class()
577                .map(|p| p.0.into())
578                .unwrap_or_else(|| cls.to_string());
579            let ty = ident_ty(cls.clone());
580
581            let kind = ast::MethodKind::Operation(ast::Operation {
582                is_static: true,
583                kind: operation_kind,
584            });
585
586            ast::ImportFunctionKind::Method { class, ty, kind }
587        } else if opts.constructor().is_some() {
588            let class = match js_ret {
589                Some(ref ty) => ty,
590                _ => bail_span!(self, "constructor returns must be bare types"),
591            };
592            let class_name = match get_ty(class) {
593                syn::Type::Path(syn::TypePath {
594                    qself: None,
595                    ref path,
596                }) => path,
597                _ => bail_span!(self, "return value of constructor must be a bare path"),
598            };
599            let class_name = extract_path_ident(class_name)?;
600            let class_name = opts
601                .js_class()
602                .map(|p| p.0.into())
603                .unwrap_or_else(|| class_name.to_string());
604
605            ast::ImportFunctionKind::Method {
606                class: class_name,
607                ty: class.clone(),
608                kind: ast::MethodKind::Constructor,
609            }
610        } else {
611            ast::ImportFunctionKind::Normal
612        };
613
614        let shim = {
615            let ns = match kind {
616                ast::ImportFunctionKind::Normal => (0, "n"),
617                ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
618            };
619            let data = (ns, &self.sig.ident, module);
620            format!(
621                "__wbg_{}_{}",
622                wasm.name
623                    .chars()
624                    .filter(|c| c.is_ascii_alphanumeric())
625                    .collect::<String>(),
626                ShortHash(data)
627            )
628        };
629        if let Some(span) = opts.r#final() {
630            if opts.structural().is_some() {
631                let msg = "cannot specify both `structural` and `final`";
632                return Err(Diagnostic::span_error(*span, msg));
633            }
634        }
635        let assert_no_shim = opts.assert_no_shim().is_some();
636
637        let mut doc_comment = String::new();
638        // Extract the doc comments from our list of attributes.
639        wasm.rust_attrs.retain(|attr| {
640            /// Returns the contents of the passed `#[doc = "..."]` attribute,
641            /// or `None` if it isn't one.
642            fn get_docs(attr: &syn::Attribute) -> Option<String> {
643                if attr.path().is_ident("doc") {
644                    if let syn::Meta::NameValue(syn::MetaNameValue {
645                        value:
646                            syn::Expr::Lit(syn::ExprLit {
647                                lit: Lit::Str(str), ..
648                            }),
649                        ..
650                    }) = &attr.meta
651                    {
652                        Some(str.value())
653                    } else {
654                        None
655                    }
656                } else {
657                    None
658                }
659            }
660
661            if let Some(docs) = get_docs(attr) {
662                if !doc_comment.is_empty() {
663                    // Add newlines between the doc comments
664                    doc_comment.push('\n');
665                }
666                // Add this doc comment to the complete docs
667                doc_comment.push_str(&docs);
668
669                // Remove it from the list of regular attributes
670                false
671            } else {
672                true
673            }
674        });
675
676        let ret = ast::ImportKind::Function(ast::ImportFunction {
677            function: wasm,
678            assert_no_shim,
679            kind,
680            js_ret,
681            catch,
682            variadic,
683            structural: opts.structural().is_some() || opts.r#final().is_none(),
684            rust_name: self.sig.ident,
685            shim: Ident::new(&shim, Span::call_site()),
686            doc_comment,
687            wasm_bindgen: program.wasm_bindgen.clone(),
688            wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
689        });
690        opts.check_used();
691
692        Ok(ret)
693    }
694}
695
696impl ConvertToAst<(&ast::Program, BindgenAttrs)> for syn::ForeignItemType {
697    type Target = ast::ImportKind;
698
699    fn convert(
700        self,
701        (program, attrs): (&ast::Program, BindgenAttrs),
702    ) -> Result<Self::Target, Diagnostic> {
703        let js_name = attrs
704            .js_name()
705            .map(|s| s.0)
706            .map_or_else(|| self.ident.to_string(), |s| s.to_string());
707        let typescript_type = attrs.typescript_type().map(|s| s.0.to_string());
708        let is_type_of = attrs.is_type_of().cloned();
709        let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
710        let mut extends = Vec::new();
711        let mut vendor_prefixes = Vec::new();
712        let no_deref = attrs.no_deref().is_some();
713        for (used, attr) in attrs.attrs.iter() {
714            match attr {
715                BindgenAttr::Extends(_, e) => {
716                    extends.push(e.clone());
717                    used.set(true);
718                }
719                BindgenAttr::VendorPrefix(_, e) => {
720                    vendor_prefixes.push(e.clone());
721                    used.set(true);
722                }
723                _ => {}
724            }
725        }
726        attrs.check_used();
727        Ok(ast::ImportKind::Type(ast::ImportType {
728            vis: self.vis,
729            attrs: self.attrs,
730            doc_comment: None,
731            instanceof_shim: shim,
732            is_type_of,
733            rust_name: self.ident,
734            typescript_type,
735            js_name,
736            extends,
737            vendor_prefixes,
738            no_deref,
739            wasm_bindgen: program.wasm_bindgen.clone(),
740        }))
741    }
742}
743
744impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
745    for syn::ForeignItemStatic
746{
747    type Target = ast::ImportKind;
748
749    fn convert(
750        self,
751        (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
752    ) -> Result<Self::Target, Diagnostic> {
753        if let syn::StaticMutability::Mut(_) = self.mutability {
754            bail_span!(self.mutability, "cannot import mutable globals yet")
755        }
756
757        if let Some(span) = opts.static_string() {
758            return Err(Diagnostic::span_error(
759                *span,
760                "static strings require a string literal",
761            ));
762        }
763
764        let default_name = self.ident.to_string();
765        let js_name = opts
766            .js_name()
767            .map(|p| p.0)
768            .unwrap_or(&default_name)
769            .to_string();
770        let shim = format!(
771            "__wbg_static_accessor_{}_{}",
772            self.ident,
773            ShortHash((&js_name, module, &self.ident)),
774        );
775        let thread_local = opts.thread_local().is_some();
776
777        opts.check_used();
778        Ok(ast::ImportKind::Static(ast::ImportStatic {
779            ty: *self.ty,
780            vis: self.vis,
781            rust_name: self.ident.clone(),
782            js_name,
783            shim: Ident::new(&shim, Span::call_site()),
784            wasm_bindgen: program.wasm_bindgen.clone(),
785            thread_local,
786        }))
787    }
788}
789
790impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
791    for syn::ItemStatic
792{
793    type Target = ast::ImportKind;
794
795    fn convert(
796        self,
797        (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
798    ) -> Result<Self::Target, Diagnostic> {
799        if let syn::StaticMutability::Mut(_) = self.mutability {
800            bail_span!(self.mutability, "cannot import mutable globals yet")
801        }
802
803        let string = if let syn::Expr::Lit(syn::ExprLit {
804            lit: syn::Lit::Str(string),
805            ..
806        }) = *self.expr.clone()
807        {
808            string.value()
809        } else {
810            bail_span!(
811                self.expr,
812                "statics with a value can only be string literals"
813            )
814        };
815
816        if opts.static_string().is_none() {
817            bail_span!(
818                self,
819                "static strings require `#[wasm_bindgen(static_string)]`"
820            )
821        }
822
823        if opts.thread_local().is_none() {
824            bail_span!(
825                self,
826                "static strings require `#[wasm_bindgen(thread_local)]`"
827            )
828        }
829
830        let shim = format!(
831            "__wbg_string_{}_{}",
832            self.ident,
833            ShortHash((&module, &self.ident)),
834        );
835        opts.check_used();
836        Ok(ast::ImportKind::String(ast::ImportString {
837            ty: *self.ty,
838            vis: self.vis,
839            rust_name: self.ident.clone(),
840            shim: Ident::new(&shim, Span::call_site()),
841            wasm_bindgen: program.wasm_bindgen.clone(),
842            js_sys: program.js_sys.clone(),
843            string,
844        }))
845    }
846}
847
848impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
849    type Target = ast::Function;
850
851    fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
852        match self.vis {
853            syn::Visibility::Public(_) => {}
854            _ if attrs.start().is_some() => {}
855            _ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
856        }
857        if self.sig.constness.is_some() {
858            bail_span!(
859                self.sig.constness,
860                "can only #[wasm_bindgen] non-const functions"
861            );
862        }
863
864        let ret = function_from_decl(
865            &self.sig.ident,
866            &attrs,
867            self.sig.clone(),
868            self.attrs,
869            self.vis,
870            false,
871            None,
872            false,
873            Some(&["default"]),
874        )?;
875        attrs.check_used();
876        Ok(ret.0)
877    }
878}
879
880pub(crate) fn is_js_keyword(keyword: &str, skip: Option<&[&str]>) -> bool {
881    JS_KEYWORDS
882        .iter()
883        .filter(|keyword| skip.filter(|skip| skip.contains(keyword)).is_none())
884        .any(|this| *this == keyword)
885}
886
887/// Construct a function (and gets the self type if appropriate) for our AST from a syn function.
888#[allow(clippy::too_many_arguments)]
889fn function_from_decl(
890    decl_name: &syn::Ident,
891    opts: &BindgenAttrs,
892    sig: syn::Signature,
893    attrs: Vec<syn::Attribute>,
894    vis: syn::Visibility,
895    allow_self: bool,
896    self_ty: Option<&Ident>,
897    is_from_impl: bool,
898    skip_keywords: Option<&[&str]>,
899) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> {
900    if sig.variadic.is_some() {
901        bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions");
902    }
903    if !sig.generics.params.is_empty() {
904        bail_span!(
905            sig.generics,
906            "can't #[wasm_bindgen] functions with lifetime or type parameters",
907        );
908    }
909
910    assert_no_lifetimes(&sig)?;
911
912    let syn::Signature { inputs, output, .. } = sig;
913
914    let replace_self = |t: syn::Type| {
915        let self_ty = match self_ty {
916            Some(i) => i,
917            None => return t,
918        };
919        let path = match get_ty(&t) {
920            syn::Type::Path(syn::TypePath { qself: None, path }) => path.clone(),
921            other => return other.clone(),
922        };
923        let new_path = if path.segments.len() == 1 && path.segments[0].ident == "Self" {
924            self_ty.clone().into()
925        } else {
926            path
927        };
928        syn::Type::Path(syn::TypePath {
929            qself: None,
930            path: new_path,
931        })
932    };
933
934    let replace_colliding_arg = |i: &mut syn::PatType| {
935        if let syn::Pat::Ident(ref mut i) = *i.pat {
936            let ident = i.ident.to_string();
937            if is_js_keyword(ident.as_str(), skip_keywords) {
938                i.ident = Ident::new(format!("_{}", ident).as_str(), i.ident.span());
939            }
940        }
941    };
942
943    let mut method_self = None;
944    let arguments = inputs
945        .into_iter()
946        .filter_map(|arg| match arg {
947            syn::FnArg::Typed(mut c) => {
948                replace_colliding_arg(&mut c);
949                c.ty = Box::new(replace_self(*c.ty));
950                Some(c)
951            }
952            syn::FnArg::Receiver(r) => {
953                if !allow_self {
954                    panic!("arguments cannot be `self`")
955                }
956                assert!(method_self.is_none());
957                if r.reference.is_none() {
958                    method_self = Some(ast::MethodSelf::ByValue);
959                } else if r.mutability.is_some() {
960                    method_self = Some(ast::MethodSelf::RefMutable);
961                } else {
962                    method_self = Some(ast::MethodSelf::RefShared);
963                }
964                None
965            }
966        })
967        .collect::<Vec<_>>();
968
969    let ret = match output {
970        syn::ReturnType::Default => None,
971        syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)),
972    };
973
974    let (name, name_span, renamed_via_js_name) =
975        if let Some((js_name, js_name_span)) = opts.js_name() {
976            let kind = operation_kind(opts);
977            let prefix = match kind {
978                OperationKind::Setter(_) => "set_",
979                _ => "",
980            };
981            let name = if prefix.is_empty()
982                && opts.method().is_none()
983                && is_js_keyword(js_name, skip_keywords)
984            {
985                format!("_{}", js_name)
986            } else {
987                format!("{}{}", prefix, js_name)
988            };
989            (name, js_name_span, true)
990        } else {
991            let name = if !is_from_impl
992                && opts.method().is_none()
993                && is_js_keyword(&decl_name.to_string(), skip_keywords)
994            {
995                format!("_{}", decl_name.unraw())
996            } else {
997                decl_name.unraw().to_string()
998            };
999            (name, decl_name.span(), false)
1000        };
1001    Ok((
1002        ast::Function {
1003            arguments,
1004            name_span,
1005            name,
1006            renamed_via_js_name,
1007            ret,
1008            rust_attrs: attrs,
1009            rust_vis: vis,
1010            r#unsafe: sig.unsafety.is_some(),
1011            r#async: sig.asyncness.is_some(),
1012            generate_typescript: opts.skip_typescript().is_none(),
1013            generate_jsdoc: opts.skip_jsdoc().is_none(),
1014            variadic: opts.variadic().is_some(),
1015        },
1016        method_self,
1017    ))
1018}
1019
1020pub(crate) trait MacroParse<Ctx> {
1021    /// Parse the contents of an object into our AST, with a context if necessary.
1022    ///
1023    /// The context is used to have access to the attributes on `#[wasm_bindgen]`, and to allow
1024    /// writing to the output `TokenStream`.
1025    fn macro_parse(self, program: &mut ast::Program, context: Ctx) -> Result<(), Diagnostic>;
1026}
1027
1028impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
1029    fn macro_parse(
1030        self,
1031        program: &mut ast::Program,
1032        (opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
1033    ) -> Result<(), Diagnostic> {
1034        match self {
1035            syn::Item::Fn(mut f) => {
1036                let opts = opts.unwrap_or_default();
1037                if let Some(path) = opts.wasm_bindgen() {
1038                    program.wasm_bindgen = path.clone();
1039                }
1040                if let Some(path) = opts.js_sys() {
1041                    program.js_sys = path.clone();
1042                }
1043                if let Some(path) = opts.wasm_bindgen_futures() {
1044                    program.wasm_bindgen_futures = path.clone();
1045                }
1046
1047                if opts.main().is_some() {
1048                    opts.check_used();
1049                    return main(program, f, tokens);
1050                }
1051
1052                let no_mangle = f
1053                    .attrs
1054                    .iter()
1055                    .enumerate()
1056                    .find(|(_, m)| m.path().is_ident("no_mangle"));
1057                if let Some((i, _)) = no_mangle {
1058                    f.attrs.remove(i);
1059                }
1060                let comments = extract_doc_comments(&f.attrs);
1061                // If the function isn't used for anything other than being exported to JS,
1062                // it'll be unused when not building for the wasm target and produce a
1063                // `dead_code` warning. So, add `#[allow(dead_code)]` before it to avoid that.
1064                tokens.extend(quote::quote! { #[allow(dead_code)] });
1065                f.to_tokens(tokens);
1066                if opts.start().is_some() {
1067                    if !f.sig.generics.params.is_empty() {
1068                        bail_span!(&f.sig.generics, "the start function cannot have generics",);
1069                    }
1070                    if !f.sig.inputs.is_empty() {
1071                        bail_span!(&f.sig.inputs, "the start function cannot have arguments",);
1072                    }
1073                }
1074                let method_kind = ast::MethodKind::Operation(ast::Operation {
1075                    is_static: true,
1076                    kind: operation_kind(&opts),
1077                });
1078                let rust_name = f.sig.ident.clone();
1079                let start = opts.start().is_some();
1080                program.exports.push(ast::Export {
1081                    comments,
1082                    function: f.convert(opts)?,
1083                    js_class: None,
1084                    method_kind,
1085                    method_self: None,
1086                    rust_class: None,
1087                    rust_name,
1088                    start,
1089                    wasm_bindgen: program.wasm_bindgen.clone(),
1090                    wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1091                });
1092            }
1093            syn::Item::Struct(mut s) => {
1094                let opts = opts.unwrap_or_default();
1095                program.structs.push((&mut s).convert((program, opts))?);
1096                s.to_tokens(tokens);
1097            }
1098            syn::Item::Impl(mut i) => {
1099                let opts = opts.unwrap_or_default();
1100                (&mut i).macro_parse(program, opts)?;
1101                i.to_tokens(tokens);
1102            }
1103            syn::Item::ForeignMod(mut f) => {
1104                let opts = match opts {
1105                    Some(opts) => opts,
1106                    None => BindgenAttrs::find(&mut f.attrs)?,
1107                };
1108                f.macro_parse(program, opts)?;
1109            }
1110            syn::Item::Enum(mut e) => {
1111                let opts = match opts {
1112                    Some(opts) => opts,
1113                    None => BindgenAttrs::find(&mut e.attrs)?,
1114                };
1115                e.macro_parse(program, (tokens, opts))?;
1116            }
1117            syn::Item::Const(mut c) => {
1118                let opts = match opts {
1119                    Some(opts) => opts,
1120                    None => BindgenAttrs::find(&mut c.attrs)?,
1121                };
1122                c.macro_parse(program, opts)?;
1123            }
1124            _ => {
1125                bail_span!(
1126                    self,
1127                    "#[wasm_bindgen] can only be applied to a function, \
1128                     struct, enum, impl, or extern block",
1129                );
1130            }
1131        }
1132
1133        Ok(())
1134    }
1135}
1136
1137impl<'a> MacroParse<BindgenAttrs> for &'a mut syn::ItemImpl {
1138    fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1139        if self.defaultness.is_some() {
1140            bail_span!(
1141                self.defaultness,
1142                "#[wasm_bindgen] default impls are not supported"
1143            );
1144        }
1145        if self.unsafety.is_some() {
1146            bail_span!(
1147                self.unsafety,
1148                "#[wasm_bindgen] unsafe impls are not supported"
1149            );
1150        }
1151        if let Some((_, path, _)) = &self.trait_ {
1152            bail_span!(path, "#[wasm_bindgen] trait impls are not supported");
1153        }
1154        if !self.generics.params.is_empty() {
1155            bail_span!(
1156                self.generics,
1157                "#[wasm_bindgen] generic impls aren't supported"
1158            );
1159        }
1160        let name = match get_ty(&self.self_ty) {
1161            syn::Type::Path(syn::TypePath {
1162                qself: None,
1163                ref path,
1164            }) => path,
1165            _ => bail_span!(
1166                self.self_ty,
1167                "unsupported self type in #[wasm_bindgen] impl"
1168            ),
1169        };
1170        let mut errors = Vec::new();
1171        for item in self.items.iter_mut() {
1172            if let Err(e) = prepare_for_impl_recursion(item, name, program, &opts) {
1173                errors.push(e);
1174            }
1175        }
1176        Diagnostic::from_vec(errors)?;
1177        opts.check_used();
1178        Ok(())
1179    }
1180}
1181
1182// Prepare for recursion into an `impl` block. Here we want to attach an
1183// internal attribute, `__wasm_bindgen_class_marker`, with any metadata we need
1184// to pass from the impl to the impl item. Recursive macro expansion will then
1185// expand the `__wasm_bindgen_class_marker` attribute.
1186//
1187// Note that we currently do this because inner items may have things like cfgs
1188// on them, so we want to expand the impl first, let the insides get cfg'd, and
1189// then go for the rest.
1190fn prepare_for_impl_recursion(
1191    item: &mut syn::ImplItem,
1192    class: &syn::Path,
1193    program: &ast::Program,
1194    impl_opts: &BindgenAttrs,
1195) -> Result<(), Diagnostic> {
1196    let method = match item {
1197        syn::ImplItem::Fn(m) => m,
1198        syn::ImplItem::Const(_) => {
1199            bail_span!(
1200                &*item,
1201                "const definitions aren't supported with #[wasm_bindgen]"
1202            );
1203        }
1204        syn::ImplItem::Type(_) => bail_span!(
1205            &*item,
1206            "type definitions in impls aren't supported with #[wasm_bindgen]"
1207        ),
1208        syn::ImplItem::Macro(_) => {
1209            // In theory we want to allow this, but we have no way of expanding
1210            // the macro and then placing our magical attributes on the expanded
1211            // functions. As a result, just disallow it for now to hopefully
1212            // ward off buggy results from this macro.
1213            bail_span!(&*item, "macros in impls aren't supported");
1214        }
1215        syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
1216        other => bail_span!(other, "failed to parse this item as a known item"),
1217    };
1218
1219    let ident = extract_path_ident(class)?;
1220
1221    let js_class = impl_opts
1222        .js_class()
1223        .map(|s| s.0.to_string())
1224        .unwrap_or(ident.to_string());
1225
1226    let wasm_bindgen = &program.wasm_bindgen;
1227    let wasm_bindgen_futures = &program.wasm_bindgen_futures;
1228    method.attrs.insert(
1229        0,
1230        syn::Attribute {
1231            pound_token: Default::default(),
1232            style: syn::AttrStyle::Outer,
1233            bracket_token: Default::default(),
1234            meta: syn::parse_quote! { #wasm_bindgen::prelude::__wasm_bindgen_class_marker(#class = #js_class, wasm_bindgen = #wasm_bindgen, wasm_bindgen_futures = #wasm_bindgen_futures) },
1235        },
1236    );
1237
1238    Ok(())
1239}
1240
1241impl<'a> MacroParse<&ClassMarker> for &'a mut syn::ImplItemFn {
1242    fn macro_parse(
1243        self,
1244        program: &mut ast::Program,
1245        ClassMarker {
1246            class,
1247            js_class,
1248            wasm_bindgen,
1249            wasm_bindgen_futures,
1250        }: &ClassMarker,
1251    ) -> Result<(), Diagnostic> {
1252        program.wasm_bindgen = wasm_bindgen.clone();
1253        program.wasm_bindgen_futures = wasm_bindgen_futures.clone();
1254
1255        match self.vis {
1256            syn::Visibility::Public(_) => {}
1257            _ => return Ok(()),
1258        }
1259        if self.defaultness.is_some() {
1260            panic!("default methods are not supported");
1261        }
1262        if self.sig.constness.is_some() {
1263            bail_span!(
1264                self.sig.constness,
1265                "can only #[wasm_bindgen] non-const functions",
1266            );
1267        }
1268
1269        let opts = BindgenAttrs::find(&mut self.attrs)?;
1270        let comments = extract_doc_comments(&self.attrs);
1271        let (function, method_self) = function_from_decl(
1272            &self.sig.ident,
1273            &opts,
1274            self.sig.clone(),
1275            self.attrs.clone(),
1276            self.vis.clone(),
1277            true,
1278            Some(class),
1279            true,
1280            None,
1281        )?;
1282        let method_kind = if opts.constructor().is_some() {
1283            ast::MethodKind::Constructor
1284        } else {
1285            let is_static = method_self.is_none();
1286            let kind = operation_kind(&opts);
1287            ast::MethodKind::Operation(ast::Operation { is_static, kind })
1288        };
1289        program.exports.push(ast::Export {
1290            comments,
1291            function,
1292            js_class: Some(js_class.to_string()),
1293            method_kind,
1294            method_self,
1295            rust_class: Some(class.clone()),
1296            rust_name: self.sig.ident.clone(),
1297            start: false,
1298            wasm_bindgen: program.wasm_bindgen.clone(),
1299            wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1300        });
1301        opts.check_used();
1302        Ok(())
1303    }
1304}
1305
1306fn string_enum(enum_: syn::ItemEnum, program: &mut ast::Program) -> Result<(), Diagnostic> {
1307    let mut variants = vec![];
1308    let mut variant_values = vec![];
1309
1310    for v in enum_.variants.iter() {
1311        match v.fields {
1312            syn::Fields::Unit => (),
1313            _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"),
1314        }
1315
1316        let (_, expr) = match &v.discriminant {
1317            Some(pair) => pair,
1318            None => {
1319                bail_span!(v, "all variants must have a value");
1320            }
1321        };
1322        match get_expr(expr) {
1323            syn::Expr::Lit(syn::ExprLit {
1324                attrs: _,
1325                lit: syn::Lit::Str(str_lit),
1326            }) => {
1327                variants.push(v.ident.clone());
1328                variant_values.push(str_lit.value());
1329            }
1330            expr => bail_span!(
1331                expr,
1332                "enums with #[wasm_bindgen] cannot mix string and non-string values",
1333            ),
1334        }
1335    }
1336
1337    program.imports.push(ast::Import {
1338        module: None,
1339        js_namespace: None,
1340        kind: ast::ImportKind::Enum(ast::StringEnum {
1341            vis: enum_.vis,
1342            name: enum_.ident,
1343            variants,
1344            variant_values,
1345            rust_attrs: enum_.attrs,
1346            wasm_bindgen: program.wasm_bindgen.clone(),
1347        }),
1348    });
1349
1350    Ok(())
1351}
1352
1353impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
1354    fn macro_parse(
1355        self,
1356        program: &mut ast::Program,
1357        (tokens, opts): (&'a mut TokenStream, BindgenAttrs),
1358    ) -> Result<(), Diagnostic> {
1359        if self.variants.is_empty() {
1360            bail_span!(self, "cannot export empty enums to JS");
1361        }
1362        let generate_typescript = opts.skip_typescript().is_none();
1363
1364        // Check if the first value is a string literal
1365        if let Some((_, expr)) = &self.variants[0].discriminant {
1366            if let syn::Expr::Lit(syn::ExprLit {
1367                lit: syn::Lit::Str(_),
1368                ..
1369            }) = get_expr(expr)
1370            {
1371                opts.check_used();
1372                return string_enum(self, program);
1373            }
1374        }
1375        let js_name = opts
1376            .js_name()
1377            .map(|s| s.0)
1378            .map_or_else(|| self.ident.to_string(), |s| s.to_string());
1379        opts.check_used();
1380
1381        let has_discriminant = self.variants[0].discriminant.is_some();
1382
1383        match self.vis {
1384            syn::Visibility::Public(_) => {}
1385            _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
1386        }
1387
1388        let variants = self
1389            .variants
1390            .iter()
1391            .enumerate()
1392            .map(|(i, v)| {
1393                match v.fields {
1394                    syn::Fields::Unit => (),
1395                    _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"),
1396                }
1397
1398                // Require that everything either has a discriminant or doesn't.
1399                // We don't really want to get in the business of emulating how
1400                // rustc assigns values to enums.
1401                if v.discriminant.is_some() != has_discriminant {
1402                    bail_span!(
1403                        v,
1404                        "must either annotate discriminant of all variants or none"
1405                    );
1406                }
1407
1408                let value = match &v.discriminant {
1409                    Some((_, expr)) => match get_expr(expr) {
1410                        syn::Expr::Lit(syn::ExprLit {
1411                            attrs: _,
1412                            lit: syn::Lit::Int(int_lit),
1413                        }) => match int_lit.base10_digits().parse::<u32>() {
1414                            Ok(v) => v,
1415                            Err(_) => {
1416                                bail_span!(
1417                                    int_lit,
1418                                    "enums with #[wasm_bindgen] can only support \
1419                                 numbers that can be represented as u32"
1420                                );
1421                            }
1422                        },
1423                        expr => bail_span!(
1424                            expr,
1425                            "enums with #[wasm_bindgen] may only have \
1426                             number literal values",
1427                        ),
1428                    },
1429                    None => i as u32,
1430                };
1431
1432                let comments = extract_doc_comments(&v.attrs);
1433                Ok(ast::Variant {
1434                    name: v.ident.clone(),
1435                    value,
1436                    comments,
1437                })
1438            })
1439            .collect::<Result<Vec<_>, Diagnostic>>()?;
1440
1441        let mut values = variants.iter().map(|v| v.value).collect::<Vec<_>>();
1442        values.sort();
1443        let hole = values
1444            .windows(2)
1445            .find_map(|window| {
1446                if window[0] + 1 != window[1] {
1447                    Some(window[0] + 1)
1448                } else {
1449                    None
1450                }
1451            })
1452            .unwrap_or(*values.last().unwrap() + 1);
1453        for value in values {
1454            assert!(hole != value);
1455        }
1456
1457        let comments = extract_doc_comments(&self.attrs);
1458
1459        self.to_tokens(tokens);
1460
1461        program.enums.push(ast::Enum {
1462            rust_name: self.ident,
1463            js_name,
1464            variants,
1465            comments,
1466            hole,
1467            generate_typescript,
1468            wasm_bindgen: program.wasm_bindgen.clone(),
1469        });
1470        Ok(())
1471    }
1472}
1473
1474impl MacroParse<BindgenAttrs> for syn::ItemConst {
1475    fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1476        // Shortcut
1477        if opts.typescript_custom_section().is_none() {
1478            bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)].");
1479        }
1480
1481        let typescript_custom_section = match get_expr(&self.expr) {
1482            syn::Expr::Lit(syn::ExprLit {
1483                lit: syn::Lit::Str(litstr),
1484                ..
1485            }) => ast::LitOrExpr::Lit(litstr.value()),
1486            expr => ast::LitOrExpr::Expr(expr.clone()),
1487        };
1488
1489        program
1490            .typescript_custom_sections
1491            .push(typescript_custom_section);
1492
1493        opts.check_used();
1494
1495        Ok(())
1496    }
1497}
1498
1499impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
1500    fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1501        let mut errors = Vec::new();
1502        if let Some(other) = self.abi.name.filter(|l| l.value() != "C") {
1503            errors.push(err_span!(
1504                other,
1505                "only foreign mods with the `C` ABI are allowed"
1506            ));
1507        }
1508        let js_namespace = opts.js_namespace().map(|(s, _)| s.to_owned());
1509        let module = module_from_opts(program, &opts)
1510            .map_err(|e| errors.push(e))
1511            .unwrap_or_default();
1512        for item in self.items.into_iter() {
1513            let ctx = ForeignItemCtx {
1514                module: module.clone(),
1515                js_namespace: js_namespace.clone(),
1516            };
1517            if let Err(e) = item.macro_parse(program, ctx) {
1518                errors.push(e);
1519            }
1520        }
1521        Diagnostic::from_vec(errors)?;
1522        opts.check_used();
1523        Ok(())
1524    }
1525}
1526
1527struct ForeignItemCtx {
1528    module: Option<ast::ImportModule>,
1529    js_namespace: Option<Vec<String>>,
1530}
1531
1532impl MacroParse<ForeignItemCtx> for syn::ForeignItem {
1533    fn macro_parse(
1534        mut self,
1535        program: &mut ast::Program,
1536        ctx: ForeignItemCtx,
1537    ) -> Result<(), Diagnostic> {
1538        let item_opts = {
1539            let attrs = match self {
1540                syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
1541                syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
1542                syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
1543                syn::ForeignItem::Verbatim(v) => {
1544                    let mut item: syn::ItemStatic =
1545                        syn::parse(v.into()).expect("only foreign functions/types allowed for now");
1546                    let item_opts = BindgenAttrs::find(&mut item.attrs)?;
1547                    let kind = item.convert((program, item_opts, &ctx.module))?;
1548
1549                    program.imports.push(ast::Import {
1550                        module: None,
1551                        js_namespace: None,
1552                        kind,
1553                    });
1554
1555                    return Ok(());
1556                }
1557                _ => panic!("only foreign functions/types allowed for now"),
1558            };
1559            BindgenAttrs::find(attrs)?
1560        };
1561
1562        let js_namespace = item_opts
1563            .js_namespace()
1564            .map(|(s, _)| s.to_owned())
1565            .or(ctx.js_namespace);
1566        let module = ctx.module;
1567
1568        let kind = match self {
1569            syn::ForeignItem::Fn(f) => f.convert((program, item_opts, &module))?,
1570            syn::ForeignItem::Type(t) => t.convert((program, item_opts))?,
1571            syn::ForeignItem::Static(s) => s.convert((program, item_opts, &module))?,
1572            _ => panic!("only foreign functions/types allowed for now"),
1573        };
1574
1575        program.imports.push(ast::Import {
1576            module,
1577            js_namespace,
1578            kind,
1579        });
1580
1581        Ok(())
1582    }
1583}
1584
1585pub fn module_from_opts(
1586    program: &mut ast::Program,
1587    opts: &BindgenAttrs,
1588) -> Result<Option<ast::ImportModule>, Diagnostic> {
1589    if let Some(path) = opts.wasm_bindgen() {
1590        program.wasm_bindgen = path.clone();
1591    }
1592
1593    if let Some(path) = opts.js_sys() {
1594        program.js_sys = path.clone();
1595    }
1596
1597    if let Some(path) = opts.wasm_bindgen_futures() {
1598        program.wasm_bindgen_futures = path.clone();
1599    }
1600
1601    let mut errors = Vec::new();
1602    let module = if let Some((name, span)) = opts.module() {
1603        if opts.inline_js().is_some() {
1604            let msg = "cannot specify both `module` and `inline_js`";
1605            errors.push(Diagnostic::span_error(span, msg));
1606        }
1607        if opts.raw_module().is_some() {
1608            let msg = "cannot specify both `module` and `raw_module`";
1609            errors.push(Diagnostic::span_error(span, msg));
1610        }
1611        Some(ast::ImportModule::Named(name.to_string(), span))
1612    } else if let Some((name, span)) = opts.raw_module() {
1613        if opts.inline_js().is_some() {
1614            let msg = "cannot specify both `raw_module` and `inline_js`";
1615            errors.push(Diagnostic::span_error(span, msg));
1616        }
1617        Some(ast::ImportModule::RawNamed(name.to_string(), span))
1618    } else if let Some((js, span)) = opts.inline_js() {
1619        let i = program.inline_js.len();
1620        program.inline_js.push(js.to_string());
1621        Some(ast::ImportModule::Inline(i, span))
1622    } else {
1623        None
1624    };
1625    Diagnostic::from_vec(errors)?;
1626    Ok(module)
1627}
1628
1629/// Get the first type parameter of a generic type, errors on incorrect input.
1630fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, Diagnostic> {
1631    let t = match ty {
1632        Some(t) => t,
1633        None => return Ok(None),
1634    };
1635    let path = match *get_ty(t) {
1636        syn::Type::Path(syn::TypePath {
1637            qself: None,
1638            ref path,
1639        }) => path,
1640        _ => bail_span!(t, "must be Result<...>"),
1641    };
1642    let seg = path
1643        .segments
1644        .last()
1645        .ok_or_else(|| err_span!(t, "must have at least one segment"))?;
1646    let generics = match seg.arguments {
1647        syn::PathArguments::AngleBracketed(ref t) => t,
1648        _ => bail_span!(t, "must be Result<...>"),
1649    };
1650    let generic = generics
1651        .args
1652        .first()
1653        .ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?;
1654    let ty = match generic {
1655        syn::GenericArgument::Type(t) => t,
1656        other => bail_span!(other, "must be a type parameter"),
1657    };
1658    match get_ty(ty) {
1659        syn::Type::Tuple(t) if t.elems.is_empty() => return Ok(None),
1660        _ => {}
1661    }
1662    Ok(Some(ty.clone()))
1663}
1664
1665/// Extract the documentation comments from a Vec of attributes
1666fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
1667    attrs
1668        .iter()
1669        .filter_map(|a| {
1670            // if the path segments include an ident of "doc" we know this
1671            // this is a doc comment
1672            if a.path().segments.iter().any(|s| s.ident == "doc") {
1673                let tokens = match &a.meta {
1674                    syn::Meta::Path(_) => None,
1675                    syn::Meta::List(list) => Some(list.tokens.clone()),
1676                    syn::Meta::NameValue(name_value) => Some(name_value.value.to_token_stream()),
1677                };
1678
1679                Some(
1680                    // We want to filter out any Puncts so just grab the Literals
1681                    tokens.into_iter().flatten().filter_map(|t| match t {
1682                        TokenTree::Literal(lit) => {
1683                            let quoted = lit.to_string();
1684                            Some(try_unescape(&quoted).unwrap_or(quoted))
1685                        }
1686                        _ => None,
1687                    }),
1688                )
1689            } else {
1690                None
1691            }
1692        })
1693        //Fold up the [[String]] iter we created into Vec<String>
1694        .fold(vec![], |mut acc, a| {
1695            acc.extend(a);
1696            acc
1697        })
1698}
1699
1700// Unescapes a quoted string. char::escape_debug() was used to escape the text.
1701fn try_unescape(mut s: &str) -> Option<String> {
1702    s = s.strip_prefix('"').unwrap_or(s);
1703    s = s.strip_suffix('"').unwrap_or(s);
1704    let mut result = String::with_capacity(s.len());
1705    let mut chars = s.chars();
1706    while let Some(c) = chars.next() {
1707        if c == '\\' {
1708            let c = chars.next()?;
1709            match c {
1710                't' => result.push('\t'),
1711                'r' => result.push('\r'),
1712                'n' => result.push('\n'),
1713                '\\' | '\'' | '"' => result.push(c),
1714                'u' => {
1715                    if chars.next() != Some('{') {
1716                        return None;
1717                    }
1718                    let (c, next) = unescape_unicode(&mut chars)?;
1719                    result.push(c);
1720                    if next != '}' {
1721                        return None;
1722                    }
1723                }
1724                _ => return None,
1725            }
1726        } else {
1727            result.push(c);
1728        }
1729    }
1730    Some(result)
1731}
1732
1733fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
1734    let mut value = 0;
1735    for (i, c) in chars.enumerate() {
1736        match (i, c.to_digit(16)) {
1737            (0..=5, Some(num)) => value = (value << 4) | num,
1738            (1.., None) => return Some((char::from_u32(value)?, c)),
1739            _ => break,
1740        }
1741    }
1742    None
1743}
1744
1745/// Check there are no lifetimes on the function.
1746fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> {
1747    struct Walk {
1748        diagnostics: Vec<Diagnostic>,
1749    }
1750
1751    impl<'ast> syn::visit::Visit<'ast> for Walk {
1752        fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
1753            self.diagnostics.push(err_span!(
1754                i,
1755                "it is currently not sound to use lifetimes in function \
1756                 signatures"
1757            ));
1758        }
1759    }
1760    let mut walk = Walk {
1761        diagnostics: Vec::new(),
1762    };
1763    syn::visit::Visit::visit_signature(&mut walk, sig);
1764    Diagnostic::from_vec(walk.diagnostics)
1765}
1766
1767/// Extracts the last ident from the path
1768fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> {
1769    for segment in path.segments.iter() {
1770        match segment.arguments {
1771            syn::PathArguments::None => {}
1772            _ => bail_span!(path, "paths with type parameters are not supported yet"),
1773        }
1774    }
1775
1776    match path.segments.last() {
1777        Some(value) => Ok(value.ident.clone()),
1778        None => {
1779            bail_span!(path, "empty idents are not supported");
1780        }
1781    }
1782}
1783
1784pub fn reset_attrs_used() {
1785    ATTRS.with(|state| {
1786        state.parsed.set(0);
1787        state.checks.set(0);
1788        state.unused_attrs.borrow_mut().clear();
1789    })
1790}
1791
1792pub fn check_unused_attrs(tokens: &mut TokenStream) {
1793    ATTRS.with(|state| {
1794        assert_eq!(state.parsed.get(), state.checks.get());
1795        let unused_attrs = &*state.unused_attrs.borrow();
1796        if !unused_attrs.is_empty() {
1797            tokens.extend(quote::quote! {
1798                // Anonymous scope to prevent name clashes.
1799                const _: () = {
1800                    #(let #unused_attrs: ();)*
1801                };
1802            });
1803        }
1804    })
1805}
1806
1807fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind {
1808    let mut operation_kind = ast::OperationKind::Regular;
1809    if let Some(g) = opts.getter() {
1810        operation_kind = ast::OperationKind::Getter(g.clone());
1811    }
1812    if let Some(s) = opts.setter() {
1813        operation_kind = ast::OperationKind::Setter(s.clone());
1814    }
1815    if opts.indexing_getter().is_some() {
1816        operation_kind = ast::OperationKind::IndexingGetter;
1817    }
1818    if opts.indexing_setter().is_some() {
1819        operation_kind = ast::OperationKind::IndexingSetter;
1820    }
1821    if opts.indexing_deleter().is_some() {
1822        operation_kind = ast::OperationKind::IndexingDeleter;
1823    }
1824    operation_kind
1825}
1826
1827pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
1828    let mut program = ast::Program::default();
1829    let module = module_from_opts(&mut program, &opts)?.ok_or_else(|| {
1830        Diagnostic::span_error(Span::call_site(), "`link_to!` requires a module.")
1831    })?;
1832    if let ast::ImportModule::Named(p, s) | ast::ImportModule::RawNamed(p, s) = &module {
1833        if !p.starts_with("./") && !p.starts_with("../") && !p.starts_with('/') {
1834            return Err(Diagnostic::span_error(
1835                *s,
1836                "`link_to!` does not support module paths.",
1837            ));
1838        }
1839    }
1840    opts.enforce_used()?;
1841    program.linked_modules.push(module);
1842    Ok(ast::LinkToModule(program))
1843}
1844
1845fn main(program: &ast::Program, mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
1846    if f.sig.ident != "main" {
1847        bail_span!(&f.sig.ident, "the main function has to be called main");
1848    }
1849    if let Some(constness) = f.sig.constness {
1850        bail_span!(&constness, "the main function cannot be const");
1851    }
1852    if !f.sig.generics.params.is_empty() {
1853        bail_span!(&f.sig.generics, "the main function cannot have generics");
1854    }
1855    if !f.sig.inputs.is_empty() {
1856        bail_span!(&f.sig.inputs, "the main function cannot have arguments");
1857    }
1858
1859    let r#return = f.sig.output;
1860    f.sig.output = ReturnType::Default;
1861    let body = f.block;
1862
1863    let wasm_bindgen = &program.wasm_bindgen;
1864    let wasm_bindgen_futures = &program.wasm_bindgen_futures;
1865
1866    if f.sig.asyncness.take().is_some() {
1867        f.block = Box::new(
1868            syn::parse2(quote::quote! {
1869                {
1870                    async fn __wasm_bindgen_generated_main() #r#return #body
1871                    #wasm_bindgen_futures::spawn_local(
1872                        async move {
1873                            use #wasm_bindgen::__rt::Main;
1874                            let __ret = __wasm_bindgen_generated_main();
1875                            (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main()
1876                        },
1877                    )
1878                }
1879            })
1880            .unwrap(),
1881        );
1882    } else {
1883        f.block = Box::new(
1884            syn::parse2(quote::quote! {
1885                {
1886                    fn __wasm_bindgen_generated_main() #r#return #body
1887                    use #wasm_bindgen::__rt::Main;
1888                    let __ret = __wasm_bindgen_generated_main();
1889                    (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main()
1890                }
1891            })
1892            .unwrap(),
1893        );
1894    }
1895
1896    f.to_tokens(tokens);
1897
1898    Ok(())
1899}
1900
1901#[cfg(test)]
1902mod tests {
1903    #[test]
1904    fn test_try_unescape() {
1905        use super::try_unescape;
1906        assert_eq!(try_unescape("hello").unwrap(), "hello");
1907        assert_eq!(try_unescape("\"hello").unwrap(), "hello");
1908        assert_eq!(try_unescape("hello\"").unwrap(), "hello");
1909        assert_eq!(try_unescape("\"hello\"").unwrap(), "hello");
1910        assert_eq!(try_unescape("hello\\\\").unwrap(), "hello\\");
1911        assert_eq!(try_unescape("hello\\n").unwrap(), "hello\n");
1912        assert_eq!(try_unescape("hello\\u"), None);
1913        assert_eq!(try_unescape("hello\\u{"), None);
1914        assert_eq!(try_unescape("hello\\u{}"), None);
1915        assert_eq!(try_unescape("hello\\u{0}").unwrap(), "hello\0");
1916        assert_eq!(try_unescape("hello\\u{000000}").unwrap(), "hello\0");
1917        assert_eq!(try_unescape("hello\\u{0000000}"), None);
1918    }
1919}