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