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(pub 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            (extends_js_class, false, ExtendsJsClass(Span, String, Span)),
100            (extends_js_namespace, false, ExtendsJsNamespace(Span, JsNamespace, Vec<Span>)),
101            (no_deref, false, NoDeref(Span)),
102            (no_upcast, false, NoUpcast(Span)),
103            (no_promising, false, NoPromising(Span)),
104            (no_into_js_generic, false, NoIntoJsGeneric(Span)),
105            (vendor_prefix, false, VendorPrefix(Span, Ident)),
106            (variadic, false, Variadic(Span)),
107            (typescript_custom_section, false, TypescriptCustomSection(Span)),
108            (skip_typescript, false, SkipTypescript(Span)),
109            (skip_jsdoc, false, SkipJsDoc(Span)),
110            (private, false, Hide(Span)),
111            (fallback, false, Fallback(Span)),
112            (main, false, Main(Span)),
113            (start, false, Start(Span)),
114            (wasm_bindgen, false, WasmBindgen(Span, syn::Path)),
115            (js_sys, false, JsSys(Span, syn::Path)),
116            (wasm_bindgen_futures, false, WasmBindgenFutures(Span, syn::Path)),
117            (skip, false, Skip(Span)),
118            (slice_to_array, false, SliceToArray(Span)),
119            (typescript_type, false, TypeScriptType(Span, String, Span)),
120            (getter_with_clone, false, GetterWithClone(Span)),
121            (static_string, false, StaticString(Span)),
122            (thread_local, false, ThreadLocal(Span)),
123            (thread_local_v2, false, ThreadLocalV2(Span)),
124            (unchecked_return_type, true, ReturnType(Span, String, Span)),
125            (return_description, true, ReturnDesc(Span, String, Span)),
126            (unchecked_param_type, true, ParamType(Span, String, Span)),
127            (unchecked_optional_param_type, true, OptionalParamType(Span, String, Span)),
128            (param_description, true, ParamDesc(Span, String, Span)),
129
130            // For testing purposes only.
131            (assert_no_shim, false, AssertNoShim(Span)),
132        }
133    };
134}
135
136macro_rules! methods {
137    ($(($name:ident, $invalid_unused:literal, $variant:ident($($contents:tt)*)),)*) => {
138        $(methods!(@method $name, $variant($($contents)*));)*
139
140        fn enforce_used(self) -> Result<(), Diagnostic> {
141            // Account for the fact this method was called
142            ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
143
144            let mut errors = Vec::new();
145            for (used, attr) in self.attrs.iter() {
146                if used.get() {
147                    continue
148                }
149                let span = match attr {
150                    $(BindgenAttr::$variant(span, ..) => span,)*
151                };
152                errors.push(Diagnostic::span_error(*span, "unused wasm_bindgen attribute"));
153            }
154            Diagnostic::from_vec(errors)
155        }
156
157        pub(crate) fn check_used(self) {
158            // Account for the fact this method was called
159            ATTRS.with(|state| {
160                state.checks.set(state.checks.get() + 1);
161
162                state.unused_attrs.borrow_mut().extend(
163                    self.attrs
164                    .iter()
165                    .filter_map(|(used, attr)| if used.get() { None } else { Some(attr) })
166                    .map(|attr| {
167                        match attr {
168                            $(BindgenAttr::$variant(span, ..) => {
169                                UnusedState {
170                                    error: $invalid_unused,
171                                    ident: syn::parse_quote_spanned!(*span => $name)
172                                }
173                            })*
174                        }
175                    })
176                );
177            });
178        }
179    };
180
181    (@method $name:ident, $variant:ident(Span, String, Span)) => {
182        pub(crate) fn $name(&self) -> Option<(&str, Span)> {
183            self.attrs
184                .iter()
185                .find_map(|a| match &a.1 {
186                    BindgenAttr::$variant(_, s, span) => {
187                        a.0.set(true);
188                        Some((&s[..], *span))
189                    }
190                    _ => None,
191                })
192        }
193    };
194
195    (@method $name:ident, $variant:ident(Span, JsNamespace, Vec<Span>)) => {
196        pub(crate) fn $name(&self) -> Option<(JsNamespace, &[Span])> {
197            self.attrs
198                .iter()
199                .find_map(|a| match &a.1 {
200                    BindgenAttr::$variant(_, ss, spans) => {
201                        a.0.set(true);
202                        Some((ss.clone(), &spans[..]))
203                    }
204                    _ => None,
205                })
206        }
207    };
208
209    (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
210        #[allow(unused)]
211        pub(crate) fn $name(&self) -> Option<&$($other)*> {
212            self.attrs
213                .iter()
214                .find_map(|a| match &a.1 {
215                    BindgenAttr::$variant(_, s) => {
216                        a.0.set(true);
217                        Some(s)
218                    }
219                    _ => None,
220                })
221        }
222    };
223
224    (@method $name:ident, $variant:ident($($other:tt)*)) => {
225        #[allow(unused)]
226        pub(crate) fn $name(&self) -> Option<&$($other)*> {
227            self.attrs
228                .iter()
229                .find_map(|a| match &a.1 {
230                    BindgenAttr::$variant(s) => {
231                        a.0.set(true);
232                        Some(s)
233                    }
234                    _ => None,
235                })
236        }
237    };
238}
239
240impl BindgenAttrs {
241    /// Find and parse the wasm_bindgen attributes.
242    fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
243        let mut ret = BindgenAttrs::default();
244        loop {
245            let pos = attrs
246                .iter()
247                .enumerate()
248                .find(|&(_, m)| m.path().segments[0].ident == "wasm_bindgen")
249                .map(|a| a.0);
250            let pos = match pos {
251                Some(i) => i,
252                None => break,
253            };
254            let attr = attrs.remove(pos);
255            let tokens = match attr.meta {
256                syn::Meta::Path(_) => continue,
257                syn::Meta::List(syn::MetaList {
258                    delimiter: MacroDelimiter::Paren(_),
259                    tokens,
260                    ..
261                }) => tokens,
262                syn::Meta::List(_) | syn::Meta::NameValue(_) => {
263                    bail_span!(attr, "malformed #[wasm_bindgen] attribute")
264                }
265            };
266            let mut attrs: BindgenAttrs = syn::parse2(tokens)?;
267            ret.attrs.append(&mut attrs.attrs);
268            attrs.check_used();
269        }
270        // Validate the shape of any computed-key (`[Symbol.<ident>]`) value
271        // appearing in `js_name`. Position-specific rejection (e.g. on
272        // structs) happens later at the convert sites; here we only enforce
273        // the global shape so that an unrelated attribute typo is reported at
274        // the value's span.
275        for (_, attr) in &ret.attrs {
276            if let BindgenAttr::JsName(_, value, span) = attr {
277                validate_computed_key("js_name", value, *span)?;
278            }
279        }
280        Ok(ret)
281    }
282
283    fn get_thread_local(&self) -> Result<Option<ThreadLocal>, Diagnostic> {
284        let mut thread_local = self.thread_local_v2().map(|_| ThreadLocal::V2);
285
286        if let Some(span) = self.thread_local() {
287            if thread_local.is_some() {
288                return Err(Diagnostic::span_error(
289                    *span,
290                    "`thread_local` can't be used with `thread_local_v2`",
291                ));
292            } else {
293                thread_local = Some(ThreadLocal::V1)
294            }
295        }
296
297        Ok(thread_local)
298    }
299
300    /// Like [`Self::js_name`], but rejects computed-key (`[Symbol.<ident>]`)
301    /// values with a position-specific error message. Used by syntactic
302    /// positions that don't make sense as a JS-side symbol property
303    /// (structs, enums, types, statics, free functions, function args).
304    fn js_name_no_symbol(&self, position: &str) -> Result<Option<&str>, Diagnostic> {
305        match self.js_name() {
306            Some((value, span)) if is_computed_key(value) => Err(Diagnostic::span_error(
307                span,
308                format!("{position} do not support symbols in js_name"),
309            )),
310            Some((value, _)) => Ok(Some(value)),
311            None => Ok(None),
312        }
313    }
314
315    attrgen!(methods);
316}
317
318impl Default for BindgenAttrs {
319    fn default() -> BindgenAttrs {
320        // Add 1 to the list of parsed attribute sets. We'll use this counter to
321        // sanity check that we call `check_used` an appropriate number of
322        // times.
323        ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1));
324        BindgenAttrs { attrs: Vec::new() }
325    }
326}
327
328impl Parse for BindgenAttrs {
329    fn parse(input: ParseStream) -> SynResult<Self> {
330        let mut attrs = BindgenAttrs::default();
331        if input.is_empty() {
332            return Ok(attrs);
333        }
334
335        let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
336        attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect();
337        Ok(attrs)
338    }
339}
340
341macro_rules! gen_bindgen_attr {
342    ($(($method:ident, $_:literal, $($variants:tt)*),)*) => {
343        /// The possible attributes in the `#[wasm_bindgen]`.
344        #[cfg_attr(feature = "extra-traits", derive(Debug))]
345        pub enum BindgenAttr {
346            $($($variants)*,)*
347        }
348    }
349}
350attrgen!(gen_bindgen_attr);
351
352impl Parse for BindgenAttr {
353    fn parse(input: ParseStream) -> SynResult<Self> {
354        let original = input.fork();
355        let attr: AnyIdent = input.parse()?;
356        let attr = attr.0;
357        let attr_span = attr.span();
358        let attr_string = attr.to_string();
359        let raw_attr_string = format!("r#{attr_string}");
360
361        macro_rules! parsers {
362            ($(($name:ident, $_:literal, $($contents:tt)*),)*) => {
363                $(
364                    if attr_string == stringify!($name) || raw_attr_string == stringify!($name) {
365                        parsers!(
366                            @parser
367                            $($contents)*
368                        );
369                    }
370                )*
371            };
372
373            (@parser $variant:ident(Span)) => ({
374                return Ok(BindgenAttr::$variant(attr_span));
375            });
376
377            (@parser $variant:ident(Span, Ident)) => ({
378                input.parse::<Token![=]>()?;
379                // Accept either a bare ident or a string literal containing one.
380                // The string-literal form lets `rustfmt` format the surrounding
381                // attribute, since rustfmt only handles `name = <literal>` arms.
382                let ident = if input.peek(syn::LitStr) {
383                    let litstr = input.parse::<syn::LitStr>()?;
384                    syn::parse_str::<Ident>(&litstr.value()).map_err(|e| {
385                        syn::Error::new(litstr.span(), format!("expected an identifier: {e}"))
386                    })?
387                } else {
388                    input.parse::<AnyIdent>()?.0
389                };
390                return Ok(BindgenAttr::$variant(attr_span, ident))
391            });
392
393            (@parser $variant:ident(Span, Option<String>)) => ({
394                if input.parse::<Token![=]>().is_ok() {
395                    if input.peek(syn::LitStr) {
396                        let litstr = input.parse::<syn::LitStr>()?;
397                        return Ok(BindgenAttr::$variant(attr_span, Some(litstr.value())))
398                    }
399
400                    let ident = input.parse::<AnyIdent>()?.0;
401                    return Ok(BindgenAttr::$variant(attr_span, Some(ident.to_string())))
402                } else {
403                    return Ok(BindgenAttr::$variant(attr_span, None));
404                }
405            });
406
407            (@parser $variant:ident(Span, syn::Path)) => ({
408                input.parse::<Token![=]>()?;
409                // Accept either a bare path or a string literal containing one.
410                // The string-literal form lets `rustfmt` format the surrounding
411                // attribute, since rustfmt only handles `name = <literal>` arms.
412                let path = if input.peek(syn::LitStr) {
413                    let litstr = input.parse::<syn::LitStr>()?;
414                    syn::parse_str::<syn::Path>(&litstr.value()).map_err(|e| {
415                        syn::Error::new(litstr.span(), format!("expected a path: {e}"))
416                    })?
417                } else {
418                    input.parse()?
419                };
420                return Ok(BindgenAttr::$variant(attr_span, path));
421            });
422
423            (@parser $variant:ident(Span, syn::Expr)) => ({
424                input.parse::<Token![=]>()?;
425                return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
426            });
427
428            (@parser $variant:ident(Span, String, Span)) => ({
429                input.parse::<Token![=]>()?;
430                let (val, span) = match input.parse::<syn::LitStr>() {
431                    Ok(str) => (str.value(), str.span()),
432                    Err(_) => {
433                        let ident = input.parse::<AnyIdent>()?.0;
434                        (ident.to_string(), ident.span())
435                    }
436                };
437                return Ok(BindgenAttr::$variant(attr_span, val, span))
438            });
439
440            (@parser $variant:ident(Span, JsNamespace, Vec<Span>)) => ({
441                input.parse::<Token![=]>()?;
442                let (vals, spans) = match input.parse::<syn::ExprArray>() {
443                    Ok(exprs) => {
444                        let mut vals = vec![];
445                        let mut spans = vec![];
446
447                        for expr in exprs.elems.iter() {
448                            if let syn::Expr::Lit(syn::ExprLit {
449                                lit: syn::Lit::Str(ref str),
450                                ..
451                            }) = expr {
452                                vals.push(str.value());
453                                spans.push(str.span());
454                            } else {
455                                return Err(syn::Error::new(expr.span(), "expected string literals"));
456                            }
457                        }
458
459                        if vals.is_empty() {
460                            return Err(syn::Error::new(exprs.span(), "Empty namespace lists are not allowed."));
461                        }
462
463                        (vals, spans)
464                    },
465                    // Try parsing as a string literal, then fall back to identifier
466                    Err(_) => match input.parse::<syn::LitStr>() {
467                        Ok(str) => (vec![str.value()], vec![str.span()]),
468                        Err(_) => {
469                            let ident = input.parse::<AnyIdent>()?.0;
470                            (vec![ident.to_string()], vec![ident.span()])
471                        }
472                    }
473                };
474
475                let first = &vals[0];
476                if is_non_value_js_keyword(first) && first != "default" {
477                    let msg = format!("Namespace cannot start with the JS keyword `{}`", first);
478                    return Err(syn::Error::new(spans[0], msg));
479                }
480
481                return Ok(BindgenAttr::$variant(attr_span, JsNamespace(vals), spans))
482            });
483        }
484
485        attrgen!(parsers);
486
487        Err(original.error(if attr_string.starts_with('_') {
488            "unknown attribute: it's safe to remove unused attributes entirely."
489        } else {
490            "unknown attribute"
491        }))
492    }
493}
494
495/// Returns whether `name` is a computed-property-key form, i.e. it
496/// starts with `[` and ends with `]`. The renderer treats such names
497/// as a JS expression to splice in directly (e.g. `[Symbol.iterator]`).
498pub(crate) fn is_computed_key(name: &str) -> bool {
499    let bytes = name.as_bytes();
500    bytes.len() >= 2 && bytes[0] == b'[' && bytes[bytes.len() - 1] == b']'
501}
502
503/// Validate a string-literal value used in `js_name`. If it looks like a
504/// computed key (`[...]`) we require it to be of the form
505/// `[Symbol.<ident>]`. Plain identifier-shaped or dotted-path values are
506/// passed through unchanged.
507fn validate_computed_key(attr: &str, value: &str, span: Span) -> Result<(), Diagnostic> {
508    if !is_computed_key(value) {
509        return Ok(());
510    }
511    let inner = &value[1..value.len() - 1];
512    let symbol_name = inner.strip_prefix("Symbol.");
513    let ok = symbol_name
514        .map(|n| !n.is_empty() && n.chars().all(|c| c.is_ascii_alphanumeric() || c == '_'))
515        .unwrap_or(false);
516    if ok {
517        Ok(())
518    } else {
519        Err(Diagnostic::span_error(
520            span,
521            format!("the only computed-key form supported in `{attr}` is `\"[Symbol.<ident>]\"`"),
522        ))
523    }
524}
525
526struct AnyIdent(Ident);
527
528impl Parse for AnyIdent {
529    fn parse(input: ParseStream) -> SynResult<Self> {
530        input.step(|cursor| match cursor.ident() {
531            Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)),
532            None => Err(cursor.error("expected an identifier")),
533        })
534    }
535}
536
537/// Conversion trait with context.
538///
539/// Used to convert syn tokens into an AST, that we can then use to generate glue code. The context
540/// (`Ctx`) is used to pass in the attributes from the `#[wasm_bindgen]`, if needed.
541pub(crate) trait ConvertToAst<Ctx> {
542    /// What we are converting to.
543    type Target;
544    /// Convert into our target.
545    ///
546    /// Since this is used in a procedural macro, use panic to fail.
547    fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
548}
549
550impl ConvertToAst<&ast::Program> for &mut syn::ItemStruct {
551    type Target = ast::Struct;
552
553    fn convert(self, program: &ast::Program) -> Result<Self::Target, Diagnostic> {
554        if !self.generics.params.is_empty() {
555            bail_span!(
556                self.generics,
557                "structs with #[wasm_bindgen] cannot have lifetime or \
558                 type parameters currently"
559            );
560        }
561        let attrs = BindgenAttrs::find(&mut self.attrs)?;
562
563        // the `wasm_bindgen` option has been used before
564        let _ = attrs.wasm_bindgen();
565
566        // Collect any `extends = Path` attributes on the struct. For Rust
567        // exports we only support a single parent in this first pass, and
568        // we reject self-extends (`extends = Foo` on `struct Foo`) — the
569        // injected `parent: Parent<Foo>` field is infinitely-sized at the
570        // type level, and the emitted JS would be `class Foo extends Foo`
571        // which fails at module load with a TDZ ReferenceError.
572        let mut extends: Option<syn::Path> = None;
573        for (used, attr) in attrs.attrs.iter() {
574            if let BindgenAttr::Extends(span, path) = attr {
575                if extends.is_some() {
576                    return Err(Diagnostic::span_error(
577                        *span,
578                        "`extends` may only be specified once on an exported struct",
579                    ));
580                }
581                if path.is_ident(&self.ident) || path.is_ident("Self") {
582                    return Err(Diagnostic::span_error(
583                        *span,
584                        "`extends = Self` (or naming the same struct) is not allowed; \
585                         a class cannot inherit from itself",
586                    ));
587                }
588                extends = Some(path.clone());
589                used.set(true);
590            }
591        }
592
593        // `extends_js_class` declares the parent's JS-side `js_name`.
594        // Required when the parent struct uses `js_name`; defaults to the
595        // last segment of the `extends` Rust path (matching the no-rename
596        // case). `extends_js_namespace` is the analogue for the parent's
597        // `js_namespace`. Both are validated against the registered parent
598        // struct's actual identity post-link in cli-support.
599        let extends_js_class: Option<String> = attrs
600            .extends_js_class()
601            .map(|(s, _)| s.to_string())
602            .or_else(|| {
603                extends
604                    .as_ref()
605                    .and_then(|p| p.segments.last().map(|seg| seg.ident.unraw().to_string()))
606            });
607        let extends_js_namespace: Option<Vec<String>> =
608            attrs.extends_js_namespace().map(|(ns, _)| ns.0);
609        if extends_js_class.is_some() && extends.is_none() {
610            return Err(Diagnostic::span_error(
611                self.ident.span(),
612                "`extends_js_class` requires `extends = ...` to be set too \
613                 (extends_js_class declares the parent's JS identity; \
614                 extends declares the parent's Rust type)",
615            ));
616        }
617        if extends_js_namespace.is_some() && extends.is_none() {
618            return Err(Diagnostic::span_error(
619                self.ident.span(),
620                "`extends_js_namespace` requires `extends = ...` to be set too \
621                 (extends_js_namespace declares the parent's JS namespace; \
622                 extends declares the parent's Rust type)",
623            ));
624        }
625
626        let mut fields = Vec::new();
627        let js_name = attrs
628            .js_name_no_symbol("structs with #[wasm_bindgen]")?
629            .map(|s| s.to_string())
630            .unwrap_or(self.ident.unraw().to_string());
631        if is_js_keyword(&js_name) && js_name != "default" {
632            bail_span!(
633                self.ident,
634                "struct cannot use the JS keyword `{}` as its name",
635                js_name
636            );
637        }
638
639        let is_inspectable = attrs.inspectable().is_some();
640        let getter_with_clone = attrs.getter_with_clone();
641        let js_namespace = attrs.js_namespace().map(|(ns, _)| ns.0);
642        let qualified_name = wasm_bindgen_shared::qualified_name(js_namespace.as_deref(), &js_name);
643        let mut parent_count = 0usize;
644        for (i, field) in self.fields.iter_mut().enumerate() {
645            let field_attrs = BindgenAttrs::find(&mut field.attrs)?;
646            // The parent field is identified purely by its type being
647            // `Parent<T>` — no dedicated attribute is required. The inner
648            // `T` is verified by the Rust type checker when the upcast
649            // helper is compiled.
650            let is_parent = is_parent_wrapper_type(&field.ty);
651            if is_parent {
652                parent_count += 1;
653            }
654
655            let is_public = matches!(field.vis, syn::Visibility::Public(..));
656            if !is_public && !is_parent {
657                field_attrs.check_used();
658                continue;
659            }
660
661            let (js_field_name, member) = match &field.ident {
662                Some(ident) => (ident.unraw().to_string(), syn::Member::Named(ident.clone())),
663                None => (i.to_string(), syn::Member::Unnamed(i.into())),
664            };
665
666            if field_attrs.skip().is_some() && !is_parent {
667                field_attrs.check_used();
668                continue;
669            }
670
671            let js_field_name = match field_attrs.js_name() {
672                Some((name, _)) => name.to_string(),
673                None => js_field_name,
674            };
675
676            let comments = extract_doc_comments(&field.attrs);
677            let getter = wasm_bindgen_shared::struct_field_get(&qualified_name, &js_field_name);
678            let setter = wasm_bindgen_shared::struct_field_set(&qualified_name, &js_field_name);
679
680            fields.push(ast::StructField {
681                rust_name: member,
682                js_name: js_field_name,
683                struct_name: self.ident.clone(),
684                readonly: field_attrs.readonly().is_some(),
685                ty: field.ty.clone(),
686                getter: Ident::new(&getter, Span::call_site()),
687                setter: Ident::new(&setter, Span::call_site()),
688                comments,
689                generate_typescript: field_attrs.skip_typescript().is_none(),
690                generate_jsdoc: field_attrs.skip_jsdoc().is_none(),
691                getter_with_clone: field_attrs
692                    .getter_with_clone()
693                    .or(getter_with_clone)
694                    .copied(),
695                is_parent,
696                wasm_bindgen: program.wasm_bindgen.clone(),
697            });
698            field_attrs.check_used();
699        }
700
701        // The `parent: Parent<T>` field is injected by `inject_parent_field`
702        // before the struct reaches this point, so a struct with `extends`
703        // should always have exactly one `Parent<T>` field. A struct without
704        // `extends` can never have one — `inject_parent_field` rejects
705        // user-declared `Parent<T>` fields.
706        match (&extends, parent_count) {
707            (Some(_), 1) => {}
708            (None, 0) => {}
709            _ => {
710                return Err(Diagnostic::span_error(
711                    self.ident.span(),
712                    "internal error: inconsistent Parent<T> field state; \
713                     this should have been caught by inject_parent_field",
714                ));
715            }
716        }
717
718        let generate_typescript = attrs.skip_typescript().is_none();
719        let private = attrs.private().is_some();
720        let comments: Vec<String> = extract_doc_comments(&self.attrs);
721        attrs.check_used();
722        Ok(ast::Struct {
723            rust_name: self.ident.clone(),
724            js_name,
725            qualified_name,
726            fields,
727            comments,
728            is_inspectable,
729            generate_typescript,
730            private,
731            js_namespace,
732            extends,
733            extends_js_class,
734            extends_js_namespace,
735            wasm_bindgen: program.wasm_bindgen.clone(),
736        })
737    }
738}
739
740fn get_ty(mut ty: &syn::Type) -> &syn::Type {
741    while let syn::Type::Group(g) = ty {
742        ty = &g.elem;
743    }
744
745    ty
746}
747
748/// Injects a hidden `parent: <wasm_bindgen>::Parent<ParentType>` field into
749/// a struct that declares `#[wasm_bindgen(extends = ParentType)]`, and
750/// rejects any user-declared `Parent<T>` field on any struct — the macro
751/// owns that field.
752///
753/// Called from the macro entry point before the struct tokens are re-emitted,
754/// so downstream type-checking sees the injected field.
755pub fn inject_parent_field(
756    s: &mut syn::ItemStruct,
757    extends_path: Option<&syn::Path>,
758    wasm_bindgen: &syn::Path,
759) -> Result<(), Diagnostic> {
760    for field in s.fields.iter() {
761        if is_parent_wrapper_type(&field.ty) {
762            let path_str = path_to_string(field_type_path(&field.ty));
763            let msg = if extends_path.is_some() {
764                format!(
765                    "do not declare a `{path_str}` field; the \
766                     `#[wasm_bindgen(extends = ...)]` macro injects \
767                     `parent: wasm_bindgen::Parent<...>` automatically"
768                )
769            } else {
770                format!(
771                    "`{path_str}` looks like `wasm_bindgen::Parent<T>`, \
772                     which is reserved for the \
773                     `#[wasm_bindgen(extends = ...)]` macro. If you intended \
774                     a different `Parent` type, qualify it (e.g. \
775                     `my_crate::tree::Parent`) so this check skips it."
776                )
777            };
778            bail_span!(&field.ty, "{}", msg);
779        }
780    }
781
782    let Some(parent_path) = extends_path else {
783        return Ok(());
784    };
785
786    for field in s.fields.iter() {
787        if let Some(ident) = &field.ident {
788            if ident == "parent" {
789                bail_span!(
790                    ident,
791                    "struct with `#[wasm_bindgen(extends = ...)]` cannot \
792                     have a field named `parent`; the macro injects one"
793                );
794            }
795        }
796    }
797
798    let injected: syn::Field = syn::parse_quote! {
799        parent: #wasm_bindgen::Parent<#parent_path>
800    };
801
802    match &mut s.fields {
803        syn::Fields::Named(named) => {
804            named.named.insert(0, injected);
805        }
806        syn::Fields::Unit => {
807            let mut named = syn::FieldsNamed {
808                brace_token: Default::default(),
809                named: syn::punctuated::Punctuated::new(),
810            };
811            named.named.push(injected);
812            s.fields = syn::Fields::Named(named);
813            s.semi_token = None;
814        }
815        syn::Fields::Unnamed(_) => {
816            bail_span!(
817                &s.ident,
818                "tuple structs cannot use `#[wasm_bindgen(extends = ...)]`; \
819                 use a named-field struct"
820            );
821        }
822    }
823
824    Ok(())
825}
826
827/// Returns true if `ty` looks like `wasm_bindgen::Parent<T>`. We can't
828/// fully resolve paths at macro-expansion time, so we accept a small set
829/// of unambiguous spellings that name `wasm_bindgen::Parent`:
830///
831/// * `Parent<T>` — the unqualified form (assumes the user has imported it,
832///   or hasn't defined a different type with the same name in scope).
833/// * `wasm_bindgen::Parent<T>` and `::wasm_bindgen::Parent<T>` — the
834///   canonical fully-qualified spellings.
835/// * `crate::Parent<T>` — re-export from the crate root, plausible only
836///   in user crates that re-export `wasm_bindgen::Parent`.
837///
838/// This is intentionally conservative: a user with their own
839/// `my_crate::tree::Parent<T>` field is left alone.
840fn is_parent_wrapper_type(ty: &syn::Type) -> bool {
841    let path = match field_type_path(ty) {
842        Some(p) => p,
843        None => return false,
844    };
845    let last = match path.segments.last() {
846        Some(seg) => seg,
847        None => return false,
848    };
849    if last.ident != "Parent" {
850        return false;
851    }
852    let one_type_arg = match &last.arguments {
853        syn::PathArguments::AngleBracketed(args) => {
854            args.args
855                .iter()
856                .filter(|a| matches!(a, syn::GenericArgument::Type(_)))
857                .count()
858                == 1
859        }
860        _ => false,
861    };
862    if !one_type_arg {
863        return false;
864    }
865    // Match only the unambiguous spellings that name wasm_bindgen::Parent.
866    let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
867    let leading_colon = path.leading_colon.is_some();
868    match (leading_colon, segs.as_slice()) {
869        (false, [only]) if only == "Parent" => true,
870        (false, [first, last]) if first == "wasm_bindgen" && last == "Parent" => true,
871        (true, [first, last]) if first == "wasm_bindgen" && last == "Parent" => true,
872        (false, [first, last]) if first == "crate" && last == "Parent" => true,
873        _ => false,
874    }
875}
876
877/// Extract the path of a `Type::Path`, peeling off any leading groups.
878fn field_type_path(ty: &syn::Type) -> Option<&syn::Path> {
879    match get_ty(ty) {
880        syn::Type::Path(p) => Some(&p.path),
881        _ => None,
882    }
883}
884
885/// Stringify a path's segment idents (without generic args) for diagnostics.
886fn path_to_string(path: Option<&syn::Path>) -> String {
887    let Some(path) = path else {
888        return String::from("<unknown>");
889    };
890    let mut out = String::new();
891    if path.leading_colon.is_some() {
892        out.push_str("::");
893    }
894    let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
895    out.push_str(&segs.join("::"));
896    out
897}
898
899fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
900    while let syn::Expr::Group(g) = expr {
901        expr = &g.expr;
902    }
903
904    expr
905}
906
907impl<'a>
908    ConvertToAst<(
909        &ast::Program,
910        BindgenAttrs,
911        &'a Option<ast::ImportModule>,
912        bool,
913    )> for syn::ForeignItemFn
914{
915    type Target = ast::ImportKind;
916
917    fn convert(
918        mut self,
919        (program, opts, module, block_slice_to_array): (
920            &ast::Program,
921            BindgenAttrs,
922            &'a Option<ast::ImportModule>,
923            bool,
924        ),
925    ) -> Result<Self::Target, Diagnostic> {
926        // `slice_to_array` is inherited from the enclosing `extern "C"`
927        // block, optionally OR'd with a fn-level attribute, which then
928        // applies to every `&[T]` / `Option<&[T]>` argument unless an
929        // individual arg overrides it via its own attribute (the
930        // attribute is additive — you can only opt in, not out).
931        let fn_slice_to_array = block_slice_to_array || opts.slice_to_array().is_some();
932        let args_attrs = extract_args_attrs(&mut self.sig, fn_slice_to_array)?;
933        let (mut wasm, _) = function_from_decl(
934            &self.sig.ident,
935            &opts,
936            self.sig.clone(),
937            self.attrs.clone(),
938            self.vis.clone(),
939            FunctionPosition::Extern,
940            Some(args_attrs),
941        )?;
942        let catch = opts.catch().is_some();
943        let variadic = opts.variadic().is_some();
944        let js_ret = if catch {
945            // TODO: this assumes a whole bunch:
946            //
947            // * The outer type is actually a `Result`
948            // * The error type is a `JsValue`
949            // * The actual type is the first type parameter
950            //
951            // should probably fix this one day...
952            extract_first_ty_param(wasm.ret.as_ref().map(|ret| &ret.r#type))?
953        } else {
954            wasm.ret.as_ref().map(|ret| ret.r#type.clone())
955        };
956
957        let operation_kind = operation_kind(&opts);
958
959        let kind = if opts.method().is_some() {
960            let class = wasm.arguments.first().ok_or_else(|| {
961                err_span!(self, "imported methods must have at least one argument")
962            })?;
963            let class = match get_ty(&class.pat_type.ty) {
964                syn::Type::Reference(syn::TypeReference {
965                    mutability: None,
966                    elem,
967                    ..
968                }) => &**elem,
969                _ => bail_span!(
970                    class.pat_type.ty,
971                    "first argument of method must be a shared reference"
972                ),
973            };
974            let class_ty = get_ty(class);
975            let js_class = opts.js_class().map(|p| p.0.to_string());
976            let kind = ast::MethodKind::Operation(ast::Operation {
977                is_static: false,
978                kind: operation_kind,
979            });
980
981            let class_name = match class_ty {
982                syn::Type::Path(syn::TypePath {
983                    qself: None,
984                    ref path,
985                }) => path,
986                _ => bail_span!(class_ty, "first argument of method must be a path"),
987            };
988
989            let class_name_str = js_class.map(Ok).unwrap_or_else(|| {
990                extract_path_ident(class_name, true).map(|i| i.unraw().to_string())
991            })?;
992
993            ast::ImportFunctionKind::Method {
994                class: class_name_str,
995                ty: class_ty.clone(),
996                kind,
997            }
998        } else if let Some(cls) = opts.static_method_of() {
999            let class = opts
1000                .js_class()
1001                .map(|p| p.0.into())
1002                .unwrap_or_else(|| cls.unraw().to_string());
1003
1004            let ty = syn::Type::Path(syn::TypePath {
1005                qself: None,
1006                path: syn::Path {
1007                    leading_colon: None,
1008                    segments: std::iter::once(syn::PathSegment {
1009                        ident: cls.clone(),
1010                        arguments: syn::PathArguments::None,
1011                    })
1012                    .collect(),
1013                },
1014            });
1015
1016            let kind = ast::MethodKind::Operation(ast::Operation {
1017                is_static: true,
1018                kind: operation_kind,
1019            });
1020
1021            ast::ImportFunctionKind::Method { class, ty, kind }
1022        } else if opts.constructor().is_some() {
1023            let class = match js_ret {
1024                Some(ref ty) => ty,
1025                _ => bail_span!(self, "constructor returns must be bare types"),
1026            };
1027            let class_name = match get_ty(class) {
1028                syn::Type::Path(syn::TypePath {
1029                    qself: None,
1030                    ref path,
1031                }) => path,
1032                _ => bail_span!(self, "return value of constructor must be a bare path"),
1033            };
1034            let class_name = extract_path_ident(class_name, true)?;
1035            let class_name = opts
1036                .js_class()
1037                .map(|p| p.0.into())
1038                .unwrap_or_else(|| class_name.unraw().to_string());
1039
1040            ast::ImportFunctionKind::Method {
1041                class: class_name,
1042                ty: class.clone(),
1043                kind: ast::MethodKind::Constructor,
1044            }
1045        } else {
1046            ast::ImportFunctionKind::Normal
1047        };
1048
1049        // Validate that reexport is not used on methods/constructors/static methods
1050        if opts.reexport().is_some() && matches!(kind, ast::ImportFunctionKind::Method { .. }) {
1051            return Err(Diagnostic::span_error(
1052                self.sig.ident.span(),
1053                "`reexport` cannot be used on methods, constructors, or static methods. \
1054                Use `reexport` on the type import instead.",
1055            ));
1056        }
1057
1058        let shim = {
1059            let ns = match kind {
1060                ast::ImportFunctionKind::Normal => (0, "n"),
1061                ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
1062            };
1063            // Include cfg attributes in the hash so that functions with different
1064            // cfg gates get different shim names, even if their signatures are identical.
1065            let cfg_attrs: String = self
1066                .attrs
1067                .iter()
1068                .filter(|attr| attr.path().is_ident("cfg"))
1069                .map(|attr| attr.to_token_stream().to_string())
1070                .collect();
1071            let data = (
1072                ns,
1073                self.sig.to_token_stream().to_string(),
1074                module,
1075                cfg_attrs,
1076            );
1077            format!(
1078                "__wbg_{}_{}",
1079                wasm.name
1080                    .chars()
1081                    .filter(|&c| c.is_ascii_alphanumeric() || c == '_')
1082                    .collect::<String>(),
1083                ShortHash(data)
1084            )
1085        };
1086        if let Some(span) = opts.r#final() {
1087            if opts.structural().is_some() {
1088                let msg = "cannot specify both `structural` and `final`";
1089                return Err(Diagnostic::span_error(*span, msg));
1090            }
1091        }
1092        let assert_no_shim = opts.assert_no_shim().is_some();
1093
1094        let mut doc_comment = String::new();
1095        // Extract the doc comments from our list of attributes.
1096        wasm.rust_attrs.retain(|attr| {
1097            /// Returns the contents of the passed `#[doc = "..."]` attribute,
1098            /// or `None` if it isn't one.
1099            fn get_docs(attr: &syn::Attribute) -> Option<String> {
1100                if attr.path().is_ident("doc") {
1101                    if let syn::Meta::NameValue(syn::MetaNameValue {
1102                        value:
1103                            syn::Expr::Lit(syn::ExprLit {
1104                                lit: Lit::Str(str), ..
1105                            }),
1106                        ..
1107                    }) = &attr.meta
1108                    {
1109                        Some(str.value())
1110                    } else {
1111                        None
1112                    }
1113                } else {
1114                    None
1115                }
1116            }
1117
1118            if let Some(docs) = get_docs(attr) {
1119                if !doc_comment.is_empty() {
1120                    // Add newlines between the doc comments
1121                    doc_comment.push('\n');
1122                }
1123                // Add this doc comment to the complete docs
1124                doc_comment.push_str(&docs);
1125
1126                // Remove it from the list of regular attributes
1127                false
1128            } else {
1129                true
1130            }
1131        });
1132
1133        validate_generics(&self.sig.generics)?;
1134
1135        let ret = ast::ImportKind::Function(ast::ImportFunction {
1136            function: wasm,
1137            assert_no_shim,
1138            kind,
1139            js_ret,
1140            catch,
1141            variadic,
1142            structural: opts.structural().is_some() || opts.r#final().is_none(),
1143            rust_name: self.sig.ident,
1144            shim: Ident::new(&shim, Span::call_site()),
1145            doc_comment,
1146            wasm_bindgen: program.wasm_bindgen.clone(),
1147            wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1148            js_sys: program.js_sys.clone(),
1149            generics: self.sig.generics,
1150        });
1151        opts.check_used();
1152
1153        Ok(ret)
1154    }
1155}
1156
1157impl ConvertToAst<(&ast::Program, BindgenAttrs)> for syn::ForeignItemType {
1158    type Target = ast::ImportKind;
1159
1160    fn convert(
1161        self,
1162        (program, attrs): (&ast::Program, BindgenAttrs),
1163    ) -> Result<Self::Target, Diagnostic> {
1164        let js_name = attrs
1165            .js_name_no_symbol("extern types with #[wasm_bindgen]")?
1166            .map_or_else(|| self.ident.unraw().to_string(), |s| s.to_string());
1167        let typescript_type = attrs.typescript_type().map(|s| s.0.to_string());
1168        let is_type_of = attrs.is_type_of().cloned();
1169        let unraw_ident = self.ident.unraw();
1170        let hash = ShortHash((attrs.js_namespace().map(|(ns, _)| ns.0), &unraw_ident));
1171        let shim = format!("__wbg_instanceof_{unraw_ident}_{hash}");
1172        let mut extends = Vec::new();
1173        let mut vendor_prefixes = Vec::new();
1174        let no_deref = attrs.no_deref().is_some();
1175        let no_upcast = attrs.no_upcast().is_some();
1176        let no_promising = attrs.no_promising().is_some();
1177        let no_into_js_generic = attrs.no_into_js_generic().is_some();
1178        for (used, attr) in attrs.attrs.iter() {
1179            match attr {
1180                BindgenAttr::Extends(_, e) => {
1181                    extends.push(e.clone());
1182                    used.set(true);
1183                }
1184                BindgenAttr::VendorPrefix(_, e) => {
1185                    vendor_prefixes.push(e.clone());
1186                    used.set(true);
1187                }
1188                _ => {}
1189            }
1190        }
1191
1192        attrs.check_used();
1193        validate_generics(&self.generics)?;
1194
1195        // Ensure defaults are set for all generic type params on imported class definitions.
1196        // JsValue as the default default.
1197        let mut generics = None;
1198        for (n, param) in self.generics.type_params().enumerate() {
1199            if param.default.is_none() {
1200                let generics = generics.get_or_insert_with(|| self.generics.clone());
1201                let type_param_mut = generics.type_params_mut().nth(n).unwrap();
1202                type_param_mut.default = Some(syn::parse_quote! { JsValue });
1203            }
1204        }
1205
1206        Ok(ast::ImportKind::Type(ast::ImportType {
1207            vis: self.vis,
1208            attrs: self.attrs,
1209            doc_comment: None,
1210            instanceof_shim: shim,
1211            is_type_of,
1212            rust_name: self.ident,
1213            typescript_type,
1214            js_name,
1215            extends,
1216            vendor_prefixes,
1217            no_deref,
1218            no_upcast,
1219            no_promising,
1220            no_into_js_generic,
1221            wasm_bindgen: program.wasm_bindgen.clone(),
1222            generics: generics.unwrap_or(self.generics),
1223        }))
1224    }
1225}
1226
1227impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
1228    for syn::ForeignItemStatic
1229{
1230    type Target = ast::ImportKind;
1231
1232    fn convert(
1233        self,
1234        (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
1235    ) -> Result<Self::Target, Diagnostic> {
1236        if let syn::StaticMutability::Mut(_) = self.mutability {
1237            bail_span!(self.mutability, "cannot import mutable globals yet")
1238        }
1239
1240        if let Some(span) = opts.static_string() {
1241            return Err(Diagnostic::span_error(
1242                *span,
1243                "static strings require a string literal",
1244            ));
1245        }
1246
1247        let default_name = self.ident.unraw().to_string();
1248        let js_name = opts
1249            .js_name_no_symbol("statics with #[wasm_bindgen]")?
1250            .unwrap_or(&default_name)
1251            .to_string();
1252        let unraw_ident = self.ident.unraw();
1253        let hash = ShortHash((&js_name, module, &unraw_ident));
1254        let shim = format!("__wbg_static_accessor_{unraw_ident}_{hash}");
1255        let thread_local = opts.get_thread_local()?;
1256
1257        opts.check_used();
1258        Ok(ast::ImportKind::Static(ast::ImportStatic {
1259            ty: *self.ty,
1260            vis: self.vis,
1261            rust_name: self.ident.clone(),
1262            js_name,
1263            shim: Ident::new(&shim, Span::call_site()),
1264            wasm_bindgen: program.wasm_bindgen.clone(),
1265            thread_local,
1266        }))
1267    }
1268}
1269
1270impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
1271    for syn::ItemStatic
1272{
1273    type Target = ast::ImportKind;
1274
1275    fn convert(
1276        self,
1277        (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
1278    ) -> Result<Self::Target, Diagnostic> {
1279        if let syn::StaticMutability::Mut(_) = self.mutability {
1280            bail_span!(self.mutability, "cannot import mutable globals yet")
1281        }
1282
1283        let string = if let syn::Expr::Lit(syn::ExprLit {
1284            lit: syn::Lit::Str(string),
1285            ..
1286        }) = *self.expr.clone()
1287        {
1288            string.value()
1289        } else {
1290            bail_span!(
1291                self.expr,
1292                "statics with a value can only be string literals"
1293            )
1294        };
1295
1296        if opts.static_string().is_none() {
1297            bail_span!(
1298                self,
1299                "static strings require `#[wasm_bindgen(static_string)]`"
1300            )
1301        }
1302
1303        let thread_local = if let Some(thread_local) = opts.get_thread_local()? {
1304            thread_local
1305        } else {
1306            bail_span!(
1307                self,
1308                "static strings require `#[wasm_bindgen(thread_local_v2)]`"
1309            )
1310        };
1311
1312        let unraw_ident = self.ident.unraw();
1313        let hash = ShortHash((&module, &unraw_ident));
1314        let shim = format!("__wbg_string_{unraw_ident}_{hash}");
1315        opts.check_used();
1316        Ok(ast::ImportKind::String(ast::ImportString {
1317            ty: *self.ty,
1318            vis: self.vis,
1319            rust_name: self.ident.clone(),
1320            shim: Ident::new(&shim, Span::call_site()),
1321            wasm_bindgen: program.wasm_bindgen.clone(),
1322            js_sys: program.js_sys.clone(),
1323            string,
1324            thread_local,
1325        }))
1326    }
1327}
1328
1329impl ConvertToAst<(BindgenAttrs, Vec<FnArgAttrs>)> for syn::ItemFn {
1330    type Target = ast::Function;
1331
1332    fn convert(
1333        self,
1334        (attrs, args_attrs): (BindgenAttrs, Vec<FnArgAttrs>),
1335    ) -> Result<Self::Target, Diagnostic> {
1336        match self.vis {
1337            syn::Visibility::Public(_) => {}
1338            _ if attrs.start().is_some() => {}
1339            _ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
1340        }
1341        if self.sig.constness.is_some() {
1342            bail_span!(
1343                self.sig.constness,
1344                "can only #[wasm_bindgen] non-const functions"
1345            );
1346        }
1347
1348        let (mut ret, _) = function_from_decl(
1349            &self.sig.ident,
1350            &attrs,
1351            self.sig.clone(),
1352            self.attrs,
1353            self.vis,
1354            FunctionPosition::Free,
1355            Some(args_attrs),
1356        )?;
1357        attrs.check_used();
1358
1359        // TODO: Deprecate this for next major
1360        // Due to legacy behavior, we need to escape all keyword identifiers as
1361        // `_keyword`, except `default`
1362        if is_js_keyword(&ret.name) && ret.name != "default" {
1363            ret.name = format!("_{}", ret.name);
1364        }
1365
1366        Ok(ret)
1367    }
1368}
1369
1370/// Returns whether `self` is passed by reference or by value.
1371fn get_self_method(r: syn::Receiver) -> ast::MethodSelf {
1372    // The tricky part here is that `r` can have many forms. E.g. `self`,
1373    // `&self`, `&mut self`, `self: Self`, `self: &Self`, `self: &mut Self`,
1374    // `self: Box<Self>`, `self: Rc<Self>`, etc.
1375    // Luckily, syn always populates the `ty` field with the type of `self`, so
1376    // e.g. `&self` gets the type `&Self`. So we only have check whether the
1377    // type is a reference or not.
1378    match &*r.ty {
1379        syn::Type::Reference(ty) => {
1380            if ty.mutability.is_some() {
1381                ast::MethodSelf::RefMutable
1382            } else {
1383                ast::MethodSelf::RefShared
1384            }
1385        }
1386        _ => ast::MethodSelf::ByValue,
1387    }
1388}
1389
1390enum FunctionPosition<'a> {
1391    Extern,
1392    Free,
1393    Impl { self_ty: &'a Ident },
1394}
1395
1396/// Construct a function (and gets the self type if appropriate) for our AST from a syn function.
1397#[allow(clippy::too_many_arguments)]
1398fn function_from_decl(
1399    decl_name: &syn::Ident,
1400    opts: &BindgenAttrs,
1401    sig: syn::Signature,
1402    attrs: Vec<syn::Attribute>,
1403    vis: syn::Visibility,
1404    position: FunctionPosition,
1405    args_attrs: Option<Vec<FnArgAttrs>>,
1406) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> {
1407    if sig.variadic.is_some() {
1408        bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions");
1409    }
1410
1411    // For imported functions (Extern position), generics are supported and validated.
1412    if !matches!(position, FunctionPosition::Extern) && !sig.generics.params.is_empty() {
1413        bail_span!(
1414            sig.generics,
1415            "can't #[wasm_bindgen] functions with lifetime or type parameters"
1416        );
1417    }
1418
1419    let syn::Signature { inputs, output, .. } = sig;
1420
1421    // A helper function to replace `Self` in the function signature of methods.
1422    // E.g. `fn get(&self) -> Option<Self>` to `fn get(&self) -> Option<MyType>`
1423    // The following comment explains why this is necessary:
1424    // https://github.com/wasm-bindgen/wasm-bindgen/issues/3105#issuecomment-1275160744
1425    let replace_self = |mut t: syn::Type| {
1426        if let FunctionPosition::Impl { self_ty } = position {
1427            // This uses a visitor to replace all occurrences of `Self` with
1428            // the actual type identifier. The visitor guarantees that we find
1429            // all occurrences of `Self`, even if deeply nested and even if
1430            // future Rust versions add more places where `Self` can appear.
1431            struct SelfReplace(Ident);
1432            impl VisitMut for SelfReplace {
1433                fn visit_ident_mut(&mut self, i: &mut proc_macro2::Ident) {
1434                    if i == "Self" {
1435                        *i = self.0.clone();
1436                    }
1437                }
1438            }
1439
1440            let mut replace = SelfReplace(self_ty.clone());
1441            replace.visit_type_mut(&mut t);
1442        }
1443        t
1444    };
1445
1446    // A helper function to replace argument names that are JS keywords.
1447    // E.g. this will replace `fn foo(class: u32)` to `fn foo(_class: u32)`
1448    let replace_colliding_arg = |i: &mut syn::PatType| {
1449        if let syn::Pat::Ident(ref mut i) = *i.pat {
1450            let ident = i.ident.unraw().to_string();
1451            // JS keywords are NEVER allowed as argument names. Since argument
1452            // names are considered an implementation detail in JS, we can
1453            // safely rename them to avoid collisions.
1454            if is_js_keyword(&ident) {
1455                i.ident = Ident::new(format!("_{ident}").as_str(), i.ident.span());
1456            }
1457        }
1458    };
1459
1460    let mut method_self = None;
1461    let mut arguments = Vec::new();
1462    for arg in inputs.into_iter() {
1463        match arg {
1464            syn::FnArg::Typed(mut c) => {
1465                // typical arguments like foo: u32
1466                replace_colliding_arg(&mut c);
1467                *c.ty = replace_self(*c.ty);
1468                arguments.push(c);
1469            }
1470            syn::FnArg::Receiver(r) => {
1471                // the self argument, so self, &self, &mut self, self: Box<Self>, etc.
1472
1473                // `self` is only allowed for `fn`s inside an `impl` block.
1474                match position {
1475                    FunctionPosition::Free => {
1476                        bail_span!(
1477                            r.self_token,
1478                            "the `self` argument is only allowed for functions in `impl` blocks.\n\n\
1479                            If the function is already in an `impl` block, did you perhaps forget to add `#[wasm_bindgen]` to the `impl` block?"
1480                        );
1481                    }
1482                    FunctionPosition::Extern => {
1483                        bail_span!(
1484                            r.self_token,
1485                            "the `self` argument is not allowed for `extern` functions.\n\n\
1486                            Did you perhaps mean `this`? For more information on importing JavaScript functions, see:\n\
1487                            https://wasm-bindgen.github.io/wasm-bindgen/examples/import-js.html"
1488                        );
1489                    }
1490                    FunctionPosition::Impl { .. } => {}
1491                }
1492
1493                // We need to know *how* `self` is passed to the method (by
1494                // value or by reference) to generate the correct JS shim.
1495                assert!(method_self.is_none());
1496                method_self = Some(get_self_method(r));
1497            }
1498        }
1499    }
1500
1501    // process function return data
1502    let ret_ty_override = opts.unchecked_return_type();
1503    let ret_desc = opts.return_description();
1504    let ret = match output {
1505        syn::ReturnType::Default => None,
1506        syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData {
1507            r#type: replace_self(*ty),
1508            js_type: ret_ty_override
1509                .as_ref()
1510                .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(ty, span)| {
1511                    check_invalid_type(ty, *span)?;
1512                    Ok(Some(ty.to_string()))
1513                })?,
1514            desc: ret_desc.as_ref().map_or::<Result<_, Diagnostic>, _>(
1515                Ok(None),
1516                |(desc, span)| {
1517                    check_js_comment_close(desc, *span)?;
1518                    Ok(Some(desc.to_string()))
1519                },
1520            )?,
1521        }),
1522    };
1523    // error if there were description or type override specified for
1524    // function return while it doesn't actually return anything
1525    if ret.is_none() && (ret_ty_override.is_some() || ret_desc.is_some()) {
1526        if let Some((_, span)) = ret_ty_override {
1527            return Err(Diagnostic::span_error(
1528                span,
1529                "cannot specify return type for a function that doesn't return",
1530            ));
1531        }
1532        if let Some((_, span)) = ret_desc {
1533            return Err(Diagnostic::span_error(
1534                span,
1535                "cannot specify return description for a function that doesn't return",
1536            ));
1537        }
1538    }
1539
1540    let (name, name_span) = if let Some((js_name, js_name_span)) = opts.js_name() {
1541        // Reject `js_name = "[Symbol.<x>]"` for free Rust functions. Other
1542        // positions (methods, getters/setters, statics, types, etc.) handle
1543        // their own validation closer to where they have full context (e.g.
1544        // a parent extern-block `js_namespace`).
1545        if is_computed_key(js_name) && matches!(position, FunctionPosition::Free) {
1546            return Err(Diagnostic::span_error(
1547                js_name_span,
1548                "free functions with #[wasm_bindgen] do not support symbols in js_name",
1549            ));
1550        }
1551        let kind = operation_kind(opts);
1552        let prefix = match kind {
1553            OperationKind::Setter(_) => "set_",
1554            _ => "",
1555        };
1556        (format!("{prefix}{js_name}"), js_name_span)
1557    } else {
1558        (decl_name.unraw().to_string(), decl_name.span())
1559    };
1560
1561    Ok((
1562        ast::Function {
1563            name_span,
1564            name,
1565            rust_attrs: attrs,
1566            rust_vis: vis,
1567            r#unsafe: sig.unsafety.is_some(),
1568            r#async: sig.asyncness.is_some(),
1569            generate_typescript: opts.skip_typescript().is_none(),
1570            generate_jsdoc: opts.skip_jsdoc().is_none(),
1571            variadic: opts.variadic().is_some(),
1572            ret,
1573            arguments: arguments
1574                .into_iter()
1575                .zip(
1576                    args_attrs
1577                        .into_iter()
1578                        .flatten()
1579                        .chain(iter::repeat(FnArgAttrs::default())),
1580                )
1581                .map(|(pat_type, attrs)| ast::FunctionArgumentData {
1582                    pat_type,
1583                    js_name: attrs.js_name,
1584                    js_type: attrs.js_type,
1585                    optional: attrs.optional,
1586                    desc: attrs.desc,
1587                    slice_to_array: attrs.slice_to_array,
1588                })
1589                .collect(),
1590        },
1591        method_self,
1592    ))
1593}
1594
1595/// Helper struct to store extracted function argument attrs
1596#[derive(Default, Clone)]
1597struct FnArgAttrs {
1598    js_name: Option<String>,
1599    js_type: Option<String>,
1600    optional: bool,
1601    desc: Option<String>,
1602    /// When set, an `&[T]` (or `Option<&[T]>`) argument is converted to a
1603    /// freshly-allocated buffer that JS receives as a plain `Array` rather
1604    /// than a typed array (for primitive element kinds). The wire format
1605    /// matches `Vec<T>` (ownership transferred + freed by JS) but the
1606    /// JS-visible type is always a plain `Array`.
1607    slice_to_array: bool,
1608}
1609
1610/// Extracts function arguments attributes. `default_slice_to_array` is the
1611/// inherited flag from the enclosing function / `extern "C"` block; per-arg
1612/// `#[wasm_bindgen(slice_to_array)]` ORs on top of it.
1613fn extract_args_attrs(
1614    sig: &mut syn::Signature,
1615    default_slice_to_array: bool,
1616) -> Result<Vec<FnArgAttrs>, Diagnostic> {
1617    let mut args_attrs = vec![];
1618    let mut seen_optional: Option<Span> = None;
1619    for input in sig.inputs.iter_mut() {
1620        if let syn::FnArg::Typed(pat_type) = input {
1621            let attrs = BindgenAttrs::find(&mut pat_type.attrs)?;
1622
1623            // Check for mutually exclusive param type attributes
1624            let param_type = attrs.unchecked_param_type();
1625            let optional_param_type = attrs.unchecked_optional_param_type();
1626
1627            if param_type.is_some() && optional_param_type.is_some() {
1628                // Find the positions and spans of both attributes in the attrs list
1629                let mut param_pos_and_span: Option<(usize, Span)> = None;
1630                let mut optional_pos_and_span: Option<(usize, Span)> = None;
1631                for (pos, (_, attr)) in attrs.attrs.iter().enumerate() {
1632                    match attr {
1633                        BindgenAttr::ParamType(span, _, _) => {
1634                            param_pos_and_span = Some((pos, *span));
1635                        }
1636                        BindgenAttr::OptionalParamType(span, _, _) => {
1637                            optional_pos_and_span = Some((pos, *span));
1638                        }
1639                        _ => {}
1640                    }
1641                }
1642                // Report error at the position of the attribute that appears later
1643                let error_span = match (param_pos_and_span, optional_pos_and_span) {
1644                    (Some((p_pos, p_span)), Some((o_pos, o_span))) => {
1645                        if p_pos > o_pos {
1646                            p_span
1647                        } else {
1648                            o_span
1649                        }
1650                    }
1651                    (Some((_, p_span)), None) => p_span,
1652                    (None, Some((_, o_span))) => o_span,
1653                    (None, None) => unreachable!(
1654                        "both param_type and optional_param_type are Some, but attrs not found"
1655                    ),
1656                };
1657                return Err(Diagnostic::span_error(
1658                    error_span,
1659                    "cannot use both `unchecked_param_type` and `unchecked_optional_param_type` on the same parameter",
1660                ));
1661            }
1662
1663            // Determine the type and whether it's optional
1664            let js_type = param_type
1665                .or(optional_param_type)
1666                .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(ty, span)| {
1667                    check_invalid_type(ty, span)?;
1668                    Ok(Some(ty.to_string()))
1669                })?;
1670
1671            let is_optional = optional_param_type.is_some();
1672
1673            // Check that a non-optional param doesn't follow an optional one
1674            if let Some(optional_span) = seen_optional {
1675                if !is_optional {
1676                    return Err(Diagnostic::span_error(
1677                        optional_span,
1678                        "a required parameter cannot follow an optional parameter",
1679                    ));
1680                }
1681            }
1682            if is_optional {
1683                if let Some((_, span)) = optional_param_type {
1684                    seen_optional = Some(span);
1685                }
1686            }
1687
1688            let arg_attrs = FnArgAttrs {
1689                js_name: attrs
1690                    .js_name()
1691                    .map_or(Ok(None), |(js_name_override, span)| {
1692                        if is_computed_key(js_name_override) {
1693                            return Err(Diagnostic::span_error(
1694                                span,
1695                                "function arguments do not support symbols in js_name",
1696                            ));
1697                        }
1698                        if is_js_keyword(js_name_override) || !is_valid_ident(js_name_override) {
1699                            return Err(Diagnostic::span_error(span, "invalid JS identifier"));
1700                        }
1701                        Ok(Some(js_name_override.to_string()))
1702                    })?,
1703                js_type,
1704                optional: is_optional,
1705                desc: attrs
1706                    .param_description()
1707                    .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(description, span)| {
1708                        check_js_comment_close(description, span)?;
1709                        Ok(Some(description.to_string()))
1710                    })?,
1711                slice_to_array: default_slice_to_array || attrs.slice_to_array().is_some(),
1712            };
1713            // throw error for any unused attrs
1714            attrs.enforce_used()?;
1715            args_attrs.push(arg_attrs);
1716        }
1717    }
1718    Ok(args_attrs)
1719}
1720
1721pub(crate) trait MacroParse<Ctx> {
1722    /// Parse the contents of an object into our AST, with a context if necessary.
1723    ///
1724    /// The context is used to have access to the attributes on `#[wasm_bindgen]`, and to allow
1725    /// writing to the output `TokenStream`.
1726    fn macro_parse(self, program: &mut ast::Program, context: Ctx) -> Result<(), Diagnostic>;
1727}
1728
1729impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
1730    fn macro_parse(
1731        self,
1732        program: &mut ast::Program,
1733        (opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
1734    ) -> Result<(), Diagnostic> {
1735        match self {
1736            syn::Item::Fn(mut f) => {
1737                let opts = opts.unwrap_or_default();
1738                if let Some(path) = opts.wasm_bindgen() {
1739                    program.wasm_bindgen = path.clone();
1740                }
1741                if let Some(path) = opts.js_sys() {
1742                    program.js_sys = path.clone();
1743                }
1744                if let Some(path) = opts.wasm_bindgen_futures() {
1745                    program.wasm_bindgen_futures = path.clone();
1746                }
1747
1748                if opts.main().is_some() {
1749                    opts.check_used();
1750                    return main(program, f, tokens);
1751                }
1752
1753                let no_mangle = f
1754                    .attrs
1755                    .iter()
1756                    .enumerate()
1757                    .find(|(_, m)| m.path().is_ident("no_mangle"));
1758                if let Some((i, _)) = no_mangle {
1759                    f.attrs.remove(i);
1760                }
1761                // extract fn args attributes before parsing to tokens stream;
1762                // `slice_to_array` is irrelevant for exported free functions.
1763                let args_attrs = extract_args_attrs(&mut f.sig, false)?;
1764                let comments = extract_doc_comments(&f.attrs);
1765                // If the function isn't used for anything other than being exported to JS,
1766                // it'll be unused when not building for the Wasm target and produce a
1767                // `dead_code` warning. So, add `#[allow(dead_code)]` before it to avoid that.
1768                tokens.extend(quote::quote! { #[allow(dead_code)] });
1769                f.to_tokens(tokens);
1770                if opts.start().is_some() {
1771                    if !f.sig.generics.params.is_empty() {
1772                        bail_span!(&f.sig.generics, "the start function cannot have generics",);
1773                    }
1774                    if !f.sig.inputs.is_empty() {
1775                        bail_span!(&f.sig.inputs, "the start function cannot have arguments",);
1776                    }
1777                }
1778                let method_kind = ast::MethodKind::Operation(ast::Operation {
1779                    is_static: true,
1780                    kind: operation_kind(&opts),
1781                });
1782                let rust_name = f.sig.ident.clone();
1783                let start = if opts.start().is_some() {
1784                    if opts.private().is_some() {
1785                        ast::StartKind::Private
1786                    } else {
1787                        ast::StartKind::Public
1788                    }
1789                } else {
1790                    ast::StartKind::None
1791                };
1792
1793                if opts.this().is_some() && f.sig.inputs.is_empty() {
1794                    bail_span!(
1795                        &f.sig.inputs,
1796                        "functions taking a 'this' argument must have at least one parameter"
1797                    );
1798                }
1799
1800                let js_namespace = opts.js_namespace().map(|(ns, _)| ns.0);
1801                program.exports.push(ast::Export {
1802                    comments,
1803                    function: f.convert((opts, args_attrs))?,
1804                    js_class: None,
1805                    js_namespace,
1806                    method_kind,
1807                    method_self: None,
1808                    rust_class: None,
1809                    rust_name,
1810                    start,
1811                    wasm_bindgen: program.wasm_bindgen.clone(),
1812                    wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1813                    js_sys: program.js_sys.clone(),
1814                });
1815            }
1816            syn::Item::Impl(mut i) => {
1817                let opts = opts.unwrap_or_default();
1818                (&mut i).macro_parse(program, opts)?;
1819                i.to_tokens(tokens);
1820            }
1821            syn::Item::ForeignMod(mut f) => {
1822                let opts = match opts {
1823                    Some(opts) => opts,
1824                    None => BindgenAttrs::find(&mut f.attrs)?,
1825                };
1826                f.macro_parse(program, opts)?;
1827            }
1828            syn::Item::Enum(mut e) => {
1829                let opts = match opts {
1830                    Some(opts) => opts,
1831                    None => BindgenAttrs::find(&mut e.attrs)?,
1832                };
1833                e.macro_parse(program, (tokens, opts))?;
1834            }
1835            syn::Item::Const(mut c) => {
1836                let opts = match opts {
1837                    Some(opts) => opts,
1838                    None => BindgenAttrs::find(&mut c.attrs)?,
1839                };
1840                c.macro_parse(program, opts)?;
1841            }
1842            _ => {
1843                bail_span!(
1844                    self,
1845                    "#[wasm_bindgen] can only be applied to a function, \
1846                     struct, enum, impl, or extern block",
1847                );
1848            }
1849        }
1850
1851        Ok(())
1852    }
1853}
1854
1855impl MacroParse<BindgenAttrs> for &mut syn::ItemImpl {
1856    fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1857        if self.defaultness.is_some() {
1858            bail_span!(
1859                self.defaultness,
1860                "#[wasm_bindgen] default impls are not supported"
1861            );
1862        }
1863        if self.unsafety.is_some() {
1864            bail_span!(
1865                self.unsafety,
1866                "#[wasm_bindgen] unsafe impls are not supported"
1867            );
1868        }
1869        if let Some((_, path, _)) = &self.trait_ {
1870            bail_span!(path, "#[wasm_bindgen] trait impls are not supported");
1871        }
1872        if !self.generics.params.is_empty() {
1873            bail_span!(
1874                self.generics,
1875                "#[wasm_bindgen] generic impls aren't supported"
1876            );
1877        }
1878        let name = match get_ty(&self.self_ty) {
1879            syn::Type::Path(syn::TypePath {
1880                qself: None,
1881                ref path,
1882            }) => path,
1883            _ => bail_span!(
1884                self.self_ty,
1885                "unsupported self type in #[wasm_bindgen] impl"
1886            ),
1887        };
1888        let mut errors = Vec::new();
1889        for item in self.items.iter_mut() {
1890            if let Err(e) = prepare_for_impl_recursion(item, name, program, &opts) {
1891                errors.push(e);
1892            }
1893        }
1894        Diagnostic::from_vec(errors)?;
1895        opts.check_used();
1896        Ok(())
1897    }
1898}
1899
1900// Prepare for recursion into an `impl` block. Here we want to attach an
1901// internal attribute, `__wasm_bindgen_class_marker`, with any metadata we need
1902// to pass from the impl to the impl item. Recursive macro expansion will then
1903// expand the `__wasm_bindgen_class_marker` attribute.
1904//
1905// Note that we currently do this because inner items may have things like cfgs
1906// on them, so we want to expand the impl first, let the insides get cfg'd, and
1907// then go for the rest.
1908fn prepare_for_impl_recursion(
1909    item: &mut syn::ImplItem,
1910    class: &syn::Path,
1911    program: &ast::Program,
1912    impl_opts: &BindgenAttrs,
1913) -> Result<(), Diagnostic> {
1914    let method = match item {
1915        syn::ImplItem::Fn(m) => m,
1916        syn::ImplItem::Const(_) => {
1917            bail_span!(
1918                &*item,
1919                "const definitions aren't supported with #[wasm_bindgen]"
1920            );
1921        }
1922        syn::ImplItem::Type(_) => bail_span!(
1923            &*item,
1924            "type definitions in impls aren't supported with #[wasm_bindgen]"
1925        ),
1926        syn::ImplItem::Macro(_) => {
1927            // In theory we want to allow this, but we have no way of expanding
1928            // the macro and then placing our magical attributes on the expanded
1929            // functions. As a result, just disallow it for now to hopefully
1930            // ward off buggy results from this macro.
1931            bail_span!(&*item, "macros in impls aren't supported");
1932        }
1933        syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
1934        other => bail_span!(other, "failed to parse this item as a known item"),
1935    };
1936
1937    let ident = extract_path_ident(class, false)?;
1938
1939    // Default `js_class` to the impl's `Self` Rust ident when not given.
1940    // This is a backwards-compatible quirk: `js_class` is semantically the
1941    // JS-side name of the class being implemented (mirror of the struct's
1942    // `js_name`), but the macro can't see the struct's attrs cross-
1943    // invocation, so it falls back to the Rust ident. The current scheme
1944    // works because:
1945    //
1946    //   * `exported_classes` is keyed by rust_name (always unique because
1947    //     wasm-ld enforces shim-symbol uniqueness, which is derived from
1948    //     `js_class` lowercased + fn name).
1949    //   * When the struct sets `js_name = "Foo"`, the struct registration
1950    //     populates `class.identifier` from `qualified_name`, so the JS
1951    //     output uses the user-intended name even though the
1952    //     `exported_classes` key is the Rust ident.
1953    //
1954    // In a future major version `js_class` should become MANDATORY when
1955    // the struct declares `js_name` (and the matching `extends_js_class` /
1956    // `extends_js_namespace` attributes should be introduced for the
1957    // inheritance counterpart). Until then the default-to-Self-ident path
1958    // is preserved for source-compatibility, and the JS-identity-vs-Rust-
1959    // identity split is patched over via wasm-ld's symbol uniqueness
1960    // enforcement plus the rust_name keyed `exported_classes` map.
1961    let js_class = impl_opts
1962        .js_class()
1963        .map(|s| s.0.to_string())
1964        .unwrap_or(ident.unraw().to_string());
1965
1966    // The impl's `js_namespace` is forwarded to the per-method `ClassMarker`
1967    // so the eventual `ast::Export` for each method carries the namespace.
1968    // This is what makes the emitted wasm shim symbol (and the
1969    // `exported_classes` lookup key in cli-support) namespace-unique — without
1970    // it, two classes sharing a `js_class` in different namespaces would emit
1971    // colliding shim symbols and route their methods to the same entry.
1972    let js_namespace_segments: Vec<String> = impl_opts
1973        .js_namespace()
1974        .map(|(ns, _)| ns.0)
1975        .unwrap_or_default();
1976    let js_namespace_lits = js_namespace_segments
1977        .iter()
1978        .map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site()))
1979        .collect::<Vec<_>>();
1980    let js_namespace_tokens = if js_namespace_lits.is_empty() {
1981        quote::quote! {}
1982    } else {
1983        quote::quote! { , js_namespace = [ #(#js_namespace_lits),* ] }
1984    };
1985
1986    let wasm_bindgen = &program.wasm_bindgen;
1987    let wasm_bindgen_futures = &program.wasm_bindgen_futures;
1988    let js_sys = &program.js_sys;
1989    method.attrs.insert(
1990        0,
1991        syn::Attribute {
1992            pound_token: Default::default(),
1993            style: syn::AttrStyle::Outer,
1994            bracket_token: Default::default(),
1995            meta: syn::parse_quote! { #wasm_bindgen::prelude::__wasm_bindgen_class_marker(#class = #js_class #js_namespace_tokens, wasm_bindgen = #wasm_bindgen, wasm_bindgen_futures = #wasm_bindgen_futures, js_sys = #js_sys) },
1996        },
1997    );
1998
1999    Ok(())
2000}
2001
2002impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn {
2003    fn macro_parse(
2004        self,
2005        program: &mut ast::Program,
2006        ClassMarker {
2007            class,
2008            js_class,
2009            js_namespace,
2010            wasm_bindgen,
2011            wasm_bindgen_futures,
2012            js_sys,
2013        }: &ClassMarker,
2014    ) -> Result<(), Diagnostic> {
2015        program.wasm_bindgen = wasm_bindgen.clone();
2016        program.wasm_bindgen_futures = wasm_bindgen_futures.clone();
2017        program.js_sys = js_sys.clone();
2018
2019        match self.vis {
2020            syn::Visibility::Public(_) => {}
2021            _ => return Ok(()),
2022        }
2023        if self.defaultness.is_some() {
2024            panic!("default methods are not supported");
2025        }
2026        if self.sig.constness.is_some() {
2027            bail_span!(
2028                self.sig.constness,
2029                "can only #[wasm_bindgen] non-const functions",
2030            );
2031        }
2032
2033        let opts = BindgenAttrs::find(&mut self.attrs)?;
2034
2035        if opts.this().is_some() {
2036            bail_span!(
2037                &self.sig.ident,
2038                "#[wasm_bindgen(this)] cannot be used on impl block methods; \
2039                 it is only valid on free functions"
2040            );
2041        }
2042
2043        let comments = extract_doc_comments(&self.attrs);
2044        // `slice_to_array` is meaningless on exported impl-block methods (the
2045        // attribute only changes the outgoing-argument codegen for imported
2046        // functions), so we always pass `false` here.
2047        let args_attrs: Vec<FnArgAttrs> = extract_args_attrs(&mut self.sig, false)?;
2048        let (function, method_self) = function_from_decl(
2049            &self.sig.ident,
2050            &opts,
2051            self.sig.clone(),
2052            self.attrs.clone(),
2053            self.vis.clone(),
2054            FunctionPosition::Impl { self_ty: class },
2055            Some(args_attrs),
2056        )?;
2057        let method_kind = if opts.constructor().is_some() {
2058            ast::MethodKind::Constructor
2059        } else {
2060            let is_static = method_self.is_none();
2061            let kind = operation_kind(&opts);
2062            ast::MethodKind::Operation(ast::Operation { is_static, kind })
2063        };
2064
2065        // Validate that js_namespace is not used on methods
2066        if let Some((_, span)) = opts.js_namespace() {
2067            return Err(Diagnostic::span_error(
2068                span[0],
2069                "`js_namespace` cannot be used on methods, getters, setters, or static methods. \
2070                Use `js_namespace` on the exported struct definition instead to put the entire class in a namespace.",
2071            ));
2072        }
2073
2074        program.exports.push(ast::Export {
2075            comments,
2076            function,
2077            js_class: Some(js_class.to_string()),
2078            // Propagate the impl-block `js_namespace` so the wasm shim
2079            // symbol and the `exported_classes` key are namespace-aware.
2080            // Two classes that share a `js_class` in different namespaces
2081            // (e.g. `P.Foo` and `Q.Foo`) would otherwise emit colliding
2082            // shim symbols and route their methods to the same entry.
2083            js_namespace: js_namespace.clone(),
2084            method_kind,
2085            method_self,
2086            rust_class: Some(class.clone()),
2087            rust_name: self.sig.ident.clone(),
2088            start: ast::StartKind::None,
2089            wasm_bindgen: program.wasm_bindgen.clone(),
2090            wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
2091            js_sys: program.js_sys.clone(),
2092        });
2093        opts.check_used();
2094        Ok(())
2095    }
2096}
2097
2098fn string_enum(
2099    enum_: syn::ItemEnum,
2100    program: &mut ast::Program,
2101    js_name: String,
2102    generate_typescript: bool,
2103    private: bool,
2104    comments: Vec<String>,
2105    js_namespace: Option<Vec<String>>,
2106) -> Result<(), Diagnostic> {
2107    let mut variants = vec![];
2108    let mut variant_values = vec![];
2109
2110    for v in enum_.variants.iter() {
2111        let (_, expr) = match &v.discriminant {
2112            Some(pair) => pair,
2113            None => {
2114                bail_span!(v, "all variants of a string enum must have a string value");
2115            }
2116        };
2117        match get_expr(expr) {
2118            syn::Expr::Lit(syn::ExprLit {
2119                attrs: _,
2120                lit: syn::Lit::Str(str_lit),
2121            }) => {
2122                variants.push(v.ident.clone());
2123                variant_values.push(str_lit.value());
2124            }
2125            expr => bail_span!(
2126                expr,
2127                "enums with #[wasm_bindgen] cannot mix string and non-string values",
2128            ),
2129        }
2130    }
2131
2132    program.imports.push(ast::Import {
2133        module: None,
2134        js_namespace: None,
2135        reexport: None,
2136        kind: ast::ImportKind::Enum(ast::StringEnum {
2137            vis: enum_.vis,
2138            name: enum_.ident,
2139            export_name: js_name,
2140            variants,
2141            variant_values,
2142            comments,
2143            rust_attrs: enum_.attrs,
2144            generate_typescript,
2145            private,
2146            js_namespace,
2147            wasm_bindgen: program.wasm_bindgen.clone(),
2148        }),
2149    });
2150
2151    Ok(())
2152}
2153
2154fn dynamic_union(
2155    enum_: syn::ItemEnum,
2156    program: &mut ast::Program,
2157    js_name: String,
2158    generate_typescript: bool,
2159    private: bool,
2160    fallback: bool,
2161    comments: Vec<String>,
2162) -> Result<(), Diagnostic> {
2163    let mut variants = vec![];
2164    let mut variant_values = vec![];
2165    let mut variant_fields = vec![];
2166
2167    for v in enum_.variants.iter() {
2168        // Check if this is a fallback variant (has fields)
2169        match &v.fields {
2170            syn::Fields::Unit => {
2171                // Regular string enum variant - must have a discriminant
2172                let (_, expr) = match &v.discriminant {
2173                    Some(pair) => pair,
2174                    None => {
2175                        bail_span!(
2176                            v,
2177                            "all unit variants of a string enum must have a string value"
2178                        );
2179                    }
2180                };
2181                match get_expr(expr) {
2182                    syn::Expr::Lit(syn::ExprLit {
2183                        attrs: _,
2184                        lit: syn::Lit::Str(str_lit),
2185                    }) => {
2186                        variants.push(v.ident.clone());
2187                        variant_values.push(str_lit.value());
2188                        variant_fields.push(Vec::new());
2189                    }
2190                    expr => bail_span!(
2191                        expr,
2192                        "enums with #[wasm_bindgen] cannot mix string and non-string values",
2193                    ),
2194                }
2195            }
2196            syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
2197                // Fallback variant - add it with an empty string value and the field type
2198                // (we'll handle this specially in codegen)
2199                variants.push(v.ident.clone());
2200                variant_values.push(String::new());
2201                variant_fields.push(vec![fields.unnamed.first().unwrap().ty.clone()]);
2202            }
2203            _ => {
2204                bail_span!(
2205                    v.fields,
2206                    "string enum variants with fields must have exactly one unnamed field"
2207                );
2208            }
2209        }
2210    }
2211
2212    program.imports.push(ast::Import {
2213        module: None,
2214        js_namespace: None,
2215        reexport: None,
2216        kind: ast::ImportKind::DynamicUnion(ast::DynamicUnion {
2217            vis: enum_.vis,
2218            name: enum_.ident,
2219            js_name,
2220            variants,
2221            variant_values,
2222            variant_fields,
2223            comments,
2224            rust_attrs: enum_.attrs,
2225            generate_typescript,
2226            private,
2227            fallback,
2228            wasm_bindgen: program.wasm_bindgen.clone(),
2229        }),
2230    });
2231
2232    Ok(())
2233}
2234
2235/// Represents a possibly negative numeric value as base 10 digits.
2236struct NumericValue<'a> {
2237    negative: bool,
2238    base10_digits: &'a str,
2239}
2240impl<'a> NumericValue<'a> {
2241    fn from_expr(expr: &'a syn::Expr) -> Option<Self> {
2242        match get_expr(expr) {
2243            syn::Expr::Lit(syn::ExprLit {
2244                lit: syn::Lit::Int(int_lit),
2245                ..
2246            }) => Some(Self {
2247                negative: false,
2248                base10_digits: int_lit.base10_digits(),
2249            }),
2250            syn::Expr::Unary(syn::ExprUnary {
2251                op: syn::UnOp::Neg(_),
2252                expr,
2253                ..
2254            }) => Self::from_expr(expr).map(|n| n.neg()),
2255            _ => None,
2256        }
2257    }
2258
2259    fn parse(&self) -> Option<i64> {
2260        let mut value = self.base10_digits.parse::<i64>().ok()?;
2261        if self.negative {
2262            value = -value;
2263        }
2264        Some(value)
2265    }
2266
2267    fn neg(self) -> Self {
2268        Self {
2269            negative: !self.negative,
2270            base10_digits: self.base10_digits,
2271        }
2272    }
2273}
2274
2275impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
2276    fn macro_parse(
2277        self,
2278        program: &mut ast::Program,
2279        (tokens, opts): (&'a mut TokenStream, BindgenAttrs),
2280    ) -> Result<(), Diagnostic> {
2281        if self.variants.is_empty() {
2282            bail_span!(self, "cannot export empty enums to JS");
2283        }
2284
2285        let generate_typescript = opts.skip_typescript().is_none();
2286        let private = opts.private().is_some();
2287        let fallback = opts.fallback().is_some();
2288        let comments = extract_doc_comments(&self.attrs);
2289        let js_name = opts
2290            .js_name_no_symbol("enums with #[wasm_bindgen]")?
2291            .map_or_else(|| self.ident.unraw().to_string(), |s| s.to_string());
2292        if is_js_keyword(&js_name) && js_name != "default" {
2293            bail_span!(
2294                self.ident,
2295                "enum cannot use the JS keyword `{}` as its name",
2296                js_name
2297            );
2298        }
2299
2300        let js_namespace = opts.js_namespace().map(|(ns, _)| ns.0);
2301        opts.check_used();
2302
2303        // Check if the enum is a dynamic union, based on having singular unnamed variants
2304        let mut has_unnamed_fields = false;
2305        for variant in self.variants.iter() {
2306            match &variant.fields {
2307                syn::Fields::Unit => (), // Unit variants are always allowed
2308                syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
2309                    has_unnamed_fields = true;
2310                }
2311                syn::Fields::Unnamed(_) => bail_span!(
2312                    variant.fields,
2313                    "enum variants with fields must have exactly one unnamed field"
2314                ),
2315                syn::Fields::Named(_) => {
2316                    bail_span!(variant.fields, "enum variants cannot have named fields")
2317                }
2318            }
2319        }
2320        if has_unnamed_fields {
2321            return dynamic_union(
2322                self,
2323                program,
2324                js_name,
2325                generate_typescript,
2326                private,
2327                fallback,
2328                comments,
2329            );
2330        }
2331
2332        // The `fallback` attribute only applies to dynamic unions.
2333        if fallback {
2334            bail_span!(
2335                self,
2336                "the `fallback` attribute is only supported on dynamic unions \
2337                 (enums with at least one tuple variant)"
2338            );
2339        }
2340
2341        // Check if the enum is a string enum, by checking whether any variant has a string discriminant.
2342        let is_string_enum = self.variants.iter().any(|v| {
2343            if let Some((_, expr)) = &v.discriminant {
2344                if let syn::Expr::Lit(syn::ExprLit {
2345                    lit: syn::Lit::Str(_),
2346                    ..
2347                }) = get_expr(expr)
2348                {
2349                    return true;
2350                }
2351            }
2352            false
2353        });
2354        if is_string_enum {
2355            return string_enum(
2356                self,
2357                program,
2358                js_name,
2359                generate_typescript,
2360                private,
2361                comments,
2362                js_namespace,
2363            );
2364        }
2365
2366        match self.vis {
2367            syn::Visibility::Public(_) => {}
2368            _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
2369        }
2370
2371        // Go through all variants once first to determine whether the enum is
2372        // signed or unsigned. We don't need to actually parse the discriminant
2373        // values yet, we just need to know their sign. The actual parsing is
2374        // done in a second pass.
2375        let signed = self.variants.iter().any(|v| match &v.discriminant {
2376            Some((_, expr)) => NumericValue::from_expr(expr).is_some_and(|n| n.negative),
2377            None => false,
2378        });
2379        let underlying_min = if signed { i32::MIN as i64 } else { 0 };
2380        let underlying_max = if signed {
2381            i32::MAX as i64
2382        } else {
2383            u32::MAX as i64
2384        };
2385
2386        let mut last_discriminant: Option<i64> = None;
2387        let mut discriminant_map: HashMap<i64, &syn::Variant> = HashMap::new();
2388
2389        let variants = self
2390            .variants
2391            .iter()
2392            .map(|v| {
2393                let value: i64 = match &v.discriminant {
2394                    Some((_, expr)) => match NumericValue::from_expr(expr).and_then(|n| n.parse()) {
2395                        Some(value) => value,
2396                        _ => bail_span!(
2397                            expr,
2398                            "C-style enums with #[wasm_bindgen] may only have \
2399                             numeric literal values that fit in a 32-bit integer as discriminants. \
2400                             Expressions or variables are not supported.",
2401                        ),
2402                    },
2403                    None => {
2404                        // Use the same algorithm as rustc to determine the next discriminant
2405                        // https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants
2406                        last_discriminant.map_or(0, |last| last + 1)
2407                    }
2408                };
2409
2410                last_discriminant = Some(value);
2411
2412                // check that the value fits within the underlying type
2413                let underlying = if signed { "i32" } else { "u32" };
2414                let numbers = if signed { "signed numbers" } else { "unsigned numbers" };
2415                if value < underlying_min {
2416                    bail_span!(
2417                        v,
2418                        "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \
2419                        but `{1}` is too small for `{2}`",
2420                        numbers,
2421                        value,
2422                        underlying
2423                    );
2424                }
2425                if value > underlying_max {
2426                    bail_span!(
2427                        v,
2428                        "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \
2429                        but `{1}` is too large for `{2}`",
2430                        numbers,
2431                        value,
2432                        underlying
2433                    );
2434                }
2435
2436                // detect duplicate discriminants
2437                if let Some(old) = discriminant_map.insert(value, v) {
2438                    bail_span!(
2439                        v,
2440                        "discriminant value `{}` is already used by {} in this enum",
2441                        value,
2442                        old.ident
2443                    );
2444                }
2445
2446                let comments = extract_doc_comments(&v.attrs);
2447                Ok(ast::Variant {
2448                    rust_name: v.ident.clone(),
2449                    js_name: v.ident.unraw().to_string(),
2450                    // due to the above checks, we know that the value fits
2451                    // within 32 bits, so this cast doesn't lose any information
2452                    value: value as u32,
2453                    comments,
2454                })
2455            })
2456            .collect::<Result<Vec<_>, Diagnostic>>()?;
2457
2458        // To make all the code handling holes simpler, we only consider
2459        // non-negative holes. This allows us to use `u32` to represent holes.
2460        let hole = (0..=underlying_max)
2461            .find(|v| !discriminant_map.contains_key(v))
2462            .unwrap() as u32;
2463
2464        self.to_tokens(tokens);
2465
2466        program.enums.push(ast::Enum {
2467            rust_name: self.ident,
2468            js_name,
2469            signed,
2470            variants,
2471            comments,
2472            hole,
2473            generate_typescript,
2474            private,
2475            js_namespace,
2476            wasm_bindgen: program.wasm_bindgen.clone(),
2477        });
2478        Ok(())
2479    }
2480}
2481
2482impl MacroParse<BindgenAttrs> for syn::ItemConst {
2483    fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
2484        // Shortcut
2485        if opts.typescript_custom_section().is_none() {
2486            bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)].");
2487        }
2488
2489        let typescript_custom_section = match get_expr(&self.expr) {
2490            syn::Expr::Lit(syn::ExprLit {
2491                lit: syn::Lit::Str(litstr),
2492                ..
2493            }) => ast::LitOrExpr::Lit(litstr.value()),
2494            expr => ast::LitOrExpr::Expr(expr.clone()),
2495        };
2496
2497        program
2498            .typescript_custom_sections
2499            .push(typescript_custom_section);
2500
2501        opts.check_used();
2502
2503        Ok(())
2504    }
2505}
2506
2507impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
2508    fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
2509        let mut errors = Vec::new();
2510        if let Some(other) = self.abi.name.filter(|l| l.value() != "C") {
2511            errors.push(err_span!(
2512                other,
2513                "only foreign mods with the `C` ABI are allowed"
2514            ));
2515        }
2516        let js_namespace = opts.js_namespace().map(|(s, _)| s);
2517        let module = module_from_opts(program, &opts)
2518            .map_err(|e| errors.push(e))
2519            .unwrap_or_default();
2520        let slice_to_array = opts.slice_to_array().is_some();
2521        for item in self.items.into_iter() {
2522            let ctx = ForeignItemCtx {
2523                module: module.clone(),
2524                js_namespace: js_namespace.clone(),
2525                slice_to_array,
2526            };
2527            if let Err(e) = item.macro_parse(program, ctx) {
2528                errors.push(e);
2529            }
2530        }
2531        Diagnostic::from_vec(errors)?;
2532        opts.check_used();
2533        Ok(())
2534    }
2535}
2536
2537struct ForeignItemCtx {
2538    module: Option<ast::ImportModule>,
2539    js_namespace: Option<JsNamespace>,
2540    /// Block-level `slice_to_array` flag inherited by every foreign
2541    /// function inside the `extern "C"` block. Per-fn / per-arg
2542    /// `slice_to_array` ORs on top of this.
2543    slice_to_array: bool,
2544}
2545
2546impl MacroParse<ForeignItemCtx> for syn::ForeignItem {
2547    fn macro_parse(
2548        mut self,
2549        program: &mut ast::Program,
2550        ctx: ForeignItemCtx,
2551    ) -> Result<(), Diagnostic> {
2552        let item_opts = {
2553            let attrs = match self {
2554                syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
2555                syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
2556                syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
2557                syn::ForeignItem::Verbatim(v) => {
2558                    let mut item: syn::ItemStatic =
2559                        syn::parse(v.into()).expect("only foreign functions/types allowed for now");
2560                    let item_opts = BindgenAttrs::find(&mut item.attrs)?;
2561                    let reexport = item_opts.reexport().cloned();
2562                    let kind = item.convert((program, item_opts, &ctx.module))?;
2563
2564                    program.imports.push(ast::Import {
2565                        module: None,
2566                        js_namespace: None,
2567                        reexport,
2568                        kind,
2569                    });
2570
2571                    return Ok(());
2572                }
2573                _ => panic!("only foreign functions/types allowed for now"),
2574            };
2575            BindgenAttrs::find(attrs)?
2576        };
2577
2578        let js_namespace = item_opts
2579            .js_namespace()
2580            .map(|(s, _)| s)
2581            .or(ctx.js_namespace)
2582            .map(|s| s.0);
2583        let module = ctx.module;
2584        let block_slice_to_array = ctx.slice_to_array;
2585        let reexport = item_opts.reexport().cloned();
2586
2587        // Symbol-form `js_name` on a free imported function only makes sense
2588        // when there is a containing `js_namespace`, because the macro emits
2589        // accesses like `<namespace>[Symbol.<x>]`. Without a namespace there
2590        // is no object to read the symbol-keyed property off, so reject early
2591        // with a position-specific error.
2592        if let syn::ForeignItem::Fn(_) = &self {
2593            let is_method_like = item_opts.method().is_some()
2594                || item_opts.constructor().is_some()
2595                || item_opts.static_method_of().is_some()
2596                || item_opts.getter().is_some()
2597                || item_opts.setter().is_some();
2598            if !is_method_like && js_namespace.is_none() {
2599                if let Some((value, span)) = item_opts.js_name() {
2600                    if is_computed_key(value) {
2601                        return Err(Diagnostic::span_error(
2602                            span,
2603                            "free imported functions do not support symbols in js_name unless a js_namespace is specified",
2604                        ));
2605                    }
2606                }
2607            }
2608        }
2609
2610        let kind = match self {
2611            syn::ForeignItem::Fn(f) => {
2612                f.convert((program, item_opts, &module, block_slice_to_array))?
2613            }
2614            syn::ForeignItem::Type(t) => t.convert((program, item_opts))?,
2615            syn::ForeignItem::Static(s) => s.convert((program, item_opts, &module))?,
2616            _ => panic!("only foreign functions/types allowed for now"),
2617        };
2618
2619        // check for JS keywords
2620
2621        // We only need to check if there isn't a JS namespace or module. If
2622        // there is namespace, then we already checked the namespace while
2623        // parsing. If there is a module, we can rename the import symbol to
2624        // avoid using keywords.
2625        let needs_check = js_namespace.is_none() && module.is_none();
2626        if needs_check {
2627            match &kind {
2628                ast::ImportKind::Function(import_function) => {
2629                    if matches!(import_function.kind, ast::ImportFunctionKind::Normal)
2630                        && is_non_value_js_keyword(&import_function.function.name)
2631                    {
2632                        bail_span!(
2633                            import_function.rust_name,
2634                            "Imported function cannot use the JS keyword `{}` as its name.",
2635                            import_function.function.name
2636                        );
2637                    }
2638                }
2639                ast::ImportKind::Static(import_static) => {
2640                    if is_non_value_js_keyword(&import_static.js_name) {
2641                        bail_span!(
2642                            import_static.rust_name,
2643                            "Imported static cannot use the JS keyword `{}` as its name.",
2644                            import_static.js_name
2645                        );
2646                    }
2647                }
2648                ast::ImportKind::String(_) => {
2649                    // static strings don't have JS names, so we don't need to check for JS keywords
2650                }
2651                ast::ImportKind::Type(import_type) => {
2652                    if is_non_value_js_keyword(&import_type.js_name) {
2653                        bail_span!(
2654                            import_type.rust_name,
2655                            "Imported type cannot use the JS keyword `{}` as its name.",
2656                            import_type.js_name
2657                        );
2658                    }
2659                }
2660                ast::ImportKind::Enum(_) => {
2661                    // string enums aren't possible here
2662                }
2663                ast::ImportKind::DynamicUnion(_) => {
2664                    // dynamic unions aren't possible here
2665                }
2666            }
2667        }
2668
2669        program.imports.push(ast::Import {
2670            module,
2671            js_namespace,
2672            reexport,
2673            kind,
2674        });
2675
2676        Ok(())
2677    }
2678}
2679
2680pub fn module_from_opts(
2681    program: &mut ast::Program,
2682    opts: &BindgenAttrs,
2683) -> Result<Option<ast::ImportModule>, Diagnostic> {
2684    if let Some(path) = opts.wasm_bindgen() {
2685        program.wasm_bindgen = path.clone();
2686    }
2687
2688    if let Some(path) = opts.js_sys() {
2689        program.js_sys = path.clone();
2690    }
2691
2692    if let Some(path) = opts.wasm_bindgen_futures() {
2693        program.wasm_bindgen_futures = path.clone();
2694    }
2695
2696    let mut errors = Vec::new();
2697    let module = if let Some((name, span)) = opts.module() {
2698        if opts.inline_js().is_some() {
2699            let msg = "cannot specify both `module` and `inline_js`";
2700            errors.push(Diagnostic::span_error(span, msg));
2701        }
2702        if opts.raw_module().is_some() {
2703            let msg = "cannot specify both `module` and `raw_module`";
2704            errors.push(Diagnostic::span_error(span, msg));
2705        }
2706        Some(ast::ImportModule::Named(name.to_string(), span))
2707    } else if let Some((name, span)) = opts.raw_module() {
2708        if opts.inline_js().is_some() {
2709            let msg = "cannot specify both `raw_module` and `inline_js`";
2710            errors.push(Diagnostic::span_error(span, msg));
2711        }
2712        Some(ast::ImportModule::RawNamed(name.to_string(), span))
2713    } else if let Some((js, _span)) = opts.inline_js() {
2714        let i = program.inline_js.len();
2715        program.inline_js.push(js.to_string());
2716        Some(ast::ImportModule::Inline(i))
2717    } else {
2718        None
2719    };
2720    Diagnostic::from_vec(errors)?;
2721    Ok(module)
2722}
2723
2724/// Get the first type parameter of a generic type, errors on incorrect input.
2725fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, Diagnostic> {
2726    let t = match ty {
2727        Some(t) => t,
2728        None => return Ok(None),
2729    };
2730    let path = match *get_ty(t) {
2731        syn::Type::Path(syn::TypePath {
2732            qself: None,
2733            ref path,
2734        }) => path,
2735        _ => bail_span!(t, "must be Result<...>"),
2736    };
2737    let seg = path
2738        .segments
2739        .last()
2740        .ok_or_else(|| err_span!(t, "must have at least one segment"))?;
2741    let generics = match seg.arguments {
2742        syn::PathArguments::AngleBracketed(ref t) => t,
2743        _ => bail_span!(t, "must be Result<...>"),
2744    };
2745    let generic = generics
2746        .args
2747        .first()
2748        .ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?;
2749    let ty = match generic {
2750        syn::GenericArgument::Type(t) => t,
2751        other => bail_span!(other, "must be a type parameter"),
2752    };
2753    match get_ty(ty) {
2754        syn::Type::Tuple(t) if t.elems.is_empty() => return Ok(None),
2755        _ => {}
2756    }
2757    Ok(Some(ty.clone()))
2758}
2759
2760/// Extract the documentation comments from a Vec of attributes
2761fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
2762    attrs
2763        .iter()
2764        .filter_map(|a| {
2765            // if the path segments include an ident of "doc" we know this
2766            // this is a doc comment
2767            if a.path().segments.iter().any(|s| s.ident == "doc") {
2768                let tokens = match &a.meta {
2769                    syn::Meta::Path(_) => None,
2770                    syn::Meta::List(list) => Some(list.tokens.clone()),
2771                    syn::Meta::NameValue(name_value) => Some(name_value.value.to_token_stream()),
2772                };
2773
2774                Some(
2775                    // We want to filter out any Puncts so just grab the Literals
2776                    tokens.into_iter().flatten().filter_map(|t| match t {
2777                        TokenTree::Literal(lit) => {
2778                            let quoted = lit.to_string();
2779                            Some(try_unescape(&quoted).unwrap_or(quoted))
2780                        }
2781                        _ => None,
2782                    }),
2783                )
2784            } else {
2785                None
2786            }
2787        })
2788        //Fold up the [[String]] iter we created into Vec<String>
2789        .fold(vec![], |mut acc, a| {
2790            acc.extend(a);
2791            acc
2792        })
2793}
2794
2795// Unescapes a quoted string. char::escape_debug() was used to escape the text.
2796fn try_unescape(mut s: &str) -> Option<String> {
2797    s = s.strip_prefix('"').unwrap_or(s);
2798    s = s.strip_suffix('"').unwrap_or(s);
2799    let mut result = String::with_capacity(s.len());
2800    let mut chars = s.chars();
2801    while let Some(c) = chars.next() {
2802        if c == '\\' {
2803            let c = chars.next()?;
2804            match c {
2805                't' => result.push('\t'),
2806                'r' => result.push('\r'),
2807                'n' => result.push('\n'),
2808                '\\' | '\'' | '"' => result.push(c),
2809                'u' => {
2810                    if chars.next() != Some('{') {
2811                        return None;
2812                    }
2813                    let (c, next) = unescape_unicode(&mut chars)?;
2814                    result.push(c);
2815                    if next != '}' {
2816                        return None;
2817                    }
2818                }
2819                _ => return None,
2820            }
2821        } else {
2822            result.push(c);
2823        }
2824    }
2825    Some(result)
2826}
2827
2828fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
2829    let mut value = 0;
2830    for (i, c) in chars.enumerate() {
2831        match (i, c.to_digit(16)) {
2832            (0..=5, Some(num)) => value = (value << 4) | num,
2833            (1.., None) => return Some((char::from_u32(value)?, c)),
2834            _ => break,
2835        }
2836    }
2837    None
2838}
2839
2840/// Extracts the last ident from the path
2841/// If generics is enabled, generics are validated
2842fn extract_path_ident(path: &syn::Path, allow_generics: bool) -> Result<Ident, Diagnostic> {
2843    for segment in path.segments.iter() {
2844        match &segment.arguments {
2845            syn::PathArguments::None => {}
2846            syn::PathArguments::AngleBracketed(_) => {
2847                if !allow_generics {
2848                    bail_span!(
2849                        path,
2850                        "paths with type parameters are not supported in this position"
2851                    )
2852                }
2853            }
2854            syn::PathArguments::Parenthesized(_) => {
2855                bail_span!(path, "parenthesized paths are not supported yet")
2856            }
2857        }
2858    }
2859
2860    match path.segments.last() {
2861        Some(value) => Ok(value.ident.clone()),
2862        None => {
2863            bail_span!(path, "empty idents are not supported");
2864        }
2865    }
2866}
2867
2868fn bail_generic_unsupported(span: impl Spanned + ToTokens) -> Result<(), Diagnostic> {
2869    bail_span!(span, "unsupported in wasm-bindgen generics");
2870}
2871
2872fn validate_generic_type_param_bound(bound: &syn::TypeParamBound) -> Result<(), Diagnostic> {
2873    match bound {
2874        syn::TypeParamBound::Trait(trait_bound) => {
2875            // Higher-ranked trait bounds (for<'a>) are now supported
2876            if let syn::TraitBoundModifier::Maybe(question) = trait_bound.modifier {
2877                bail_generic_unsupported(question)?;
2878            }
2879        }
2880        syn::TypeParamBound::Lifetime(_) => {
2881            // Lifetime bounds (e.g., T: 'a) are now supported
2882        }
2883        syn::TypeParamBound::Verbatim(_) => {}
2884        _ => {}
2885    }
2886    Ok(())
2887}
2888
2889/// Validates generic type parameters and their bounds both for inline parameters and where clauses.
2890/// Bails for const params. Lifetimes are supported via hoisting.
2891fn validate_generics(generics: &syn::Generics) -> Result<(), Diagnostic> {
2892    if let Some(where_clause) = &generics.where_clause {
2893        for predicate in &where_clause.predicates {
2894            match predicate {
2895                syn::WherePredicate::Type(predicate_type) => {
2896                    // Lifetime bounds on types (for<'a>) are now supported
2897                    predicate_type
2898                        .bounds
2899                        .iter()
2900                        .try_for_each(validate_generic_type_param_bound)?;
2901                }
2902                syn::WherePredicate::Lifetime(_) => {
2903                    // Lifetime bounds (e.g., 'a: 'b) are now supported
2904                }
2905                _ => bail_generic_unsupported(predicate)?,
2906            }
2907        }
2908    }
2909
2910    for param in &generics.params {
2911        match param {
2912            syn::GenericParam::Lifetime(_) => {
2913                // Lifetimes are now supported via hoisting
2914            }
2915            syn::GenericParam::Type(type_param) => {
2916                type_param
2917                    .bounds
2918                    .iter()
2919                    .try_for_each(validate_generic_type_param_bound)?;
2920            }
2921            syn::GenericParam::Const(const_param) => bail_generic_unsupported(const_param)?,
2922        }
2923    }
2924
2925    Ok(())
2926}
2927
2928pub fn reset_attrs_used() {
2929    ATTRS.with(|state| {
2930        state.parsed.set(0);
2931        state.checks.set(0);
2932        state.unused_attrs.borrow_mut().clear();
2933    })
2934}
2935
2936pub fn check_unused_attrs(tokens: &mut TokenStream) {
2937    ATTRS.with(|state| {
2938        assert_eq!(state.parsed.get(), state.checks.get());
2939        let unused_attrs = &*state.unused_attrs.borrow();
2940        if !unused_attrs.is_empty() {
2941            let unused_attrs = unused_attrs.iter().map(|UnusedState { error, ident }| {
2942                if *error {
2943                    let text = format!("invalid attribute {ident} in this position");
2944                    quote::quote_spanned! { ident.span() => ::core::compile_error!(#text); }
2945                } else {
2946                    quote::quote! { let #ident: (); }
2947                }
2948            });
2949            tokens.extend(quote::quote! {
2950                // Anonymous scope to prevent name clashes.
2951                const _: () = {
2952                    #(#unused_attrs)*
2953                };
2954            });
2955        }
2956    })
2957}
2958
2959pub fn unused_attrs_diagnostic() -> Result<(), Diagnostic> {
2960    ATTRS.with(|state| {
2961        assert_eq!(state.parsed.get(), state.checks.get());
2962        let errors = state
2963            .unused_attrs
2964            .borrow()
2965            .iter()
2966            .filter_map(|UnusedState { error, ident }| {
2967                if *error {
2968                    Some(Diagnostic::span_error(
2969                        ident.span(),
2970                        format!("invalid attribute {ident} in this position"),
2971                    ))
2972                } else {
2973                    None
2974                }
2975            })
2976            .collect();
2977        Diagnostic::from_vec(errors)
2978    })
2979}
2980
2981fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind {
2982    let mut operation_kind = ast::OperationKind::Regular;
2983    if opts.this().is_some() {
2984        operation_kind = ast::OperationKind::RegularThis;
2985    }
2986    if let Some(g) = opts.getter() {
2987        operation_kind = ast::OperationKind::Getter(g.clone());
2988    }
2989    if let Some(s) = opts.setter() {
2990        operation_kind = ast::OperationKind::Setter(s.clone());
2991    }
2992    if opts.indexing_getter().is_some() {
2993        operation_kind = ast::OperationKind::IndexingGetter;
2994    }
2995    if opts.indexing_setter().is_some() {
2996        operation_kind = ast::OperationKind::IndexingSetter;
2997    }
2998    if opts.indexing_deleter().is_some() {
2999        operation_kind = ast::OperationKind::IndexingDeleter;
3000    }
3001    operation_kind
3002}
3003
3004pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
3005    let mut program = ast::Program::default();
3006    let module = module_from_opts(&mut program, &opts)?.ok_or_else(|| {
3007        Diagnostic::span_error(Span::call_site(), "`link_to!` requires a module.")
3008    })?;
3009    if let ast::ImportModule::Named(p, s) | ast::ImportModule::RawNamed(p, s) = &module {
3010        if !p.starts_with("./") && !p.starts_with("../") && !p.starts_with('/') {
3011            return Err(Diagnostic::span_error(
3012                *s,
3013                "`link_to!` does not support module paths.",
3014            ));
3015        }
3016    }
3017    opts.enforce_used()?;
3018    program.linked_modules.push(module);
3019    Ok(ast::LinkToModule(program))
3020}
3021
3022fn main(program: &ast::Program, mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
3023    if f.sig.ident != "main" {
3024        bail_span!(&f.sig.ident, "the main function has to be called main");
3025    }
3026    if let Some(constness) = f.sig.constness {
3027        bail_span!(&constness, "the main function cannot be const");
3028    }
3029    if !f.sig.generics.params.is_empty() {
3030        bail_span!(&f.sig.generics, "the main function cannot have generics");
3031    }
3032    if !f.sig.inputs.is_empty() {
3033        bail_span!(&f.sig.inputs, "the main function cannot have arguments");
3034    }
3035
3036    let r#return = f.sig.output;
3037    f.sig.output = ReturnType::Default;
3038    let body = f.block.as_ref();
3039
3040    let wasm_bindgen = &program.wasm_bindgen;
3041    let wasm_bindgen_futures = &program.wasm_bindgen_futures;
3042    let js_sys = &program.js_sys;
3043    let futures = if ast::use_js_sys_futures() {
3044        quote::quote! { #js_sys::futures }
3045    } else {
3046        quote::quote! { #wasm_bindgen_futures }
3047    };
3048
3049    if f.sig.asyncness.take().is_some() {
3050        *f.block = syn::parse2(quote::quote! {
3051                {
3052                    async fn __wasm_bindgen_generated_main() #r#return #body
3053                    #futures::spawn_local(
3054                        async move {
3055                            use #wasm_bindgen::__rt::Main;
3056                            let __ret = __wasm_bindgen_generated_main();
3057                            (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main()
3058                        },
3059                    )
3060                }
3061            })
3062            .unwrap();
3063    } else {
3064        *f.block = syn::parse2(quote::quote! {
3065            {
3066                fn __wasm_bindgen_generated_main() #r#return #body
3067                use #wasm_bindgen::__rt::Main;
3068                let __ret = __wasm_bindgen_generated_main();
3069                (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main()
3070            }
3071        })
3072        .unwrap();
3073    }
3074
3075    f.to_tokens(tokens);
3076
3077    Ok(())
3078}
3079
3080#[cfg(test)]
3081mod tests {
3082    #[test]
3083    fn test_try_unescape() {
3084        use super::try_unescape;
3085        assert_eq!(try_unescape("hello").unwrap(), "hello");
3086        assert_eq!(try_unescape("\"hello").unwrap(), "hello");
3087        assert_eq!(try_unescape("hello\"").unwrap(), "hello");
3088        assert_eq!(try_unescape("\"hello\"").unwrap(), "hello");
3089        assert_eq!(try_unescape("hello\\\\").unwrap(), "hello\\");
3090        assert_eq!(try_unescape("hello\\n").unwrap(), "hello\n");
3091        assert_eq!(try_unescape("hello\\u"), None);
3092        assert_eq!(try_unescape("hello\\u{"), None);
3093        assert_eq!(try_unescape("hello\\u{}"), None);
3094        assert_eq!(try_unescape("hello\\u{0}").unwrap(), "hello\0");
3095        assert_eq!(try_unescape("hello\\u{000000}").unwrap(), "hello\0");
3096        assert_eq!(try_unescape("hello\\u{0000000}"), None);
3097    }
3098}