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