1use std::cell::{Cell, RefCell};
2use std::collections::HashMap;
3use std::str::Chars;
4use std::{char, iter};
5
6use ast::OperationKind;
7use proc_macro2::{Ident, Span, TokenStream, TokenTree};
8use quote::ToTokens;
9use syn::ext::IdentExt;
10use syn::parse::{Parse, ParseStream, Result as SynResult};
11use syn::spanned::Spanned;
12use syn::visit_mut::VisitMut;
13use syn::Token;
14use syn::{ItemFn, Lit, MacroDelimiter, ReturnType};
15use wasm_bindgen_shared::identifier::{is_js_keyword, is_non_value_js_keyword, is_valid_ident};
16
17use crate::ast::{self, ThreadLocal};
18use crate::hash::ShortHash;
19use crate::ClassMarker;
20use crate::Diagnostic;
21
22thread_local!(static ATTRS: AttributeParseState = Default::default());
23
24fn check_js_comment_close(str: &str, span: Span) -> Result<(), Diagnostic> {
26 if str.contains("*/") {
27 Err(Diagnostic::span_error(
28 span,
29 "contains comment close syntax",
30 ))
31 } else {
32 Ok(())
33 }
34}
35
36fn check_invalid_type(str: &str, span: Span) -> Result<(), Diagnostic> {
38 if is_js_keyword(str) {
39 return Err(Diagnostic::span_error(span, "collides with JS keyword"));
40 }
41 check_js_comment_close(str, span)?;
42 Ok(())
43}
44
45#[derive(Default)]
46struct AttributeParseState {
47 parsed: Cell<usize>,
48 checks: Cell<usize>,
49 unused_attrs: RefCell<Vec<UnusedState>>,
50}
51
52struct UnusedState {
53 error: bool,
54 ident: Ident,
55}
56
57#[cfg_attr(feature = "extra-traits", derive(Debug))]
59pub struct BindgenAttrs {
60 pub attrs: Vec<(Cell<bool>, BindgenAttr)>,
62}
63
64#[cfg_attr(feature = "extra-traits", derive(Debug))]
70#[derive(Clone)]
71pub struct JsNamespace(Vec<String>);
72
73macro_rules! attrgen {
74 ($mac:ident) => {
75 $mac! {
76 (catch, false, Catch(Span)),
77 (constructor, false, Constructor(Span)),
78 (method, false, Method(Span)),
79 (r#this, false, This(Span)),
80 (static_method_of, false, StaticMethodOf(Span, Ident)),
81 (js_namespace, false, JsNamespace(Span, JsNamespace, Vec<Span>)),
82 (module, true, Module(Span, String, Span)),
83 (raw_module, true, RawModule(Span, String, Span)),
84 (inline_js, true, InlineJs(Span, String, Span)),
85 (getter, false, Getter(Span, Option<String>)),
86 (setter, false, Setter(Span, Option<String>)),
87 (indexing_getter, false, IndexingGetter(Span)),
88 (indexing_setter, false, IndexingSetter(Span)),
89 (indexing_deleter, false, IndexingDeleter(Span)),
90 (structural, false, Structural(Span)),
91 (r#final, false, Final(Span)),
92 (readonly, false, Readonly(Span)),
93 (js_name, false, JsName(Span, String, Span)),
94 (js_class, false, JsClass(Span, String, Span)),
95 (reexport, false, Reexport(Span, Option<String>)),
96 (inspectable, false, Inspectable(Span)),
97 (is_type_of, false, IsTypeOf(Span, syn::Expr)),
98 (extends, false, Extends(Span, syn::Path)),
99 (no_deref, false, NoDeref(Span)),
100 (vendor_prefix, false, VendorPrefix(Span, Ident)),
101 (variadic, false, Variadic(Span)),
102 (typescript_custom_section, false, TypescriptCustomSection(Span)),
103 (skip_typescript, false, SkipTypescript(Span)),
104 (skip_jsdoc, false, SkipJsDoc(Span)),
105 (main, false, Main(Span)),
106 (start, false, Start(Span)),
107 (wasm_bindgen, false, WasmBindgen(Span, syn::Path)),
108 (js_sys, false, JsSys(Span, syn::Path)),
109 (wasm_bindgen_futures, false, WasmBindgenFutures(Span, syn::Path)),
110 (skip, false, Skip(Span)),
111 (typescript_type, false, TypeScriptType(Span, String, Span)),
112 (getter_with_clone, false, GetterWithClone(Span)),
113 (static_string, false, StaticString(Span)),
114 (thread_local, false, ThreadLocal(Span)),
115 (thread_local_v2, false, ThreadLocalV2(Span)),
116 (unchecked_return_type, true, ReturnType(Span, String, Span)),
117 (return_description, true, ReturnDesc(Span, String, Span)),
118 (unchecked_param_type, true, ParamType(Span, String, Span)),
119 (param_description, true, ParamDesc(Span, String, Span)),
120
121 (assert_no_shim, false, AssertNoShim(Span)),
123 }
124 };
125}
126
127macro_rules! methods {
128 ($(($name:ident, $invalid_unused:literal, $variant:ident($($contents:tt)*)),)*) => {
129 $(methods!(@method $name, $variant($($contents)*));)*
130
131 fn enforce_used(self) -> Result<(), Diagnostic> {
132 ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
134
135 let mut errors = Vec::new();
136 for (used, attr) in self.attrs.iter() {
137 if used.get() {
138 continue
139 }
140 let span = match attr {
141 $(BindgenAttr::$variant(span, ..) => span,)*
142 };
143 errors.push(Diagnostic::span_error(*span, "unused wasm_bindgen attribute"));
144 }
145 Diagnostic::from_vec(errors)
146 }
147
148 fn check_used(self) {
149 ATTRS.with(|state| {
151 state.checks.set(state.checks.get() + 1);
152
153 state.unused_attrs.borrow_mut().extend(
154 self.attrs
155 .iter()
156 .filter_map(|(used, attr)| if used.get() { None } else { Some(attr) })
157 .map(|attr| {
158 match attr {
159 $(BindgenAttr::$variant(span, ..) => {
160 UnusedState {
161 error: $invalid_unused,
162 ident: syn::parse_quote_spanned!(*span => $name)
163 }
164 })*
165 }
166 })
167 );
168 });
169 }
170 };
171
172 (@method $name:ident, $variant:ident(Span, String, Span)) => {
173 pub(crate) fn $name(&self) -> Option<(&str, Span)> {
174 self.attrs
175 .iter()
176 .find_map(|a| match &a.1 {
177 BindgenAttr::$variant(_, s, span) => {
178 a.0.set(true);
179 Some((&s[..], *span))
180 }
181 _ => None,
182 })
183 }
184 };
185
186 (@method $name:ident, $variant:ident(Span, JsNamespace, Vec<Span>)) => {
187 pub(crate) fn $name(&self) -> Option<(JsNamespace, &[Span])> {
188 self.attrs
189 .iter()
190 .find_map(|a| match &a.1 {
191 BindgenAttr::$variant(_, ss, spans) => {
192 a.0.set(true);
193 Some((ss.clone(), &spans[..]))
194 }
195 _ => None,
196 })
197 }
198 };
199
200 (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
201 #[allow(unused)]
202 pub(crate) fn $name(&self) -> Option<&$($other)*> {
203 self.attrs
204 .iter()
205 .find_map(|a| match &a.1 {
206 BindgenAttr::$variant(_, s) => {
207 a.0.set(true);
208 Some(s)
209 }
210 _ => None,
211 })
212 }
213 };
214
215 (@method $name:ident, $variant:ident($($other:tt)*)) => {
216 #[allow(unused)]
217 pub(crate) fn $name(&self) -> Option<&$($other)*> {
218 self.attrs
219 .iter()
220 .find_map(|a| match &a.1 {
221 BindgenAttr::$variant(s) => {
222 a.0.set(true);
223 Some(s)
224 }
225 _ => None,
226 })
227 }
228 };
229}
230
231impl BindgenAttrs {
232 fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
234 let mut ret = BindgenAttrs::default();
235 loop {
236 let pos = attrs
237 .iter()
238 .enumerate()
239 .find(|&(_, m)| m.path().segments[0].ident == "wasm_bindgen")
240 .map(|a| a.0);
241 let pos = match pos {
242 Some(i) => i,
243 None => return Ok(ret),
244 };
245 let attr = attrs.remove(pos);
246 let tokens = match attr.meta {
247 syn::Meta::Path(_) => continue,
248 syn::Meta::List(syn::MetaList {
249 delimiter: MacroDelimiter::Paren(_),
250 tokens,
251 ..
252 }) => tokens,
253 syn::Meta::List(_) | syn::Meta::NameValue(_) => {
254 bail_span!(attr, "malformed #[wasm_bindgen] attribute")
255 }
256 };
257 let mut attrs: BindgenAttrs = syn::parse2(tokens)?;
258 ret.attrs.append(&mut attrs.attrs);
259 attrs.check_used();
260 }
261 }
262
263 fn get_thread_local(&self) -> Result<Option<ThreadLocal>, Diagnostic> {
264 let mut thread_local = self.thread_local_v2().map(|_| ThreadLocal::V2);
265
266 if let Some(span) = self.thread_local() {
267 if thread_local.is_some() {
268 return Err(Diagnostic::span_error(
269 *span,
270 "`thread_local` can't be used with `thread_local_v2`",
271 ));
272 } else {
273 thread_local = Some(ThreadLocal::V1)
274 }
275 }
276
277 Ok(thread_local)
278 }
279
280 attrgen!(methods);
281}
282
283impl Default for BindgenAttrs {
284 fn default() -> BindgenAttrs {
285 ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1));
289 BindgenAttrs { attrs: Vec::new() }
290 }
291}
292
293impl Parse for BindgenAttrs {
294 fn parse(input: ParseStream) -> SynResult<Self> {
295 let mut attrs = BindgenAttrs::default();
296 if input.is_empty() {
297 return Ok(attrs);
298 }
299
300 let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
301 attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect();
302 Ok(attrs)
303 }
304}
305
306macro_rules! gen_bindgen_attr {
307 ($(($method:ident, $_:literal, $($variants:tt)*),)*) => {
308 #[cfg_attr(feature = "extra-traits", derive(Debug))]
310 pub enum BindgenAttr {
311 $($($variants)*,)*
312 }
313 }
314}
315attrgen!(gen_bindgen_attr);
316
317impl Parse for BindgenAttr {
318 fn parse(input: ParseStream) -> SynResult<Self> {
319 let original = input.fork();
320 let attr: AnyIdent = input.parse()?;
321 let attr = attr.0;
322 let attr_span = attr.span();
323 let attr_string = attr.to_string();
324 let raw_attr_string = format!("r#{attr_string}");
325
326 macro_rules! parsers {
327 ($(($name:ident, $_:literal, $($contents:tt)*),)*) => {
328 $(
329 if attr_string == stringify!($name) || raw_attr_string == stringify!($name) {
330 parsers!(
331 @parser
332 $($contents)*
333 );
334 }
335 )*
336 };
337
338 (@parser $variant:ident(Span)) => ({
339 return Ok(BindgenAttr::$variant(attr_span));
340 });
341
342 (@parser $variant:ident(Span, Ident)) => ({
343 input.parse::<Token![=]>()?;
344 let ident = input.parse::<AnyIdent>()?.0;
345 return Ok(BindgenAttr::$variant(attr_span, ident))
346 });
347
348 (@parser $variant:ident(Span, Option<String>)) => ({
349 if input.parse::<Token![=]>().is_ok() {
350 if input.peek(syn::LitStr) {
351 let litstr = input.parse::<syn::LitStr>()?;
352 return Ok(BindgenAttr::$variant(attr_span, Some(litstr.value())))
353 }
354
355 let ident = input.parse::<AnyIdent>()?.0;
356 return Ok(BindgenAttr::$variant(attr_span, Some(ident.to_string())))
357 } else {
358 return Ok(BindgenAttr::$variant(attr_span, None));
359 }
360 });
361
362 (@parser $variant:ident(Span, syn::Path)) => ({
363 input.parse::<Token![=]>()?;
364 return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
365 });
366
367 (@parser $variant:ident(Span, syn::Expr)) => ({
368 input.parse::<Token![=]>()?;
369 return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
370 });
371
372 (@parser $variant:ident(Span, String, Span)) => ({
373 input.parse::<Token![=]>()?;
374 let (val, span) = match input.parse::<syn::LitStr>() {
375 Ok(str) => (str.value(), str.span()),
376 Err(_) => {
377 let ident = input.parse::<AnyIdent>()?.0;
378 (ident.to_string(), ident.span())
379 }
380 };
381 return Ok(BindgenAttr::$variant(attr_span, val, span))
382 });
383
384 (@parser $variant:ident(Span, JsNamespace, Vec<Span>)) => ({
385 input.parse::<Token![=]>()?;
386 let (vals, spans) = match input.parse::<syn::ExprArray>() {
387 Ok(exprs) => {
388 let mut vals = vec![];
389 let mut spans = vec![];
390
391 for expr in exprs.elems.iter() {
392 if let syn::Expr::Lit(syn::ExprLit {
393 lit: syn::Lit::Str(ref str),
394 ..
395 }) = expr {
396 vals.push(str.value());
397 spans.push(str.span());
398 } else {
399 return Err(syn::Error::new(expr.span(), "expected string literals"));
400 }
401 }
402
403 if vals.is_empty() {
404 return Err(syn::Error::new(exprs.span(), "Empty namespace lists are not allowed."));
405 }
406
407 (vals, spans)
408 },
409 Err(_) => match input.parse::<syn::LitStr>() {
411 Ok(str) => (vec![str.value()], vec![str.span()]),
412 Err(_) => {
413 let ident = input.parse::<AnyIdent>()?.0;
414 (vec![ident.to_string()], vec![ident.span()])
415 }
416 }
417 };
418
419 let first = &vals[0];
420 if is_non_value_js_keyword(first) && first != "default" {
421 let msg = format!("Namespace cannot start with the JS keyword `{}`", first);
422 return Err(syn::Error::new(spans[0], msg));
423 }
424
425 return Ok(BindgenAttr::$variant(attr_span, JsNamespace(vals), spans))
426 });
427 }
428
429 attrgen!(parsers);
430
431 Err(original.error(if attr_string.starts_with('_') {
432 "unknown attribute: it's safe to remove unused attributes entirely."
433 } else {
434 "unknown attribute"
435 }))
436 }
437}
438
439struct AnyIdent(Ident);
440
441impl Parse for AnyIdent {
442 fn parse(input: ParseStream) -> SynResult<Self> {
443 input.step(|cursor| match cursor.ident() {
444 Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)),
445 None => Err(cursor.error("expected an identifier")),
446 })
447 }
448}
449
450pub(crate) trait ConvertToAst<Ctx> {
455 type Target;
457 fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
461}
462
463impl ConvertToAst<&ast::Program> for &mut syn::ItemStruct {
464 type Target = ast::Struct;
465
466 fn convert(self, program: &ast::Program) -> Result<Self::Target, Diagnostic> {
467 if !self.generics.params.is_empty() {
468 bail_span!(
469 self.generics,
470 "structs with #[wasm_bindgen] cannot have lifetime or \
471 type parameters currently"
472 );
473 }
474 let attrs = BindgenAttrs::find(&mut self.attrs)?;
475
476 let _ = attrs.wasm_bindgen();
478
479 let mut fields = Vec::new();
480 let js_name = attrs
481 .js_name()
482 .map(|s| s.0.to_string())
483 .unwrap_or(self.ident.unraw().to_string());
484 if is_js_keyword(&js_name) && js_name != "default" {
485 bail_span!(
486 self.ident,
487 "struct cannot use the JS keyword `{}` as its name",
488 js_name
489 );
490 }
491
492 let is_inspectable = attrs.inspectable().is_some();
493 let getter_with_clone = attrs.getter_with_clone();
494 for (i, field) in self.fields.iter_mut().enumerate() {
495 match field.vis {
496 syn::Visibility::Public(..) => {}
497 _ => continue,
498 }
499 let (js_field_name, member) = match &field.ident {
500 Some(ident) => (ident.unraw().to_string(), syn::Member::Named(ident.clone())),
501 None => (i.to_string(), syn::Member::Unnamed(i.into())),
502 };
503
504 let attrs = BindgenAttrs::find(&mut field.attrs)?;
505 if attrs.skip().is_some() {
506 attrs.check_used();
507 continue;
508 }
509
510 let js_field_name = match attrs.js_name() {
511 Some((name, _)) => name.to_string(),
512 None => js_field_name,
513 };
514
515 let comments = extract_doc_comments(&field.attrs);
516 let getter = wasm_bindgen_shared::struct_field_get(&js_name, &js_field_name);
517 let setter = wasm_bindgen_shared::struct_field_set(&js_name, &js_field_name);
518
519 fields.push(ast::StructField {
520 rust_name: member,
521 js_name: js_field_name,
522 struct_name: self.ident.clone(),
523 readonly: attrs.readonly().is_some(),
524 ty: field.ty.clone(),
525 getter: Ident::new(&getter, Span::call_site()),
526 setter: Ident::new(&setter, Span::call_site()),
527 comments,
528 generate_typescript: attrs.skip_typescript().is_none(),
529 generate_jsdoc: attrs.skip_jsdoc().is_none(),
530 getter_with_clone: attrs.getter_with_clone().or(getter_with_clone).copied(),
531 wasm_bindgen: program.wasm_bindgen.clone(),
532 });
533 attrs.check_used();
534 }
535 let generate_typescript = attrs.skip_typescript().is_none();
536 let comments: Vec<String> = extract_doc_comments(&self.attrs);
537 let js_namespace = attrs.js_namespace().map(|(ns, _)| ns.0);
538 attrs.check_used();
539 Ok(ast::Struct {
540 rust_name: self.ident.clone(),
541 js_name,
542 fields,
543 comments,
544 is_inspectable,
545 generate_typescript,
546 js_namespace,
547 wasm_bindgen: program.wasm_bindgen.clone(),
548 })
549 }
550}
551
552fn get_ty(mut ty: &syn::Type) -> &syn::Type {
553 while let syn::Type::Group(g) = ty {
554 ty = &g.elem;
555 }
556
557 ty
558}
559
560fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
561 while let syn::Expr::Group(g) = expr {
562 expr = &g.expr;
563 }
564
565 expr
566}
567
568impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
569 for syn::ForeignItemFn
570{
571 type Target = ast::ImportKind;
572
573 fn convert(
574 self,
575 (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
576 ) -> Result<Self::Target, Diagnostic> {
577 let (mut wasm, _) = function_from_decl(
578 &self.sig.ident,
579 &opts,
580 self.sig.clone(),
581 self.attrs.clone(),
582 self.vis.clone(),
583 FunctionPosition::Extern,
584 None,
585 )?;
586 let catch = opts.catch().is_some();
587 let variadic = opts.variadic().is_some();
588 let js_ret = if catch {
589 extract_first_ty_param(wasm.ret.as_ref().map(|ret| &ret.r#type))?
597 } else {
598 wasm.ret.as_ref().map(|ret| ret.r#type.clone())
599 };
600
601 let operation_kind = operation_kind(&opts);
602
603 let kind = if opts.method().is_some() {
604 let class = wasm.arguments.first().ok_or_else(|| {
605 err_span!(self, "imported methods must have at least one argument")
606 })?;
607 let class = match get_ty(&class.pat_type.ty) {
608 syn::Type::Reference(syn::TypeReference {
609 mutability: None,
610 elem,
611 ..
612 }) => &**elem,
613 _ => bail_span!(
614 class.pat_type.ty,
615 "first argument of method must be a shared reference"
616 ),
617 };
618 let class_name = match get_ty(class) {
619 syn::Type::Path(syn::TypePath {
620 qself: None,
621 ref path,
622 }) => path,
623 _ => bail_span!(class, "first argument of method must be a path"),
624 };
625 let class_name = extract_path_ident(class_name)?;
626 let class_name = opts
627 .js_class()
628 .map(|p| p.0.into())
629 .unwrap_or_else(|| class_name.to_string());
630
631 let kind = ast::MethodKind::Operation(ast::Operation {
632 is_static: false,
633 kind: operation_kind,
634 });
635
636 ast::ImportFunctionKind::Method {
637 class: class_name,
638 ty: class.clone(),
639 kind,
640 }
641 } else if let Some(cls) = opts.static_method_of() {
642 let class = opts
643 .js_class()
644 .map(|p| p.0.into())
645 .unwrap_or_else(|| cls.to_string());
646
647 let ty = syn::Type::Path(syn::TypePath {
648 qself: None,
649 path: syn::Path {
650 leading_colon: None,
651 segments: std::iter::once(syn::PathSegment {
652 ident: cls.clone(),
653 arguments: syn::PathArguments::None,
654 })
655 .collect(),
656 },
657 });
658
659 let kind = ast::MethodKind::Operation(ast::Operation {
660 is_static: true,
661 kind: operation_kind,
662 });
663
664 ast::ImportFunctionKind::Method { class, ty, kind }
665 } else if opts.constructor().is_some() {
666 let class = match js_ret {
667 Some(ref ty) => ty,
668 _ => bail_span!(self, "constructor returns must be bare types"),
669 };
670 let class_name = match get_ty(class) {
671 syn::Type::Path(syn::TypePath {
672 qself: None,
673 ref path,
674 }) => path,
675 _ => bail_span!(self, "return value of constructor must be a bare path"),
676 };
677 let class_name = extract_path_ident(class_name)?;
678 let class_name = opts
679 .js_class()
680 .map(|p| p.0.into())
681 .unwrap_or_else(|| class_name.to_string());
682
683 ast::ImportFunctionKind::Method {
684 class: class_name,
685 ty: class.clone(),
686 kind: ast::MethodKind::Constructor,
687 }
688 } else {
689 ast::ImportFunctionKind::Normal
690 };
691
692 if opts.reexport().is_some() && matches!(kind, ast::ImportFunctionKind::Method { .. }) {
694 return Err(Diagnostic::span_error(
695 self.sig.ident.span(),
696 "`reexport` cannot be used on methods, constructors, or static methods. \
697 Use `reexport` on the type import instead.",
698 ));
699 }
700
701 let shim = {
702 let ns = match kind {
703 ast::ImportFunctionKind::Normal => (0, "n"),
704 ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
705 };
706 let data = (ns, self.sig.to_token_stream().to_string(), module);
707 format!(
708 "__wbg_{}_{}",
709 wasm.name
710 .chars()
711 .filter(|&c| c.is_ascii_alphanumeric() || c == '_')
712 .collect::<String>(),
713 ShortHash(data)
714 )
715 };
716 if let Some(span) = opts.r#final() {
717 if opts.structural().is_some() {
718 let msg = "cannot specify both `structural` and `final`";
719 return Err(Diagnostic::span_error(*span, msg));
720 }
721 }
722 let assert_no_shim = opts.assert_no_shim().is_some();
723
724 let mut doc_comment = String::new();
725 wasm.rust_attrs.retain(|attr| {
727 fn get_docs(attr: &syn::Attribute) -> Option<String> {
730 if attr.path().is_ident("doc") {
731 if let syn::Meta::NameValue(syn::MetaNameValue {
732 value:
733 syn::Expr::Lit(syn::ExprLit {
734 lit: Lit::Str(str), ..
735 }),
736 ..
737 }) = &attr.meta
738 {
739 Some(str.value())
740 } else {
741 None
742 }
743 } else {
744 None
745 }
746 }
747
748 if let Some(docs) = get_docs(attr) {
749 if !doc_comment.is_empty() {
750 doc_comment.push('\n');
752 }
753 doc_comment.push_str(&docs);
755
756 false
758 } else {
759 true
760 }
761 });
762
763 let ret = ast::ImportKind::Function(ast::ImportFunction {
764 function: wasm,
765 assert_no_shim,
766 kind,
767 js_ret,
768 catch,
769 variadic,
770 structural: opts.structural().is_some() || opts.r#final().is_none(),
771 rust_name: self.sig.ident,
772 shim: Ident::new(&shim, Span::call_site()),
773 doc_comment,
774 wasm_bindgen: program.wasm_bindgen.clone(),
775 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
776 });
777 opts.check_used();
778
779 Ok(ret)
780 }
781}
782
783impl ConvertToAst<(&ast::Program, BindgenAttrs)> for syn::ForeignItemType {
784 type Target = ast::ImportKind;
785
786 fn convert(
787 self,
788 (program, attrs): (&ast::Program, BindgenAttrs),
789 ) -> Result<Self::Target, Diagnostic> {
790 let js_name = attrs
791 .js_name()
792 .map(|s| s.0)
793 .map_or_else(|| self.ident.to_string(), |s| s.to_string());
794 let typescript_type = attrs.typescript_type().map(|s| s.0.to_string());
795 let is_type_of = attrs.is_type_of().cloned();
796 let shim = format!(
797 "__wbg_instanceof_{}_{}",
798 self.ident,
799 ShortHash((attrs.js_namespace().map(|(ns, _)| ns.0), &self.ident))
800 );
801 let mut extends = Vec::new();
802 let mut vendor_prefixes = Vec::new();
803 let no_deref = attrs.no_deref().is_some();
804 for (used, attr) in attrs.attrs.iter() {
805 match attr {
806 BindgenAttr::Extends(_, e) => {
807 extends.push(e.clone());
808 used.set(true);
809 }
810 BindgenAttr::VendorPrefix(_, e) => {
811 vendor_prefixes.push(e.clone());
812 used.set(true);
813 }
814 _ => {}
815 }
816 }
817 attrs.check_used();
818 Ok(ast::ImportKind::Type(ast::ImportType {
819 vis: self.vis,
820 attrs: self.attrs,
821 doc_comment: None,
822 instanceof_shim: shim,
823 is_type_of,
824 rust_name: self.ident,
825 typescript_type,
826 js_name,
827 extends,
828 vendor_prefixes,
829 no_deref,
830 wasm_bindgen: program.wasm_bindgen.clone(),
831 }))
832 }
833}
834
835impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
836 for syn::ForeignItemStatic
837{
838 type Target = ast::ImportKind;
839
840 fn convert(
841 self,
842 (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
843 ) -> Result<Self::Target, Diagnostic> {
844 if let syn::StaticMutability::Mut(_) = self.mutability {
845 bail_span!(self.mutability, "cannot import mutable globals yet")
846 }
847
848 if let Some(span) = opts.static_string() {
849 return Err(Diagnostic::span_error(
850 *span,
851 "static strings require a string literal",
852 ));
853 }
854
855 let default_name = self.ident.to_string();
856 let js_name = opts
857 .js_name()
858 .map(|p| p.0)
859 .unwrap_or(&default_name)
860 .to_string();
861 let shim = format!(
862 "__wbg_static_accessor_{}_{}",
863 self.ident,
864 ShortHash((&js_name, module, &self.ident)),
865 );
866 let thread_local = opts.get_thread_local()?;
867
868 opts.check_used();
869 Ok(ast::ImportKind::Static(ast::ImportStatic {
870 ty: *self.ty,
871 vis: self.vis,
872 rust_name: self.ident.clone(),
873 js_name,
874 shim: Ident::new(&shim, Span::call_site()),
875 wasm_bindgen: program.wasm_bindgen.clone(),
876 thread_local,
877 }))
878 }
879}
880
881impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
882 for syn::ItemStatic
883{
884 type Target = ast::ImportKind;
885
886 fn convert(
887 self,
888 (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
889 ) -> Result<Self::Target, Diagnostic> {
890 if let syn::StaticMutability::Mut(_) = self.mutability {
891 bail_span!(self.mutability, "cannot import mutable globals yet")
892 }
893
894 let string = if let syn::Expr::Lit(syn::ExprLit {
895 lit: syn::Lit::Str(string),
896 ..
897 }) = *self.expr.clone()
898 {
899 string.value()
900 } else {
901 bail_span!(
902 self.expr,
903 "statics with a value can only be string literals"
904 )
905 };
906
907 if opts.static_string().is_none() {
908 bail_span!(
909 self,
910 "static strings require `#[wasm_bindgen(static_string)]`"
911 )
912 }
913
914 let thread_local = if let Some(thread_local) = opts.get_thread_local()? {
915 thread_local
916 } else {
917 bail_span!(
918 self,
919 "static strings require `#[wasm_bindgen(thread_local_v2)]`"
920 )
921 };
922
923 let shim = format!(
924 "__wbg_string_{}_{}",
925 self.ident,
926 ShortHash((&module, &self.ident)),
927 );
928 opts.check_used();
929 Ok(ast::ImportKind::String(ast::ImportString {
930 ty: *self.ty,
931 vis: self.vis,
932 rust_name: self.ident.clone(),
933 shim: Ident::new(&shim, Span::call_site()),
934 wasm_bindgen: program.wasm_bindgen.clone(),
935 js_sys: program.js_sys.clone(),
936 string,
937 thread_local,
938 }))
939 }
940}
941
942impl ConvertToAst<(BindgenAttrs, Vec<FnArgAttrs>)> for syn::ItemFn {
943 type Target = ast::Function;
944
945 fn convert(
946 self,
947 (attrs, args_attrs): (BindgenAttrs, Vec<FnArgAttrs>),
948 ) -> Result<Self::Target, Diagnostic> {
949 match self.vis {
950 syn::Visibility::Public(_) => {}
951 _ if attrs.start().is_some() => {}
952 _ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
953 }
954 if self.sig.constness.is_some() {
955 bail_span!(
956 self.sig.constness,
957 "can only #[wasm_bindgen] non-const functions"
958 );
959 }
960
961 let (mut ret, _) = function_from_decl(
962 &self.sig.ident,
963 &attrs,
964 self.sig.clone(),
965 self.attrs,
966 self.vis,
967 FunctionPosition::Free,
968 Some(args_attrs),
969 )?;
970 attrs.check_used();
971
972 if is_js_keyword(&ret.name) && ret.name != "default" {
976 ret.name = format!("_{}", ret.name);
977 }
978
979 Ok(ret)
980 }
981}
982
983fn get_self_method(r: syn::Receiver) -> ast::MethodSelf {
985 match &*r.ty {
992 syn::Type::Reference(ty) => {
993 if ty.mutability.is_some() {
994 ast::MethodSelf::RefMutable
995 } else {
996 ast::MethodSelf::RefShared
997 }
998 }
999 _ => ast::MethodSelf::ByValue,
1000 }
1001}
1002
1003enum FunctionPosition<'a> {
1004 Extern,
1005 Free,
1006 Impl { self_ty: &'a Ident },
1007}
1008
1009#[allow(clippy::too_many_arguments)]
1011fn function_from_decl(
1012 decl_name: &syn::Ident,
1013 opts: &BindgenAttrs,
1014 sig: syn::Signature,
1015 attrs: Vec<syn::Attribute>,
1016 vis: syn::Visibility,
1017 position: FunctionPosition,
1018 args_attrs: Option<Vec<FnArgAttrs>>,
1019) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> {
1020 if sig.variadic.is_some() {
1021 bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions");
1022 }
1023 if !sig.generics.params.is_empty() {
1024 bail_span!(
1025 sig.generics,
1026 "can't #[wasm_bindgen] functions with lifetime or type parameters",
1027 );
1028 }
1029
1030 assert_no_lifetimes(&sig)?;
1031
1032 let syn::Signature { inputs, output, .. } = sig;
1033
1034 let replace_self = |mut t: syn::Type| {
1039 if let FunctionPosition::Impl { self_ty } = position {
1040 struct SelfReplace(Ident);
1045 impl VisitMut for SelfReplace {
1046 fn visit_ident_mut(&mut self, i: &mut proc_macro2::Ident) {
1047 if i == "Self" {
1048 *i = self.0.clone();
1049 }
1050 }
1051 }
1052
1053 let mut replace = SelfReplace(self_ty.clone());
1054 replace.visit_type_mut(&mut t);
1055 }
1056 t
1057 };
1058
1059 let replace_colliding_arg = |i: &mut syn::PatType| {
1062 if let syn::Pat::Ident(ref mut i) = *i.pat {
1063 let ident = i.ident.unraw().to_string();
1064 if is_js_keyword(&ident) {
1068 i.ident = Ident::new(format!("_{ident}").as_str(), i.ident.span());
1069 }
1070 }
1071 };
1072
1073 let mut method_self = None;
1074 let mut arguments = Vec::new();
1075 for arg in inputs.into_iter() {
1076 match arg {
1077 syn::FnArg::Typed(mut c) => {
1078 replace_colliding_arg(&mut c);
1080 *c.ty = replace_self(*c.ty);
1081 arguments.push(c);
1082 }
1083 syn::FnArg::Receiver(r) => {
1084 match position {
1088 FunctionPosition::Free => {
1089 bail_span!(
1090 r.self_token,
1091 "the `self` argument is only allowed for functions in `impl` blocks.\n\n\
1092 If the function is already in an `impl` block, did you perhaps forget to add `#[wasm_bindgen]` to the `impl` block?"
1093 );
1094 }
1095 FunctionPosition::Extern => {
1096 bail_span!(
1097 r.self_token,
1098 "the `self` argument is not allowed for `extern` functions.\n\n\
1099 Did you perhaps mean `this`? For more information on importing JavaScript functions, see:\n\
1100 https://wasm-bindgen.github.io/wasm-bindgen/examples/import-js.html"
1101 );
1102 }
1103 FunctionPosition::Impl { .. } => {}
1104 }
1105
1106 assert!(method_self.is_none());
1109 method_self = Some(get_self_method(r));
1110 }
1111 }
1112 }
1113
1114 let ret_ty_override = opts.unchecked_return_type();
1116 let ret_desc = opts.return_description();
1117 let ret = match output {
1118 syn::ReturnType::Default => None,
1119 syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData {
1120 r#type: replace_self(*ty),
1121 js_type: ret_ty_override
1122 .as_ref()
1123 .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(ty, span)| {
1124 check_invalid_type(ty, *span)?;
1125 Ok(Some(ty.to_string()))
1126 })?,
1127 desc: ret_desc.as_ref().map_or::<Result<_, Diagnostic>, _>(
1128 Ok(None),
1129 |(desc, span)| {
1130 check_js_comment_close(desc, *span)?;
1131 Ok(Some(desc.to_string()))
1132 },
1133 )?,
1134 }),
1135 };
1136 if ret.is_none() && (ret_ty_override.is_some() || ret_desc.is_some()) {
1139 if let Some((_, span)) = ret_ty_override {
1140 return Err(Diagnostic::span_error(
1141 span,
1142 "cannot specify return type for a function that doesn't return",
1143 ));
1144 }
1145 if let Some((_, span)) = ret_desc {
1146 return Err(Diagnostic::span_error(
1147 span,
1148 "cannot specify return description for a function that doesn't return",
1149 ));
1150 }
1151 }
1152
1153 let (name, name_span) = if let Some((js_name, js_name_span)) = opts.js_name() {
1154 let kind = operation_kind(opts);
1155 let prefix = match kind {
1156 OperationKind::Setter(_) => "set_",
1157 _ => "",
1158 };
1159 (format!("{prefix}{js_name}"), js_name_span)
1160 } else {
1161 (decl_name.unraw().to_string(), decl_name.span())
1162 };
1163
1164 Ok((
1165 ast::Function {
1166 name_span,
1167 name,
1168 rust_attrs: attrs,
1169 rust_vis: vis,
1170 r#unsafe: sig.unsafety.is_some(),
1171 r#async: sig.asyncness.is_some(),
1172 generate_typescript: opts.skip_typescript().is_none(),
1173 generate_jsdoc: opts.skip_jsdoc().is_none(),
1174 variadic: opts.variadic().is_some(),
1175 ret,
1176 arguments: arguments
1177 .into_iter()
1178 .zip(
1179 args_attrs
1180 .into_iter()
1181 .flatten()
1182 .chain(iter::repeat(FnArgAttrs::default())),
1183 )
1184 .map(|(pat_type, attrs)| ast::FunctionArgumentData {
1185 pat_type,
1186 js_name: attrs.js_name,
1187 js_type: attrs.js_type,
1188 desc: attrs.desc,
1189 })
1190 .collect(),
1191 },
1192 method_self,
1193 ))
1194}
1195
1196#[derive(Default, Clone)]
1198struct FnArgAttrs {
1199 js_name: Option<String>,
1200 js_type: Option<String>,
1201 desc: Option<String>,
1202}
1203
1204fn extract_args_attrs(sig: &mut syn::Signature) -> Result<Vec<FnArgAttrs>, Diagnostic> {
1206 let mut args_attrs = vec![];
1207 for input in sig.inputs.iter_mut() {
1208 if let syn::FnArg::Typed(pat_type) = input {
1209 let attrs = BindgenAttrs::find(&mut pat_type.attrs)?;
1210 let arg_attrs = FnArgAttrs {
1211 js_name: attrs
1212 .js_name()
1213 .map_or(Ok(None), |(js_name_override, span)| {
1214 if is_js_keyword(js_name_override) || !is_valid_ident(js_name_override) {
1215 return Err(Diagnostic::span_error(span, "invalid JS identifier"));
1216 }
1217 Ok(Some(js_name_override.to_string()))
1218 })?,
1219 js_type: attrs
1220 .unchecked_param_type()
1221 .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(ty, span)| {
1222 check_invalid_type(ty, span)?;
1223 Ok(Some(ty.to_string()))
1224 })?,
1225 desc: attrs
1226 .param_description()
1227 .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(description, span)| {
1228 check_js_comment_close(description, span)?;
1229 Ok(Some(description.to_string()))
1230 })?,
1231 };
1232 attrs.enforce_used()?;
1234 args_attrs.push(arg_attrs);
1235 }
1236 }
1237 Ok(args_attrs)
1238}
1239
1240pub(crate) trait MacroParse<Ctx> {
1241 fn macro_parse(self, program: &mut ast::Program, context: Ctx) -> Result<(), Diagnostic>;
1246}
1247
1248impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
1249 fn macro_parse(
1250 self,
1251 program: &mut ast::Program,
1252 (opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
1253 ) -> Result<(), Diagnostic> {
1254 match self {
1255 syn::Item::Fn(mut f) => {
1256 let opts = opts.unwrap_or_default();
1257 if let Some(path) = opts.wasm_bindgen() {
1258 program.wasm_bindgen = path.clone();
1259 }
1260 if let Some(path) = opts.js_sys() {
1261 program.js_sys = path.clone();
1262 }
1263 if let Some(path) = opts.wasm_bindgen_futures() {
1264 program.wasm_bindgen_futures = path.clone();
1265 }
1266
1267 if opts.main().is_some() {
1268 opts.check_used();
1269 return main(program, f, tokens);
1270 }
1271
1272 let no_mangle = f
1273 .attrs
1274 .iter()
1275 .enumerate()
1276 .find(|(_, m)| m.path().is_ident("no_mangle"));
1277 if let Some((i, _)) = no_mangle {
1278 f.attrs.remove(i);
1279 }
1280 let args_attrs = extract_args_attrs(&mut f.sig)?;
1282 let comments = extract_doc_comments(&f.attrs);
1283 tokens.extend(quote::quote! { #[allow(dead_code)] });
1287 f.to_tokens(tokens);
1288 if opts.start().is_some() {
1289 if !f.sig.generics.params.is_empty() {
1290 bail_span!(&f.sig.generics, "the start function cannot have generics",);
1291 }
1292 if !f.sig.inputs.is_empty() {
1293 bail_span!(&f.sig.inputs, "the start function cannot have arguments",);
1294 }
1295 }
1296 let method_kind = ast::MethodKind::Operation(ast::Operation {
1297 is_static: true,
1298 kind: operation_kind(&opts),
1299 });
1300 let rust_name = f.sig.ident.clone();
1301 let start = opts.start().is_some();
1302
1303 if opts.this().is_some() && f.sig.inputs.is_empty() {
1304 bail_span!(
1305 &f.sig.inputs,
1306 "functions taking a 'this' argument must have at least one parameter"
1307 );
1308 }
1309
1310 let js_namespace = opts.js_namespace().map(|(ns, _)| ns.0);
1311 program.exports.push(ast::Export {
1312 comments,
1313 function: f.convert((opts, args_attrs))?,
1314 js_class: None,
1315 js_namespace,
1316 method_kind,
1317 method_self: None,
1318 rust_class: None,
1319 rust_name,
1320 start,
1321 wasm_bindgen: program.wasm_bindgen.clone(),
1322 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1323 });
1324 }
1325 syn::Item::Impl(mut i) => {
1326 let opts = opts.unwrap_or_default();
1327 (&mut i).macro_parse(program, opts)?;
1328 i.to_tokens(tokens);
1329 }
1330 syn::Item::ForeignMod(mut f) => {
1331 let opts = match opts {
1332 Some(opts) => opts,
1333 None => BindgenAttrs::find(&mut f.attrs)?,
1334 };
1335 f.macro_parse(program, opts)?;
1336 }
1337 syn::Item::Enum(mut e) => {
1338 let opts = match opts {
1339 Some(opts) => opts,
1340 None => BindgenAttrs::find(&mut e.attrs)?,
1341 };
1342 e.macro_parse(program, (tokens, opts))?;
1343 }
1344 syn::Item::Const(mut c) => {
1345 let opts = match opts {
1346 Some(opts) => opts,
1347 None => BindgenAttrs::find(&mut c.attrs)?,
1348 };
1349 c.macro_parse(program, opts)?;
1350 }
1351 _ => {
1352 bail_span!(
1353 self,
1354 "#[wasm_bindgen] can only be applied to a function, \
1355 struct, enum, impl, or extern block",
1356 );
1357 }
1358 }
1359
1360 Ok(())
1361 }
1362}
1363
1364impl MacroParse<BindgenAttrs> for &mut syn::ItemImpl {
1365 fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1366 if self.defaultness.is_some() {
1367 bail_span!(
1368 self.defaultness,
1369 "#[wasm_bindgen] default impls are not supported"
1370 );
1371 }
1372 if self.unsafety.is_some() {
1373 bail_span!(
1374 self.unsafety,
1375 "#[wasm_bindgen] unsafe impls are not supported"
1376 );
1377 }
1378 if let Some((_, path, _)) = &self.trait_ {
1379 bail_span!(path, "#[wasm_bindgen] trait impls are not supported");
1380 }
1381 if !self.generics.params.is_empty() {
1382 bail_span!(
1383 self.generics,
1384 "#[wasm_bindgen] generic impls aren't supported"
1385 );
1386 }
1387 let name = match get_ty(&self.self_ty) {
1388 syn::Type::Path(syn::TypePath {
1389 qself: None,
1390 ref path,
1391 }) => path,
1392 _ => bail_span!(
1393 self.self_ty,
1394 "unsupported self type in #[wasm_bindgen] impl"
1395 ),
1396 };
1397 let mut errors = Vec::new();
1398 for item in self.items.iter_mut() {
1399 if let Err(e) = prepare_for_impl_recursion(item, name, program, &opts) {
1400 errors.push(e);
1401 }
1402 }
1403 Diagnostic::from_vec(errors)?;
1404 opts.check_used();
1405 Ok(())
1406 }
1407}
1408
1409fn prepare_for_impl_recursion(
1418 item: &mut syn::ImplItem,
1419 class: &syn::Path,
1420 program: &ast::Program,
1421 impl_opts: &BindgenAttrs,
1422) -> Result<(), Diagnostic> {
1423 let method = match item {
1424 syn::ImplItem::Fn(m) => m,
1425 syn::ImplItem::Const(_) => {
1426 bail_span!(
1427 &*item,
1428 "const definitions aren't supported with #[wasm_bindgen]"
1429 );
1430 }
1431 syn::ImplItem::Type(_) => bail_span!(
1432 &*item,
1433 "type definitions in impls aren't supported with #[wasm_bindgen]"
1434 ),
1435 syn::ImplItem::Macro(_) => {
1436 bail_span!(&*item, "macros in impls aren't supported");
1441 }
1442 syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
1443 other => bail_span!(other, "failed to parse this item as a known item"),
1444 };
1445
1446 let ident = extract_path_ident(class)?;
1447
1448 let js_class = impl_opts
1449 .js_class()
1450 .map(|s| s.0.to_string())
1451 .unwrap_or(ident.to_string());
1452
1453 let wasm_bindgen = &program.wasm_bindgen;
1454 let wasm_bindgen_futures = &program.wasm_bindgen_futures;
1455 method.attrs.insert(
1456 0,
1457 syn::Attribute {
1458 pound_token: Default::default(),
1459 style: syn::AttrStyle::Outer,
1460 bracket_token: Default::default(),
1461 meta: syn::parse_quote! { #wasm_bindgen::prelude::__wasm_bindgen_class_marker(#class = #js_class, wasm_bindgen = #wasm_bindgen, wasm_bindgen_futures = #wasm_bindgen_futures) },
1462 },
1463 );
1464
1465 Ok(())
1466}
1467
1468impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn {
1469 fn macro_parse(
1470 self,
1471 program: &mut ast::Program,
1472 ClassMarker {
1473 class,
1474 js_class,
1475 wasm_bindgen,
1476 wasm_bindgen_futures,
1477 }: &ClassMarker,
1478 ) -> Result<(), Diagnostic> {
1479 program.wasm_bindgen = wasm_bindgen.clone();
1480 program.wasm_bindgen_futures = wasm_bindgen_futures.clone();
1481
1482 match self.vis {
1483 syn::Visibility::Public(_) => {}
1484 _ => return Ok(()),
1485 }
1486 if self.defaultness.is_some() {
1487 panic!("default methods are not supported");
1488 }
1489 if self.sig.constness.is_some() {
1490 bail_span!(
1491 self.sig.constness,
1492 "can only #[wasm_bindgen] non-const functions",
1493 );
1494 }
1495
1496 let opts = BindgenAttrs::find(&mut self.attrs)?;
1497
1498 if opts.this().is_some() {
1499 bail_span!(
1500 &self.sig.ident,
1501 "#[wasm_bindgen(this)] cannot be used on impl block methods; \
1502 it is only valid on free functions"
1503 );
1504 }
1505
1506 let comments = extract_doc_comments(&self.attrs);
1507 let args_attrs: Vec<FnArgAttrs> = extract_args_attrs(&mut self.sig)?;
1508 let (function, method_self) = function_from_decl(
1509 &self.sig.ident,
1510 &opts,
1511 self.sig.clone(),
1512 self.attrs.clone(),
1513 self.vis.clone(),
1514 FunctionPosition::Impl { self_ty: class },
1515 Some(args_attrs),
1516 )?;
1517 let method_kind = if opts.constructor().is_some() {
1518 ast::MethodKind::Constructor
1519 } else {
1520 let is_static = method_self.is_none();
1521 let kind = operation_kind(&opts);
1522 ast::MethodKind::Operation(ast::Operation { is_static, kind })
1523 };
1524
1525 if let Some((_, span)) = opts.js_namespace() {
1527 return Err(Diagnostic::span_error(
1528 span[0],
1529 "`js_namespace` cannot be used on methods, getters, setters, or static methods. \
1530 Use `js_namespace` on the exported struct definition instead to put the entire class in a namespace.",
1531 ));
1532 }
1533
1534 program.exports.push(ast::Export {
1535 comments,
1536 function,
1537 js_class: Some(js_class.to_string()),
1538 js_namespace: None,
1539 method_kind,
1540 method_self,
1541 rust_class: Some(class.clone()),
1542 rust_name: self.sig.ident.clone(),
1543 start: false,
1544 wasm_bindgen: program.wasm_bindgen.clone(),
1545 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1546 });
1547 opts.check_used();
1548 Ok(())
1549 }
1550}
1551
1552fn string_enum(
1553 enum_: syn::ItemEnum,
1554 program: &mut ast::Program,
1555 js_name: String,
1556 generate_typescript: bool,
1557 comments: Vec<String>,
1558 js_namespace: Option<Vec<String>>,
1559) -> Result<(), Diagnostic> {
1560 let mut variants = vec![];
1561 let mut variant_values = vec![];
1562
1563 for v in enum_.variants.iter() {
1564 let (_, expr) = match &v.discriminant {
1565 Some(pair) => pair,
1566 None => {
1567 bail_span!(v, "all variants of a string enum must have a string value");
1568 }
1569 };
1570 match get_expr(expr) {
1571 syn::Expr::Lit(syn::ExprLit {
1572 attrs: _,
1573 lit: syn::Lit::Str(str_lit),
1574 }) => {
1575 variants.push(v.ident.clone());
1576 variant_values.push(str_lit.value());
1577 }
1578 expr => bail_span!(
1579 expr,
1580 "enums with #[wasm_bindgen] cannot mix string and non-string values",
1581 ),
1582 }
1583 }
1584
1585 program.imports.push(ast::Import {
1586 module: None,
1587 js_namespace: None,
1588 reexport: None,
1589 kind: ast::ImportKind::Enum(ast::StringEnum {
1590 vis: enum_.vis,
1591 name: enum_.ident,
1592 export_name: js_name,
1593 variants,
1594 variant_values,
1595 comments,
1596 rust_attrs: enum_.attrs,
1597 generate_typescript,
1598 js_namespace,
1599 wasm_bindgen: program.wasm_bindgen.clone(),
1600 }),
1601 });
1602
1603 Ok(())
1604}
1605
1606struct NumericValue<'a> {
1608 negative: bool,
1609 base10_digits: &'a str,
1610}
1611impl<'a> NumericValue<'a> {
1612 fn from_expr(expr: &'a syn::Expr) -> Option<Self> {
1613 match get_expr(expr) {
1614 syn::Expr::Lit(syn::ExprLit {
1615 lit: syn::Lit::Int(int_lit),
1616 ..
1617 }) => Some(Self {
1618 negative: false,
1619 base10_digits: int_lit.base10_digits(),
1620 }),
1621 syn::Expr::Unary(syn::ExprUnary {
1622 op: syn::UnOp::Neg(_),
1623 expr,
1624 ..
1625 }) => Self::from_expr(expr).map(|n| n.neg()),
1626 _ => None,
1627 }
1628 }
1629
1630 fn parse(&self) -> Option<i64> {
1631 let mut value = self.base10_digits.parse::<i64>().ok()?;
1632 if self.negative {
1633 value = -value;
1634 }
1635 Some(value)
1636 }
1637
1638 fn neg(self) -> Self {
1639 Self {
1640 negative: !self.negative,
1641 base10_digits: self.base10_digits,
1642 }
1643 }
1644}
1645
1646impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
1647 fn macro_parse(
1648 self,
1649 program: &mut ast::Program,
1650 (tokens, opts): (&'a mut TokenStream, BindgenAttrs),
1651 ) -> Result<(), Diagnostic> {
1652 if self.variants.is_empty() {
1653 bail_span!(self, "cannot export empty enums to JS");
1654 }
1655 for variant in self.variants.iter() {
1656 match variant.fields {
1657 syn::Fields::Unit => (),
1658 _ => bail_span!(
1659 variant.fields,
1660 "enum variants with associated data are not supported with #[wasm_bindgen]"
1661 ),
1662 }
1663 }
1664
1665 let generate_typescript = opts.skip_typescript().is_none();
1666 let comments = extract_doc_comments(&self.attrs);
1667 let js_name = opts
1668 .js_name()
1669 .map(|s| s.0)
1670 .map_or_else(|| self.ident.to_string(), |s| s.to_string());
1671 if is_js_keyword(&js_name) && js_name != "default" {
1672 bail_span!(
1673 self.ident,
1674 "enum cannot use the JS keyword `{}` as its name",
1675 js_name
1676 );
1677 }
1678
1679 let js_namespace = opts.js_namespace().map(|(ns, _)| ns.0);
1680 opts.check_used();
1681
1682 let is_string_enum = self.variants.iter().any(|v| {
1684 if let Some((_, expr)) = &v.discriminant {
1685 if let syn::Expr::Lit(syn::ExprLit {
1686 lit: syn::Lit::Str(_),
1687 ..
1688 }) = get_expr(expr)
1689 {
1690 return true;
1691 }
1692 }
1693 false
1694 });
1695 if is_string_enum {
1696 return string_enum(
1697 self,
1698 program,
1699 js_name,
1700 generate_typescript,
1701 comments,
1702 js_namespace,
1703 );
1704 }
1705
1706 match self.vis {
1707 syn::Visibility::Public(_) => {}
1708 _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
1709 }
1710
1711 let signed = self.variants.iter().any(|v| match &v.discriminant {
1716 Some((_, expr)) => NumericValue::from_expr(expr).is_some_and(|n| n.negative),
1717 None => false,
1718 });
1719 let underlying_min = if signed { i32::MIN as i64 } else { 0 };
1720 let underlying_max = if signed {
1721 i32::MAX as i64
1722 } else {
1723 u32::MAX as i64
1724 };
1725
1726 let mut last_discriminant: Option<i64> = None;
1727 let mut discriminant_map: HashMap<i64, &syn::Variant> = HashMap::new();
1728
1729 let variants = self
1730 .variants
1731 .iter()
1732 .map(|v| {
1733 let value: i64 = match &v.discriminant {
1734 Some((_, expr)) => match NumericValue::from_expr(expr).and_then(|n| n.parse()) {
1735 Some(value) => value,
1736 _ => bail_span!(
1737 expr,
1738 "C-style enums with #[wasm_bindgen] may only have \
1739 numeric literal values that fit in a 32-bit integer as discriminants. \
1740 Expressions or variables are not supported.",
1741 ),
1742 },
1743 None => {
1744 last_discriminant.map_or(0, |last| last + 1)
1747 }
1748 };
1749
1750 last_discriminant = Some(value);
1751
1752 let underlying = if signed { "i32" } else { "u32" };
1754 let numbers = if signed { "signed numbers" } else { "unsigned numbers" };
1755 if value < underlying_min {
1756 bail_span!(
1757 v,
1758 "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \
1759 but `{1}` is too small for `{2}`",
1760 numbers,
1761 value,
1762 underlying
1763 );
1764 }
1765 if value > underlying_max {
1766 bail_span!(
1767 v,
1768 "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \
1769 but `{1}` is too large for `{2}`",
1770 numbers,
1771 value,
1772 underlying
1773 );
1774 }
1775
1776 if let Some(old) = discriminant_map.insert(value, v) {
1778 bail_span!(
1779 v,
1780 "discriminant value `{}` is already used by {} in this enum",
1781 value,
1782 old.ident
1783 );
1784 }
1785
1786 let comments = extract_doc_comments(&v.attrs);
1787 Ok(ast::Variant {
1788 name: v.ident.clone(),
1789 value: value as u32,
1792 comments,
1793 })
1794 })
1795 .collect::<Result<Vec<_>, Diagnostic>>()?;
1796
1797 let hole = (0..=underlying_max)
1800 .find(|v| !discriminant_map.contains_key(v))
1801 .unwrap() as u32;
1802
1803 self.to_tokens(tokens);
1804
1805 program.enums.push(ast::Enum {
1806 rust_name: self.ident,
1807 js_name,
1808 signed,
1809 variants,
1810 comments,
1811 hole,
1812 generate_typescript,
1813 js_namespace,
1814 wasm_bindgen: program.wasm_bindgen.clone(),
1815 });
1816 Ok(())
1817 }
1818}
1819
1820impl MacroParse<BindgenAttrs> for syn::ItemConst {
1821 fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1822 if opts.typescript_custom_section().is_none() {
1824 bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)].");
1825 }
1826
1827 let typescript_custom_section = match get_expr(&self.expr) {
1828 syn::Expr::Lit(syn::ExprLit {
1829 lit: syn::Lit::Str(litstr),
1830 ..
1831 }) => ast::LitOrExpr::Lit(litstr.value()),
1832 expr => ast::LitOrExpr::Expr(expr.clone()),
1833 };
1834
1835 program
1836 .typescript_custom_sections
1837 .push(typescript_custom_section);
1838
1839 opts.check_used();
1840
1841 Ok(())
1842 }
1843}
1844
1845impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
1846 fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1847 let mut errors = Vec::new();
1848 if let Some(other) = self.abi.name.filter(|l| l.value() != "C") {
1849 errors.push(err_span!(
1850 other,
1851 "only foreign mods with the `C` ABI are allowed"
1852 ));
1853 }
1854 let js_namespace = opts.js_namespace().map(|(s, _)| s);
1855 let module = module_from_opts(program, &opts)
1856 .map_err(|e| errors.push(e))
1857 .unwrap_or_default();
1858 for item in self.items.into_iter() {
1859 let ctx = ForeignItemCtx {
1860 module: module.clone(),
1861 js_namespace: js_namespace.clone(),
1862 };
1863 if let Err(e) = item.macro_parse(program, ctx) {
1864 errors.push(e);
1865 }
1866 }
1867 Diagnostic::from_vec(errors)?;
1868 opts.check_used();
1869 Ok(())
1870 }
1871}
1872
1873struct ForeignItemCtx {
1874 module: Option<ast::ImportModule>,
1875 js_namespace: Option<JsNamespace>,
1876}
1877
1878impl MacroParse<ForeignItemCtx> for syn::ForeignItem {
1879 fn macro_parse(
1880 mut self,
1881 program: &mut ast::Program,
1882 ctx: ForeignItemCtx,
1883 ) -> Result<(), Diagnostic> {
1884 let item_opts = {
1885 let attrs = match self {
1886 syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
1887 syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
1888 syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
1889 syn::ForeignItem::Verbatim(v) => {
1890 let mut item: syn::ItemStatic =
1891 syn::parse(v.into()).expect("only foreign functions/types allowed for now");
1892 let item_opts = BindgenAttrs::find(&mut item.attrs)?;
1893 let reexport = item_opts.reexport().cloned();
1894 let kind = item.convert((program, item_opts, &ctx.module))?;
1895
1896 program.imports.push(ast::Import {
1897 module: None,
1898 js_namespace: None,
1899 reexport,
1900 kind,
1901 });
1902
1903 return Ok(());
1904 }
1905 _ => panic!("only foreign functions/types allowed for now"),
1906 };
1907 BindgenAttrs::find(attrs)?
1908 };
1909
1910 let js_namespace = item_opts
1911 .js_namespace()
1912 .map(|(s, _)| s)
1913 .or(ctx.js_namespace)
1914 .map(|s| s.0);
1915 let module = ctx.module;
1916 let reexport = item_opts.reexport().cloned();
1917
1918 let kind = match self {
1919 syn::ForeignItem::Fn(f) => f.convert((program, item_opts, &module))?,
1920 syn::ForeignItem::Type(t) => t.convert((program, item_opts))?,
1921 syn::ForeignItem::Static(s) => s.convert((program, item_opts, &module))?,
1922 _ => panic!("only foreign functions/types allowed for now"),
1923 };
1924
1925 let needs_check = js_namespace.is_none() && module.is_none();
1932 if needs_check {
1933 match &kind {
1934 ast::ImportKind::Function(import_function) => {
1935 if matches!(import_function.kind, ast::ImportFunctionKind::Normal)
1936 && is_non_value_js_keyword(&import_function.function.name)
1937 {
1938 bail_span!(
1939 import_function.rust_name,
1940 "Imported function cannot use the JS keyword `{}` as its name.",
1941 import_function.function.name
1942 );
1943 }
1944 }
1945 ast::ImportKind::Static(import_static) => {
1946 if is_non_value_js_keyword(&import_static.js_name) {
1947 bail_span!(
1948 import_static.rust_name,
1949 "Imported static cannot use the JS keyword `{}` as its name.",
1950 import_static.js_name
1951 );
1952 }
1953 }
1954 ast::ImportKind::String(_) => {
1955 }
1957 ast::ImportKind::Type(import_type) => {
1958 if is_non_value_js_keyword(&import_type.js_name) {
1959 bail_span!(
1960 import_type.rust_name,
1961 "Imported type cannot use the JS keyword `{}` as its name.",
1962 import_type.js_name
1963 );
1964 }
1965 }
1966 ast::ImportKind::Enum(_) => {
1967 }
1969 }
1970 }
1971
1972 program.imports.push(ast::Import {
1973 module,
1974 js_namespace,
1975 reexport,
1976 kind,
1977 });
1978
1979 Ok(())
1980 }
1981}
1982
1983pub fn module_from_opts(
1984 program: &mut ast::Program,
1985 opts: &BindgenAttrs,
1986) -> Result<Option<ast::ImportModule>, Diagnostic> {
1987 if let Some(path) = opts.wasm_bindgen() {
1988 program.wasm_bindgen = path.clone();
1989 }
1990
1991 if let Some(path) = opts.js_sys() {
1992 program.js_sys = path.clone();
1993 }
1994
1995 if let Some(path) = opts.wasm_bindgen_futures() {
1996 program.wasm_bindgen_futures = path.clone();
1997 }
1998
1999 let mut errors = Vec::new();
2000 let module = if let Some((name, span)) = opts.module() {
2001 if opts.inline_js().is_some() {
2002 let msg = "cannot specify both `module` and `inline_js`";
2003 errors.push(Diagnostic::span_error(span, msg));
2004 }
2005 if opts.raw_module().is_some() {
2006 let msg = "cannot specify both `module` and `raw_module`";
2007 errors.push(Diagnostic::span_error(span, msg));
2008 }
2009 Some(ast::ImportModule::Named(name.to_string(), span))
2010 } else if let Some((name, span)) = opts.raw_module() {
2011 if opts.inline_js().is_some() {
2012 let msg = "cannot specify both `raw_module` and `inline_js`";
2013 errors.push(Diagnostic::span_error(span, msg));
2014 }
2015 Some(ast::ImportModule::RawNamed(name.to_string(), span))
2016 } else if let Some((js, _span)) = opts.inline_js() {
2017 let i = program.inline_js.len();
2018 program.inline_js.push(js.to_string());
2019 Some(ast::ImportModule::Inline(i))
2020 } else {
2021 None
2022 };
2023 Diagnostic::from_vec(errors)?;
2024 Ok(module)
2025}
2026
2027fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, Diagnostic> {
2029 let t = match ty {
2030 Some(t) => t,
2031 None => return Ok(None),
2032 };
2033 let path = match *get_ty(t) {
2034 syn::Type::Path(syn::TypePath {
2035 qself: None,
2036 ref path,
2037 }) => path,
2038 _ => bail_span!(t, "must be Result<...>"),
2039 };
2040 let seg = path
2041 .segments
2042 .last()
2043 .ok_or_else(|| err_span!(t, "must have at least one segment"))?;
2044 let generics = match seg.arguments {
2045 syn::PathArguments::AngleBracketed(ref t) => t,
2046 _ => bail_span!(t, "must be Result<...>"),
2047 };
2048 let generic = generics
2049 .args
2050 .first()
2051 .ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?;
2052 let ty = match generic {
2053 syn::GenericArgument::Type(t) => t,
2054 other => bail_span!(other, "must be a type parameter"),
2055 };
2056 match get_ty(ty) {
2057 syn::Type::Tuple(t) if t.elems.is_empty() => return Ok(None),
2058 _ => {}
2059 }
2060 Ok(Some(ty.clone()))
2061}
2062
2063fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
2065 attrs
2066 .iter()
2067 .filter_map(|a| {
2068 if a.path().segments.iter().any(|s| s.ident == "doc") {
2071 let tokens = match &a.meta {
2072 syn::Meta::Path(_) => None,
2073 syn::Meta::List(list) => Some(list.tokens.clone()),
2074 syn::Meta::NameValue(name_value) => Some(name_value.value.to_token_stream()),
2075 };
2076
2077 Some(
2078 tokens.into_iter().flatten().filter_map(|t| match t {
2080 TokenTree::Literal(lit) => {
2081 let quoted = lit.to_string();
2082 Some(try_unescape("ed).unwrap_or(quoted))
2083 }
2084 _ => None,
2085 }),
2086 )
2087 } else {
2088 None
2089 }
2090 })
2091 .fold(vec![], |mut acc, a| {
2093 acc.extend(a);
2094 acc
2095 })
2096}
2097
2098fn try_unescape(mut s: &str) -> Option<String> {
2100 s = s.strip_prefix('"').unwrap_or(s);
2101 s = s.strip_suffix('"').unwrap_or(s);
2102 let mut result = String::with_capacity(s.len());
2103 let mut chars = s.chars();
2104 while let Some(c) = chars.next() {
2105 if c == '\\' {
2106 let c = chars.next()?;
2107 match c {
2108 't' => result.push('\t'),
2109 'r' => result.push('\r'),
2110 'n' => result.push('\n'),
2111 '\\' | '\'' | '"' => result.push(c),
2112 'u' => {
2113 if chars.next() != Some('{') {
2114 return None;
2115 }
2116 let (c, next) = unescape_unicode(&mut chars)?;
2117 result.push(c);
2118 if next != '}' {
2119 return None;
2120 }
2121 }
2122 _ => return None,
2123 }
2124 } else {
2125 result.push(c);
2126 }
2127 }
2128 Some(result)
2129}
2130
2131fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
2132 let mut value = 0;
2133 for (i, c) in chars.enumerate() {
2134 match (i, c.to_digit(16)) {
2135 (0..=5, Some(num)) => value = (value << 4) | num,
2136 (1.., None) => return Some((char::from_u32(value)?, c)),
2137 _ => break,
2138 }
2139 }
2140 None
2141}
2142
2143fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> {
2145 struct Walk {
2146 diagnostics: Vec<Diagnostic>,
2147 }
2148
2149 impl<'ast> syn::visit::Visit<'ast> for Walk {
2150 fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
2151 self.diagnostics.push(err_span!(
2152 i,
2153 "it is currently not sound to use lifetimes in function \
2154 signatures"
2155 ));
2156 }
2157 }
2158 let mut walk = Walk {
2159 diagnostics: Vec::new(),
2160 };
2161 syn::visit::Visit::visit_signature(&mut walk, sig);
2162 Diagnostic::from_vec(walk.diagnostics)
2163}
2164
2165fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> {
2167 for segment in path.segments.iter() {
2168 match segment.arguments {
2169 syn::PathArguments::None => {}
2170 _ => bail_span!(path, "paths with type parameters are not supported yet"),
2171 }
2172 }
2173
2174 match path.segments.last() {
2175 Some(value) => Ok(value.ident.clone()),
2176 None => {
2177 bail_span!(path, "empty idents are not supported");
2178 }
2179 }
2180}
2181
2182pub fn reset_attrs_used() {
2183 ATTRS.with(|state| {
2184 state.parsed.set(0);
2185 state.checks.set(0);
2186 state.unused_attrs.borrow_mut().clear();
2187 })
2188}
2189
2190pub fn check_unused_attrs(tokens: &mut TokenStream) {
2191 ATTRS.with(|state| {
2192 assert_eq!(state.parsed.get(), state.checks.get());
2193 let unused_attrs = &*state.unused_attrs.borrow();
2194 if !unused_attrs.is_empty() {
2195 let unused_attrs = unused_attrs.iter().map(|UnusedState { error, ident }| {
2196 if *error {
2197 let text = format!("invalid attribute {ident} in this position");
2198 quote::quote_spanned! { ident.span() => ::core::compile_error!(#text); }
2199 } else {
2200 quote::quote! { let #ident: (); }
2201 }
2202 });
2203 tokens.extend(quote::quote! {
2204 const _: () = {
2206 #(#unused_attrs)*
2207 };
2208 });
2209 }
2210 })
2211}
2212
2213fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind {
2214 let mut operation_kind = ast::OperationKind::Regular;
2215 if opts.this().is_some() {
2216 operation_kind = ast::OperationKind::RegularThis;
2217 }
2218 if let Some(g) = opts.getter() {
2219 operation_kind = ast::OperationKind::Getter(g.clone());
2220 }
2221 if let Some(s) = opts.setter() {
2222 operation_kind = ast::OperationKind::Setter(s.clone());
2223 }
2224 if opts.indexing_getter().is_some() {
2225 operation_kind = ast::OperationKind::IndexingGetter;
2226 }
2227 if opts.indexing_setter().is_some() {
2228 operation_kind = ast::OperationKind::IndexingSetter;
2229 }
2230 if opts.indexing_deleter().is_some() {
2231 operation_kind = ast::OperationKind::IndexingDeleter;
2232 }
2233 operation_kind
2234}
2235
2236pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
2237 let mut program = ast::Program::default();
2238 let module = module_from_opts(&mut program, &opts)?.ok_or_else(|| {
2239 Diagnostic::span_error(Span::call_site(), "`link_to!` requires a module.")
2240 })?;
2241 if let ast::ImportModule::Named(p, s) | ast::ImportModule::RawNamed(p, s) = &module {
2242 if !p.starts_with("./") && !p.starts_with("../") && !p.starts_with('/') {
2243 return Err(Diagnostic::span_error(
2244 *s,
2245 "`link_to!` does not support module paths.",
2246 ));
2247 }
2248 }
2249 opts.enforce_used()?;
2250 program.linked_modules.push(module);
2251 Ok(ast::LinkToModule(program))
2252}
2253
2254fn main(program: &ast::Program, mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
2255 if f.sig.ident != "main" {
2256 bail_span!(&f.sig.ident, "the main function has to be called main");
2257 }
2258 if let Some(constness) = f.sig.constness {
2259 bail_span!(&constness, "the main function cannot be const");
2260 }
2261 if !f.sig.generics.params.is_empty() {
2262 bail_span!(&f.sig.generics, "the main function cannot have generics");
2263 }
2264 if !f.sig.inputs.is_empty() {
2265 bail_span!(&f.sig.inputs, "the main function cannot have arguments");
2266 }
2267
2268 let r#return = f.sig.output;
2269 f.sig.output = ReturnType::Default;
2270 let body = f.block.as_ref();
2271
2272 let wasm_bindgen = &program.wasm_bindgen;
2273 let wasm_bindgen_futures = &program.wasm_bindgen_futures;
2274
2275 if f.sig.asyncness.take().is_some() {
2276 *f.block = syn::parse2(quote::quote! {
2277 {
2278 async fn __wasm_bindgen_generated_main() #r#return #body
2279 #wasm_bindgen_futures::spawn_local(
2280 async move {
2281 use #wasm_bindgen::__rt::Main;
2282 let __ret = __wasm_bindgen_generated_main();
2283 (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main()
2284 },
2285 )
2286 }
2287 })
2288 .unwrap();
2289 } else {
2290 *f.block = syn::parse2(quote::quote! {
2291 {
2292 fn __wasm_bindgen_generated_main() #r#return #body
2293 use #wasm_bindgen::__rt::Main;
2294 let __ret = __wasm_bindgen_generated_main();
2295 (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main()
2296 }
2297 })
2298 .unwrap();
2299 }
2300
2301 f.to_tokens(tokens);
2302
2303 Ok(())
2304}
2305
2306#[cfg(test)]
2307mod tests {
2308 #[test]
2309 fn test_try_unescape() {
2310 use super::try_unescape;
2311 assert_eq!(try_unescape("hello").unwrap(), "hello");
2312 assert_eq!(try_unescape("\"hello").unwrap(), "hello");
2313 assert_eq!(try_unescape("hello\"").unwrap(), "hello");
2314 assert_eq!(try_unescape("\"hello\"").unwrap(), "hello");
2315 assert_eq!(try_unescape("hello\\\\").unwrap(), "hello\\");
2316 assert_eq!(try_unescape("hello\\n").unwrap(), "hello\n");
2317 assert_eq!(try_unescape("hello\\u"), None);
2318 assert_eq!(try_unescape("hello\\u{"), None);
2319 assert_eq!(try_unescape("hello\\u{}"), None);
2320 assert_eq!(try_unescape("hello\\u{0}").unwrap(), "hello\0");
2321 assert_eq!(try_unescape("hello\\u{000000}").unwrap(), "hello\0");
2322 assert_eq!(try_unescape("hello\\u{0000000}"), None);
2323 }
2324}