Skip to main content

wasm_bindgen_macro_support/
lib.rs

1//! This crate contains implementation APIs for the `#[wasm_bindgen]` attribute.
2
3#![doc(html_root_url = "https://docs.rs/wasm-bindgen-macro-support/0.2")]
4
5#[macro_use]
6mod error;
7
8pub mod ast;
9#[cfg(feature = "expand")]
10mod codegen;
11#[cfg(feature = "expand")]
12mod encode;
13#[cfg(feature = "expand")]
14mod generics;
15mod hash;
16pub mod parser;
17
18#[cfg(feature = "expand")]
19use codegen::TryToTokens;
20pub use error::Diagnostic;
21pub use parser::{BindgenAttr, BindgenAttrs, JsNamespace};
22use parser::{ConvertToAst, MacroParse};
23use proc_macro2::TokenStream;
24use quote::quote;
25use quote::ToTokens;
26#[cfg(feature = "expand")]
27use quote::TokenStreamExt;
28use syn::ext::IdentExt;
29use syn::parse::{Parse, ParseStream, Result as SynResult};
30use syn::Token;
31
32/// Parsed upstream macro-support AST plus Rust tokens preserved by the parser.
33///
34/// The parser emits these tokens for user Rust items that must remain in the
35/// expanded output while the AST carries wasm-bindgen metadata for generated
36/// glue.
37pub struct ParseOutput {
38    pub program: ast::Program,
39    pub tokens: TokenStream,
40    pub main: Option<syn::Ident>,
41}
42
43/// Parse a `#[wasm_bindgen]` input item into the upstream macro-support AST.
44pub fn parse(attr: TokenStream, input: TokenStream) -> Result<ast::Program, Diagnostic> {
45    Ok(parse_with_tokens(attr, input)?.program)
46}
47
48/// Parse a `#[wasm_bindgen]` input item into the upstream macro-support AST and
49/// return the Rust tokens the upstream parser preserves.
50pub fn parse_with_tokens(attr: TokenStream, input: TokenStream) -> Result<ParseOutput, Diagnostic> {
51    parser::reset_attrs_used();
52
53    let item = match syn::parse2::<syn::Item>(input)? {
54        syn::Item::Struct(item) => return parse_struct_item_with_tokens(attr, item),
55        syn::Item::Impl(item) => return parse_impl_item_with_tokens(attr, item),
56        syn::Item::Const(item) => return parse_const_item_with_tokens(attr, item),
57        item => item,
58    };
59
60    let opts: BindgenAttrs = syn::parse2(attr)?;
61    let main = match &item {
62        syn::Item::Fn(item) if opts.main().is_some() => Some(item.sig.ident.clone()),
63        _ => None,
64    };
65    let mut tokens = TokenStream::new();
66    let mut program = ast::Program::default();
67    item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
68    parser::unused_attrs_diagnostic()?;
69
70    Ok(ParseOutput {
71        program,
72        tokens,
73        main,
74    })
75}
76
77fn parse_const_item_with_tokens(
78    attr: TokenStream,
79    item: syn::ItemConst,
80) -> Result<ParseOutput, Diagnostic> {
81    let opts = syn::parse2(attr)?;
82    let mut program = ast::Program::default();
83    item.clone().macro_parse(&mut program, opts)?;
84    parser::unused_attrs_diagnostic()?;
85
86    Ok(ParseOutput {
87        program,
88        tokens: item.to_token_stream(),
89        main: None,
90    })
91}
92
93fn parse_struct_item_with_tokens(
94    attr: TokenStream,
95    mut item: syn::ItemStruct,
96) -> Result<ParseOutput, Diagnostic> {
97    let opts: BindgenAttrs = syn::parse2(attr.clone())?;
98    let wasm_bindgen = opts
99        .wasm_bindgen()
100        .cloned()
101        .unwrap_or_else(|| syn::parse_quote! { ::wasm_bindgen });
102    let extends_path = opts.attrs.iter().find_map(|(_, a)| match a {
103        parser::BindgenAttr::Extends(_, path) => Some(path.clone()),
104        _ => None,
105    });
106    parser::inject_parent_field(&mut item, extends_path.as_ref(), &wasm_bindgen)?;
107
108    // The upstream struct path is two-stage: the attribute macro re-emits the
109    // struct with `#[wasm_bindgen(...)]`, then the derive marker parses it.
110    // Reset attribute accounting for that marker-like parse.
111    parser::reset_attrs_used();
112    let mut item: syn::ItemStruct = syn::parse2(quote! {
113        #[wasm_bindgen(#attr)]
114        #item
115    })?;
116
117    let mut program = ast::Program::default();
118    program.structs.push((&mut item).convert(&program)?);
119    parser::unused_attrs_diagnostic()?;
120
121    Ok(ParseOutput {
122        program,
123        tokens: item.to_token_stream(),
124        main: None,
125    })
126}
127
128fn parse_impl_item_with_tokens(
129    attr: TokenStream,
130    mut item: syn::ItemImpl,
131) -> Result<ParseOutput, Diagnostic> {
132    let opts: BindgenAttrs = syn::parse2(attr)?;
133
134    if item.defaultness.is_some() {
135        return Err(Diagnostic::spanned_error(
136            &item.defaultness,
137            "#[wasm_bindgen] default impls are not supported",
138        ));
139    }
140    if item.unsafety.is_some() {
141        return Err(Diagnostic::spanned_error(
142            &item.unsafety,
143            "#[wasm_bindgen] unsafe impls are not supported",
144        ));
145    }
146    if let Some((_, path, _)) = &item.trait_ {
147        return Err(Diagnostic::spanned_error(
148            path,
149            "#[wasm_bindgen] trait impls are not supported",
150        ));
151    }
152    if !item.generics.params.is_empty() {
153        return Err(Diagnostic::spanned_error(
154            &item.generics,
155            "#[wasm_bindgen] generic impls aren't supported",
156        ));
157    }
158
159    let class_path = match item.self_ty.as_ref() {
160        syn::Type::Path(path) if path.qself.is_none() => &path.path,
161        other => {
162            return Err(Diagnostic::spanned_error(
163                other,
164                "unsupported self type in #[wasm_bindgen] impl",
165            ));
166        }
167    };
168    let class = class_path
169        .segments
170        .last()
171        .ok_or_else(|| Diagnostic::spanned_error(class_path, "expected an impl self type"))?
172        .ident
173        .clone();
174
175    let mut program = ast::Program::default();
176    if let Some(path) = opts.wasm_bindgen() {
177        program.wasm_bindgen = path.clone();
178    }
179    if let Some(path) = opts.wasm_bindgen_futures() {
180        program.wasm_bindgen_futures = path.clone();
181    }
182    if let Some(path) = opts.js_sys() {
183        program.js_sys = path.clone();
184    }
185
186    let marker = ClassMarker {
187        class: class.clone(),
188        js_class: opts
189            .js_class()
190            .map(|s| s.0.to_string())
191            .unwrap_or_else(|| class.unraw().to_string()),
192        js_namespace: opts.js_namespace().map(|(ns, _)| ns.0),
193        wasm_bindgen: program.wasm_bindgen.clone(),
194        wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
195        js_sys: program.js_sys.clone(),
196    };
197
198    let mut errors = Vec::new();
199    for impl_item in item.items.iter_mut() {
200        match impl_item {
201            syn::ImplItem::Fn(method) => {
202                if let Err(error) = method.macro_parse(&mut program, &marker) {
203                    errors.push(error);
204                }
205            }
206            syn::ImplItem::Const(_) => errors.push(Diagnostic::spanned_error(
207                impl_item,
208                "const definitions aren't supported with #[wasm_bindgen]",
209            )),
210            syn::ImplItem::Type(_) => errors.push(Diagnostic::spanned_error(
211                impl_item,
212                "type definitions in impls aren't supported with #[wasm_bindgen]",
213            )),
214            syn::ImplItem::Macro(_) => errors.push(Diagnostic::spanned_error(
215                impl_item,
216                "macros in impls aren't supported",
217            )),
218            syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
219            other => errors.push(Diagnostic::spanned_error(
220                other,
221                "failed to parse this item as a known item",
222            )),
223        }
224    }
225    Diagnostic::from_vec(errors)?;
226    opts.check_used();
227    parser::unused_attrs_diagnostic()?;
228
229    Ok(ParseOutput {
230        program,
231        tokens: item.to_token_stream(),
232        main: None,
233    })
234}
235
236/// Parse a struct body through the upstream exported-struct converter.
237pub fn parse_struct(input: TokenStream) -> Result<ast::Struct, Diagnostic> {
238    parser::reset_attrs_used();
239
240    let mut item = syn::parse2::<syn::ItemStruct>(input)?;
241    let program = ast::Program::default();
242    let parsed = (&mut item).convert(&program)?;
243    parser::unused_attrs_diagnostic()?;
244
245    Ok(parsed)
246}
247
248/// Parse an internal class marker method into the upstream macro-support AST.
249pub fn parse_class_marker(
250    attr: TokenStream,
251    input: TokenStream,
252) -> Result<ast::Program, Diagnostic> {
253    Ok(parse_class_marker_with_tokens(attr, input)?.program)
254}
255
256/// Parse an internal class marker method into the upstream macro-support AST
257/// and return the method tokens preserved by the upstream parser.
258pub fn parse_class_marker_with_tokens(
259    attr: TokenStream,
260    input: TokenStream,
261) -> Result<ParseOutput, Diagnostic> {
262    parser::reset_attrs_used();
263
264    let mut item = syn::parse2::<syn::ImplItemFn>(input)?;
265    let opts: ClassMarker = syn::parse2(attr)?;
266    let mut program = ast::Program::default();
267    item.macro_parse(&mut program, &opts)?;
268    parser::unused_attrs_diagnostic()?;
269
270    Ok(ParseOutput {
271        program,
272        tokens: item.to_token_stream(),
273        main: None,
274    })
275}
276
277/// Parse a `wasm_bindgen::link_to!` input into its module AST.
278pub fn parse_link_to(input: TokenStream) -> Result<ast::LinkToModule, Diagnostic> {
279    parser::reset_attrs_used();
280
281    let opts = syn::parse2(input)?;
282    let parsed = parser::link_to(opts)?;
283    parser::unused_attrs_diagnostic()?;
284
285    Ok(parsed)
286}
287
288/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
289#[cfg(feature = "expand")]
290pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
291    parser::reset_attrs_used();
292    // if struct is encountered, add `derive` attribute and let everything happen there (workaround
293    // to help parsing cfg_attr correctly).
294    let item = syn::parse2::<syn::Item>(input)?;
295    if let syn::Item::Struct(mut s) = item {
296        let opts: BindgenAttrs = syn::parse2(attr.clone())?;
297        let wasm_bindgen = opts
298            .wasm_bindgen()
299            .cloned()
300            .unwrap_or_else(|| syn::parse_quote! { ::wasm_bindgen });
301
302        // Inject `parent: <wasm_bindgen>::Parent<Parent>` when the struct
303        // declares `#[wasm_bindgen(extends = Parent)]`, so users never write
304        // the field themselves. Also rejects user-declared `Parent<T>` fields.
305        let extends_path = opts.attrs.iter().find_map(|(_, a)| match a {
306            parser::BindgenAttr::Extends(_, path) => Some(path.clone()),
307            _ => None,
308        });
309        parser::inject_parent_field(&mut s, extends_path.as_ref(), &wasm_bindgen)?;
310
311        let item = quote! {
312            #[derive(#wasm_bindgen::__rt::BindgenedStruct)]
313            #[wasm_bindgen(#attr)]
314            #s
315        };
316        return Ok(item);
317    }
318
319    let opts = syn::parse2(attr)?;
320    let mut tokens = proc_macro2::TokenStream::new();
321    let mut program = ast::Program::default();
322    item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
323    program.try_to_tokens(&mut tokens)?;
324
325    // If we successfully got here then we should have used up all attributes
326    // and considered all of them to see if they were used. If one was forgotten
327    // that's a bug on our end, so sanity check here.
328    parser::check_unused_attrs(&mut tokens);
329
330    Ok(tokens)
331}
332
333/// Takes the parsed input from a `wasm_bindgen::link_to` macro and returns the generated link
334#[cfg(feature = "expand")]
335pub fn expand_link_to(input: TokenStream) -> Result<TokenStream, Diagnostic> {
336    parser::reset_attrs_used();
337    let opts = syn::parse2(input)?;
338
339    let mut tokens = proc_macro2::TokenStream::new();
340    let link = parser::link_to(opts)?;
341    link.try_to_tokens(&mut tokens)?;
342
343    Ok(tokens)
344}
345
346/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
347#[cfg(feature = "expand")]
348pub fn expand_class_marker(
349    attr: TokenStream,
350    input: TokenStream,
351) -> Result<TokenStream, Diagnostic> {
352    parser::reset_attrs_used();
353    let mut item = syn::parse2::<syn::ImplItemFn>(input)?;
354    let opts: ClassMarker = syn::parse2(attr)?;
355
356    let mut program = ast::Program::default();
357    item.macro_parse(&mut program, &opts)?;
358
359    // This is where things are slightly different, we are being expanded in the
360    // context of an impl so we can't inject arbitrary item-like tokens into the
361    // output stream. If we were to do that then it wouldn't parse!
362    //
363    // Instead what we want to do is to generate the tokens for `program` into
364    // the header of the function. This'll inject some no_mangle functions and
365    // statics and such, and they should all be valid in the context of the
366    // start of a function.
367    //
368    // We manually implement `ToTokens for ImplItemFn` here, injecting our
369    // program's tokens before the actual method's inner body tokens.
370    let mut tokens = proc_macro2::TokenStream::new();
371    tokens.append_all(
372        item.attrs
373            .iter()
374            .filter(|attr| matches!(attr.style, syn::AttrStyle::Outer)),
375    );
376    item.vis.to_tokens(&mut tokens);
377    item.sig.to_tokens(&mut tokens);
378    let mut err = None;
379    item.block.brace_token.surround(&mut tokens, |tokens| {
380        if let Err(e) = program.try_to_tokens(tokens) {
381            err = Some(e);
382        }
383        parser::check_unused_attrs(tokens); // same as above
384        tokens.append_all(
385            item.attrs
386                .iter()
387                .filter(|attr| matches!(attr.style, syn::AttrStyle::Inner(_))),
388        );
389        tokens.append_all(&item.block.stmts);
390    });
391
392    if let Some(err) = err {
393        return Err(err);
394    }
395
396    Ok(tokens)
397}
398
399struct ClassMarker {
400    class: syn::Ident,
401    js_class: String,
402    js_namespace: Option<Vec<String>>,
403    wasm_bindgen: syn::Path,
404    wasm_bindgen_futures: syn::Path,
405    js_sys: syn::Path,
406}
407
408impl Parse for ClassMarker {
409    fn parse(input: ParseStream) -> SynResult<Self> {
410        let class = input.parse::<syn::Ident>()?;
411        input.parse::<Token![=]>()?;
412        let mut js_class = input.parse::<syn::LitStr>()?.value();
413        js_class = js_class
414            .strip_prefix("r#")
415            .map(String::from)
416            .unwrap_or(js_class);
417
418        let mut js_namespace: Option<Vec<String>> = None;
419        let mut wasm_bindgen = None;
420        let mut wasm_bindgen_futures = None;
421        let mut js_sys = None;
422
423        loop {
424            if input.parse::<Option<Token![,]>>()?.is_some() {
425                let ident = input.parse::<syn::Ident>()?;
426
427                if ident == "js_namespace" {
428                    if js_namespace.is_some() {
429                        return Err(syn::Error::new(
430                            ident.span(),
431                            "found duplicate `js_namespace`",
432                        ));
433                    }
434                    input.parse::<Token![=]>()?;
435                    let content;
436                    syn::bracketed!(content in input);
437                    let segs: syn::punctuated::Punctuated<syn::LitStr, Token![,]> = content
438                        .parse_terminated(|p: ParseStream| p.parse::<syn::LitStr>(), Token![,])?;
439                    js_namespace = Some(segs.into_iter().map(|s| s.value()).collect());
440                } else if ident == "wasm_bindgen" {
441                    if wasm_bindgen.is_some() {
442                        return Err(syn::Error::new(
443                            ident.span(),
444                            "found duplicate `wasm_bindgen`",
445                        ));
446                    }
447
448                    input.parse::<Token![=]>()?;
449                    wasm_bindgen = Some(input.parse::<syn::Path>()?);
450                } else if ident == "wasm_bindgen_futures" {
451                    if wasm_bindgen_futures.is_some() {
452                        return Err(syn::Error::new(
453                            ident.span(),
454                            "found duplicate `wasm_bindgen_futures`",
455                        ));
456                    }
457
458                    input.parse::<Token![=]>()?;
459                    wasm_bindgen_futures = Some(input.parse::<syn::Path>()?);
460                } else if ident == "js_sys" {
461                    if js_sys.is_some() {
462                        return Err(syn::Error::new(ident.span(), "found duplicate `js_sys`"));
463                    }
464
465                    input.parse::<Token![=]>()?;
466                    js_sys = Some(input.parse::<syn::Path>()?);
467                } else {
468                    return Err(syn::Error::new(
469                        ident.span(),
470                        "expected `js_namespace`, `wasm_bindgen`, `wasm_bindgen_futures`, or `js_sys`",
471                    ));
472                }
473            } else {
474                break;
475            }
476        }
477
478        Ok(ClassMarker {
479            class,
480            js_class,
481            js_namespace,
482            wasm_bindgen: wasm_bindgen.unwrap_or_else(|| syn::parse_quote! { wasm_bindgen }),
483            wasm_bindgen_futures: wasm_bindgen_futures
484                .unwrap_or_else(|| syn::parse_quote! { wasm_bindgen_futures }),
485            js_sys: js_sys.unwrap_or_else(|| syn::parse_quote! { js_sys }),
486        })
487    }
488}
489
490#[cfg(feature = "expand")]
491pub fn expand_struct_marker(item: TokenStream) -> Result<TokenStream, Diagnostic> {
492    parser::reset_attrs_used();
493
494    let mut s: syn::ItemStruct = syn::parse2(item)?;
495
496    let mut program = ast::Program::default();
497    program.structs.push((&mut s).convert(&program)?);
498
499    let mut tokens = proc_macro2::TokenStream::new();
500    program.try_to_tokens(&mut tokens)?;
501
502    parser::check_unused_attrs(&mut tokens);
503
504    Ok(tokens)
505}