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