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