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