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