wasm_bindgen_macro_support/
parser.rs

1use std::cell::{Cell, RefCell};
2use std::collections::HashMap;
3use std::str::Chars;
4use std::{char, iter};
5
6use ast::OperationKind;
7use proc_macro2::{Ident, Span, TokenStream, TokenTree};
8use quote::ToTokens;
9use syn::ext::IdentExt;
10use syn::parse::{Parse, ParseStream, Result as SynResult};
11use syn::spanned::Spanned;
12use syn::visit_mut::VisitMut;
13use syn::Token;
14use syn::{ItemFn, Lit, MacroDelimiter, ReturnType};
15use wasm_bindgen_shared::identifier::is_valid_ident;
16
17use crate::ast::{self, ThreadLocal};
18use crate::hash::ShortHash;
19use crate::ClassMarker;
20use crate::Diagnostic;
21
22thread_local!(static ATTRS: AttributeParseState = Default::default());
23
24/// Javascript keywords.
25///
26/// Note that some of these keywords are only reserved in strict mode. Since we
27/// generate strict mode JS code, we treat all of these as reserved.
28///
29/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
30const JS_KEYWORDS: [&str; 47] = [
31    "arguments",
32    "break",
33    "case",
34    "catch",
35    "class",
36    "const",
37    "continue",
38    "debugger",
39    "default",
40    "delete",
41    "do",
42    "else",
43    "enum",
44    "eval",
45    "export",
46    "extends",
47    "false",
48    "finally",
49    "for",
50    "function",
51    "if",
52    "implements",
53    "import",
54    "in",
55    "instanceof",
56    "interface",
57    "let",
58    "new",
59    "null",
60    "package",
61    "private",
62    "protected",
63    "public",
64    "return",
65    "static",
66    "super",
67    "switch",
68    "this",
69    "throw",
70    "true",
71    "try",
72    "typeof",
73    "var",
74    "void",
75    "while",
76    "with",
77    "yield",
78];
79
80/// Javascript keywords that behave like values in that they can be called like
81/// functions or have properties accessed on them.
82///
83/// Naturally, this list is a subset of `JS_KEYWORDS`.
84const VALUE_LIKE_JS_KEYWORDS: [&str; 7] = [
85    "eval",   // eval is a function-like keyword, so e.g. `eval(...)` is valid
86    "false",  // false resolves to a boolean value, so e.g. `false.toString()` is valid
87    "import", // import.meta and import()
88    "new",    // new.target
89    "super", // super can be used for a function call (`super(...)`) or property lookup (`super.prop`)
90    "this",  // this obviously can be used as a value
91    "true",  // true resolves to a boolean value, so e.g. `false.toString()` is valid
92];
93
94/// Returns whether the given string is a JS keyword.
95fn is_js_keyword(keyword: &str) -> bool {
96    JS_KEYWORDS.contains(&keyword)
97}
98/// Returns whether the given string is a JS keyword that does NOT behave like
99/// a value.
100///
101/// Value-like keywords can be called like functions or have properties
102/// accessed, which makes it possible to use them in imports. In general,
103/// imports should use this function to check for reserved keywords.
104fn is_non_value_js_keyword(keyword: &str) -> bool {
105    JS_KEYWORDS.contains(&keyword) && !VALUE_LIKE_JS_KEYWORDS.contains(&keyword)
106}
107
108/// Return an [`Err`] if the given string contains a comment close syntax (`*/``).
109fn check_js_comment_close(str: &str, span: Span) -> Result<(), Diagnostic> {
110    if str.contains("*/") {
111        Err(Diagnostic::span_error(
112            span,
113            "contains comment close syntax",
114        ))
115    } else {
116        Ok(())
117    }
118}
119
120/// Return an [`Err`] if the given string is a JS keyword or contains a comment close syntax (`*/``).
121fn check_invalid_type(str: &str, span: Span) -> Result<(), Diagnostic> {
122    if is_js_keyword(str) {
123        return Err(Diagnostic::span_error(span, "collides with JS keyword"));
124    }
125    check_js_comment_close(str, span)?;
126    Ok(())
127}
128
129#[derive(Default)]
130struct AttributeParseState {
131    parsed: Cell<usize>,
132    checks: Cell<usize>,
133    unused_attrs: RefCell<Vec<UnusedState>>,
134}
135
136struct UnusedState {
137    error: bool,
138    ident: Ident,
139}
140
141/// Parsed attributes from a `#[wasm_bindgen(..)]`.
142#[cfg_attr(feature = "extra-traits", derive(Debug))]
143pub struct BindgenAttrs {
144    /// List of parsed attributes
145    pub attrs: Vec<(Cell<bool>, BindgenAttr)>,
146}
147
148/// A list of identifiers representing the namespace prefix of an imported
149/// function or constant.
150///
151/// The list is guaranteed to be non-empty and not start with a JS keyword.
152#[cfg_attr(feature = "extra-traits", derive(Debug))]
153#[derive(Clone)]
154pub struct JsNamespace(Vec<String>);
155
156macro_rules! attrgen {
157    ($mac:ident) => {
158        $mac! {
159            (catch, false, Catch(Span)),
160            (constructor, false, Constructor(Span)),
161            (method, false, Method(Span)),
162            (static_method_of, false, StaticMethodOf(Span, Ident)),
163            (js_namespace, false, JsNamespace(Span, JsNamespace, Vec<Span>)),
164            (module, true, Module(Span, String, Span)),
165            (raw_module, true, RawModule(Span, String, Span)),
166            (inline_js, true, InlineJs(Span, String, Span)),
167            (getter, false, Getter(Span, Option<String>)),
168            (setter, false, Setter(Span, Option<String>)),
169            (indexing_getter, false, IndexingGetter(Span)),
170            (indexing_setter, false, IndexingSetter(Span)),
171            (indexing_deleter, false, IndexingDeleter(Span)),
172            (structural, false, Structural(Span)),
173            (r#final, false, Final(Span)),
174            (readonly, false, Readonly(Span)),
175            (js_name, false, JsName(Span, String, Span)),
176            (js_class, false, JsClass(Span, String, Span)),
177            (inspectable, false, Inspectable(Span)),
178            (is_type_of, false, IsTypeOf(Span, syn::Expr)),
179            (extends, false, Extends(Span, syn::Path)),
180            (no_deref, false, NoDeref(Span)),
181            (vendor_prefix, false, VendorPrefix(Span, Ident)),
182            (variadic, false, Variadic(Span)),
183            (typescript_custom_section, false, TypescriptCustomSection(Span)),
184            (skip_typescript, false, SkipTypescript(Span)),
185            (skip_jsdoc, false, SkipJsDoc(Span)),
186            (main, false, Main(Span)),
187            (start, false, Start(Span)),
188            (wasm_bindgen, false, WasmBindgen(Span, syn::Path)),
189            (js_sys, false, JsSys(Span, syn::Path)),
190            (wasm_bindgen_futures, false, WasmBindgenFutures(Span, syn::Path)),
191            (skip, false, Skip(Span)),
192            (typescript_type, false, TypeScriptType(Span, String, Span)),
193            (getter_with_clone, false, GetterWithClone(Span)),
194            (static_string, false, StaticString(Span)),
195            (thread_local, false, ThreadLocal(Span)),
196            (thread_local_v2, false, ThreadLocalV2(Span)),
197            (unchecked_return_type, true, ReturnType(Span, String, Span)),
198            (return_description, true, ReturnDesc(Span, String, Span)),
199            (unchecked_param_type, true, ParamType(Span, String, Span)),
200            (param_description, true, ParamDesc(Span, String, Span)),
201
202            // For testing purposes only.
203            (assert_no_shim, false, AssertNoShim(Span)),
204        }
205    };
206}
207
208macro_rules! methods {
209    ($(($name:ident, $invalid_unused:literal, $variant:ident($($contents:tt)*)),)*) => {
210        $(methods!(@method $name, $variant($($contents)*));)*
211
212        fn enforce_used(self) -> Result<(), Diagnostic> {
213            // Account for the fact this method was called
214            ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
215
216            let mut errors = Vec::new();
217            for (used, attr) in self.attrs.iter() {
218                if used.get() {
219                    continue
220                }
221                let span = match attr {
222                    $(BindgenAttr::$variant(span, ..) => span,)*
223                };
224                errors.push(Diagnostic::span_error(*span, "unused wasm_bindgen attribute"));
225            }
226            Diagnostic::from_vec(errors)
227        }
228
229        fn check_used(self) {
230            // Account for the fact this method was called
231            ATTRS.with(|state| {
232                state.checks.set(state.checks.get() + 1);
233
234                state.unused_attrs.borrow_mut().extend(
235                    self.attrs
236                    .iter()
237                    .filter_map(|(used, attr)| if used.get() { None } else { Some(attr) })
238                    .map(|attr| {
239                        match attr {
240                            $(BindgenAttr::$variant(span, ..) => {
241                                UnusedState {
242                                    error: $invalid_unused,
243                                    ident: syn::parse_quote_spanned!(*span => $name)
244                                }
245                            })*
246                        }
247                    })
248                );
249            });
250        }
251    };
252
253    (@method $name:ident, $variant:ident(Span, String, Span)) => {
254        pub(crate) fn $name(&self) -> Option<(&str, Span)> {
255            self.attrs
256                .iter()
257                .find_map(|a| match &a.1 {
258                    BindgenAttr::$variant(_, s, span) => {
259                        a.0.set(true);
260                        Some((&s[..], *span))
261                    }
262                    _ => None,
263                })
264        }
265    };
266
267    (@method $name:ident, $variant:ident(Span, JsNamespace, Vec<Span>)) => {
268        pub(crate) fn $name(&self) -> Option<(JsNamespace, &[Span])> {
269            self.attrs
270                .iter()
271                .find_map(|a| match &a.1 {
272                    BindgenAttr::$variant(_, ss, spans) => {
273                        a.0.set(true);
274                        Some((ss.clone(), &spans[..]))
275                    }
276                    _ => None,
277                })
278        }
279    };
280
281    (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
282        #[allow(unused)]
283        pub(crate) fn $name(&self) -> Option<&$($other)*> {
284            self.attrs
285                .iter()
286                .find_map(|a| match &a.1 {
287                    BindgenAttr::$variant(_, s) => {
288                        a.0.set(true);
289                        Some(s)
290                    }
291                    _ => None,
292                })
293        }
294    };
295
296    (@method $name:ident, $variant:ident($($other:tt)*)) => {
297        #[allow(unused)]
298        pub(crate) fn $name(&self) -> Option<&$($other)*> {
299            self.attrs
300                .iter()
301                .find_map(|a| match &a.1 {
302                    BindgenAttr::$variant(s) => {
303                        a.0.set(true);
304                        Some(s)
305                    }
306                    _ => None,
307                })
308        }
309    };
310}
311
312impl BindgenAttrs {
313    /// Find and parse the wasm_bindgen attributes.
314    fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
315        let mut ret = BindgenAttrs::default();
316        loop {
317            let pos = attrs
318                .iter()
319                .enumerate()
320                .find(|&(_, m)| m.path().segments[0].ident == "wasm_bindgen")
321                .map(|a| a.0);
322            let pos = match pos {
323                Some(i) => i,
324                None => return Ok(ret),
325            };
326            let attr = attrs.remove(pos);
327            let tokens = match attr.meta {
328                syn::Meta::Path(_) => continue,
329                syn::Meta::List(syn::MetaList {
330                    delimiter: MacroDelimiter::Paren(_),
331                    tokens,
332                    ..
333                }) => tokens,
334                syn::Meta::List(_) | syn::Meta::NameValue(_) => {
335                    bail_span!(attr, "malformed #[wasm_bindgen] attribute")
336                }
337            };
338            let mut attrs: BindgenAttrs = syn::parse2(tokens)?;
339            ret.attrs.append(&mut attrs.attrs);
340            attrs.check_used();
341        }
342    }
343
344    fn get_thread_local(&self) -> Result<Option<ThreadLocal>, Diagnostic> {
345        let mut thread_local = self.thread_local_v2().map(|_| ThreadLocal::V2);
346
347        if let Some(span) = self.thread_local() {
348            if thread_local.is_some() {
349                return Err(Diagnostic::span_error(
350                    *span,
351                    "`thread_local` can't be used with `thread_local_v2`",
352                ));
353            } else {
354                thread_local = Some(ThreadLocal::V1)
355            }
356        }
357
358        Ok(thread_local)
359    }
360
361    attrgen!(methods);
362}
363
364impl Default for BindgenAttrs {
365    fn default() -> BindgenAttrs {
366        // Add 1 to the list of parsed attribute sets. We'll use this counter to
367        // sanity check that we call `check_used` an appropriate number of
368        // times.
369        ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1));
370        BindgenAttrs { attrs: Vec::new() }
371    }
372}
373
374impl Parse for BindgenAttrs {
375    fn parse(input: ParseStream) -> SynResult<Self> {
376        let mut attrs = BindgenAttrs::default();
377        if input.is_empty() {
378            return Ok(attrs);
379        }
380
381        let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
382        attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect();
383        Ok(attrs)
384    }
385}
386
387macro_rules! gen_bindgen_attr {
388    ($(($method:ident, $_:literal, $($variants:tt)*),)*) => {
389        /// The possible attributes in the `#[wasm_bindgen]`.
390        #[cfg_attr(feature = "extra-traits", derive(Debug))]
391        pub enum BindgenAttr {
392            $($($variants)*,)*
393        }
394    }
395}
396attrgen!(gen_bindgen_attr);
397
398impl Parse for BindgenAttr {
399    fn parse(input: ParseStream) -> SynResult<Self> {
400        let original = input.fork();
401        let attr: AnyIdent = input.parse()?;
402        let attr = attr.0;
403        let attr_span = attr.span();
404        let attr_string = attr.to_string();
405        let raw_attr_string = format!("r#{}", attr_string);
406
407        macro_rules! parsers {
408            ($(($name:ident, $_:literal, $($contents:tt)*),)*) => {
409                $(
410                    if attr_string == stringify!($name) || raw_attr_string == stringify!($name) {
411                        parsers!(
412                            @parser
413                            $($contents)*
414                        );
415                    }
416                )*
417            };
418
419            (@parser $variant:ident(Span)) => ({
420                return Ok(BindgenAttr::$variant(attr_span));
421            });
422
423            (@parser $variant:ident(Span, Ident)) => ({
424                input.parse::<Token![=]>()?;
425                let ident = input.parse::<AnyIdent>()?.0;
426                return Ok(BindgenAttr::$variant(attr_span, ident))
427            });
428
429            (@parser $variant:ident(Span, Option<String>)) => ({
430                if input.parse::<Token![=]>().is_ok() {
431                    if input.peek(syn::LitStr) {
432                        let litstr = input.parse::<syn::LitStr>()?;
433                        return Ok(BindgenAttr::$variant(attr_span, Some(litstr.value())))
434                    }
435
436                    let ident = input.parse::<AnyIdent>()?.0;
437                    return Ok(BindgenAttr::$variant(attr_span, Some(ident.to_string())))
438                } else {
439                    return Ok(BindgenAttr::$variant(attr_span, None));
440                }
441            });
442
443            (@parser $variant:ident(Span, syn::Path)) => ({
444                input.parse::<Token![=]>()?;
445                return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
446            });
447
448            (@parser $variant:ident(Span, syn::Expr)) => ({
449                input.parse::<Token![=]>()?;
450                return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
451            });
452
453            (@parser $variant:ident(Span, String, Span)) => ({
454                input.parse::<Token![=]>()?;
455                let (val, span) = match input.parse::<syn::LitStr>() {
456                    Ok(str) => (str.value(), str.span()),
457                    Err(_) => {
458                        let ident = input.parse::<AnyIdent>()?.0;
459                        (ident.to_string(), ident.span())
460                    }
461                };
462                return Ok(BindgenAttr::$variant(attr_span, val, span))
463            });
464
465            (@parser $variant:ident(Span, JsNamespace, Vec<Span>)) => ({
466                input.parse::<Token![=]>()?;
467                let (vals, spans) = match input.parse::<syn::ExprArray>() {
468                    Ok(exprs) => {
469                        let mut vals = vec![];
470                        let mut spans = vec![];
471
472                        for expr in exprs.elems.iter() {
473                            if let syn::Expr::Lit(syn::ExprLit {
474                                lit: syn::Lit::Str(ref str),
475                                ..
476                            }) = expr {
477                                vals.push(str.value());
478                                spans.push(str.span());
479                            } else {
480                                return Err(syn::Error::new(expr.span(), "expected string literals"));
481                            }
482                        }
483
484                        if vals.is_empty() {
485                            return Err(syn::Error::new(exprs.span(), "Empty namespace lists are not allowed."));
486                        }
487
488                        (vals, spans)
489                    },
490                    Err(_) => {
491                        let ident = input.parse::<AnyIdent>()?.0;
492                        (vec![ident.to_string()], vec![ident.span()])
493                    }
494                };
495
496                let first = &vals[0];
497                if is_non_value_js_keyword(first) {
498                    let msg = format!("Namespace cannot start with the JS keyword `{}`", first);
499                    return Err(syn::Error::new(spans[0], msg));
500                }
501
502                return Ok(BindgenAttr::$variant(attr_span, JsNamespace(vals), spans))
503            });
504        }
505
506        attrgen!(parsers);
507
508        Err(original.error(if attr_string.starts_with('_') {
509            "unknown attribute: it's safe to remove unused attributes entirely."
510        } else {
511            "unknown attribute"
512        }))
513    }
514}
515
516struct AnyIdent(Ident);
517
518impl Parse for AnyIdent {
519    fn parse(input: ParseStream) -> SynResult<Self> {
520        input.step(|cursor| match cursor.ident() {
521            Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)),
522            None => Err(cursor.error("expected an identifier")),
523        })
524    }
525}
526
527/// Conversion trait with context.
528///
529/// Used to convert syn tokens into an AST, that we can then use to generate glue code. The context
530/// (`Ctx`) is used to pass in the attributes from the `#[wasm_bindgen]`, if needed.
531pub(crate) trait ConvertToAst<Ctx> {
532    /// What we are converting to.
533    type Target;
534    /// Convert into our target.
535    ///
536    /// Since this is used in a procedural macro, use panic to fail.
537    fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
538}
539
540impl ConvertToAst<&ast::Program> for &mut syn::ItemStruct {
541    type Target = ast::Struct;
542
543    fn convert(self, program: &ast::Program) -> Result<Self::Target, Diagnostic> {
544        if !self.generics.params.is_empty() {
545            bail_span!(
546                self.generics,
547                "structs with #[wasm_bindgen] cannot have lifetime or \
548                 type parameters currently"
549            );
550        }
551        let attrs = BindgenAttrs::find(&mut self.attrs)?;
552
553        // the `wasm_bindgen` option has been used before
554        let _ = attrs.wasm_bindgen();
555
556        let mut fields = Vec::new();
557        let js_name = attrs
558            .js_name()
559            .map(|s| s.0.to_string())
560            .unwrap_or(self.ident.unraw().to_string());
561        if is_js_keyword(&js_name) {
562            bail_span!(
563                self.ident,
564                "struct cannot use the JS keyword `{}` as its name",
565                js_name
566            );
567        }
568
569        let is_inspectable = attrs.inspectable().is_some();
570        let getter_with_clone = attrs.getter_with_clone();
571        for (i, field) in self.fields.iter_mut().enumerate() {
572            match field.vis {
573                syn::Visibility::Public(..) => {}
574                _ => continue,
575            }
576            let (js_field_name, member) = match &field.ident {
577                Some(ident) => (ident.unraw().to_string(), syn::Member::Named(ident.clone())),
578                None => (i.to_string(), syn::Member::Unnamed(i.into())),
579            };
580
581            let attrs = BindgenAttrs::find(&mut field.attrs)?;
582            if attrs.skip().is_some() {
583                attrs.check_used();
584                continue;
585            }
586
587            let js_field_name = match attrs.js_name() {
588                Some((name, _)) => name.to_string(),
589                None => js_field_name,
590            };
591
592            let comments = extract_doc_comments(&field.attrs);
593            let getter = wasm_bindgen_shared::struct_field_get(&js_name, &js_field_name);
594            let setter = wasm_bindgen_shared::struct_field_set(&js_name, &js_field_name);
595
596            fields.push(ast::StructField {
597                rust_name: member,
598                js_name: js_field_name,
599                struct_name: self.ident.clone(),
600                readonly: attrs.readonly().is_some(),
601                ty: field.ty.clone(),
602                getter: Ident::new(&getter, Span::call_site()),
603                setter: Ident::new(&setter, Span::call_site()),
604                comments,
605                generate_typescript: attrs.skip_typescript().is_none(),
606                generate_jsdoc: attrs.skip_jsdoc().is_none(),
607                getter_with_clone: attrs.getter_with_clone().or(getter_with_clone).copied(),
608                wasm_bindgen: program.wasm_bindgen.clone(),
609            });
610            attrs.check_used();
611        }
612        let generate_typescript = attrs.skip_typescript().is_none();
613        let comments: Vec<String> = extract_doc_comments(&self.attrs);
614        attrs.check_used();
615        Ok(ast::Struct {
616            rust_name: self.ident.clone(),
617            js_name,
618            fields,
619            comments,
620            is_inspectable,
621            generate_typescript,
622            wasm_bindgen: program.wasm_bindgen.clone(),
623        })
624    }
625}
626
627fn get_ty(mut ty: &syn::Type) -> &syn::Type {
628    while let syn::Type::Group(g) = ty {
629        ty = &g.elem;
630    }
631
632    ty
633}
634
635fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
636    while let syn::Expr::Group(g) = expr {
637        expr = &g.expr;
638    }
639
640    expr
641}
642
643impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
644    for syn::ForeignItemFn
645{
646    type Target = ast::ImportKind;
647
648    fn convert(
649        self,
650        (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
651    ) -> Result<Self::Target, Diagnostic> {
652        let (mut wasm, _) = function_from_decl(
653            &self.sig.ident,
654            &opts,
655            self.sig.clone(),
656            self.attrs.clone(),
657            self.vis.clone(),
658            FunctionPosition::Extern,
659            None,
660        )?;
661        let catch = opts.catch().is_some();
662        let variadic = opts.variadic().is_some();
663        let js_ret = if catch {
664            // TODO: this assumes a whole bunch:
665            //
666            // * The outer type is actually a `Result`
667            // * The error type is a `JsValue`
668            // * The actual type is the first type parameter
669            //
670            // should probably fix this one day...
671            extract_first_ty_param(wasm.ret.as_ref().map(|ret| &ret.r#type))?
672        } else {
673            wasm.ret.as_ref().map(|ret| ret.r#type.clone())
674        };
675
676        let operation_kind = operation_kind(&opts);
677
678        let kind = if opts.method().is_some() {
679            let class = wasm.arguments.first().ok_or_else(|| {
680                err_span!(self, "imported methods must have at least one argument")
681            })?;
682            let class = match get_ty(&class.pat_type.ty) {
683                syn::Type::Reference(syn::TypeReference {
684                    mutability: None,
685                    elem,
686                    ..
687                }) => &**elem,
688                _ => bail_span!(
689                    class.pat_type.ty,
690                    "first argument of method must be a shared reference"
691                ),
692            };
693            let class_name = match get_ty(class) {
694                syn::Type::Path(syn::TypePath {
695                    qself: None,
696                    ref path,
697                }) => path,
698                _ => bail_span!(class, "first argument of method must be a path"),
699            };
700            let class_name = extract_path_ident(class_name)?;
701            let class_name = opts
702                .js_class()
703                .map(|p| p.0.into())
704                .unwrap_or_else(|| class_name.to_string());
705
706            let kind = ast::MethodKind::Operation(ast::Operation {
707                is_static: false,
708                kind: operation_kind,
709            });
710
711            ast::ImportFunctionKind::Method {
712                class: class_name,
713                ty: class.clone(),
714                kind,
715            }
716        } else if let Some(cls) = opts.static_method_of() {
717            let class = opts
718                .js_class()
719                .map(|p| p.0.into())
720                .unwrap_or_else(|| cls.to_string());
721
722            let ty = syn::Type::Path(syn::TypePath {
723                qself: None,
724                path: syn::Path {
725                    leading_colon: None,
726                    segments: std::iter::once(syn::PathSegment {
727                        ident: cls.clone(),
728                        arguments: syn::PathArguments::None,
729                    })
730                    .collect(),
731                },
732            });
733
734            let kind = ast::MethodKind::Operation(ast::Operation {
735                is_static: true,
736                kind: operation_kind,
737            });
738
739            ast::ImportFunctionKind::Method { class, ty, kind }
740        } else if opts.constructor().is_some() {
741            let class = match js_ret {
742                Some(ref ty) => ty,
743                _ => bail_span!(self, "constructor returns must be bare types"),
744            };
745            let class_name = match get_ty(class) {
746                syn::Type::Path(syn::TypePath {
747                    qself: None,
748                    ref path,
749                }) => path,
750                _ => bail_span!(self, "return value of constructor must be a bare path"),
751            };
752            let class_name = extract_path_ident(class_name)?;
753            let class_name = opts
754                .js_class()
755                .map(|p| p.0.into())
756                .unwrap_or_else(|| class_name.to_string());
757
758            ast::ImportFunctionKind::Method {
759                class: class_name,
760                ty: class.clone(),
761                kind: ast::MethodKind::Constructor,
762            }
763        } else {
764            ast::ImportFunctionKind::Normal
765        };
766
767        let shim = {
768            let ns = match kind {
769                ast::ImportFunctionKind::Normal => (0, "n"),
770                ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
771            };
772            let data = (ns, self.sig.to_token_stream().to_string(), module);
773            format!(
774                "__wbg_{}_{}",
775                wasm.name
776                    .chars()
777                    .filter(|&c| c.is_ascii_alphanumeric() || c == '_')
778                    .collect::<String>(),
779                ShortHash(data)
780            )
781        };
782        if let Some(span) = opts.r#final() {
783            if opts.structural().is_some() {
784                let msg = "cannot specify both `structural` and `final`";
785                return Err(Diagnostic::span_error(*span, msg));
786            }
787        }
788        let assert_no_shim = opts.assert_no_shim().is_some();
789
790        let mut doc_comment = String::new();
791        // Extract the doc comments from our list of attributes.
792        wasm.rust_attrs.retain(|attr| {
793            /// Returns the contents of the passed `#[doc = "..."]` attribute,
794            /// or `None` if it isn't one.
795            fn get_docs(attr: &syn::Attribute) -> Option<String> {
796                if attr.path().is_ident("doc") {
797                    if let syn::Meta::NameValue(syn::MetaNameValue {
798                        value:
799                            syn::Expr::Lit(syn::ExprLit {
800                                lit: Lit::Str(str), ..
801                            }),
802                        ..
803                    }) = &attr.meta
804                    {
805                        Some(str.value())
806                    } else {
807                        None
808                    }
809                } else {
810                    None
811                }
812            }
813
814            if let Some(docs) = get_docs(attr) {
815                if !doc_comment.is_empty() {
816                    // Add newlines between the doc comments
817                    doc_comment.push('\n');
818                }
819                // Add this doc comment to the complete docs
820                doc_comment.push_str(&docs);
821
822                // Remove it from the list of regular attributes
823                false
824            } else {
825                true
826            }
827        });
828
829        let ret = ast::ImportKind::Function(ast::ImportFunction {
830            function: wasm,
831            assert_no_shim,
832            kind,
833            js_ret,
834            catch,
835            variadic,
836            structural: opts.structural().is_some() || opts.r#final().is_none(),
837            rust_name: self.sig.ident,
838            shim: Ident::new(&shim, Span::call_site()),
839            doc_comment,
840            wasm_bindgen: program.wasm_bindgen.clone(),
841            wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
842        });
843        opts.check_used();
844
845        Ok(ret)
846    }
847}
848
849impl ConvertToAst<(&ast::Program, BindgenAttrs)> for syn::ForeignItemType {
850    type Target = ast::ImportKind;
851
852    fn convert(
853        self,
854        (program, attrs): (&ast::Program, BindgenAttrs),
855    ) -> Result<Self::Target, Diagnostic> {
856        let js_name = attrs
857            .js_name()
858            .map(|s| s.0)
859            .map_or_else(|| self.ident.to_string(), |s| s.to_string());
860        let typescript_type = attrs.typescript_type().map(|s| s.0.to_string());
861        let is_type_of = attrs.is_type_of().cloned();
862        let shim = format!(
863            "__wbg_instanceof_{}_{}",
864            self.ident,
865            ShortHash((attrs.js_namespace().map(|(ns, _)| ns.0), &self.ident))
866        );
867        let mut extends = Vec::new();
868        let mut vendor_prefixes = Vec::new();
869        let no_deref = attrs.no_deref().is_some();
870        for (used, attr) in attrs.attrs.iter() {
871            match attr {
872                BindgenAttr::Extends(_, e) => {
873                    extends.push(e.clone());
874                    used.set(true);
875                }
876                BindgenAttr::VendorPrefix(_, e) => {
877                    vendor_prefixes.push(e.clone());
878                    used.set(true);
879                }
880                _ => {}
881            }
882        }
883        attrs.check_used();
884        Ok(ast::ImportKind::Type(ast::ImportType {
885            vis: self.vis,
886            attrs: self.attrs,
887            doc_comment: None,
888            instanceof_shim: shim,
889            is_type_of,
890            rust_name: self.ident,
891            typescript_type,
892            js_name,
893            extends,
894            vendor_prefixes,
895            no_deref,
896            wasm_bindgen: program.wasm_bindgen.clone(),
897        }))
898    }
899}
900
901impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
902    for syn::ForeignItemStatic
903{
904    type Target = ast::ImportKind;
905
906    fn convert(
907        self,
908        (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
909    ) -> Result<Self::Target, Diagnostic> {
910        if let syn::StaticMutability::Mut(_) = self.mutability {
911            bail_span!(self.mutability, "cannot import mutable globals yet")
912        }
913
914        if let Some(span) = opts.static_string() {
915            return Err(Diagnostic::span_error(
916                *span,
917                "static strings require a string literal",
918            ));
919        }
920
921        let default_name = self.ident.to_string();
922        let js_name = opts
923            .js_name()
924            .map(|p| p.0)
925            .unwrap_or(&default_name)
926            .to_string();
927        let shim = format!(
928            "__wbg_static_accessor_{}_{}",
929            self.ident,
930            ShortHash((&js_name, module, &self.ident)),
931        );
932        let thread_local = opts.get_thread_local()?;
933
934        opts.check_used();
935        Ok(ast::ImportKind::Static(ast::ImportStatic {
936            ty: *self.ty,
937            vis: self.vis,
938            rust_name: self.ident.clone(),
939            js_name,
940            shim: Ident::new(&shim, Span::call_site()),
941            wasm_bindgen: program.wasm_bindgen.clone(),
942            thread_local,
943        }))
944    }
945}
946
947impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
948    for syn::ItemStatic
949{
950    type Target = ast::ImportKind;
951
952    fn convert(
953        self,
954        (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
955    ) -> Result<Self::Target, Diagnostic> {
956        if let syn::StaticMutability::Mut(_) = self.mutability {
957            bail_span!(self.mutability, "cannot import mutable globals yet")
958        }
959
960        let string = if let syn::Expr::Lit(syn::ExprLit {
961            lit: syn::Lit::Str(string),
962            ..
963        }) = *self.expr.clone()
964        {
965            string.value()
966        } else {
967            bail_span!(
968                self.expr,
969                "statics with a value can only be string literals"
970            )
971        };
972
973        if opts.static_string().is_none() {
974            bail_span!(
975                self,
976                "static strings require `#[wasm_bindgen(static_string)]`"
977            )
978        }
979
980        let thread_local = if let Some(thread_local) = opts.get_thread_local()? {
981            thread_local
982        } else {
983            bail_span!(
984                self,
985                "static strings require `#[wasm_bindgen(thread_local_v2)]`"
986            )
987        };
988
989        let shim = format!(
990            "__wbg_string_{}_{}",
991            self.ident,
992            ShortHash((&module, &self.ident)),
993        );
994        opts.check_used();
995        Ok(ast::ImportKind::String(ast::ImportString {
996            ty: *self.ty,
997            vis: self.vis,
998            rust_name: self.ident.clone(),
999            shim: Ident::new(&shim, Span::call_site()),
1000            wasm_bindgen: program.wasm_bindgen.clone(),
1001            js_sys: program.js_sys.clone(),
1002            string,
1003            thread_local,
1004        }))
1005    }
1006}
1007
1008impl ConvertToAst<(BindgenAttrs, Vec<FnArgAttrs>)> for syn::ItemFn {
1009    type Target = ast::Function;
1010
1011    fn convert(
1012        self,
1013        (attrs, args_attrs): (BindgenAttrs, Vec<FnArgAttrs>),
1014    ) -> Result<Self::Target, Diagnostic> {
1015        match self.vis {
1016            syn::Visibility::Public(_) => {}
1017            _ if attrs.start().is_some() => {}
1018            _ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
1019        }
1020        if self.sig.constness.is_some() {
1021            bail_span!(
1022                self.sig.constness,
1023                "can only #[wasm_bindgen] non-const functions"
1024            );
1025        }
1026
1027        let (mut ret, _) = function_from_decl(
1028            &self.sig.ident,
1029            &attrs,
1030            self.sig.clone(),
1031            self.attrs,
1032            self.vis,
1033            FunctionPosition::Free,
1034            Some(args_attrs),
1035        )?;
1036        attrs.check_used();
1037
1038        // Due to legacy behavior, we need to escape all keyword identifiers as
1039        // `_keyword`, except `default`
1040        if is_js_keyword(&ret.name) && ret.name != "default" {
1041            ret.name = format!("_{}", ret.name);
1042        }
1043
1044        Ok(ret)
1045    }
1046}
1047
1048/// Returns whether `self` is passed by reference or by value.
1049fn get_self_method(r: syn::Receiver) -> ast::MethodSelf {
1050    // The tricky part here is that `r` can have many forms. E.g. `self`,
1051    // `&self`, `&mut self`, `self: Self`, `self: &Self`, `self: &mut Self`,
1052    // `self: Box<Self>`, `self: Rc<Self>`, etc.
1053    // Luckily, syn always populates the `ty` field with the type of `self`, so
1054    // e.g. `&self` gets the type `&Self`. So we only have check whether the
1055    // type is a reference or not.
1056    match &*r.ty {
1057        syn::Type::Reference(ty) => {
1058            if ty.mutability.is_some() {
1059                ast::MethodSelf::RefMutable
1060            } else {
1061                ast::MethodSelf::RefShared
1062            }
1063        }
1064        _ => ast::MethodSelf::ByValue,
1065    }
1066}
1067
1068enum FunctionPosition<'a> {
1069    Extern,
1070    Free,
1071    Impl { self_ty: &'a Ident },
1072}
1073
1074/// Construct a function (and gets the self type if appropriate) for our AST from a syn function.
1075#[allow(clippy::too_many_arguments)]
1076fn function_from_decl(
1077    decl_name: &syn::Ident,
1078    opts: &BindgenAttrs,
1079    sig: syn::Signature,
1080    attrs: Vec<syn::Attribute>,
1081    vis: syn::Visibility,
1082    position: FunctionPosition,
1083    args_attrs: Option<Vec<FnArgAttrs>>,
1084) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> {
1085    if sig.variadic.is_some() {
1086        bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions");
1087    }
1088    if !sig.generics.params.is_empty() {
1089        bail_span!(
1090            sig.generics,
1091            "can't #[wasm_bindgen] functions with lifetime or type parameters",
1092        );
1093    }
1094
1095    assert_no_lifetimes(&sig)?;
1096
1097    let syn::Signature { inputs, output, .. } = sig;
1098
1099    // A helper function to replace `Self` in the function signature of methods.
1100    // E.g. `fn get(&self) -> Option<Self>` to `fn get(&self) -> Option<MyType>`
1101    // The following comment explains why this is necessary:
1102    // https://github.com/wasm-bindgen/wasm-bindgen/issues/3105#issuecomment-1275160744
1103    let replace_self = |mut t: syn::Type| {
1104        if let FunctionPosition::Impl { self_ty } = position {
1105            // This uses a visitor to replace all occurrences of `Self` with
1106            // the actual type identifier. The visitor guarantees that we find
1107            // all occurrences of `Self`, even if deeply nested and even if
1108            // future Rust versions add more places where `Self` can appear.
1109            struct SelfReplace(Ident);
1110            impl VisitMut for SelfReplace {
1111                fn visit_ident_mut(&mut self, i: &mut proc_macro2::Ident) {
1112                    if i == "Self" {
1113                        *i = self.0.clone();
1114                    }
1115                }
1116            }
1117
1118            let mut replace = SelfReplace(self_ty.clone());
1119            replace.visit_type_mut(&mut t);
1120        }
1121        t
1122    };
1123
1124    // A helper function to replace argument names that are JS keywords.
1125    // E.g. this will replace `fn foo(class: u32)` to `fn foo(_class: u32)`
1126    let replace_colliding_arg = |i: &mut syn::PatType| {
1127        if let syn::Pat::Ident(ref mut i) = *i.pat {
1128            let ident = i.ident.unraw().to_string();
1129            // JS keywords are NEVER allowed as argument names. Since argument
1130            // names are considered an implementation detail in JS, we can
1131            // safely rename them to avoid collisions.
1132            if is_js_keyword(&ident) {
1133                i.ident = Ident::new(format!("_{}", ident).as_str(), i.ident.span());
1134            }
1135        }
1136    };
1137
1138    let mut method_self = None;
1139    let mut arguments = Vec::new();
1140    for arg in inputs.into_iter() {
1141        match arg {
1142            syn::FnArg::Typed(mut c) => {
1143                // typical arguments like foo: u32
1144                replace_colliding_arg(&mut c);
1145                c.ty = Box::new(replace_self(*c.ty));
1146                arguments.push(c);
1147            }
1148            syn::FnArg::Receiver(r) => {
1149                // the self argument, so self, &self, &mut self, self: Box<Self>, etc.
1150
1151                // `self` is only allowed for `fn`s inside an `impl` block.
1152                match position {
1153                    FunctionPosition::Free => {
1154                        bail_span!(
1155                            r.self_token,
1156                            "the `self` argument is only allowed for functions in `impl` blocks.\n\n\
1157                            If the function is already in an `impl` block, did you perhaps forget to add `#[wasm_bindgen]` to the `impl` block?"
1158                        );
1159                    }
1160                    FunctionPosition::Extern => {
1161                        bail_span!(
1162                            r.self_token,
1163                            "the `self` argument is not allowed for `extern` functions.\n\n\
1164                            Did you perhaps mean `this`? For more information on importing JavaScript functions, see:\n\
1165                            https://wasm-bindgen.github.io/wasm-bindgen/examples/import-js.html"
1166                        );
1167                    }
1168                    FunctionPosition::Impl { .. } => {}
1169                }
1170
1171                // We need to know *how* `self` is passed to the method (by
1172                // value or by reference) to generate the correct JS shim.
1173                assert!(method_self.is_none());
1174                method_self = Some(get_self_method(r));
1175            }
1176        }
1177    }
1178
1179    // process function return data
1180    let ret_ty_override = opts.unchecked_return_type();
1181    let ret_desc = opts.return_description();
1182    let ret = match output {
1183        syn::ReturnType::Default => None,
1184        syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData {
1185            r#type: replace_self(*ty),
1186            js_type: ret_ty_override
1187                .as_ref()
1188                .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(ty, span)| {
1189                    check_invalid_type(ty, *span)?;
1190                    Ok(Some(ty.to_string()))
1191                })?,
1192            desc: ret_desc.as_ref().map_or::<Result<_, Diagnostic>, _>(
1193                Ok(None),
1194                |(desc, span)| {
1195                    check_js_comment_close(desc, *span)?;
1196                    Ok(Some(desc.to_string()))
1197                },
1198            )?,
1199        }),
1200    };
1201    // error if there were description or type override specified for
1202    // function return while it doesn't actually return anything
1203    if ret.is_none() && (ret_ty_override.is_some() || ret_desc.is_some()) {
1204        if let Some((_, span)) = ret_ty_override {
1205            return Err(Diagnostic::span_error(
1206                span,
1207                "cannot specify return type for a function that doesn't return",
1208            ));
1209        }
1210        if let Some((_, span)) = ret_desc {
1211            return Err(Diagnostic::span_error(
1212                span,
1213                "cannot specify return description for a function that doesn't return",
1214            ));
1215        }
1216    }
1217
1218    let (name, name_span) = if let Some((js_name, js_name_span)) = opts.js_name() {
1219        let kind = operation_kind(opts);
1220        let prefix = match kind {
1221            OperationKind::Setter(_) => "set_",
1222            _ => "",
1223        };
1224        (format!("{}{}", prefix, js_name), js_name_span)
1225    } else {
1226        (decl_name.unraw().to_string(), decl_name.span())
1227    };
1228
1229    Ok((
1230        ast::Function {
1231            name_span,
1232            name,
1233            rust_attrs: attrs,
1234            rust_vis: vis,
1235            r#unsafe: sig.unsafety.is_some(),
1236            r#async: sig.asyncness.is_some(),
1237            generate_typescript: opts.skip_typescript().is_none(),
1238            generate_jsdoc: opts.skip_jsdoc().is_none(),
1239            variadic: opts.variadic().is_some(),
1240            ret,
1241            arguments: arguments
1242                .into_iter()
1243                .zip(
1244                    args_attrs
1245                        .into_iter()
1246                        .flatten()
1247                        .chain(iter::repeat(FnArgAttrs::default())),
1248                )
1249                .map(|(pat_type, attrs)| ast::FunctionArgumentData {
1250                    pat_type,
1251                    js_name: attrs.js_name,
1252                    js_type: attrs.js_type,
1253                    desc: attrs.desc,
1254                })
1255                .collect(),
1256        },
1257        method_self,
1258    ))
1259}
1260
1261/// Helper struct to store extracted function argument attrs
1262#[derive(Default, Clone)]
1263struct FnArgAttrs {
1264    js_name: Option<String>,
1265    js_type: Option<String>,
1266    desc: Option<String>,
1267}
1268
1269/// Extracts function arguments attributes
1270fn extract_args_attrs(sig: &mut syn::Signature) -> Result<Vec<FnArgAttrs>, Diagnostic> {
1271    let mut args_attrs = vec![];
1272    for input in sig.inputs.iter_mut() {
1273        if let syn::FnArg::Typed(pat_type) = input {
1274            let attrs = BindgenAttrs::find(&mut pat_type.attrs)?;
1275            let arg_attrs = FnArgAttrs {
1276                js_name: attrs
1277                    .js_name()
1278                    .map_or(Ok(None), |(js_name_override, span)| {
1279                        if is_js_keyword(js_name_override) || !is_valid_ident(js_name_override) {
1280                            return Err(Diagnostic::span_error(span, "invalid JS identifier"));
1281                        }
1282                        Ok(Some(js_name_override.to_string()))
1283                    })?,
1284                js_type: attrs
1285                    .unchecked_param_type()
1286                    .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(ty, span)| {
1287                        check_invalid_type(ty, span)?;
1288                        Ok(Some(ty.to_string()))
1289                    })?,
1290                desc: attrs
1291                    .param_description()
1292                    .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(description, span)| {
1293                        check_js_comment_close(description, span)?;
1294                        Ok(Some(description.to_string()))
1295                    })?,
1296            };
1297            // throw error for any unused attrs
1298            attrs.enforce_used()?;
1299            args_attrs.push(arg_attrs);
1300        }
1301    }
1302    Ok(args_attrs)
1303}
1304
1305pub(crate) trait MacroParse<Ctx> {
1306    /// Parse the contents of an object into our AST, with a context if necessary.
1307    ///
1308    /// The context is used to have access to the attributes on `#[wasm_bindgen]`, and to allow
1309    /// writing to the output `TokenStream`.
1310    fn macro_parse(self, program: &mut ast::Program, context: Ctx) -> Result<(), Diagnostic>;
1311}
1312
1313impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
1314    fn macro_parse(
1315        self,
1316        program: &mut ast::Program,
1317        (opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
1318    ) -> Result<(), Diagnostic> {
1319        match self {
1320            syn::Item::Fn(mut f) => {
1321                let opts = opts.unwrap_or_default();
1322                if let Some(path) = opts.wasm_bindgen() {
1323                    program.wasm_bindgen = path.clone();
1324                }
1325                if let Some(path) = opts.js_sys() {
1326                    program.js_sys = path.clone();
1327                }
1328                if let Some(path) = opts.wasm_bindgen_futures() {
1329                    program.wasm_bindgen_futures = path.clone();
1330                }
1331
1332                if opts.main().is_some() {
1333                    opts.check_used();
1334                    return main(program, f, tokens);
1335                }
1336
1337                let no_mangle = f
1338                    .attrs
1339                    .iter()
1340                    .enumerate()
1341                    .find(|(_, m)| m.path().is_ident("no_mangle"));
1342                if let Some((i, _)) = no_mangle {
1343                    f.attrs.remove(i);
1344                }
1345                // extract fn args attributes before parsing to tokens stream
1346                let args_attrs = extract_args_attrs(&mut f.sig)?;
1347                let comments = extract_doc_comments(&f.attrs);
1348                // If the function isn't used for anything other than being exported to JS,
1349                // it'll be unused when not building for the Wasm target and produce a
1350                // `dead_code` warning. So, add `#[allow(dead_code)]` before it to avoid that.
1351                tokens.extend(quote::quote! { #[allow(dead_code)] });
1352                f.to_tokens(tokens);
1353                if opts.start().is_some() {
1354                    if !f.sig.generics.params.is_empty() {
1355                        bail_span!(&f.sig.generics, "the start function cannot have generics",);
1356                    }
1357                    if !f.sig.inputs.is_empty() {
1358                        bail_span!(&f.sig.inputs, "the start function cannot have arguments",);
1359                    }
1360                }
1361                let method_kind = ast::MethodKind::Operation(ast::Operation {
1362                    is_static: true,
1363                    kind: operation_kind(&opts),
1364                });
1365                let rust_name = f.sig.ident.clone();
1366                let start = opts.start().is_some();
1367
1368                program.exports.push(ast::Export {
1369                    comments,
1370                    function: f.convert((opts, args_attrs))?,
1371                    js_class: None,
1372                    method_kind,
1373                    method_self: None,
1374                    rust_class: None,
1375                    rust_name,
1376                    start,
1377                    wasm_bindgen: program.wasm_bindgen.clone(),
1378                    wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1379                });
1380            }
1381            syn::Item::Impl(mut i) => {
1382                let opts = opts.unwrap_or_default();
1383                (&mut i).macro_parse(program, opts)?;
1384                i.to_tokens(tokens);
1385            }
1386            syn::Item::ForeignMod(mut f) => {
1387                let opts = match opts {
1388                    Some(opts) => opts,
1389                    None => BindgenAttrs::find(&mut f.attrs)?,
1390                };
1391                f.macro_parse(program, opts)?;
1392            }
1393            syn::Item::Enum(mut e) => {
1394                let opts = match opts {
1395                    Some(opts) => opts,
1396                    None => BindgenAttrs::find(&mut e.attrs)?,
1397                };
1398                e.macro_parse(program, (tokens, opts))?;
1399            }
1400            syn::Item::Const(mut c) => {
1401                let opts = match opts {
1402                    Some(opts) => opts,
1403                    None => BindgenAttrs::find(&mut c.attrs)?,
1404                };
1405                c.macro_parse(program, opts)?;
1406            }
1407            _ => {
1408                bail_span!(
1409                    self,
1410                    "#[wasm_bindgen] can only be applied to a function, \
1411                     struct, enum, impl, or extern block",
1412                );
1413            }
1414        }
1415
1416        Ok(())
1417    }
1418}
1419
1420impl MacroParse<BindgenAttrs> for &mut syn::ItemImpl {
1421    fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1422        if self.defaultness.is_some() {
1423            bail_span!(
1424                self.defaultness,
1425                "#[wasm_bindgen] default impls are not supported"
1426            );
1427        }
1428        if self.unsafety.is_some() {
1429            bail_span!(
1430                self.unsafety,
1431                "#[wasm_bindgen] unsafe impls are not supported"
1432            );
1433        }
1434        if let Some((_, path, _)) = &self.trait_ {
1435            bail_span!(path, "#[wasm_bindgen] trait impls are not supported");
1436        }
1437        if !self.generics.params.is_empty() {
1438            bail_span!(
1439                self.generics,
1440                "#[wasm_bindgen] generic impls aren't supported"
1441            );
1442        }
1443        let name = match get_ty(&self.self_ty) {
1444            syn::Type::Path(syn::TypePath {
1445                qself: None,
1446                ref path,
1447            }) => path,
1448            _ => bail_span!(
1449                self.self_ty,
1450                "unsupported self type in #[wasm_bindgen] impl"
1451            ),
1452        };
1453        let mut errors = Vec::new();
1454        for item in self.items.iter_mut() {
1455            if let Err(e) = prepare_for_impl_recursion(item, name, program, &opts) {
1456                errors.push(e);
1457            }
1458        }
1459        Diagnostic::from_vec(errors)?;
1460        opts.check_used();
1461        Ok(())
1462    }
1463}
1464
1465// Prepare for recursion into an `impl` block. Here we want to attach an
1466// internal attribute, `__wasm_bindgen_class_marker`, with any metadata we need
1467// to pass from the impl to the impl item. Recursive macro expansion will then
1468// expand the `__wasm_bindgen_class_marker` attribute.
1469//
1470// Note that we currently do this because inner items may have things like cfgs
1471// on them, so we want to expand the impl first, let the insides get cfg'd, and
1472// then go for the rest.
1473fn prepare_for_impl_recursion(
1474    item: &mut syn::ImplItem,
1475    class: &syn::Path,
1476    program: &ast::Program,
1477    impl_opts: &BindgenAttrs,
1478) -> Result<(), Diagnostic> {
1479    let method = match item {
1480        syn::ImplItem::Fn(m) => m,
1481        syn::ImplItem::Const(_) => {
1482            bail_span!(
1483                &*item,
1484                "const definitions aren't supported with #[wasm_bindgen]"
1485            );
1486        }
1487        syn::ImplItem::Type(_) => bail_span!(
1488            &*item,
1489            "type definitions in impls aren't supported with #[wasm_bindgen]"
1490        ),
1491        syn::ImplItem::Macro(_) => {
1492            // In theory we want to allow this, but we have no way of expanding
1493            // the macro and then placing our magical attributes on the expanded
1494            // functions. As a result, just disallow it for now to hopefully
1495            // ward off buggy results from this macro.
1496            bail_span!(&*item, "macros in impls aren't supported");
1497        }
1498        syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
1499        other => bail_span!(other, "failed to parse this item as a known item"),
1500    };
1501
1502    let ident = extract_path_ident(class)?;
1503
1504    let js_class = impl_opts
1505        .js_class()
1506        .map(|s| s.0.to_string())
1507        .unwrap_or(ident.to_string());
1508
1509    let wasm_bindgen = &program.wasm_bindgen;
1510    let wasm_bindgen_futures = &program.wasm_bindgen_futures;
1511    method.attrs.insert(
1512        0,
1513        syn::Attribute {
1514            pound_token: Default::default(),
1515            style: syn::AttrStyle::Outer,
1516            bracket_token: Default::default(),
1517            meta: syn::parse_quote! { #wasm_bindgen::prelude::__wasm_bindgen_class_marker(#class = #js_class, wasm_bindgen = #wasm_bindgen, wasm_bindgen_futures = #wasm_bindgen_futures) },
1518        },
1519    );
1520
1521    Ok(())
1522}
1523
1524impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn {
1525    fn macro_parse(
1526        self,
1527        program: &mut ast::Program,
1528        ClassMarker {
1529            class,
1530            js_class,
1531            wasm_bindgen,
1532            wasm_bindgen_futures,
1533        }: &ClassMarker,
1534    ) -> Result<(), Diagnostic> {
1535        program.wasm_bindgen = wasm_bindgen.clone();
1536        program.wasm_bindgen_futures = wasm_bindgen_futures.clone();
1537
1538        match self.vis {
1539            syn::Visibility::Public(_) => {}
1540            _ => return Ok(()),
1541        }
1542        if self.defaultness.is_some() {
1543            panic!("default methods are not supported");
1544        }
1545        if self.sig.constness.is_some() {
1546            bail_span!(
1547                self.sig.constness,
1548                "can only #[wasm_bindgen] non-const functions",
1549            );
1550        }
1551
1552        let opts = BindgenAttrs::find(&mut self.attrs)?;
1553        let comments = extract_doc_comments(&self.attrs);
1554        let args_attrs: Vec<FnArgAttrs> = extract_args_attrs(&mut self.sig)?;
1555        let (function, method_self) = function_from_decl(
1556            &self.sig.ident,
1557            &opts,
1558            self.sig.clone(),
1559            self.attrs.clone(),
1560            self.vis.clone(),
1561            FunctionPosition::Impl { self_ty: class },
1562            Some(args_attrs),
1563        )?;
1564        let method_kind = if opts.constructor().is_some() {
1565            ast::MethodKind::Constructor
1566        } else {
1567            let is_static = method_self.is_none();
1568            let kind = operation_kind(&opts);
1569            ast::MethodKind::Operation(ast::Operation { is_static, kind })
1570        };
1571        program.exports.push(ast::Export {
1572            comments,
1573            function,
1574            js_class: Some(js_class.to_string()),
1575            method_kind,
1576            method_self,
1577            rust_class: Some(class.clone()),
1578            rust_name: self.sig.ident.clone(),
1579            start: false,
1580            wasm_bindgen: program.wasm_bindgen.clone(),
1581            wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1582        });
1583        opts.check_used();
1584        Ok(())
1585    }
1586}
1587
1588fn string_enum(
1589    enum_: syn::ItemEnum,
1590    program: &mut ast::Program,
1591    js_name: String,
1592    generate_typescript: bool,
1593    comments: Vec<String>,
1594) -> Result<(), Diagnostic> {
1595    let mut variants = vec![];
1596    let mut variant_values = vec![];
1597
1598    for v in enum_.variants.iter() {
1599        let (_, expr) = match &v.discriminant {
1600            Some(pair) => pair,
1601            None => {
1602                bail_span!(v, "all variants of a string enum must have a string value");
1603            }
1604        };
1605        match get_expr(expr) {
1606            syn::Expr::Lit(syn::ExprLit {
1607                attrs: _,
1608                lit: syn::Lit::Str(str_lit),
1609            }) => {
1610                variants.push(v.ident.clone());
1611                variant_values.push(str_lit.value());
1612            }
1613            expr => bail_span!(
1614                expr,
1615                "enums with #[wasm_bindgen] cannot mix string and non-string values",
1616            ),
1617        }
1618    }
1619
1620    program.imports.push(ast::Import {
1621        module: None,
1622        js_namespace: None,
1623        kind: ast::ImportKind::Enum(ast::StringEnum {
1624            vis: enum_.vis,
1625            name: enum_.ident,
1626            js_name,
1627            variants,
1628            variant_values,
1629            comments,
1630            rust_attrs: enum_.attrs,
1631            generate_typescript,
1632            wasm_bindgen: program.wasm_bindgen.clone(),
1633        }),
1634    });
1635
1636    Ok(())
1637}
1638
1639/// Represents a possibly negative numeric value as base 10 digits.
1640struct NumericValue<'a> {
1641    negative: bool,
1642    base10_digits: &'a str,
1643}
1644impl<'a> NumericValue<'a> {
1645    fn from_expr(expr: &'a syn::Expr) -> Option<Self> {
1646        match get_expr(expr) {
1647            syn::Expr::Lit(syn::ExprLit {
1648                lit: syn::Lit::Int(int_lit),
1649                ..
1650            }) => Some(Self {
1651                negative: false,
1652                base10_digits: int_lit.base10_digits(),
1653            }),
1654            syn::Expr::Unary(syn::ExprUnary {
1655                op: syn::UnOp::Neg(_),
1656                expr,
1657                ..
1658            }) => Self::from_expr(expr).map(|n| n.neg()),
1659            _ => None,
1660        }
1661    }
1662
1663    fn parse(&self) -> Option<i64> {
1664        let mut value = self.base10_digits.parse::<i64>().ok()?;
1665        if self.negative {
1666            value = -value;
1667        }
1668        Some(value)
1669    }
1670
1671    fn neg(self) -> Self {
1672        Self {
1673            negative: !self.negative,
1674            base10_digits: self.base10_digits,
1675        }
1676    }
1677}
1678
1679impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
1680    fn macro_parse(
1681        self,
1682        program: &mut ast::Program,
1683        (tokens, opts): (&'a mut TokenStream, BindgenAttrs),
1684    ) -> Result<(), Diagnostic> {
1685        if self.variants.is_empty() {
1686            bail_span!(self, "cannot export empty enums to JS");
1687        }
1688        for variant in self.variants.iter() {
1689            match variant.fields {
1690                syn::Fields::Unit => (),
1691                _ => bail_span!(
1692                    variant.fields,
1693                    "enum variants with associated data are not supported with #[wasm_bindgen]"
1694                ),
1695            }
1696        }
1697
1698        let generate_typescript = opts.skip_typescript().is_none();
1699        let comments = extract_doc_comments(&self.attrs);
1700        let js_name = opts
1701            .js_name()
1702            .map(|s| s.0)
1703            .map_or_else(|| self.ident.to_string(), |s| s.to_string());
1704        if is_js_keyword(&js_name) {
1705            bail_span!(
1706                self.ident,
1707                "enum cannot use the JS keyword `{}` as its name",
1708                js_name
1709            );
1710        }
1711
1712        opts.check_used();
1713
1714        // Check if the enum is a string enum, by checking whether any variant has a string discriminant.
1715        let is_string_enum = self.variants.iter().any(|v| {
1716            if let Some((_, expr)) = &v.discriminant {
1717                if let syn::Expr::Lit(syn::ExprLit {
1718                    lit: syn::Lit::Str(_),
1719                    ..
1720                }) = get_expr(expr)
1721                {
1722                    return true;
1723                }
1724            }
1725            false
1726        });
1727        if is_string_enum {
1728            return string_enum(self, program, js_name, generate_typescript, comments);
1729        }
1730
1731        match self.vis {
1732            syn::Visibility::Public(_) => {}
1733            _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
1734        }
1735
1736        // Go through all variants once first to determine whether the enum is
1737        // signed or unsigned. We don't need to actually parse the discriminant
1738        // values yet, we just need to know their sign. The actual parsing is
1739        // done in a second pass.
1740        let signed = self.variants.iter().any(|v| match &v.discriminant {
1741            Some((_, expr)) => NumericValue::from_expr(expr).map_or(false, |n| n.negative),
1742            None => false,
1743        });
1744        let underlying_min = if signed { i32::MIN as i64 } else { 0 };
1745        let underlying_max = if signed {
1746            i32::MAX as i64
1747        } else {
1748            u32::MAX as i64
1749        };
1750
1751        let mut last_discriminant: Option<i64> = None;
1752        let mut discriminant_map: HashMap<i64, &syn::Variant> = HashMap::new();
1753
1754        let variants = self
1755            .variants
1756            .iter()
1757            .map(|v| {
1758                let value: i64 = match &v.discriminant {
1759                    Some((_, expr)) => match NumericValue::from_expr(expr).and_then(|n| n.parse()) {
1760                        Some(value) => value,
1761                        _ => bail_span!(
1762                            expr,
1763                            "C-style enums with #[wasm_bindgen] may only have \
1764                             numeric literal values that fit in a 32-bit integer as discriminants. \
1765                             Expressions or variables are not supported.",
1766                        ),
1767                    },
1768                    None => {
1769                        // Use the same algorithm as rustc to determine the next discriminant
1770                        // https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants
1771                        last_discriminant.map_or(0, |last| last + 1)
1772                    }
1773                };
1774
1775                last_discriminant = Some(value);
1776
1777                // check that the value fits within the underlying type
1778                let underlying = if signed { "i32" } else { "u32" };
1779                let numbers = if signed { "signed numbers" } else { "unsigned numbers" };
1780                if value < underlying_min {
1781                    bail_span!(
1782                        v,
1783                        "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \
1784                        but `{1}` is too small for `{2}`",
1785                        numbers,
1786                        value,
1787                        underlying
1788                    );
1789                }
1790                if value > underlying_max {
1791                    bail_span!(
1792                        v,
1793                        "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \
1794                        but `{1}` is too large for `{2}`",
1795                        numbers,
1796                        value,
1797                        underlying
1798                    );
1799                }
1800
1801                // detect duplicate discriminants
1802                if let Some(old) = discriminant_map.insert(value, v) {
1803                    bail_span!(
1804                        v,
1805                        "discriminant value `{}` is already used by {} in this enum",
1806                        value,
1807                        old.ident
1808                    );
1809                }
1810
1811                let comments = extract_doc_comments(&v.attrs);
1812                Ok(ast::Variant {
1813                    name: v.ident.clone(),
1814                    // due to the above checks, we know that the value fits
1815                    // within 32 bits, so this cast doesn't lose any information
1816                    value: value as u32,
1817                    comments,
1818                })
1819            })
1820            .collect::<Result<Vec<_>, Diagnostic>>()?;
1821
1822        // To make all the code handling holes simpler, we only consider
1823        // non-negative holes. This allows us to use `u32` to represent holes.
1824        let hole = (0..=underlying_max)
1825            .find(|v| !discriminant_map.contains_key(v))
1826            .unwrap() as u32;
1827
1828        self.to_tokens(tokens);
1829
1830        program.enums.push(ast::Enum {
1831            rust_name: self.ident,
1832            js_name,
1833            signed,
1834            variants,
1835            comments,
1836            hole,
1837            generate_typescript,
1838            wasm_bindgen: program.wasm_bindgen.clone(),
1839        });
1840        Ok(())
1841    }
1842}
1843
1844impl MacroParse<BindgenAttrs> for syn::ItemConst {
1845    fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1846        // Shortcut
1847        if opts.typescript_custom_section().is_none() {
1848            bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)].");
1849        }
1850
1851        let typescript_custom_section = match get_expr(&self.expr) {
1852            syn::Expr::Lit(syn::ExprLit {
1853                lit: syn::Lit::Str(litstr),
1854                ..
1855            }) => ast::LitOrExpr::Lit(litstr.value()),
1856            expr => ast::LitOrExpr::Expr(expr.clone()),
1857        };
1858
1859        program
1860            .typescript_custom_sections
1861            .push(typescript_custom_section);
1862
1863        opts.check_used();
1864
1865        Ok(())
1866    }
1867}
1868
1869impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
1870    fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1871        let mut errors = Vec::new();
1872        if let Some(other) = self.abi.name.filter(|l| l.value() != "C") {
1873            errors.push(err_span!(
1874                other,
1875                "only foreign mods with the `C` ABI are allowed"
1876            ));
1877        }
1878        let js_namespace = opts.js_namespace().map(|(s, _)| s);
1879        let module = module_from_opts(program, &opts)
1880            .map_err(|e| errors.push(e))
1881            .unwrap_or_default();
1882        for item in self.items.into_iter() {
1883            let ctx = ForeignItemCtx {
1884                module: module.clone(),
1885                js_namespace: js_namespace.clone(),
1886            };
1887            if let Err(e) = item.macro_parse(program, ctx) {
1888                errors.push(e);
1889            }
1890        }
1891        Diagnostic::from_vec(errors)?;
1892        opts.check_used();
1893        Ok(())
1894    }
1895}
1896
1897struct ForeignItemCtx {
1898    module: Option<ast::ImportModule>,
1899    js_namespace: Option<JsNamespace>,
1900}
1901
1902impl MacroParse<ForeignItemCtx> for syn::ForeignItem {
1903    fn macro_parse(
1904        mut self,
1905        program: &mut ast::Program,
1906        ctx: ForeignItemCtx,
1907    ) -> Result<(), Diagnostic> {
1908        let item_opts = {
1909            let attrs = match self {
1910                syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
1911                syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
1912                syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
1913                syn::ForeignItem::Verbatim(v) => {
1914                    let mut item: syn::ItemStatic =
1915                        syn::parse(v.into()).expect("only foreign functions/types allowed for now");
1916                    let item_opts = BindgenAttrs::find(&mut item.attrs)?;
1917                    let kind = item.convert((program, item_opts, &ctx.module))?;
1918
1919                    program.imports.push(ast::Import {
1920                        module: None,
1921                        js_namespace: None,
1922                        kind,
1923                    });
1924
1925                    return Ok(());
1926                }
1927                _ => panic!("only foreign functions/types allowed for now"),
1928            };
1929            BindgenAttrs::find(attrs)?
1930        };
1931
1932        let js_namespace = item_opts
1933            .js_namespace()
1934            .map(|(s, _)| s)
1935            .or(ctx.js_namespace)
1936            .map(|s| s.0);
1937        let module = ctx.module;
1938
1939        let kind = match self {
1940            syn::ForeignItem::Fn(f) => f.convert((program, item_opts, &module))?,
1941            syn::ForeignItem::Type(t) => t.convert((program, item_opts))?,
1942            syn::ForeignItem::Static(s) => s.convert((program, item_opts, &module))?,
1943            _ => panic!("only foreign functions/types allowed for now"),
1944        };
1945
1946        // check for JS keywords
1947
1948        // We only need to check if there isn't a JS namespace or module. If
1949        // there is namespace, then we already checked the namespace while
1950        // parsing. If there is a module, we can rename the import symbol to
1951        // avoid using keywords.
1952        let needs_check = js_namespace.is_none() && module.is_none();
1953        if needs_check {
1954            match &kind {
1955                ast::ImportKind::Function(import_function) => {
1956                    if matches!(import_function.kind, ast::ImportFunctionKind::Normal)
1957                        && is_non_value_js_keyword(&import_function.function.name)
1958                    {
1959                        bail_span!(
1960                            import_function.rust_name,
1961                            "Imported function cannot use the JS keyword `{}` as its name.",
1962                            import_function.function.name
1963                        );
1964                    }
1965                }
1966                ast::ImportKind::Static(import_static) => {
1967                    if is_non_value_js_keyword(&import_static.js_name) {
1968                        bail_span!(
1969                            import_static.rust_name,
1970                            "Imported static cannot use the JS keyword `{}` as its name.",
1971                            import_static.js_name
1972                        );
1973                    }
1974                }
1975                ast::ImportKind::String(_) => {
1976                    // static strings don't have JS names, so we don't need to check for JS keywords
1977                }
1978                ast::ImportKind::Type(import_type) => {
1979                    if is_non_value_js_keyword(&import_type.js_name) {
1980                        bail_span!(
1981                            import_type.rust_name,
1982                            "Imported type cannot use the JS keyword `{}` as its name.",
1983                            import_type.js_name
1984                        );
1985                    }
1986                }
1987                ast::ImportKind::Enum(_) => {
1988                    // string enums aren't possible here
1989                }
1990            }
1991        }
1992
1993        program.imports.push(ast::Import {
1994            module,
1995            js_namespace,
1996            kind,
1997        });
1998
1999        Ok(())
2000    }
2001}
2002
2003pub fn module_from_opts(
2004    program: &mut ast::Program,
2005    opts: &BindgenAttrs,
2006) -> Result<Option<ast::ImportModule>, Diagnostic> {
2007    if let Some(path) = opts.wasm_bindgen() {
2008        program.wasm_bindgen = path.clone();
2009    }
2010
2011    if let Some(path) = opts.js_sys() {
2012        program.js_sys = path.clone();
2013    }
2014
2015    if let Some(path) = opts.wasm_bindgen_futures() {
2016        program.wasm_bindgen_futures = path.clone();
2017    }
2018
2019    let mut errors = Vec::new();
2020    let module = if let Some((name, span)) = opts.module() {
2021        if opts.inline_js().is_some() {
2022            let msg = "cannot specify both `module` and `inline_js`";
2023            errors.push(Diagnostic::span_error(span, msg));
2024        }
2025        if opts.raw_module().is_some() {
2026            let msg = "cannot specify both `module` and `raw_module`";
2027            errors.push(Diagnostic::span_error(span, msg));
2028        }
2029        Some(ast::ImportModule::Named(name.to_string(), span))
2030    } else if let Some((name, span)) = opts.raw_module() {
2031        if opts.inline_js().is_some() {
2032            let msg = "cannot specify both `raw_module` and `inline_js`";
2033            errors.push(Diagnostic::span_error(span, msg));
2034        }
2035        Some(ast::ImportModule::RawNamed(name.to_string(), span))
2036    } else if let Some((js, _span)) = opts.inline_js() {
2037        let i = program.inline_js.len();
2038        program.inline_js.push(js.to_string());
2039        Some(ast::ImportModule::Inline(i))
2040    } else {
2041        None
2042    };
2043    Diagnostic::from_vec(errors)?;
2044    Ok(module)
2045}
2046
2047/// Get the first type parameter of a generic type, errors on incorrect input.
2048fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, Diagnostic> {
2049    let t = match ty {
2050        Some(t) => t,
2051        None => return Ok(None),
2052    };
2053    let path = match *get_ty(t) {
2054        syn::Type::Path(syn::TypePath {
2055            qself: None,
2056            ref path,
2057        }) => path,
2058        _ => bail_span!(t, "must be Result<...>"),
2059    };
2060    let seg = path
2061        .segments
2062        .last()
2063        .ok_or_else(|| err_span!(t, "must have at least one segment"))?;
2064    let generics = match seg.arguments {
2065        syn::PathArguments::AngleBracketed(ref t) => t,
2066        _ => bail_span!(t, "must be Result<...>"),
2067    };
2068    let generic = generics
2069        .args
2070        .first()
2071        .ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?;
2072    let ty = match generic {
2073        syn::GenericArgument::Type(t) => t,
2074        other => bail_span!(other, "must be a type parameter"),
2075    };
2076    match get_ty(ty) {
2077        syn::Type::Tuple(t) if t.elems.is_empty() => return Ok(None),
2078        _ => {}
2079    }
2080    Ok(Some(ty.clone()))
2081}
2082
2083/// Extract the documentation comments from a Vec of attributes
2084fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
2085    attrs
2086        .iter()
2087        .filter_map(|a| {
2088            // if the path segments include an ident of "doc" we know this
2089            // this is a doc comment
2090            if a.path().segments.iter().any(|s| s.ident == "doc") {
2091                let tokens = match &a.meta {
2092                    syn::Meta::Path(_) => None,
2093                    syn::Meta::List(list) => Some(list.tokens.clone()),
2094                    syn::Meta::NameValue(name_value) => Some(name_value.value.to_token_stream()),
2095                };
2096
2097                Some(
2098                    // We want to filter out any Puncts so just grab the Literals
2099                    tokens.into_iter().flatten().filter_map(|t| match t {
2100                        TokenTree::Literal(lit) => {
2101                            let quoted = lit.to_string();
2102                            Some(try_unescape(&quoted).unwrap_or(quoted))
2103                        }
2104                        _ => None,
2105                    }),
2106                )
2107            } else {
2108                None
2109            }
2110        })
2111        //Fold up the [[String]] iter we created into Vec<String>
2112        .fold(vec![], |mut acc, a| {
2113            acc.extend(a);
2114            acc
2115        })
2116}
2117
2118// Unescapes a quoted string. char::escape_debug() was used to escape the text.
2119fn try_unescape(mut s: &str) -> Option<String> {
2120    s = s.strip_prefix('"').unwrap_or(s);
2121    s = s.strip_suffix('"').unwrap_or(s);
2122    let mut result = String::with_capacity(s.len());
2123    let mut chars = s.chars();
2124    while let Some(c) = chars.next() {
2125        if c == '\\' {
2126            let c = chars.next()?;
2127            match c {
2128                't' => result.push('\t'),
2129                'r' => result.push('\r'),
2130                'n' => result.push('\n'),
2131                '\\' | '\'' | '"' => result.push(c),
2132                'u' => {
2133                    if chars.next() != Some('{') {
2134                        return None;
2135                    }
2136                    let (c, next) = unescape_unicode(&mut chars)?;
2137                    result.push(c);
2138                    if next != '}' {
2139                        return None;
2140                    }
2141                }
2142                _ => return None,
2143            }
2144        } else {
2145            result.push(c);
2146        }
2147    }
2148    Some(result)
2149}
2150
2151fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
2152    let mut value = 0;
2153    for (i, c) in chars.enumerate() {
2154        match (i, c.to_digit(16)) {
2155            (0..=5, Some(num)) => value = (value << 4) | num,
2156            (1.., None) => return Some((char::from_u32(value)?, c)),
2157            _ => break,
2158        }
2159    }
2160    None
2161}
2162
2163/// Check there are no lifetimes on the function.
2164fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> {
2165    struct Walk {
2166        diagnostics: Vec<Diagnostic>,
2167    }
2168
2169    impl<'ast> syn::visit::Visit<'ast> for Walk {
2170        fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
2171            self.diagnostics.push(err_span!(
2172                i,
2173                "it is currently not sound to use lifetimes in function \
2174                 signatures"
2175            ));
2176        }
2177    }
2178    let mut walk = Walk {
2179        diagnostics: Vec::new(),
2180    };
2181    syn::visit::Visit::visit_signature(&mut walk, sig);
2182    Diagnostic::from_vec(walk.diagnostics)
2183}
2184
2185/// Extracts the last ident from the path
2186fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> {
2187    for segment in path.segments.iter() {
2188        match segment.arguments {
2189            syn::PathArguments::None => {}
2190            _ => bail_span!(path, "paths with type parameters are not supported yet"),
2191        }
2192    }
2193
2194    match path.segments.last() {
2195        Some(value) => Ok(value.ident.clone()),
2196        None => {
2197            bail_span!(path, "empty idents are not supported");
2198        }
2199    }
2200}
2201
2202pub fn reset_attrs_used() {
2203    ATTRS.with(|state| {
2204        state.parsed.set(0);
2205        state.checks.set(0);
2206        state.unused_attrs.borrow_mut().clear();
2207    })
2208}
2209
2210pub fn check_unused_attrs(tokens: &mut TokenStream) {
2211    ATTRS.with(|state| {
2212        assert_eq!(state.parsed.get(), state.checks.get());
2213        let unused_attrs = &*state.unused_attrs.borrow();
2214        if !unused_attrs.is_empty() {
2215            let unused_attrs = unused_attrs.iter().map(|UnusedState { error, ident }| {
2216                if *error {
2217                    let text = format!("invalid attribute {} in this position", ident);
2218                    quote::quote_spanned! { ident.span() => ::core::compile_error!(#text); }
2219                } else {
2220                    quote::quote! { let #ident: (); }
2221                }
2222            });
2223            tokens.extend(quote::quote! {
2224                // Anonymous scope to prevent name clashes.
2225                const _: () = {
2226                    #(#unused_attrs)*
2227                };
2228            });
2229        }
2230    })
2231}
2232
2233fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind {
2234    let mut operation_kind = ast::OperationKind::Regular;
2235    if let Some(g) = opts.getter() {
2236        operation_kind = ast::OperationKind::Getter(g.clone());
2237    }
2238    if let Some(s) = opts.setter() {
2239        operation_kind = ast::OperationKind::Setter(s.clone());
2240    }
2241    if opts.indexing_getter().is_some() {
2242        operation_kind = ast::OperationKind::IndexingGetter;
2243    }
2244    if opts.indexing_setter().is_some() {
2245        operation_kind = ast::OperationKind::IndexingSetter;
2246    }
2247    if opts.indexing_deleter().is_some() {
2248        operation_kind = ast::OperationKind::IndexingDeleter;
2249    }
2250    operation_kind
2251}
2252
2253pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
2254    let mut program = ast::Program::default();
2255    let module = module_from_opts(&mut program, &opts)?.ok_or_else(|| {
2256        Diagnostic::span_error(Span::call_site(), "`link_to!` requires a module.")
2257    })?;
2258    if let ast::ImportModule::Named(p, s) | ast::ImportModule::RawNamed(p, s) = &module {
2259        if !p.starts_with("./") && !p.starts_with("../") && !p.starts_with('/') {
2260            return Err(Diagnostic::span_error(
2261                *s,
2262                "`link_to!` does not support module paths.",
2263            ));
2264        }
2265    }
2266    opts.enforce_used()?;
2267    program.linked_modules.push(module);
2268    Ok(ast::LinkToModule(program))
2269}
2270
2271fn main(program: &ast::Program, mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
2272    if f.sig.ident != "main" {
2273        bail_span!(&f.sig.ident, "the main function has to be called main");
2274    }
2275    if let Some(constness) = f.sig.constness {
2276        bail_span!(&constness, "the main function cannot be const");
2277    }
2278    if !f.sig.generics.params.is_empty() {
2279        bail_span!(&f.sig.generics, "the main function cannot have generics");
2280    }
2281    if !f.sig.inputs.is_empty() {
2282        bail_span!(&f.sig.inputs, "the main function cannot have arguments");
2283    }
2284
2285    let r#return = f.sig.output;
2286    f.sig.output = ReturnType::Default;
2287    let body = f.block;
2288
2289    let wasm_bindgen = &program.wasm_bindgen;
2290    let wasm_bindgen_futures = &program.wasm_bindgen_futures;
2291
2292    if f.sig.asyncness.take().is_some() {
2293        f.block = Box::new(
2294            syn::parse2(quote::quote! {
2295                {
2296                    async fn __wasm_bindgen_generated_main() #r#return #body
2297                    #wasm_bindgen_futures::spawn_local(
2298                        async move {
2299                            use #wasm_bindgen::__rt::Main;
2300                            let __ret = __wasm_bindgen_generated_main();
2301                            (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main()
2302                        },
2303                    )
2304                }
2305            })
2306            .unwrap(),
2307        );
2308    } else {
2309        f.block = Box::new(
2310            syn::parse2(quote::quote! {
2311                {
2312                    fn __wasm_bindgen_generated_main() #r#return #body
2313                    use #wasm_bindgen::__rt::Main;
2314                    let __ret = __wasm_bindgen_generated_main();
2315                    (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main()
2316                }
2317            })
2318            .unwrap(),
2319        );
2320    }
2321
2322    f.to_tokens(tokens);
2323
2324    Ok(())
2325}
2326
2327#[cfg(test)]
2328mod tests {
2329    #[test]
2330    fn test_try_unescape() {
2331        use super::try_unescape;
2332        assert_eq!(try_unescape("hello").unwrap(), "hello");
2333        assert_eq!(try_unescape("\"hello").unwrap(), "hello");
2334        assert_eq!(try_unescape("hello\"").unwrap(), "hello");
2335        assert_eq!(try_unescape("\"hello\"").unwrap(), "hello");
2336        assert_eq!(try_unescape("hello\\\\").unwrap(), "hello\\");
2337        assert_eq!(try_unescape("hello\\n").unwrap(), "hello\n");
2338        assert_eq!(try_unescape("hello\\u"), None);
2339        assert_eq!(try_unescape("hello\\u{"), None);
2340        assert_eq!(try_unescape("hello\\u{}"), None);
2341        assert_eq!(try_unescape("hello\\u{0}").unwrap(), "hello\0");
2342        assert_eq!(try_unescape("hello\\u{000000}").unwrap(), "hello\0");
2343        assert_eq!(try_unescape("hello\\u{0000000}"), None);
2344    }
2345}