Skip to main content

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