wasm_bindgen_macro_support/
lib.rs

1//! This crate contains the part of the implementation of the `#[wasm_bindgen]` optsibute that is
2//! not in the shared backend crate.
3
4#![doc(html_root_url = "https://docs.rs/wasm-bindgen-macro-support/0.2")]
5
6extern crate proc_macro2;
7extern crate quote;
8#[macro_use]
9extern crate syn;
10#[macro_use]
11extern crate wasm_bindgen_backend as backend;
12extern crate wasm_bindgen_shared as shared;
13
14pub use crate::parser::BindgenAttrs;
15use crate::parser::MacroParse;
16use backend::{Diagnostic, TryToTokens};
17use proc_macro2::TokenStream;
18use quote::ToTokens;
19use quote::TokenStreamExt;
20use syn::parse::{Parse, ParseStream, Result as SynResult};
21
22mod parser;
23
24/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
25pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
26    parser::reset_attrs_used();
27    let item = syn::parse2::<syn::Item>(input)?;
28    let opts = syn::parse2(attr)?;
29
30    let mut tokens = proc_macro2::TokenStream::new();
31    let mut program = backend::ast::Program::default();
32    item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
33    program.try_to_tokens(&mut tokens)?;
34
35    // If we successfully got here then we should have used up all attributes
36    // and considered all of them to see if they were used. If one was forgotten
37    // that's a bug on our end, so sanity check here.
38    parser::check_unused_attrs(&mut tokens);
39
40    Ok(tokens)
41}
42
43/// Takes the parsed input from a `wasm_bindgen::link_to` macro and returns the generated link
44pub fn expand_link_to(input: TokenStream) -> Result<TokenStream, Diagnostic> {
45    parser::reset_attrs_used();
46    let opts = syn::parse2(input)?;
47
48    let mut tokens = proc_macro2::TokenStream::new();
49    let link = parser::link_to(opts)?;
50    link.try_to_tokens(&mut tokens)?;
51
52    Ok(tokens)
53}
54
55/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
56pub fn expand_class_marker(
57    attr: TokenStream,
58    input: TokenStream,
59) -> Result<TokenStream, Diagnostic> {
60    parser::reset_attrs_used();
61    let mut item = syn::parse2::<syn::ImplItemFn>(input)?;
62    let opts: ClassMarker = syn::parse2(attr)?;
63
64    let mut program = backend::ast::Program::default();
65    item.macro_parse(&mut program, &opts)?;
66
67    // This is where things are slightly different, we are being expanded in the
68    // context of an impl so we can't inject arbitrary item-like tokens into the
69    // output stream. If we were to do that then it wouldn't parse!
70    //
71    // Instead what we want to do is to generate the tokens for `program` into
72    // the header of the function. This'll inject some no_mangle functions and
73    // statics and such, and they should all be valid in the context of the
74    // start of a function.
75    //
76    // We manually implement `ToTokens for ImplItemFn` here, injecting our
77    // program's tokens before the actual method's inner body tokens.
78    let mut tokens = proc_macro2::TokenStream::new();
79    tokens.append_all(
80        item.attrs
81            .iter()
82            .filter(|attr| matches!(attr.style, syn::AttrStyle::Outer)),
83    );
84    item.vis.to_tokens(&mut tokens);
85    item.sig.to_tokens(&mut tokens);
86    let mut err = None;
87    item.block.brace_token.surround(&mut tokens, |tokens| {
88        if let Err(e) = program.try_to_tokens(tokens) {
89            err = Some(e);
90        }
91        parser::check_unused_attrs(tokens); // same as above
92        tokens.append_all(
93            item.attrs
94                .iter()
95                .filter(|attr| matches!(attr.style, syn::AttrStyle::Inner(_))),
96        );
97        tokens.append_all(&item.block.stmts);
98    });
99
100    if let Some(err) = err {
101        return Err(err);
102    }
103
104    Ok(tokens)
105}
106
107struct ClassMarker {
108    class: syn::Ident,
109    js_class: String,
110    wasm_bindgen: syn::Path,
111    wasm_bindgen_futures: syn::Path,
112}
113
114impl Parse for ClassMarker {
115    fn parse(input: ParseStream) -> SynResult<Self> {
116        let class = input.parse::<syn::Ident>()?;
117        input.parse::<Token![=]>()?;
118        let mut js_class = input.parse::<syn::LitStr>()?.value();
119        js_class = js_class
120            .strip_prefix("r#")
121            .map(String::from)
122            .unwrap_or(js_class);
123
124        let mut wasm_bindgen = None;
125        let mut wasm_bindgen_futures = None;
126
127        loop {
128            if input.parse::<Option<Token![,]>>()?.is_some() {
129                let ident = input.parse::<syn::Ident>()?;
130
131                if ident == "wasm_bindgen" {
132                    if wasm_bindgen.is_some() {
133                        return Err(syn::Error::new(
134                            ident.span(),
135                            "found duplicate `wasm_bindgen`",
136                        ));
137                    }
138
139                    input.parse::<Token![=]>()?;
140                    wasm_bindgen = Some(input.parse::<syn::Path>()?);
141                } else if ident == "wasm_bindgen_futures" {
142                    if wasm_bindgen_futures.is_some() {
143                        return Err(syn::Error::new(
144                            ident.span(),
145                            "found duplicate `wasm_bindgen_futures`",
146                        ));
147                    }
148
149                    input.parse::<Token![=]>()?;
150                    wasm_bindgen_futures = Some(input.parse::<syn::Path>()?);
151                } else {
152                    return Err(syn::Error::new(
153                        ident.span(),
154                        "expected `wasm_bindgen` or `wasm_bindgen_futures`",
155                    ));
156                }
157            } else {
158                break;
159            }
160        }
161
162        Ok(ClassMarker {
163            class,
164            js_class,
165            wasm_bindgen: wasm_bindgen.unwrap_or_else(|| syn::parse_quote! { wasm_bindgen }),
166            wasm_bindgen_futures: wasm_bindgen_futures
167                .unwrap_or_else(|| syn::parse_quote! { wasm_bindgen_futures }),
168        })
169    }
170}