1extern crate proc_macro;
5
6use proc_macro2::*;
7use quote::quote;
8use quote::quote_spanned;
9
10#[proc_macro_attribute]
11pub fn wasm_bindgen_bench(
12 attr: proc_macro::TokenStream,
13 body: proc_macro::TokenStream,
14) -> proc_macro::TokenStream {
15 bindgen(attr, body, true)
16}
17
18#[proc_macro_attribute]
19pub fn wasm_bindgen_test(
20 attr: proc_macro::TokenStream,
21 body: proc_macro::TokenStream,
22) -> proc_macro::TokenStream {
23 bindgen(attr, body, false)
24}
25
26fn bindgen(
27 attr: proc_macro::TokenStream,
28 body: proc_macro::TokenStream,
29 is_bench: bool,
30) -> proc_macro::TokenStream {
31 let mut attributes = Attributes::default();
32 let attribute_parser = syn::meta::parser(|meta| attributes.parse(meta));
33
34 syn::parse_macro_input!(attr with attribute_parser);
35 let mut should_panic = None;
36 let mut ignore = None;
37
38 let mut body = TokenStream::from(body).into_iter().peekable();
39
40 let mut leading_tokens = Vec::new();
42 while let Some(token) = body.next() {
43 match parse_should_panic(&mut body, &token) {
44 Ok(Some((new_should_panic, span))) => {
45 if should_panic.replace(new_should_panic).is_some() {
46 return compile_error(span, "duplicate `should_panic` attribute");
47 }
48
49 body.next();
52 continue;
53 }
54 Ok(None) => (),
55 Err(error) => return error,
56 }
57
58 match parse_ignore(&mut body, &token) {
59 Ok(Some((new_ignore, span))) => {
60 if ignore.replace(new_ignore).is_some() {
61 return compile_error(span, "duplicate `ignore` attribute");
62 }
63
64 body.next();
67 continue;
68 }
69 Ok(None) => (),
70 Err(error) => return error,
71 }
72
73 leading_tokens.push(token.clone());
74 if let TokenTree::Ident(token) = token {
75 if token == "async" {
76 attributes.r#async = true;
77 }
78 if token == "fn" {
79 break;
80 }
81 }
82 }
83 let ident = find_ident(&mut body).expect("expected a function name");
84
85 let mut tokens = Vec::<TokenTree>::new();
86
87 let should_panic_par = match &should_panic {
88 Some(Some(lit)) => {
89 quote! { ::core::option::Option::Some(::core::option::Option::Some(#lit)) }
90 }
91 Some(None) => quote! { ::core::option::Option::Some(::core::option::Option::None) },
92 None => quote! { ::core::option::Option::None },
93 };
94
95 let ignore_par = match &ignore {
96 Some(Some(lit)) => {
97 quote! { ::core::option::Option::Some(::core::option::Option::Some(#lit)) }
98 }
99 Some(None) => quote! { ::core::option::Option::Some(::core::option::Option::None) },
100 None => quote! { ::core::option::Option::None },
101 };
102
103 let exec_ident = if is_bench {
104 let body = if attributes.r#async {
105 quote! { #ident(&mut bencher).await; }
106 } else {
107 quote! { #ident(&mut bencher); }
108 };
109 let bench_ident = quote::format_ident!("__wbg_bench_{ident}");
110 tokens.extend(quote! {
111 async fn #bench_ident() {
112 let mut bencher = Criterion::default()
113 .with_location(file!(), module_path!());
114 #body
115 }
116 });
117 bench_ident
118 } else {
119 ident.clone()
120 };
121
122 let test_body = if attributes.r#async || is_bench {
123 quote! { cx.execute_async(test_name, #exec_ident, #should_panic_par, #ignore_par); }
124 } else {
125 quote! { cx.execute_sync(test_name, #exec_ident, #should_panic_par, #ignore_par); }
126 };
127
128 let ignore_name = if ignore.is_some() { "$" } else { "" };
129
130 let wasm_bindgen_path = attributes.wasm_bindgen_path;
131 let prefix = if is_bench { "__wbgb_" } else { "__wbgt_" };
132 tokens.extend(
133 quote! {
134 const _: () = {
135 #wasm_bindgen_path::__rt::wasm_bindgen::__wbindgen_coverage! {
136 #[export_name = ::core::concat!(#prefix, #ignore_name, "_", ::core::module_path!(), "::", ::core::stringify!(#ident))]
137 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
138 extern "C" fn __wbgt_test(cx: &#wasm_bindgen_path::__rt::Context) {
139 let test_name = ::core::concat!(::core::module_path!(), "::", ::core::stringify!(#ident));
140 #test_body
141 }
142 }
143 };
144 },
145 );
146
147 if let Some(path) = attributes.unsupported {
148 tokens.extend(
149 quote! { #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))), #path)] },
150 );
151
152 if let Some(should_panic) = should_panic {
153 let should_panic = if let Some(lit) = should_panic {
154 quote! { should_panic = #lit }
155 } else {
156 quote! { should_panic }
157 };
158
159 tokens.extend(
160 quote! { #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))), #should_panic)] }
161 )
162 }
163
164 if let Some(ignore) = ignore {
165 let ignore = if let Some(lit) = ignore {
166 quote! { ignore = #lit }
167 } else {
168 quote! { ignore }
169 };
170
171 tokens.extend(
172 quote! { #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))), #ignore)] }
173 )
174 }
175 } else {
176 tokens.extend(quote! {
177 #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))), allow(dead_code))]
178 });
179 }
180
181 tokens.extend(leading_tokens);
182 tokens.push(ident.into());
183 tokens.extend(body);
184
185 tokens.into_iter().collect::<TokenStream>().into()
186}
187
188fn parse_should_panic(
189 body: &mut std::iter::Peekable<token_stream::IntoIter>,
190 token: &TokenTree,
191) -> Result<Option<(Option<Literal>, Span)>, proc_macro::TokenStream> {
192 match token {
194 TokenTree::Punct(op) if op.as_char() == '#' => (),
195 _ => return Ok(None),
196 }
197
198 let group = match body.peek() {
200 Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Bracket => group,
201 _ => return Ok(None),
202 };
203
204 let mut stream = group.stream().into_iter();
205
206 let mut span = match stream.next() {
208 Some(TokenTree::Ident(token)) if token == "should_panic" => token.span(),
209 _ => return Ok(None),
210 };
211
212 let should_panic = span;
213
214 match stream.next() {
216 Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => {
218 let span = group.span();
219 stream = group.stream().into_iter();
220
221 match stream.next() {
223 Some(TokenTree::Ident(token)) if token == "expected" => (),
224 _ => {
225 return Err(compile_error(
226 span,
227 "malformed `#[should_panic(...)]` attribute",
228 ))
229 }
230 }
231
232 match stream.next() {
234 Some(TokenTree::Punct(op)) if op.as_char() == '=' => (),
235 _ => {
236 return Err(compile_error(
237 span,
238 "malformed `#[should_panic(...)]` attribute",
239 ))
240 }
241 }
242 }
243 Some(TokenTree::Punct(op)) if op.as_char() == '=' => (),
245 Some(token) => {
246 return Err(compile_error(
247 token.span(),
248 "malformed `#[should_panic = \"...\"]` attribute",
249 ))
250 }
251 None => {
252 return Ok(Some((None, should_panic)));
253 }
254 }
255
256 if let Some(TokenTree::Literal(lit)) = stream.next() {
258 span = lit.span();
259 let string = lit.to_string();
260
261 if string.starts_with('"') && string.ends_with('"') {
263 return Ok(Some((Some(lit), should_panic)));
264 }
265 }
266
267 Err(compile_error(span, "malformed `#[should_panic]` attribute"))
268}
269
270fn parse_ignore(
271 body: &mut std::iter::Peekable<token_stream::IntoIter>,
272 token: &TokenTree,
273) -> Result<Option<(Option<Literal>, Span)>, proc_macro::TokenStream> {
274 match token {
276 TokenTree::Punct(op) if op.as_char() == '#' => (),
277 _ => return Ok(None),
278 }
279
280 let group = match body.peek() {
282 Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Bracket => group,
283 _ => return Ok(None),
284 };
285
286 let mut stream = group.stream().into_iter();
287
288 let mut span = match stream.next() {
290 Some(TokenTree::Ident(token)) if token == "ignore" => token.span(),
291 _ => return Ok(None),
292 };
293
294 let ignore = span;
295
296 match stream.next() {
298 Some(TokenTree::Punct(op)) if op.as_char() == '=' => (),
300 Some(token) => {
301 return Err(compile_error(
302 token.span(),
303 "malformed `#[ignore = \"...\"]` attribute",
304 ))
305 }
306 None => {
307 return Ok(Some((None, ignore)));
308 }
309 }
310
311 if let Some(TokenTree::Literal(lit)) = stream.next() {
313 span = lit.span();
314 let string = lit.to_string();
315
316 if string.starts_with('"') && string.ends_with('"') {
318 return Ok(Some((Some(lit), ignore)));
319 }
320 }
321
322 Err(compile_error(span, "malformed `#[ignore]` attribute"))
323}
324
325fn find_ident(iter: &mut impl Iterator<Item = TokenTree>) -> Option<Ident> {
326 match iter.next()? {
327 TokenTree::Ident(i) => Some(i),
328 TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
329 find_ident(&mut g.stream().into_iter())
330 }
331 _ => None,
332 }
333}
334
335fn compile_error(span: Span, msg: &str) -> proc_macro::TokenStream {
336 quote_spanned! { span => compile_error!(#msg); }.into()
337}
338
339struct Attributes {
340 r#async: bool,
341 wasm_bindgen_path: syn::Path,
342 unsupported: Option<syn::Meta>,
343}
344
345impl Default for Attributes {
346 fn default() -> Self {
347 Self {
348 r#async: false,
349 wasm_bindgen_path: syn::parse_quote!(::wasm_bindgen_test),
350 unsupported: None,
351 }
352 }
353}
354
355impl Attributes {
356 fn parse(&mut self, meta: syn::meta::ParseNestedMeta) -> syn::parse::Result<()> {
357 if meta.path.is_ident("async") {
358 self.r#async = true;
359 } else if meta.path.is_ident("crate") {
360 self.wasm_bindgen_path = meta.value()?.parse::<syn::Path>()?;
361 } else if meta.path.is_ident("unsupported") {
362 self.unsupported = Some(meta.value()?.parse::<syn::Meta>()?);
363 } else {
364 return Err(meta.error("unknown attribute"));
365 }
366 Ok(())
367 }
368}