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 | |
6 | extern crate proc_macro2; |
7 | extern crate quote; |
8 | #[macro_use ] |
9 | extern crate syn; |
10 | #[macro_use ] |
11 | extern crate wasm_bindgen_backend as backend; |
12 | extern crate wasm_bindgen_shared as shared; |
13 | |
14 | pub use crate::parser::BindgenAttrs; |
15 | use crate::parser::{ConvertToAst, MacroParse}; |
16 | use backend::{Diagnostic, TryToTokens}; |
17 | use proc_macro2::TokenStream; |
18 | use quote::quote; |
19 | use quote::ToTokens; |
20 | use quote::TokenStreamExt; |
21 | use syn::parse::{Parse, ParseStream, Result as SynResult}; |
22 | |
23 | mod parser; |
24 | |
25 | /// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings |
26 | pub 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 |
61 | pub fn expand_link_to(input: TokenStream) -> Result<TokenStream, Diagnostic> { |
62 | parser::reset_attrs_used(); |
63 | let opts: BindgenAttrs = syn::parse2(tokens:input)?; |
64 | |
65 | let mut tokens: TokenStream = proc_macro2::TokenStream::new(); |
66 | let link: LinkToModule = 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 |
73 | pub 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 | |
124 | struct ClassMarker { |
125 | class: syn::Ident, |
126 | js_class: String, |
127 | wasm_bindgen: syn::Path, |
128 | wasm_bindgen_futures: syn::Path, |
129 | } |
130 | |
131 | impl 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 | |
189 | pub fn expand_struct_marker(item: TokenStream) -> Result<TokenStream, Diagnostic> { |
190 | parser::reset_attrs_used(); |
191 | |
192 | let mut s: syn::ItemStruct = syn::parse2(tokens:item)?; |
193 | |
194 | let mut program: Program = backend::ast::Program::default(); |
195 | program.structs.push((&mut s).convert(&program)?); |
196 | |
197 | let mut tokens: TokenStream = proc_macro2::TokenStream::new(); |
198 | program.try_to_tokens(&mut tokens)?; |
199 | |
200 | parser::check_unused_attrs(&mut tokens); |
201 | |
202 | Ok(tokens) |
203 | } |
204 | |