wasm_bindgen_macro_support/
lib.rs1#![doc(html_root_url = "https://docs.rs/wasm-bindgen-macro-support/0.2")]
4
5#[macro_use]
6mod error;
7
8pub mod ast;
9#[cfg(feature = "expand")]
10mod codegen;
11#[cfg(feature = "expand")]
12mod encode;
13#[cfg(feature = "expand")]
14mod generics;
15mod hash;
16pub mod parser;
17
18#[cfg(feature = "expand")]
19use codegen::TryToTokens;
20pub use error::Diagnostic;
21pub use parser::{BindgenAttr, BindgenAttrs, JsNamespace};
22use parser::{ConvertToAst, MacroParse};
23use proc_macro2::TokenStream;
24use quote::quote;
25use quote::ToTokens;
26#[cfg(feature = "expand")]
27use quote::TokenStreamExt;
28use syn::ext::IdentExt;
29use syn::parse::{Parse, ParseStream, Result as SynResult};
30use syn::Token;
31
32pub struct ParseOutput {
38 pub program: ast::Program,
39 pub tokens: TokenStream,
40 pub main: Option<syn::Ident>,
41}
42
43pub fn parse(attr: TokenStream, input: TokenStream) -> Result<ast::Program, Diagnostic> {
45 Ok(parse_with_tokens(attr, input)?.program)
46}
47
48pub fn parse_with_tokens(attr: TokenStream, input: TokenStream) -> Result<ParseOutput, Diagnostic> {
51 parser::reset_attrs_used();
52
53 let item = match syn::parse2::<syn::Item>(input)? {
54 syn::Item::Struct(item) => return parse_struct_item_with_tokens(attr, item),
55 syn::Item::Impl(item) => return parse_impl_item_with_tokens(attr, item),
56 syn::Item::Const(item) => return parse_const_item_with_tokens(attr, item),
57 item => item,
58 };
59
60 let opts: BindgenAttrs = syn::parse2(attr)?;
61 let main = match &item {
62 syn::Item::Fn(item) if opts.main().is_some() => Some(item.sig.ident.clone()),
63 _ => None,
64 };
65 let mut tokens = TokenStream::new();
66 let mut program = ast::Program::default();
67 item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
68 parser::unused_attrs_diagnostic()?;
69
70 Ok(ParseOutput {
71 program,
72 tokens,
73 main,
74 })
75}
76
77fn parse_const_item_with_tokens(
78 attr: TokenStream,
79 item: syn::ItemConst,
80) -> Result<ParseOutput, Diagnostic> {
81 let opts = syn::parse2(attr)?;
82 let mut program = ast::Program::default();
83 item.clone().macro_parse(&mut program, opts)?;
84 parser::unused_attrs_diagnostic()?;
85
86 Ok(ParseOutput {
87 program,
88 tokens: item.to_token_stream(),
89 main: None,
90 })
91}
92
93fn parse_struct_item_with_tokens(
94 attr: TokenStream,
95 mut item: syn::ItemStruct,
96) -> Result<ParseOutput, Diagnostic> {
97 let opts: BindgenAttrs = syn::parse2(attr.clone())?;
98 let wasm_bindgen = opts
99 .wasm_bindgen()
100 .cloned()
101 .unwrap_or_else(|| syn::parse_quote! { ::wasm_bindgen });
102 let extends_path = opts.attrs.iter().find_map(|(_, a)| match a {
103 parser::BindgenAttr::Extends(_, path) => Some(path.clone()),
104 _ => None,
105 });
106 parser::inject_parent_field(&mut item, extends_path.as_ref(), &wasm_bindgen)?;
107
108 parser::reset_attrs_used();
112 let mut item: syn::ItemStruct = syn::parse2(quote! {
113 #[wasm_bindgen(#attr)]
114 #item
115 })?;
116
117 let mut program = ast::Program::default();
118 program.structs.push((&mut item).convert(&program)?);
119 parser::unused_attrs_diagnostic()?;
120
121 Ok(ParseOutput {
122 program,
123 tokens: item.to_token_stream(),
124 main: None,
125 })
126}
127
128fn parse_impl_item_with_tokens(
129 attr: TokenStream,
130 mut item: syn::ItemImpl,
131) -> Result<ParseOutput, Diagnostic> {
132 let opts: BindgenAttrs = syn::parse2(attr)?;
133
134 if item.defaultness.is_some() {
135 return Err(Diagnostic::spanned_error(
136 &item.defaultness,
137 "#[wasm_bindgen] default impls are not supported",
138 ));
139 }
140 if item.unsafety.is_some() {
141 return Err(Diagnostic::spanned_error(
142 &item.unsafety,
143 "#[wasm_bindgen] unsafe impls are not supported",
144 ));
145 }
146 if let Some((_, path, _)) = &item.trait_ {
147 return Err(Diagnostic::spanned_error(
148 path,
149 "#[wasm_bindgen] trait impls are not supported",
150 ));
151 }
152 if !item.generics.params.is_empty() {
153 return Err(Diagnostic::spanned_error(
154 &item.generics,
155 "#[wasm_bindgen] generic impls aren't supported",
156 ));
157 }
158
159 let class_path = match item.self_ty.as_ref() {
160 syn::Type::Path(path) if path.qself.is_none() => &path.path,
161 other => {
162 return Err(Diagnostic::spanned_error(
163 other,
164 "unsupported self type in #[wasm_bindgen] impl",
165 ));
166 }
167 };
168 let class = class_path
169 .segments
170 .last()
171 .ok_or_else(|| Diagnostic::spanned_error(class_path, "expected an impl self type"))?
172 .ident
173 .clone();
174
175 let mut program = ast::Program::default();
176 if let Some(path) = opts.wasm_bindgen() {
177 program.wasm_bindgen = path.clone();
178 }
179 if let Some(path) = opts.wasm_bindgen_futures() {
180 program.wasm_bindgen_futures = path.clone();
181 }
182 if let Some(path) = opts.js_sys() {
183 program.js_sys = path.clone();
184 }
185
186 let marker = ClassMarker {
187 class: class.clone(),
188 js_class: opts
189 .js_class()
190 .map(|s| s.0.to_string())
191 .unwrap_or_else(|| class.unraw().to_string()),
192 js_namespace: opts.js_namespace().map(|(ns, _)| ns.0),
193 wasm_bindgen: program.wasm_bindgen.clone(),
194 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
195 js_sys: program.js_sys.clone(),
196 };
197
198 let mut errors = Vec::new();
199 for impl_item in item.items.iter_mut() {
200 match impl_item {
201 syn::ImplItem::Fn(method) => {
202 if let Err(error) = method.macro_parse(&mut program, &marker) {
203 errors.push(error);
204 }
205 }
206 syn::ImplItem::Const(_) => errors.push(Diagnostic::spanned_error(
207 impl_item,
208 "const definitions aren't supported with #[wasm_bindgen]",
209 )),
210 syn::ImplItem::Type(_) => errors.push(Diagnostic::spanned_error(
211 impl_item,
212 "type definitions in impls aren't supported with #[wasm_bindgen]",
213 )),
214 syn::ImplItem::Macro(_) => errors.push(Diagnostic::spanned_error(
215 impl_item,
216 "macros in impls aren't supported",
217 )),
218 syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
219 other => errors.push(Diagnostic::spanned_error(
220 other,
221 "failed to parse this item as a known item",
222 )),
223 }
224 }
225 Diagnostic::from_vec(errors)?;
226 opts.check_used();
227 parser::unused_attrs_diagnostic()?;
228
229 Ok(ParseOutput {
230 program,
231 tokens: item.to_token_stream(),
232 main: None,
233 })
234}
235
236pub fn parse_struct(input: TokenStream) -> Result<ast::Struct, Diagnostic> {
238 parser::reset_attrs_used();
239
240 let mut item = syn::parse2::<syn::ItemStruct>(input)?;
241 let program = ast::Program::default();
242 let parsed = (&mut item).convert(&program)?;
243 parser::unused_attrs_diagnostic()?;
244
245 Ok(parsed)
246}
247
248pub fn parse_class_marker(
250 attr: TokenStream,
251 input: TokenStream,
252) -> Result<ast::Program, Diagnostic> {
253 Ok(parse_class_marker_with_tokens(attr, input)?.program)
254}
255
256pub fn parse_class_marker_with_tokens(
259 attr: TokenStream,
260 input: TokenStream,
261) -> Result<ParseOutput, Diagnostic> {
262 parser::reset_attrs_used();
263
264 let mut item = syn::parse2::<syn::ImplItemFn>(input)?;
265 let opts: ClassMarker = syn::parse2(attr)?;
266 let mut program = ast::Program::default();
267 item.macro_parse(&mut program, &opts)?;
268 parser::unused_attrs_diagnostic()?;
269
270 Ok(ParseOutput {
271 program,
272 tokens: item.to_token_stream(),
273 main: None,
274 })
275}
276
277pub fn parse_link_to(input: TokenStream) -> Result<ast::LinkToModule, Diagnostic> {
279 parser::reset_attrs_used();
280
281 let opts = syn::parse2(input)?;
282 let parsed = parser::link_to(opts)?;
283 parser::unused_attrs_diagnostic()?;
284
285 Ok(parsed)
286}
287
288#[cfg(feature = "expand")]
290pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
291 parser::reset_attrs_used();
292 let item = syn::parse2::<syn::Item>(input)?;
295 if let syn::Item::Struct(mut s) = item {
296 let opts: BindgenAttrs = syn::parse2(attr.clone())?;
297 let wasm_bindgen = opts
298 .wasm_bindgen()
299 .cloned()
300 .unwrap_or_else(|| syn::parse_quote! { ::wasm_bindgen });
301
302 let extends_path = opts.attrs.iter().find_map(|(_, a)| match a {
306 parser::BindgenAttr::Extends(_, path) => Some(path.clone()),
307 _ => None,
308 });
309 parser::inject_parent_field(&mut s, extends_path.as_ref(), &wasm_bindgen)?;
310
311 let item = quote! {
312 #[derive(#wasm_bindgen::__rt::BindgenedStruct)]
313 #[wasm_bindgen(#attr)]
314 #s
315 };
316 return Ok(item);
317 }
318
319 let opts = syn::parse2(attr)?;
320 let mut tokens = proc_macro2::TokenStream::new();
321 let mut program = ast::Program::default();
322 item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
323 program.try_to_tokens(&mut tokens)?;
324
325 parser::check_unused_attrs(&mut tokens);
329
330 Ok(tokens)
331}
332
333#[cfg(feature = "expand")]
335pub fn expand_link_to(input: TokenStream) -> Result<TokenStream, Diagnostic> {
336 parser::reset_attrs_used();
337 let opts = syn::parse2(input)?;
338
339 let mut tokens = proc_macro2::TokenStream::new();
340 let link = parser::link_to(opts)?;
341 link.try_to_tokens(&mut tokens)?;
342
343 Ok(tokens)
344}
345
346#[cfg(feature = "expand")]
348pub fn expand_class_marker(
349 attr: TokenStream,
350 input: TokenStream,
351) -> Result<TokenStream, Diagnostic> {
352 parser::reset_attrs_used();
353 let mut item = syn::parse2::<syn::ImplItemFn>(input)?;
354 let opts: ClassMarker = syn::parse2(attr)?;
355
356 let mut program = ast::Program::default();
357 item.macro_parse(&mut program, &opts)?;
358
359 let mut tokens = proc_macro2::TokenStream::new();
371 tokens.append_all(
372 item.attrs
373 .iter()
374 .filter(|attr| matches!(attr.style, syn::AttrStyle::Outer)),
375 );
376 item.vis.to_tokens(&mut tokens);
377 item.sig.to_tokens(&mut tokens);
378 let mut err = None;
379 item.block.brace_token.surround(&mut tokens, |tokens| {
380 if let Err(e) = program.try_to_tokens(tokens) {
381 err = Some(e);
382 }
383 parser::check_unused_attrs(tokens); tokens.append_all(
385 item.attrs
386 .iter()
387 .filter(|attr| matches!(attr.style, syn::AttrStyle::Inner(_))),
388 );
389 tokens.append_all(&item.block.stmts);
390 });
391
392 if let Some(err) = err {
393 return Err(err);
394 }
395
396 Ok(tokens)
397}
398
399struct ClassMarker {
400 class: syn::Ident,
401 js_class: String,
402 js_namespace: Option<Vec<String>>,
403 wasm_bindgen: syn::Path,
404 wasm_bindgen_futures: syn::Path,
405 js_sys: syn::Path,
406}
407
408impl Parse for ClassMarker {
409 fn parse(input: ParseStream) -> SynResult<Self> {
410 let class = input.parse::<syn::Ident>()?;
411 input.parse::<Token![=]>()?;
412 let mut js_class = input.parse::<syn::LitStr>()?.value();
413 js_class = js_class
414 .strip_prefix("r#")
415 .map(String::from)
416 .unwrap_or(js_class);
417
418 let mut js_namespace: Option<Vec<String>> = None;
419 let mut wasm_bindgen = None;
420 let mut wasm_bindgen_futures = None;
421 let mut js_sys = None;
422
423 loop {
424 if input.parse::<Option<Token![,]>>()?.is_some() {
425 let ident = input.parse::<syn::Ident>()?;
426
427 if ident == "js_namespace" {
428 if js_namespace.is_some() {
429 return Err(syn::Error::new(
430 ident.span(),
431 "found duplicate `js_namespace`",
432 ));
433 }
434 input.parse::<Token![=]>()?;
435 let content;
436 syn::bracketed!(content in input);
437 let segs: syn::punctuated::Punctuated<syn::LitStr, Token![,]> = content
438 .parse_terminated(|p: ParseStream| p.parse::<syn::LitStr>(), Token![,])?;
439 js_namespace = Some(segs.into_iter().map(|s| s.value()).collect());
440 } else if ident == "wasm_bindgen" {
441 if wasm_bindgen.is_some() {
442 return Err(syn::Error::new(
443 ident.span(),
444 "found duplicate `wasm_bindgen`",
445 ));
446 }
447
448 input.parse::<Token![=]>()?;
449 wasm_bindgen = Some(input.parse::<syn::Path>()?);
450 } else if ident == "wasm_bindgen_futures" {
451 if wasm_bindgen_futures.is_some() {
452 return Err(syn::Error::new(
453 ident.span(),
454 "found duplicate `wasm_bindgen_futures`",
455 ));
456 }
457
458 input.parse::<Token![=]>()?;
459 wasm_bindgen_futures = Some(input.parse::<syn::Path>()?);
460 } else if ident == "js_sys" {
461 if js_sys.is_some() {
462 return Err(syn::Error::new(ident.span(), "found duplicate `js_sys`"));
463 }
464
465 input.parse::<Token![=]>()?;
466 js_sys = Some(input.parse::<syn::Path>()?);
467 } else {
468 return Err(syn::Error::new(
469 ident.span(),
470 "expected `js_namespace`, `wasm_bindgen`, `wasm_bindgen_futures`, or `js_sys`",
471 ));
472 }
473 } else {
474 break;
475 }
476 }
477
478 Ok(ClassMarker {
479 class,
480 js_class,
481 js_namespace,
482 wasm_bindgen: wasm_bindgen.unwrap_or_else(|| syn::parse_quote! { wasm_bindgen }),
483 wasm_bindgen_futures: wasm_bindgen_futures
484 .unwrap_or_else(|| syn::parse_quote! { wasm_bindgen_futures }),
485 js_sys: js_sys.unwrap_or_else(|| syn::parse_quote! { js_sys }),
486 })
487 }
488}
489
490#[cfg(feature = "expand")]
491pub fn expand_struct_marker(item: TokenStream) -> Result<TokenStream, Diagnostic> {
492 parser::reset_attrs_used();
493
494 let mut s: syn::ItemStruct = syn::parse2(item)?;
495
496 let mut program = ast::Program::default();
497 program.structs.push((&mut s).convert(&program)?);
498
499 let mut tokens = proc_macro2::TokenStream::new();
500 program.try_to_tokens(&mut tokens)?;
501
502 parser::check_unused_attrs(&mut tokens);
503
504 Ok(tokens)
505}