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(pub 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 (extends_js_class, false, ExtendsJsClass(Span, String, Span)),
100 (extends_js_namespace, false, ExtendsJsNamespace(Span, JsNamespace, Vec<Span>)),
101 (no_deref, false, NoDeref(Span)),
102 (no_upcast, false, NoUpcast(Span)),
103 (no_promising, false, NoPromising(Span)),
104 (no_into_js_generic, false, NoIntoJsGeneric(Span)),
105 (vendor_prefix, false, VendorPrefix(Span, Ident)),
106 (variadic, false, Variadic(Span)),
107 (typescript_custom_section, false, TypescriptCustomSection(Span)),
108 (skip_typescript, false, SkipTypescript(Span)),
109 (skip_jsdoc, false, SkipJsDoc(Span)),
110 (private, false, Hide(Span)),
111 (fallback, false, Fallback(Span)),
112 (main, false, Main(Span)),
113 (start, false, Start(Span)),
114 (wasm_bindgen, false, WasmBindgen(Span, syn::Path)),
115 (js_sys, false, JsSys(Span, syn::Path)),
116 (wasm_bindgen_futures, false, WasmBindgenFutures(Span, syn::Path)),
117 (skip, false, Skip(Span)),
118 (slice_to_array, false, SliceToArray(Span)),
119 (typescript_type, false, TypeScriptType(Span, String, Span)),
120 (getter_with_clone, false, GetterWithClone(Span)),
121 (static_string, false, StaticString(Span)),
122 (thread_local, false, ThreadLocal(Span)),
123 (thread_local_v2, false, ThreadLocalV2(Span)),
124 (unchecked_return_type, true, ReturnType(Span, String, Span)),
125 (return_description, true, ReturnDesc(Span, String, Span)),
126 (unchecked_param_type, true, ParamType(Span, String, Span)),
127 (unchecked_optional_param_type, true, OptionalParamType(Span, String, Span)),
128 (param_description, true, ParamDesc(Span, String, Span)),
129
130 (assert_no_shim, false, AssertNoShim(Span)),
132 }
133 };
134}
135
136macro_rules! methods {
137 ($(($name:ident, $invalid_unused:literal, $variant:ident($($contents:tt)*)),)*) => {
138 $(methods!(@method $name, $variant($($contents)*));)*
139
140 fn enforce_used(self) -> Result<(), Diagnostic> {
141 ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
143
144 let mut errors = Vec::new();
145 for (used, attr) in self.attrs.iter() {
146 if used.get() {
147 continue
148 }
149 let span = match attr {
150 $(BindgenAttr::$variant(span, ..) => span,)*
151 };
152 errors.push(Diagnostic::span_error(*span, "unused wasm_bindgen attribute"));
153 }
154 Diagnostic::from_vec(errors)
155 }
156
157 pub(crate) fn check_used(self) {
158 ATTRS.with(|state| {
160 state.checks.set(state.checks.get() + 1);
161
162 state.unused_attrs.borrow_mut().extend(
163 self.attrs
164 .iter()
165 .filter_map(|(used, attr)| if used.get() { None } else { Some(attr) })
166 .map(|attr| {
167 match attr {
168 $(BindgenAttr::$variant(span, ..) => {
169 UnusedState {
170 error: $invalid_unused,
171 ident: syn::parse_quote_spanned!(*span => $name)
172 }
173 })*
174 }
175 })
176 );
177 });
178 }
179 };
180
181 (@method $name:ident, $variant:ident(Span, String, Span)) => {
182 pub(crate) fn $name(&self) -> Option<(&str, Span)> {
183 self.attrs
184 .iter()
185 .find_map(|a| match &a.1 {
186 BindgenAttr::$variant(_, s, span) => {
187 a.0.set(true);
188 Some((&s[..], *span))
189 }
190 _ => None,
191 })
192 }
193 };
194
195 (@method $name:ident, $variant:ident(Span, JsNamespace, Vec<Span>)) => {
196 pub(crate) fn $name(&self) -> Option<(JsNamespace, &[Span])> {
197 self.attrs
198 .iter()
199 .find_map(|a| match &a.1 {
200 BindgenAttr::$variant(_, ss, spans) => {
201 a.0.set(true);
202 Some((ss.clone(), &spans[..]))
203 }
204 _ => None,
205 })
206 }
207 };
208
209 (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
210 #[allow(unused)]
211 pub(crate) fn $name(&self) -> Option<&$($other)*> {
212 self.attrs
213 .iter()
214 .find_map(|a| match &a.1 {
215 BindgenAttr::$variant(_, s) => {
216 a.0.set(true);
217 Some(s)
218 }
219 _ => None,
220 })
221 }
222 };
223
224 (@method $name:ident, $variant:ident($($other:tt)*)) => {
225 #[allow(unused)]
226 pub(crate) fn $name(&self) -> Option<&$($other)*> {
227 self.attrs
228 .iter()
229 .find_map(|a| match &a.1 {
230 BindgenAttr::$variant(s) => {
231 a.0.set(true);
232 Some(s)
233 }
234 _ => None,
235 })
236 }
237 };
238}
239
240impl BindgenAttrs {
241 fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
243 let mut ret = BindgenAttrs::default();
244 loop {
245 let pos = attrs
246 .iter()
247 .enumerate()
248 .find(|&(_, m)| m.path().segments[0].ident == "wasm_bindgen")
249 .map(|a| a.0);
250 let pos = match pos {
251 Some(i) => i,
252 None => break,
253 };
254 let attr = attrs.remove(pos);
255 let tokens = match attr.meta {
256 syn::Meta::Path(_) => continue,
257 syn::Meta::List(syn::MetaList {
258 delimiter: MacroDelimiter::Paren(_),
259 tokens,
260 ..
261 }) => tokens,
262 syn::Meta::List(_) | syn::Meta::NameValue(_) => {
263 bail_span!(attr, "malformed #[wasm_bindgen] attribute")
264 }
265 };
266 let mut attrs: BindgenAttrs = syn::parse2(tokens)?;
267 ret.attrs.append(&mut attrs.attrs);
268 attrs.check_used();
269 }
270 for (_, attr) in &ret.attrs {
276 if let BindgenAttr::JsName(_, value, span) = attr {
277 validate_computed_key("js_name", value, *span)?;
278 }
279 }
280 Ok(ret)
281 }
282
283 fn get_thread_local(&self) -> Result<Option<ThreadLocal>, Diagnostic> {
284 let mut thread_local = self.thread_local_v2().map(|_| ThreadLocal::V2);
285
286 if let Some(span) = self.thread_local() {
287 if thread_local.is_some() {
288 return Err(Diagnostic::span_error(
289 *span,
290 "`thread_local` can't be used with `thread_local_v2`",
291 ));
292 } else {
293 thread_local = Some(ThreadLocal::V1)
294 }
295 }
296
297 Ok(thread_local)
298 }
299
300 fn js_name_no_symbol(&self, position: &str) -> Result<Option<&str>, Diagnostic> {
305 match self.js_name() {
306 Some((value, span)) if is_computed_key(value) => Err(Diagnostic::span_error(
307 span,
308 format!("{position} do not support symbols in js_name"),
309 )),
310 Some((value, _)) => Ok(Some(value)),
311 None => Ok(None),
312 }
313 }
314
315 attrgen!(methods);
316}
317
318impl Default for BindgenAttrs {
319 fn default() -> BindgenAttrs {
320 ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1));
324 BindgenAttrs { attrs: Vec::new() }
325 }
326}
327
328impl Parse for BindgenAttrs {
329 fn parse(input: ParseStream) -> SynResult<Self> {
330 let mut attrs = BindgenAttrs::default();
331 if input.is_empty() {
332 return Ok(attrs);
333 }
334
335 let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
336 attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect();
337 Ok(attrs)
338 }
339}
340
341macro_rules! gen_bindgen_attr {
342 ($(($method:ident, $_:literal, $($variants:tt)*),)*) => {
343 #[cfg_attr(feature = "extra-traits", derive(Debug))]
345 pub enum BindgenAttr {
346 $($($variants)*,)*
347 }
348 }
349}
350attrgen!(gen_bindgen_attr);
351
352impl Parse for BindgenAttr {
353 fn parse(input: ParseStream) -> SynResult<Self> {
354 let original = input.fork();
355 let attr: AnyIdent = input.parse()?;
356 let attr = attr.0;
357 let attr_span = attr.span();
358 let attr_string = attr.to_string();
359 let raw_attr_string = format!("r#{attr_string}");
360
361 macro_rules! parsers {
362 ($(($name:ident, $_:literal, $($contents:tt)*),)*) => {
363 $(
364 if attr_string == stringify!($name) || raw_attr_string == stringify!($name) {
365 parsers!(
366 @parser
367 $($contents)*
368 );
369 }
370 )*
371 };
372
373 (@parser $variant:ident(Span)) => ({
374 return Ok(BindgenAttr::$variant(attr_span));
375 });
376
377 (@parser $variant:ident(Span, Ident)) => ({
378 input.parse::<Token![=]>()?;
379 let ident = if input.peek(syn::LitStr) {
383 let litstr = input.parse::<syn::LitStr>()?;
384 syn::parse_str::<Ident>(&litstr.value()).map_err(|e| {
385 syn::Error::new(litstr.span(), format!("expected an identifier: {e}"))
386 })?
387 } else {
388 input.parse::<AnyIdent>()?.0
389 };
390 return Ok(BindgenAttr::$variant(attr_span, ident))
391 });
392
393 (@parser $variant:ident(Span, Option<String>)) => ({
394 if input.parse::<Token![=]>().is_ok() {
395 if input.peek(syn::LitStr) {
396 let litstr = input.parse::<syn::LitStr>()?;
397 return Ok(BindgenAttr::$variant(attr_span, Some(litstr.value())))
398 }
399
400 let ident = input.parse::<AnyIdent>()?.0;
401 return Ok(BindgenAttr::$variant(attr_span, Some(ident.to_string())))
402 } else {
403 return Ok(BindgenAttr::$variant(attr_span, None));
404 }
405 });
406
407 (@parser $variant:ident(Span, syn::Path)) => ({
408 input.parse::<Token![=]>()?;
409 let path = if input.peek(syn::LitStr) {
413 let litstr = input.parse::<syn::LitStr>()?;
414 syn::parse_str::<syn::Path>(&litstr.value()).map_err(|e| {
415 syn::Error::new(litstr.span(), format!("expected a path: {e}"))
416 })?
417 } else {
418 input.parse()?
419 };
420 return Ok(BindgenAttr::$variant(attr_span, path));
421 });
422
423 (@parser $variant:ident(Span, syn::Expr)) => ({
424 input.parse::<Token![=]>()?;
425 return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
426 });
427
428 (@parser $variant:ident(Span, String, Span)) => ({
429 input.parse::<Token![=]>()?;
430 let (val, span) = match input.parse::<syn::LitStr>() {
431 Ok(str) => (str.value(), str.span()),
432 Err(_) => {
433 let ident = input.parse::<AnyIdent>()?.0;
434 (ident.to_string(), ident.span())
435 }
436 };
437 return Ok(BindgenAttr::$variant(attr_span, val, span))
438 });
439
440 (@parser $variant:ident(Span, JsNamespace, Vec<Span>)) => ({
441 input.parse::<Token![=]>()?;
442 let (vals, spans) = match input.parse::<syn::ExprArray>() {
443 Ok(exprs) => {
444 let mut vals = vec![];
445 let mut spans = vec![];
446
447 for expr in exprs.elems.iter() {
448 if let syn::Expr::Lit(syn::ExprLit {
449 lit: syn::Lit::Str(ref str),
450 ..
451 }) = expr {
452 vals.push(str.value());
453 spans.push(str.span());
454 } else {
455 return Err(syn::Error::new(expr.span(), "expected string literals"));
456 }
457 }
458
459 if vals.is_empty() {
460 return Err(syn::Error::new(exprs.span(), "Empty namespace lists are not allowed."));
461 }
462
463 (vals, spans)
464 },
465 Err(_) => match input.parse::<syn::LitStr>() {
467 Ok(str) => (vec![str.value()], vec![str.span()]),
468 Err(_) => {
469 let ident = input.parse::<AnyIdent>()?.0;
470 (vec![ident.to_string()], vec![ident.span()])
471 }
472 }
473 };
474
475 let first = &vals[0];
476 if is_non_value_js_keyword(first) && first != "default" {
477 let msg = format!("Namespace cannot start with the JS keyword `{}`", first);
478 return Err(syn::Error::new(spans[0], msg));
479 }
480
481 return Ok(BindgenAttr::$variant(attr_span, JsNamespace(vals), spans))
482 });
483 }
484
485 attrgen!(parsers);
486
487 Err(original.error(if attr_string.starts_with('_') {
488 "unknown attribute: it's safe to remove unused attributes entirely."
489 } else {
490 "unknown attribute"
491 }))
492 }
493}
494
495pub(crate) fn is_computed_key(name: &str) -> bool {
499 let bytes = name.as_bytes();
500 bytes.len() >= 2 && bytes[0] == b'[' && bytes[bytes.len() - 1] == b']'
501}
502
503fn validate_computed_key(attr: &str, value: &str, span: Span) -> Result<(), Diagnostic> {
508 if !is_computed_key(value) {
509 return Ok(());
510 }
511 let inner = &value[1..value.len() - 1];
512 let symbol_name = inner.strip_prefix("Symbol.");
513 let ok = symbol_name
514 .map(|n| !n.is_empty() && n.chars().all(|c| c.is_ascii_alphanumeric() || c == '_'))
515 .unwrap_or(false);
516 if ok {
517 Ok(())
518 } else {
519 Err(Diagnostic::span_error(
520 span,
521 format!("the only computed-key form supported in `{attr}` is `\"[Symbol.<ident>]\"`"),
522 ))
523 }
524}
525
526struct AnyIdent(Ident);
527
528impl Parse for AnyIdent {
529 fn parse(input: ParseStream) -> SynResult<Self> {
530 input.step(|cursor| match cursor.ident() {
531 Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)),
532 None => Err(cursor.error("expected an identifier")),
533 })
534 }
535}
536
537pub(crate) trait ConvertToAst<Ctx> {
542 type Target;
544 fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
548}
549
550impl ConvertToAst<&ast::Program> for &mut syn::ItemStruct {
551 type Target = ast::Struct;
552
553 fn convert(self, program: &ast::Program) -> Result<Self::Target, Diagnostic> {
554 if !self.generics.params.is_empty() {
555 bail_span!(
556 self.generics,
557 "structs with #[wasm_bindgen] cannot have lifetime or \
558 type parameters currently"
559 );
560 }
561 let attrs = BindgenAttrs::find(&mut self.attrs)?;
562
563 let _ = attrs.wasm_bindgen();
565
566 let mut extends: Option<syn::Path> = None;
573 for (used, attr) in attrs.attrs.iter() {
574 if let BindgenAttr::Extends(span, path) = attr {
575 if extends.is_some() {
576 return Err(Diagnostic::span_error(
577 *span,
578 "`extends` may only be specified once on an exported struct",
579 ));
580 }
581 if path.is_ident(&self.ident) || path.is_ident("Self") {
582 return Err(Diagnostic::span_error(
583 *span,
584 "`extends = Self` (or naming the same struct) is not allowed; \
585 a class cannot inherit from itself",
586 ));
587 }
588 extends = Some(path.clone());
589 used.set(true);
590 }
591 }
592
593 let extends_js_class: Option<String> = attrs
600 .extends_js_class()
601 .map(|(s, _)| s.to_string())
602 .or_else(|| {
603 extends
604 .as_ref()
605 .and_then(|p| p.segments.last().map(|seg| seg.ident.unraw().to_string()))
606 });
607 let extends_js_namespace: Option<Vec<String>> =
608 attrs.extends_js_namespace().map(|(ns, _)| ns.0);
609 if extends_js_class.is_some() && extends.is_none() {
610 return Err(Diagnostic::span_error(
611 self.ident.span(),
612 "`extends_js_class` requires `extends = ...` to be set too \
613 (extends_js_class declares the parent's JS identity; \
614 extends declares the parent's Rust type)",
615 ));
616 }
617 if extends_js_namespace.is_some() && extends.is_none() {
618 return Err(Diagnostic::span_error(
619 self.ident.span(),
620 "`extends_js_namespace` requires `extends = ...` to be set too \
621 (extends_js_namespace declares the parent's JS namespace; \
622 extends declares the parent's Rust type)",
623 ));
624 }
625
626 let mut fields = Vec::new();
627 let js_name = attrs
628 .js_name_no_symbol("structs with #[wasm_bindgen]")?
629 .map(|s| s.to_string())
630 .unwrap_or(self.ident.unraw().to_string());
631 if is_js_keyword(&js_name) && js_name != "default" {
632 bail_span!(
633 self.ident,
634 "struct cannot use the JS keyword `{}` as its name",
635 js_name
636 );
637 }
638
639 let is_inspectable = attrs.inspectable().is_some();
640 let getter_with_clone = attrs.getter_with_clone();
641 let js_namespace = attrs.js_namespace().map(|(ns, _)| ns.0);
642 let qualified_name = wasm_bindgen_shared::qualified_name(js_namespace.as_deref(), &js_name);
643 let mut parent_count = 0usize;
644 for (i, field) in self.fields.iter_mut().enumerate() {
645 let field_attrs = BindgenAttrs::find(&mut field.attrs)?;
646 let is_parent = is_parent_wrapper_type(&field.ty);
651 if is_parent {
652 parent_count += 1;
653 }
654
655 let is_public = matches!(field.vis, syn::Visibility::Public(..));
656 if !is_public && !is_parent {
657 field_attrs.check_used();
658 continue;
659 }
660
661 let (js_field_name, member) = match &field.ident {
662 Some(ident) => (ident.unraw().to_string(), syn::Member::Named(ident.clone())),
663 None => (i.to_string(), syn::Member::Unnamed(i.into())),
664 };
665
666 if field_attrs.skip().is_some() && !is_parent {
667 field_attrs.check_used();
668 continue;
669 }
670
671 let js_field_name = match field_attrs.js_name() {
672 Some((name, _)) => name.to_string(),
673 None => js_field_name,
674 };
675
676 let comments = extract_doc_comments(&field.attrs);
677 let getter = wasm_bindgen_shared::struct_field_get(&qualified_name, &js_field_name);
678 let setter = wasm_bindgen_shared::struct_field_set(&qualified_name, &js_field_name);
679
680 fields.push(ast::StructField {
681 rust_name: member,
682 js_name: js_field_name,
683 struct_name: self.ident.clone(),
684 readonly: field_attrs.readonly().is_some(),
685 ty: field.ty.clone(),
686 getter: Ident::new(&getter, Span::call_site()),
687 setter: Ident::new(&setter, Span::call_site()),
688 comments,
689 generate_typescript: field_attrs.skip_typescript().is_none(),
690 generate_jsdoc: field_attrs.skip_jsdoc().is_none(),
691 getter_with_clone: field_attrs
692 .getter_with_clone()
693 .or(getter_with_clone)
694 .copied(),
695 is_parent,
696 wasm_bindgen: program.wasm_bindgen.clone(),
697 });
698 field_attrs.check_used();
699 }
700
701 match (&extends, parent_count) {
707 (Some(_), 1) => {}
708 (None, 0) => {}
709 _ => {
710 return Err(Diagnostic::span_error(
711 self.ident.span(),
712 "internal error: inconsistent Parent<T> field state; \
713 this should have been caught by inject_parent_field",
714 ));
715 }
716 }
717
718 let generate_typescript = attrs.skip_typescript().is_none();
719 let private = attrs.private().is_some();
720 let comments: Vec<String> = extract_doc_comments(&self.attrs);
721 attrs.check_used();
722 Ok(ast::Struct {
723 rust_name: self.ident.clone(),
724 js_name,
725 qualified_name,
726 fields,
727 comments,
728 is_inspectable,
729 generate_typescript,
730 private,
731 js_namespace,
732 extends,
733 extends_js_class,
734 extends_js_namespace,
735 wasm_bindgen: program.wasm_bindgen.clone(),
736 })
737 }
738}
739
740fn get_ty(mut ty: &syn::Type) -> &syn::Type {
741 while let syn::Type::Group(g) = ty {
742 ty = &g.elem;
743 }
744
745 ty
746}
747
748pub fn inject_parent_field(
756 s: &mut syn::ItemStruct,
757 extends_path: Option<&syn::Path>,
758 wasm_bindgen: &syn::Path,
759) -> Result<(), Diagnostic> {
760 for field in s.fields.iter() {
761 if is_parent_wrapper_type(&field.ty) {
762 let path_str = path_to_string(field_type_path(&field.ty));
763 let msg = if extends_path.is_some() {
764 format!(
765 "do not declare a `{path_str}` field; the \
766 `#[wasm_bindgen(extends = ...)]` macro injects \
767 `parent: wasm_bindgen::Parent<...>` automatically"
768 )
769 } else {
770 format!(
771 "`{path_str}` looks like `wasm_bindgen::Parent<T>`, \
772 which is reserved for the \
773 `#[wasm_bindgen(extends = ...)]` macro. If you intended \
774 a different `Parent` type, qualify it (e.g. \
775 `my_crate::tree::Parent`) so this check skips it."
776 )
777 };
778 bail_span!(&field.ty, "{}", msg);
779 }
780 }
781
782 let Some(parent_path) = extends_path else {
783 return Ok(());
784 };
785
786 for field in s.fields.iter() {
787 if let Some(ident) = &field.ident {
788 if ident == "parent" {
789 bail_span!(
790 ident,
791 "struct with `#[wasm_bindgen(extends = ...)]` cannot \
792 have a field named `parent`; the macro injects one"
793 );
794 }
795 }
796 }
797
798 let injected: syn::Field = syn::parse_quote! {
799 parent: #wasm_bindgen::Parent<#parent_path>
800 };
801
802 match &mut s.fields {
803 syn::Fields::Named(named) => {
804 named.named.insert(0, injected);
805 }
806 syn::Fields::Unit => {
807 let mut named = syn::FieldsNamed {
808 brace_token: Default::default(),
809 named: syn::punctuated::Punctuated::new(),
810 };
811 named.named.push(injected);
812 s.fields = syn::Fields::Named(named);
813 s.semi_token = None;
814 }
815 syn::Fields::Unnamed(_) => {
816 bail_span!(
817 &s.ident,
818 "tuple structs cannot use `#[wasm_bindgen(extends = ...)]`; \
819 use a named-field struct"
820 );
821 }
822 }
823
824 Ok(())
825}
826
827fn is_parent_wrapper_type(ty: &syn::Type) -> bool {
841 let path = match field_type_path(ty) {
842 Some(p) => p,
843 None => return false,
844 };
845 let last = match path.segments.last() {
846 Some(seg) => seg,
847 None => return false,
848 };
849 if last.ident != "Parent" {
850 return false;
851 }
852 let one_type_arg = match &last.arguments {
853 syn::PathArguments::AngleBracketed(args) => {
854 args.args
855 .iter()
856 .filter(|a| matches!(a, syn::GenericArgument::Type(_)))
857 .count()
858 == 1
859 }
860 _ => false,
861 };
862 if !one_type_arg {
863 return false;
864 }
865 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
867 let leading_colon = path.leading_colon.is_some();
868 match (leading_colon, segs.as_slice()) {
869 (false, [only]) if only == "Parent" => true,
870 (false, [first, last]) if first == "wasm_bindgen" && last == "Parent" => true,
871 (true, [first, last]) if first == "wasm_bindgen" && last == "Parent" => true,
872 (false, [first, last]) if first == "crate" && last == "Parent" => true,
873 _ => false,
874 }
875}
876
877fn field_type_path(ty: &syn::Type) -> Option<&syn::Path> {
879 match get_ty(ty) {
880 syn::Type::Path(p) => Some(&p.path),
881 _ => None,
882 }
883}
884
885fn path_to_string(path: Option<&syn::Path>) -> String {
887 let Some(path) = path else {
888 return String::from("<unknown>");
889 };
890 let mut out = String::new();
891 if path.leading_colon.is_some() {
892 out.push_str("::");
893 }
894 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
895 out.push_str(&segs.join("::"));
896 out
897}
898
899fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
900 while let syn::Expr::Group(g) = expr {
901 expr = &g.expr;
902 }
903
904 expr
905}
906
907impl<'a>
908 ConvertToAst<(
909 &ast::Program,
910 BindgenAttrs,
911 &'a Option<ast::ImportModule>,
912 bool,
913 )> for syn::ForeignItemFn
914{
915 type Target = ast::ImportKind;
916
917 fn convert(
918 mut self,
919 (program, opts, module, block_slice_to_array): (
920 &ast::Program,
921 BindgenAttrs,
922 &'a Option<ast::ImportModule>,
923 bool,
924 ),
925 ) -> Result<Self::Target, Diagnostic> {
926 let fn_slice_to_array = block_slice_to_array || opts.slice_to_array().is_some();
932 let args_attrs = extract_args_attrs(&mut self.sig, fn_slice_to_array)?;
933 let (mut wasm, _) = function_from_decl(
934 &self.sig.ident,
935 &opts,
936 self.sig.clone(),
937 self.attrs.clone(),
938 self.vis.clone(),
939 FunctionPosition::Extern,
940 Some(args_attrs),
941 )?;
942 let catch = opts.catch().is_some();
943 let variadic = opts.variadic().is_some();
944 let js_ret = if catch {
945 extract_first_ty_param(wasm.ret.as_ref().map(|ret| &ret.r#type))?
953 } else {
954 wasm.ret.as_ref().map(|ret| ret.r#type.clone())
955 };
956
957 let operation_kind = operation_kind(&opts);
958
959 let kind = if opts.method().is_some() {
960 let class = wasm.arguments.first().ok_or_else(|| {
961 err_span!(self, "imported methods must have at least one argument")
962 })?;
963 let class = match get_ty(&class.pat_type.ty) {
964 syn::Type::Reference(syn::TypeReference {
965 mutability: None,
966 elem,
967 ..
968 }) => &**elem,
969 _ => bail_span!(
970 class.pat_type.ty,
971 "first argument of method must be a shared reference"
972 ),
973 };
974 let class_ty = get_ty(class);
975 let js_class = opts.js_class().map(|p| p.0.to_string());
976 let kind = ast::MethodKind::Operation(ast::Operation {
977 is_static: false,
978 kind: operation_kind,
979 });
980
981 let class_name = match class_ty {
982 syn::Type::Path(syn::TypePath {
983 qself: None,
984 ref path,
985 }) => path,
986 _ => bail_span!(class_ty, "first argument of method must be a path"),
987 };
988
989 let class_name_str = js_class.map(Ok).unwrap_or_else(|| {
990 extract_path_ident(class_name, true).map(|i| i.unraw().to_string())
991 })?;
992
993 ast::ImportFunctionKind::Method {
994 class: class_name_str,
995 ty: class_ty.clone(),
996 kind,
997 }
998 } else if let Some(cls) = opts.static_method_of() {
999 let class = opts
1000 .js_class()
1001 .map(|p| p.0.into())
1002 .unwrap_or_else(|| cls.unraw().to_string());
1003
1004 let ty = syn::Type::Path(syn::TypePath {
1005 qself: None,
1006 path: syn::Path {
1007 leading_colon: None,
1008 segments: std::iter::once(syn::PathSegment {
1009 ident: cls.clone(),
1010 arguments: syn::PathArguments::None,
1011 })
1012 .collect(),
1013 },
1014 });
1015
1016 let kind = ast::MethodKind::Operation(ast::Operation {
1017 is_static: true,
1018 kind: operation_kind,
1019 });
1020
1021 ast::ImportFunctionKind::Method { class, ty, kind }
1022 } else if opts.constructor().is_some() {
1023 let class = match js_ret {
1024 Some(ref ty) => ty,
1025 _ => bail_span!(self, "constructor returns must be bare types"),
1026 };
1027 let class_name = match get_ty(class) {
1028 syn::Type::Path(syn::TypePath {
1029 qself: None,
1030 ref path,
1031 }) => path,
1032 _ => bail_span!(self, "return value of constructor must be a bare path"),
1033 };
1034 let class_name = extract_path_ident(class_name, true)?;
1035 let class_name = opts
1036 .js_class()
1037 .map(|p| p.0.into())
1038 .unwrap_or_else(|| class_name.unraw().to_string());
1039
1040 ast::ImportFunctionKind::Method {
1041 class: class_name,
1042 ty: class.clone(),
1043 kind: ast::MethodKind::Constructor,
1044 }
1045 } else {
1046 ast::ImportFunctionKind::Normal
1047 };
1048
1049 if opts.reexport().is_some() && matches!(kind, ast::ImportFunctionKind::Method { .. }) {
1051 return Err(Diagnostic::span_error(
1052 self.sig.ident.span(),
1053 "`reexport` cannot be used on methods, constructors, or static methods. \
1054 Use `reexport` on the type import instead.",
1055 ));
1056 }
1057
1058 let shim = {
1059 let ns = match kind {
1060 ast::ImportFunctionKind::Normal => (0, "n"),
1061 ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
1062 };
1063 let cfg_attrs: String = self
1066 .attrs
1067 .iter()
1068 .filter(|attr| attr.path().is_ident("cfg"))
1069 .map(|attr| attr.to_token_stream().to_string())
1070 .collect();
1071 let data = (
1072 ns,
1073 self.sig.to_token_stream().to_string(),
1074 module,
1075 cfg_attrs,
1076 );
1077 format!(
1078 "__wbg_{}_{}",
1079 wasm.name
1080 .chars()
1081 .filter(|&c| c.is_ascii_alphanumeric() || c == '_')
1082 .collect::<String>(),
1083 ShortHash(data)
1084 )
1085 };
1086 if let Some(span) = opts.r#final() {
1087 if opts.structural().is_some() {
1088 let msg = "cannot specify both `structural` and `final`";
1089 return Err(Diagnostic::span_error(*span, msg));
1090 }
1091 }
1092 let assert_no_shim = opts.assert_no_shim().is_some();
1093
1094 let mut doc_comment = String::new();
1095 wasm.rust_attrs.retain(|attr| {
1097 fn get_docs(attr: &syn::Attribute) -> Option<String> {
1100 if attr.path().is_ident("doc") {
1101 if let syn::Meta::NameValue(syn::MetaNameValue {
1102 value:
1103 syn::Expr::Lit(syn::ExprLit {
1104 lit: Lit::Str(str), ..
1105 }),
1106 ..
1107 }) = &attr.meta
1108 {
1109 Some(str.value())
1110 } else {
1111 None
1112 }
1113 } else {
1114 None
1115 }
1116 }
1117
1118 if let Some(docs) = get_docs(attr) {
1119 if !doc_comment.is_empty() {
1120 doc_comment.push('\n');
1122 }
1123 doc_comment.push_str(&docs);
1125
1126 false
1128 } else {
1129 true
1130 }
1131 });
1132
1133 validate_generics(&self.sig.generics)?;
1134
1135 let ret = ast::ImportKind::Function(ast::ImportFunction {
1136 function: wasm,
1137 assert_no_shim,
1138 kind,
1139 js_ret,
1140 catch,
1141 variadic,
1142 structural: opts.structural().is_some() || opts.r#final().is_none(),
1143 rust_name: self.sig.ident,
1144 shim: Ident::new(&shim, Span::call_site()),
1145 doc_comment,
1146 wasm_bindgen: program.wasm_bindgen.clone(),
1147 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1148 js_sys: program.js_sys.clone(),
1149 generics: self.sig.generics,
1150 });
1151 opts.check_used();
1152
1153 Ok(ret)
1154 }
1155}
1156
1157impl ConvertToAst<(&ast::Program, BindgenAttrs)> for syn::ForeignItemType {
1158 type Target = ast::ImportKind;
1159
1160 fn convert(
1161 self,
1162 (program, attrs): (&ast::Program, BindgenAttrs),
1163 ) -> Result<Self::Target, Diagnostic> {
1164 let js_name = attrs
1165 .js_name_no_symbol("extern types with #[wasm_bindgen]")?
1166 .map_or_else(|| self.ident.unraw().to_string(), |s| s.to_string());
1167 let typescript_type = attrs.typescript_type().map(|s| s.0.to_string());
1168 let is_type_of = attrs.is_type_of().cloned();
1169 let unraw_ident = self.ident.unraw();
1170 let hash = ShortHash((attrs.js_namespace().map(|(ns, _)| ns.0), &unraw_ident));
1171 let shim = format!("__wbg_instanceof_{unraw_ident}_{hash}");
1172 let mut extends = Vec::new();
1173 let mut vendor_prefixes = Vec::new();
1174 let no_deref = attrs.no_deref().is_some();
1175 let no_upcast = attrs.no_upcast().is_some();
1176 let no_promising = attrs.no_promising().is_some();
1177 let no_into_js_generic = attrs.no_into_js_generic().is_some();
1178 for (used, attr) in attrs.attrs.iter() {
1179 match attr {
1180 BindgenAttr::Extends(_, e) => {
1181 extends.push(e.clone());
1182 used.set(true);
1183 }
1184 BindgenAttr::VendorPrefix(_, e) => {
1185 vendor_prefixes.push(e.clone());
1186 used.set(true);
1187 }
1188 _ => {}
1189 }
1190 }
1191
1192 attrs.check_used();
1193 validate_generics(&self.generics)?;
1194
1195 let mut generics = None;
1198 for (n, param) in self.generics.type_params().enumerate() {
1199 if param.default.is_none() {
1200 let generics = generics.get_or_insert_with(|| self.generics.clone());
1201 let type_param_mut = generics.type_params_mut().nth(n).unwrap();
1202 type_param_mut.default = Some(syn::parse_quote! { JsValue });
1203 }
1204 }
1205
1206 Ok(ast::ImportKind::Type(ast::ImportType {
1207 vis: self.vis,
1208 attrs: self.attrs,
1209 doc_comment: None,
1210 instanceof_shim: shim,
1211 is_type_of,
1212 rust_name: self.ident,
1213 typescript_type,
1214 js_name,
1215 extends,
1216 vendor_prefixes,
1217 no_deref,
1218 no_upcast,
1219 no_promising,
1220 no_into_js_generic,
1221 wasm_bindgen: program.wasm_bindgen.clone(),
1222 generics: generics.unwrap_or(self.generics),
1223 }))
1224 }
1225}
1226
1227impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
1228 for syn::ForeignItemStatic
1229{
1230 type Target = ast::ImportKind;
1231
1232 fn convert(
1233 self,
1234 (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
1235 ) -> Result<Self::Target, Diagnostic> {
1236 if let syn::StaticMutability::Mut(_) = self.mutability {
1237 bail_span!(self.mutability, "cannot import mutable globals yet")
1238 }
1239
1240 if let Some(span) = opts.static_string() {
1241 return Err(Diagnostic::span_error(
1242 *span,
1243 "static strings require a string literal",
1244 ));
1245 }
1246
1247 let default_name = self.ident.unraw().to_string();
1248 let js_name = opts
1249 .js_name_no_symbol("statics with #[wasm_bindgen]")?
1250 .unwrap_or(&default_name)
1251 .to_string();
1252 let unraw_ident = self.ident.unraw();
1253 let hash = ShortHash((&js_name, module, &unraw_ident));
1254 let shim = format!("__wbg_static_accessor_{unraw_ident}_{hash}");
1255 let thread_local = opts.get_thread_local()?;
1256
1257 opts.check_used();
1258 Ok(ast::ImportKind::Static(ast::ImportStatic {
1259 ty: *self.ty,
1260 vis: self.vis,
1261 rust_name: self.ident.clone(),
1262 js_name,
1263 shim: Ident::new(&shim, Span::call_site()),
1264 wasm_bindgen: program.wasm_bindgen.clone(),
1265 thread_local,
1266 }))
1267 }
1268}
1269
1270impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
1271 for syn::ItemStatic
1272{
1273 type Target = ast::ImportKind;
1274
1275 fn convert(
1276 self,
1277 (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
1278 ) -> Result<Self::Target, Diagnostic> {
1279 if let syn::StaticMutability::Mut(_) = self.mutability {
1280 bail_span!(self.mutability, "cannot import mutable globals yet")
1281 }
1282
1283 let string = if let syn::Expr::Lit(syn::ExprLit {
1284 lit: syn::Lit::Str(string),
1285 ..
1286 }) = *self.expr.clone()
1287 {
1288 string.value()
1289 } else {
1290 bail_span!(
1291 self.expr,
1292 "statics with a value can only be string literals"
1293 )
1294 };
1295
1296 if opts.static_string().is_none() {
1297 bail_span!(
1298 self,
1299 "static strings require `#[wasm_bindgen(static_string)]`"
1300 )
1301 }
1302
1303 let thread_local = if let Some(thread_local) = opts.get_thread_local()? {
1304 thread_local
1305 } else {
1306 bail_span!(
1307 self,
1308 "static strings require `#[wasm_bindgen(thread_local_v2)]`"
1309 )
1310 };
1311
1312 let unraw_ident = self.ident.unraw();
1313 let hash = ShortHash((&module, &unraw_ident));
1314 let shim = format!("__wbg_string_{unraw_ident}_{hash}");
1315 opts.check_used();
1316 Ok(ast::ImportKind::String(ast::ImportString {
1317 ty: *self.ty,
1318 vis: self.vis,
1319 rust_name: self.ident.clone(),
1320 shim: Ident::new(&shim, Span::call_site()),
1321 wasm_bindgen: program.wasm_bindgen.clone(),
1322 js_sys: program.js_sys.clone(),
1323 string,
1324 thread_local,
1325 }))
1326 }
1327}
1328
1329impl ConvertToAst<(BindgenAttrs, Vec<FnArgAttrs>)> for syn::ItemFn {
1330 type Target = ast::Function;
1331
1332 fn convert(
1333 self,
1334 (attrs, args_attrs): (BindgenAttrs, Vec<FnArgAttrs>),
1335 ) -> Result<Self::Target, Diagnostic> {
1336 match self.vis {
1337 syn::Visibility::Public(_) => {}
1338 _ if attrs.start().is_some() => {}
1339 _ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
1340 }
1341 if self.sig.constness.is_some() {
1342 bail_span!(
1343 self.sig.constness,
1344 "can only #[wasm_bindgen] non-const functions"
1345 );
1346 }
1347
1348 let (mut ret, _) = function_from_decl(
1349 &self.sig.ident,
1350 &attrs,
1351 self.sig.clone(),
1352 self.attrs,
1353 self.vis,
1354 FunctionPosition::Free,
1355 Some(args_attrs),
1356 )?;
1357 attrs.check_used();
1358
1359 if is_js_keyword(&ret.name) && ret.name != "default" {
1363 ret.name = format!("_{}", ret.name);
1364 }
1365
1366 Ok(ret)
1367 }
1368}
1369
1370fn get_self_method(r: syn::Receiver) -> ast::MethodSelf {
1372 match &*r.ty {
1379 syn::Type::Reference(ty) => {
1380 if ty.mutability.is_some() {
1381 ast::MethodSelf::RefMutable
1382 } else {
1383 ast::MethodSelf::RefShared
1384 }
1385 }
1386 _ => ast::MethodSelf::ByValue,
1387 }
1388}
1389
1390enum FunctionPosition<'a> {
1391 Extern,
1392 Free,
1393 Impl { self_ty: &'a Ident },
1394}
1395
1396#[allow(clippy::too_many_arguments)]
1398fn function_from_decl(
1399 decl_name: &syn::Ident,
1400 opts: &BindgenAttrs,
1401 sig: syn::Signature,
1402 attrs: Vec<syn::Attribute>,
1403 vis: syn::Visibility,
1404 position: FunctionPosition,
1405 args_attrs: Option<Vec<FnArgAttrs>>,
1406) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> {
1407 if sig.variadic.is_some() {
1408 bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions");
1409 }
1410
1411 if !matches!(position, FunctionPosition::Extern) && !sig.generics.params.is_empty() {
1413 bail_span!(
1414 sig.generics,
1415 "can't #[wasm_bindgen] functions with lifetime or type parameters"
1416 );
1417 }
1418
1419 let syn::Signature { inputs, output, .. } = sig;
1420
1421 let replace_self = |mut t: syn::Type| {
1426 if let FunctionPosition::Impl { self_ty } = position {
1427 struct SelfReplace(Ident);
1432 impl VisitMut for SelfReplace {
1433 fn visit_ident_mut(&mut self, i: &mut proc_macro2::Ident) {
1434 if i == "Self" {
1435 *i = self.0.clone();
1436 }
1437 }
1438 }
1439
1440 let mut replace = SelfReplace(self_ty.clone());
1441 replace.visit_type_mut(&mut t);
1442 }
1443 t
1444 };
1445
1446 let replace_colliding_arg = |i: &mut syn::PatType| {
1449 if let syn::Pat::Ident(ref mut i) = *i.pat {
1450 let ident = i.ident.unraw().to_string();
1451 if is_js_keyword(&ident) {
1455 i.ident = Ident::new(format!("_{ident}").as_str(), i.ident.span());
1456 }
1457 }
1458 };
1459
1460 let mut method_self = None;
1461 let mut arguments = Vec::new();
1462 for arg in inputs.into_iter() {
1463 match arg {
1464 syn::FnArg::Typed(mut c) => {
1465 replace_colliding_arg(&mut c);
1467 *c.ty = replace_self(*c.ty);
1468 arguments.push(c);
1469 }
1470 syn::FnArg::Receiver(r) => {
1471 match position {
1475 FunctionPosition::Free => {
1476 bail_span!(
1477 r.self_token,
1478 "the `self` argument is only allowed for functions in `impl` blocks.\n\n\
1479 If the function is already in an `impl` block, did you perhaps forget to add `#[wasm_bindgen]` to the `impl` block?"
1480 );
1481 }
1482 FunctionPosition::Extern => {
1483 bail_span!(
1484 r.self_token,
1485 "the `self` argument is not allowed for `extern` functions.\n\n\
1486 Did you perhaps mean `this`? For more information on importing JavaScript functions, see:\n\
1487 https://wasm-bindgen.github.io/wasm-bindgen/examples/import-js.html"
1488 );
1489 }
1490 FunctionPosition::Impl { .. } => {}
1491 }
1492
1493 assert!(method_self.is_none());
1496 method_self = Some(get_self_method(r));
1497 }
1498 }
1499 }
1500
1501 let ret_ty_override = opts.unchecked_return_type();
1503 let ret_desc = opts.return_description();
1504 let ret = match output {
1505 syn::ReturnType::Default => None,
1506 syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData {
1507 r#type: replace_self(*ty),
1508 js_type: ret_ty_override
1509 .as_ref()
1510 .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(ty, span)| {
1511 check_invalid_type(ty, *span)?;
1512 Ok(Some(ty.to_string()))
1513 })?,
1514 desc: ret_desc.as_ref().map_or::<Result<_, Diagnostic>, _>(
1515 Ok(None),
1516 |(desc, span)| {
1517 check_js_comment_close(desc, *span)?;
1518 Ok(Some(desc.to_string()))
1519 },
1520 )?,
1521 }),
1522 };
1523 if ret.is_none() && (ret_ty_override.is_some() || ret_desc.is_some()) {
1526 if let Some((_, span)) = ret_ty_override {
1527 return Err(Diagnostic::span_error(
1528 span,
1529 "cannot specify return type for a function that doesn't return",
1530 ));
1531 }
1532 if let Some((_, span)) = ret_desc {
1533 return Err(Diagnostic::span_error(
1534 span,
1535 "cannot specify return description for a function that doesn't return",
1536 ));
1537 }
1538 }
1539
1540 let (name, name_span) = if let Some((js_name, js_name_span)) = opts.js_name() {
1541 if is_computed_key(js_name) && matches!(position, FunctionPosition::Free) {
1546 return Err(Diagnostic::span_error(
1547 js_name_span,
1548 "free functions with #[wasm_bindgen] do not support symbols in js_name",
1549 ));
1550 }
1551 let kind = operation_kind(opts);
1552 let prefix = match kind {
1553 OperationKind::Setter(_) => "set_",
1554 _ => "",
1555 };
1556 (format!("{prefix}{js_name}"), js_name_span)
1557 } else {
1558 (decl_name.unraw().to_string(), decl_name.span())
1559 };
1560
1561 Ok((
1562 ast::Function {
1563 name_span,
1564 name,
1565 rust_attrs: attrs,
1566 rust_vis: vis,
1567 r#unsafe: sig.unsafety.is_some(),
1568 r#async: sig.asyncness.is_some(),
1569 generate_typescript: opts.skip_typescript().is_none(),
1570 generate_jsdoc: opts.skip_jsdoc().is_none(),
1571 variadic: opts.variadic().is_some(),
1572 ret,
1573 arguments: arguments
1574 .into_iter()
1575 .zip(
1576 args_attrs
1577 .into_iter()
1578 .flatten()
1579 .chain(iter::repeat(FnArgAttrs::default())),
1580 )
1581 .map(|(pat_type, attrs)| ast::FunctionArgumentData {
1582 pat_type,
1583 js_name: attrs.js_name,
1584 js_type: attrs.js_type,
1585 optional: attrs.optional,
1586 desc: attrs.desc,
1587 slice_to_array: attrs.slice_to_array,
1588 })
1589 .collect(),
1590 },
1591 method_self,
1592 ))
1593}
1594
1595#[derive(Default, Clone)]
1597struct FnArgAttrs {
1598 js_name: Option<String>,
1599 js_type: Option<String>,
1600 optional: bool,
1601 desc: Option<String>,
1602 slice_to_array: bool,
1608}
1609
1610fn extract_args_attrs(
1614 sig: &mut syn::Signature,
1615 default_slice_to_array: bool,
1616) -> Result<Vec<FnArgAttrs>, Diagnostic> {
1617 let mut args_attrs = vec![];
1618 let mut seen_optional: Option<Span> = None;
1619 for input in sig.inputs.iter_mut() {
1620 if let syn::FnArg::Typed(pat_type) = input {
1621 let attrs = BindgenAttrs::find(&mut pat_type.attrs)?;
1622
1623 let param_type = attrs.unchecked_param_type();
1625 let optional_param_type = attrs.unchecked_optional_param_type();
1626
1627 if param_type.is_some() && optional_param_type.is_some() {
1628 let mut param_pos_and_span: Option<(usize, Span)> = None;
1630 let mut optional_pos_and_span: Option<(usize, Span)> = None;
1631 for (pos, (_, attr)) in attrs.attrs.iter().enumerate() {
1632 match attr {
1633 BindgenAttr::ParamType(span, _, _) => {
1634 param_pos_and_span = Some((pos, *span));
1635 }
1636 BindgenAttr::OptionalParamType(span, _, _) => {
1637 optional_pos_and_span = Some((pos, *span));
1638 }
1639 _ => {}
1640 }
1641 }
1642 let error_span = match (param_pos_and_span, optional_pos_and_span) {
1644 (Some((p_pos, p_span)), Some((o_pos, o_span))) => {
1645 if p_pos > o_pos {
1646 p_span
1647 } else {
1648 o_span
1649 }
1650 }
1651 (Some((_, p_span)), None) => p_span,
1652 (None, Some((_, o_span))) => o_span,
1653 (None, None) => unreachable!(
1654 "both param_type and optional_param_type are Some, but attrs not found"
1655 ),
1656 };
1657 return Err(Diagnostic::span_error(
1658 error_span,
1659 "cannot use both `unchecked_param_type` and `unchecked_optional_param_type` on the same parameter",
1660 ));
1661 }
1662
1663 let js_type = param_type
1665 .or(optional_param_type)
1666 .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(ty, span)| {
1667 check_invalid_type(ty, span)?;
1668 Ok(Some(ty.to_string()))
1669 })?;
1670
1671 let is_optional = optional_param_type.is_some();
1672
1673 if let Some(optional_span) = seen_optional {
1675 if !is_optional {
1676 return Err(Diagnostic::span_error(
1677 optional_span,
1678 "a required parameter cannot follow an optional parameter",
1679 ));
1680 }
1681 }
1682 if is_optional {
1683 if let Some((_, span)) = optional_param_type {
1684 seen_optional = Some(span);
1685 }
1686 }
1687
1688 let arg_attrs = FnArgAttrs {
1689 js_name: attrs
1690 .js_name()
1691 .map_or(Ok(None), |(js_name_override, span)| {
1692 if is_computed_key(js_name_override) {
1693 return Err(Diagnostic::span_error(
1694 span,
1695 "function arguments do not support symbols in js_name",
1696 ));
1697 }
1698 if is_js_keyword(js_name_override) || !is_valid_ident(js_name_override) {
1699 return Err(Diagnostic::span_error(span, "invalid JS identifier"));
1700 }
1701 Ok(Some(js_name_override.to_string()))
1702 })?,
1703 js_type,
1704 optional: is_optional,
1705 desc: attrs
1706 .param_description()
1707 .map_or::<Result<_, Diagnostic>, _>(Ok(None), |(description, span)| {
1708 check_js_comment_close(description, span)?;
1709 Ok(Some(description.to_string()))
1710 })?,
1711 slice_to_array: default_slice_to_array || attrs.slice_to_array().is_some(),
1712 };
1713 attrs.enforce_used()?;
1715 args_attrs.push(arg_attrs);
1716 }
1717 }
1718 Ok(args_attrs)
1719}
1720
1721pub(crate) trait MacroParse<Ctx> {
1722 fn macro_parse(self, program: &mut ast::Program, context: Ctx) -> Result<(), Diagnostic>;
1727}
1728
1729impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
1730 fn macro_parse(
1731 self,
1732 program: &mut ast::Program,
1733 (opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
1734 ) -> Result<(), Diagnostic> {
1735 match self {
1736 syn::Item::Fn(mut f) => {
1737 let opts = opts.unwrap_or_default();
1738 if let Some(path) = opts.wasm_bindgen() {
1739 program.wasm_bindgen = path.clone();
1740 }
1741 if let Some(path) = opts.js_sys() {
1742 program.js_sys = path.clone();
1743 }
1744 if let Some(path) = opts.wasm_bindgen_futures() {
1745 program.wasm_bindgen_futures = path.clone();
1746 }
1747
1748 if opts.main().is_some() {
1749 opts.check_used();
1750 return main(program, f, tokens);
1751 }
1752
1753 let no_mangle = f
1754 .attrs
1755 .iter()
1756 .enumerate()
1757 .find(|(_, m)| m.path().is_ident("no_mangle"));
1758 if let Some((i, _)) = no_mangle {
1759 f.attrs.remove(i);
1760 }
1761 let args_attrs = extract_args_attrs(&mut f.sig, false)?;
1764 let comments = extract_doc_comments(&f.attrs);
1765 tokens.extend(quote::quote! { #[allow(dead_code)] });
1769 f.to_tokens(tokens);
1770 if opts.start().is_some() {
1771 if !f.sig.generics.params.is_empty() {
1772 bail_span!(&f.sig.generics, "the start function cannot have generics",);
1773 }
1774 if !f.sig.inputs.is_empty() {
1775 bail_span!(&f.sig.inputs, "the start function cannot have arguments",);
1776 }
1777 }
1778 let method_kind = ast::MethodKind::Operation(ast::Operation {
1779 is_static: true,
1780 kind: operation_kind(&opts),
1781 });
1782 let rust_name = f.sig.ident.clone();
1783 let start = if opts.start().is_some() {
1784 if opts.private().is_some() {
1785 ast::StartKind::Private
1786 } else {
1787 ast::StartKind::Public
1788 }
1789 } else {
1790 ast::StartKind::None
1791 };
1792
1793 if opts.this().is_some() && f.sig.inputs.is_empty() {
1794 bail_span!(
1795 &f.sig.inputs,
1796 "functions taking a 'this' argument must have at least one parameter"
1797 );
1798 }
1799
1800 let js_namespace = opts.js_namespace().map(|(ns, _)| ns.0);
1801 program.exports.push(ast::Export {
1802 comments,
1803 function: f.convert((opts, args_attrs))?,
1804 js_class: None,
1805 js_namespace,
1806 method_kind,
1807 method_self: None,
1808 rust_class: None,
1809 rust_name,
1810 start,
1811 wasm_bindgen: program.wasm_bindgen.clone(),
1812 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1813 js_sys: program.js_sys.clone(),
1814 });
1815 }
1816 syn::Item::Impl(mut i) => {
1817 let opts = opts.unwrap_or_default();
1818 (&mut i).macro_parse(program, opts)?;
1819 i.to_tokens(tokens);
1820 }
1821 syn::Item::ForeignMod(mut f) => {
1822 let opts = match opts {
1823 Some(opts) => opts,
1824 None => BindgenAttrs::find(&mut f.attrs)?,
1825 };
1826 f.macro_parse(program, opts)?;
1827 }
1828 syn::Item::Enum(mut e) => {
1829 let opts = match opts {
1830 Some(opts) => opts,
1831 None => BindgenAttrs::find(&mut e.attrs)?,
1832 };
1833 e.macro_parse(program, (tokens, opts))?;
1834 }
1835 syn::Item::Const(mut c) => {
1836 let opts = match opts {
1837 Some(opts) => opts,
1838 None => BindgenAttrs::find(&mut c.attrs)?,
1839 };
1840 c.macro_parse(program, opts)?;
1841 }
1842 _ => {
1843 bail_span!(
1844 self,
1845 "#[wasm_bindgen] can only be applied to a function, \
1846 struct, enum, impl, or extern block",
1847 );
1848 }
1849 }
1850
1851 Ok(())
1852 }
1853}
1854
1855impl MacroParse<BindgenAttrs> for &mut syn::ItemImpl {
1856 fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1857 if self.defaultness.is_some() {
1858 bail_span!(
1859 self.defaultness,
1860 "#[wasm_bindgen] default impls are not supported"
1861 );
1862 }
1863 if self.unsafety.is_some() {
1864 bail_span!(
1865 self.unsafety,
1866 "#[wasm_bindgen] unsafe impls are not supported"
1867 );
1868 }
1869 if let Some((_, path, _)) = &self.trait_ {
1870 bail_span!(path, "#[wasm_bindgen] trait impls are not supported");
1871 }
1872 if !self.generics.params.is_empty() {
1873 bail_span!(
1874 self.generics,
1875 "#[wasm_bindgen] generic impls aren't supported"
1876 );
1877 }
1878 let name = match get_ty(&self.self_ty) {
1879 syn::Type::Path(syn::TypePath {
1880 qself: None,
1881 ref path,
1882 }) => path,
1883 _ => bail_span!(
1884 self.self_ty,
1885 "unsupported self type in #[wasm_bindgen] impl"
1886 ),
1887 };
1888 let mut errors = Vec::new();
1889 for item in self.items.iter_mut() {
1890 if let Err(e) = prepare_for_impl_recursion(item, name, program, &opts) {
1891 errors.push(e);
1892 }
1893 }
1894 Diagnostic::from_vec(errors)?;
1895 opts.check_used();
1896 Ok(())
1897 }
1898}
1899
1900fn prepare_for_impl_recursion(
1909 item: &mut syn::ImplItem,
1910 class: &syn::Path,
1911 program: &ast::Program,
1912 impl_opts: &BindgenAttrs,
1913) -> Result<(), Diagnostic> {
1914 let method = match item {
1915 syn::ImplItem::Fn(m) => m,
1916 syn::ImplItem::Const(_) => {
1917 bail_span!(
1918 &*item,
1919 "const definitions aren't supported with #[wasm_bindgen]"
1920 );
1921 }
1922 syn::ImplItem::Type(_) => bail_span!(
1923 &*item,
1924 "type definitions in impls aren't supported with #[wasm_bindgen]"
1925 ),
1926 syn::ImplItem::Macro(_) => {
1927 bail_span!(&*item, "macros in impls aren't supported");
1932 }
1933 syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
1934 other => bail_span!(other, "failed to parse this item as a known item"),
1935 };
1936
1937 let ident = extract_path_ident(class, false)?;
1938
1939 let js_class = impl_opts
1962 .js_class()
1963 .map(|s| s.0.to_string())
1964 .unwrap_or(ident.unraw().to_string());
1965
1966 let js_namespace_segments: Vec<String> = impl_opts
1973 .js_namespace()
1974 .map(|(ns, _)| ns.0)
1975 .unwrap_or_default();
1976 let js_namespace_lits = js_namespace_segments
1977 .iter()
1978 .map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site()))
1979 .collect::<Vec<_>>();
1980 let js_namespace_tokens = if js_namespace_lits.is_empty() {
1981 quote::quote! {}
1982 } else {
1983 quote::quote! { , js_namespace = [ #(#js_namespace_lits),* ] }
1984 };
1985
1986 let wasm_bindgen = &program.wasm_bindgen;
1987 let wasm_bindgen_futures = &program.wasm_bindgen_futures;
1988 let js_sys = &program.js_sys;
1989 method.attrs.insert(
1990 0,
1991 syn::Attribute {
1992 pound_token: Default::default(),
1993 style: syn::AttrStyle::Outer,
1994 bracket_token: Default::default(),
1995 meta: syn::parse_quote! { #wasm_bindgen::prelude::__wasm_bindgen_class_marker(#class = #js_class #js_namespace_tokens, wasm_bindgen = #wasm_bindgen, wasm_bindgen_futures = #wasm_bindgen_futures, js_sys = #js_sys) },
1996 },
1997 );
1998
1999 Ok(())
2000}
2001
2002impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn {
2003 fn macro_parse(
2004 self,
2005 program: &mut ast::Program,
2006 ClassMarker {
2007 class,
2008 js_class,
2009 js_namespace,
2010 wasm_bindgen,
2011 wasm_bindgen_futures,
2012 js_sys,
2013 }: &ClassMarker,
2014 ) -> Result<(), Diagnostic> {
2015 program.wasm_bindgen = wasm_bindgen.clone();
2016 program.wasm_bindgen_futures = wasm_bindgen_futures.clone();
2017 program.js_sys = js_sys.clone();
2018
2019 match self.vis {
2020 syn::Visibility::Public(_) => {}
2021 _ => return Ok(()),
2022 }
2023 if self.defaultness.is_some() {
2024 panic!("default methods are not supported");
2025 }
2026 if self.sig.constness.is_some() {
2027 bail_span!(
2028 self.sig.constness,
2029 "can only #[wasm_bindgen] non-const functions",
2030 );
2031 }
2032
2033 let opts = BindgenAttrs::find(&mut self.attrs)?;
2034
2035 if opts.this().is_some() {
2036 bail_span!(
2037 &self.sig.ident,
2038 "#[wasm_bindgen(this)] cannot be used on impl block methods; \
2039 it is only valid on free functions"
2040 );
2041 }
2042
2043 let comments = extract_doc_comments(&self.attrs);
2044 let args_attrs: Vec<FnArgAttrs> = extract_args_attrs(&mut self.sig, false)?;
2048 let (function, method_self) = function_from_decl(
2049 &self.sig.ident,
2050 &opts,
2051 self.sig.clone(),
2052 self.attrs.clone(),
2053 self.vis.clone(),
2054 FunctionPosition::Impl { self_ty: class },
2055 Some(args_attrs),
2056 )?;
2057 let method_kind = if opts.constructor().is_some() {
2058 ast::MethodKind::Constructor
2059 } else {
2060 let is_static = method_self.is_none();
2061 let kind = operation_kind(&opts);
2062 ast::MethodKind::Operation(ast::Operation { is_static, kind })
2063 };
2064
2065 if let Some((_, span)) = opts.js_namespace() {
2067 return Err(Diagnostic::span_error(
2068 span[0],
2069 "`js_namespace` cannot be used on methods, getters, setters, or static methods. \
2070 Use `js_namespace` on the exported struct definition instead to put the entire class in a namespace.",
2071 ));
2072 }
2073
2074 program.exports.push(ast::Export {
2075 comments,
2076 function,
2077 js_class: Some(js_class.to_string()),
2078 js_namespace: js_namespace.clone(),
2084 method_kind,
2085 method_self,
2086 rust_class: Some(class.clone()),
2087 rust_name: self.sig.ident.clone(),
2088 start: ast::StartKind::None,
2089 wasm_bindgen: program.wasm_bindgen.clone(),
2090 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
2091 js_sys: program.js_sys.clone(),
2092 });
2093 opts.check_used();
2094 Ok(())
2095 }
2096}
2097
2098fn string_enum(
2099 enum_: syn::ItemEnum,
2100 program: &mut ast::Program,
2101 js_name: String,
2102 generate_typescript: bool,
2103 private: bool,
2104 comments: Vec<String>,
2105 js_namespace: Option<Vec<String>>,
2106) -> Result<(), Diagnostic> {
2107 let mut variants = vec![];
2108 let mut variant_values = vec![];
2109
2110 for v in enum_.variants.iter() {
2111 let (_, expr) = match &v.discriminant {
2112 Some(pair) => pair,
2113 None => {
2114 bail_span!(v, "all variants of a string enum must have a string value");
2115 }
2116 };
2117 match get_expr(expr) {
2118 syn::Expr::Lit(syn::ExprLit {
2119 attrs: _,
2120 lit: syn::Lit::Str(str_lit),
2121 }) => {
2122 variants.push(v.ident.clone());
2123 variant_values.push(str_lit.value());
2124 }
2125 expr => bail_span!(
2126 expr,
2127 "enums with #[wasm_bindgen] cannot mix string and non-string values",
2128 ),
2129 }
2130 }
2131
2132 program.imports.push(ast::Import {
2133 module: None,
2134 js_namespace: None,
2135 reexport: None,
2136 kind: ast::ImportKind::Enum(ast::StringEnum {
2137 vis: enum_.vis,
2138 name: enum_.ident,
2139 export_name: js_name,
2140 variants,
2141 variant_values,
2142 comments,
2143 rust_attrs: enum_.attrs,
2144 generate_typescript,
2145 private,
2146 js_namespace,
2147 wasm_bindgen: program.wasm_bindgen.clone(),
2148 }),
2149 });
2150
2151 Ok(())
2152}
2153
2154fn dynamic_union(
2155 enum_: syn::ItemEnum,
2156 program: &mut ast::Program,
2157 js_name: String,
2158 generate_typescript: bool,
2159 private: bool,
2160 fallback: bool,
2161 comments: Vec<String>,
2162) -> Result<(), Diagnostic> {
2163 let mut variants = vec![];
2164 let mut variant_values = vec![];
2165 let mut variant_fields = vec![];
2166
2167 for v in enum_.variants.iter() {
2168 match &v.fields {
2170 syn::Fields::Unit => {
2171 let (_, expr) = match &v.discriminant {
2173 Some(pair) => pair,
2174 None => {
2175 bail_span!(
2176 v,
2177 "all unit variants of a string enum must have a string value"
2178 );
2179 }
2180 };
2181 match get_expr(expr) {
2182 syn::Expr::Lit(syn::ExprLit {
2183 attrs: _,
2184 lit: syn::Lit::Str(str_lit),
2185 }) => {
2186 variants.push(v.ident.clone());
2187 variant_values.push(str_lit.value());
2188 variant_fields.push(Vec::new());
2189 }
2190 expr => bail_span!(
2191 expr,
2192 "enums with #[wasm_bindgen] cannot mix string and non-string values",
2193 ),
2194 }
2195 }
2196 syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
2197 variants.push(v.ident.clone());
2200 variant_values.push(String::new());
2201 variant_fields.push(vec![fields.unnamed.first().unwrap().ty.clone()]);
2202 }
2203 _ => {
2204 bail_span!(
2205 v.fields,
2206 "string enum variants with fields must have exactly one unnamed field"
2207 );
2208 }
2209 }
2210 }
2211
2212 program.imports.push(ast::Import {
2213 module: None,
2214 js_namespace: None,
2215 reexport: None,
2216 kind: ast::ImportKind::DynamicUnion(ast::DynamicUnion {
2217 vis: enum_.vis,
2218 name: enum_.ident,
2219 js_name,
2220 variants,
2221 variant_values,
2222 variant_fields,
2223 comments,
2224 rust_attrs: enum_.attrs,
2225 generate_typescript,
2226 private,
2227 fallback,
2228 wasm_bindgen: program.wasm_bindgen.clone(),
2229 }),
2230 });
2231
2232 Ok(())
2233}
2234
2235struct NumericValue<'a> {
2237 negative: bool,
2238 base10_digits: &'a str,
2239}
2240impl<'a> NumericValue<'a> {
2241 fn from_expr(expr: &'a syn::Expr) -> Option<Self> {
2242 match get_expr(expr) {
2243 syn::Expr::Lit(syn::ExprLit {
2244 lit: syn::Lit::Int(int_lit),
2245 ..
2246 }) => Some(Self {
2247 negative: false,
2248 base10_digits: int_lit.base10_digits(),
2249 }),
2250 syn::Expr::Unary(syn::ExprUnary {
2251 op: syn::UnOp::Neg(_),
2252 expr,
2253 ..
2254 }) => Self::from_expr(expr).map(|n| n.neg()),
2255 _ => None,
2256 }
2257 }
2258
2259 fn parse(&self) -> Option<i64> {
2260 let mut value = self.base10_digits.parse::<i64>().ok()?;
2261 if self.negative {
2262 value = -value;
2263 }
2264 Some(value)
2265 }
2266
2267 fn neg(self) -> Self {
2268 Self {
2269 negative: !self.negative,
2270 base10_digits: self.base10_digits,
2271 }
2272 }
2273}
2274
2275impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
2276 fn macro_parse(
2277 self,
2278 program: &mut ast::Program,
2279 (tokens, opts): (&'a mut TokenStream, BindgenAttrs),
2280 ) -> Result<(), Diagnostic> {
2281 if self.variants.is_empty() {
2282 bail_span!(self, "cannot export empty enums to JS");
2283 }
2284
2285 let generate_typescript = opts.skip_typescript().is_none();
2286 let private = opts.private().is_some();
2287 let fallback = opts.fallback().is_some();
2288 let comments = extract_doc_comments(&self.attrs);
2289 let js_name = opts
2290 .js_name_no_symbol("enums with #[wasm_bindgen]")?
2291 .map_or_else(|| self.ident.unraw().to_string(), |s| s.to_string());
2292 if is_js_keyword(&js_name) && js_name != "default" {
2293 bail_span!(
2294 self.ident,
2295 "enum cannot use the JS keyword `{}` as its name",
2296 js_name
2297 );
2298 }
2299
2300 let js_namespace = opts.js_namespace().map(|(ns, _)| ns.0);
2301 opts.check_used();
2302
2303 let mut has_unnamed_fields = false;
2305 for variant in self.variants.iter() {
2306 match &variant.fields {
2307 syn::Fields::Unit => (), syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
2309 has_unnamed_fields = true;
2310 }
2311 syn::Fields::Unnamed(_) => bail_span!(
2312 variant.fields,
2313 "enum variants with fields must have exactly one unnamed field"
2314 ),
2315 syn::Fields::Named(_) => {
2316 bail_span!(variant.fields, "enum variants cannot have named fields")
2317 }
2318 }
2319 }
2320 if has_unnamed_fields {
2321 return dynamic_union(
2322 self,
2323 program,
2324 js_name,
2325 generate_typescript,
2326 private,
2327 fallback,
2328 comments,
2329 );
2330 }
2331
2332 if fallback {
2334 bail_span!(
2335 self,
2336 "the `fallback` attribute is only supported on dynamic unions \
2337 (enums with at least one tuple variant)"
2338 );
2339 }
2340
2341 let is_string_enum = self.variants.iter().any(|v| {
2343 if let Some((_, expr)) = &v.discriminant {
2344 if let syn::Expr::Lit(syn::ExprLit {
2345 lit: syn::Lit::Str(_),
2346 ..
2347 }) = get_expr(expr)
2348 {
2349 return true;
2350 }
2351 }
2352 false
2353 });
2354 if is_string_enum {
2355 return string_enum(
2356 self,
2357 program,
2358 js_name,
2359 generate_typescript,
2360 private,
2361 comments,
2362 js_namespace,
2363 );
2364 }
2365
2366 match self.vis {
2367 syn::Visibility::Public(_) => {}
2368 _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
2369 }
2370
2371 let signed = self.variants.iter().any(|v| match &v.discriminant {
2376 Some((_, expr)) => NumericValue::from_expr(expr).is_some_and(|n| n.negative),
2377 None => false,
2378 });
2379 let underlying_min = if signed { i32::MIN as i64 } else { 0 };
2380 let underlying_max = if signed {
2381 i32::MAX as i64
2382 } else {
2383 u32::MAX as i64
2384 };
2385
2386 let mut last_discriminant: Option<i64> = None;
2387 let mut discriminant_map: HashMap<i64, &syn::Variant> = HashMap::new();
2388
2389 let variants = self
2390 .variants
2391 .iter()
2392 .map(|v| {
2393 let value: i64 = match &v.discriminant {
2394 Some((_, expr)) => match NumericValue::from_expr(expr).and_then(|n| n.parse()) {
2395 Some(value) => value,
2396 _ => bail_span!(
2397 expr,
2398 "C-style enums with #[wasm_bindgen] may only have \
2399 numeric literal values that fit in a 32-bit integer as discriminants. \
2400 Expressions or variables are not supported.",
2401 ),
2402 },
2403 None => {
2404 last_discriminant.map_or(0, |last| last + 1)
2407 }
2408 };
2409
2410 last_discriminant = Some(value);
2411
2412 let underlying = if signed { "i32" } else { "u32" };
2414 let numbers = if signed { "signed numbers" } else { "unsigned numbers" };
2415 if value < underlying_min {
2416 bail_span!(
2417 v,
2418 "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \
2419 but `{1}` is too small for `{2}`",
2420 numbers,
2421 value,
2422 underlying
2423 );
2424 }
2425 if value > underlying_max {
2426 bail_span!(
2427 v,
2428 "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \
2429 but `{1}` is too large for `{2}`",
2430 numbers,
2431 value,
2432 underlying
2433 );
2434 }
2435
2436 if let Some(old) = discriminant_map.insert(value, v) {
2438 bail_span!(
2439 v,
2440 "discriminant value `{}` is already used by {} in this enum",
2441 value,
2442 old.ident
2443 );
2444 }
2445
2446 let comments = extract_doc_comments(&v.attrs);
2447 Ok(ast::Variant {
2448 rust_name: v.ident.clone(),
2449 js_name: v.ident.unraw().to_string(),
2450 value: value as u32,
2453 comments,
2454 })
2455 })
2456 .collect::<Result<Vec<_>, Diagnostic>>()?;
2457
2458 let hole = (0..=underlying_max)
2461 .find(|v| !discriminant_map.contains_key(v))
2462 .unwrap() as u32;
2463
2464 self.to_tokens(tokens);
2465
2466 program.enums.push(ast::Enum {
2467 rust_name: self.ident,
2468 js_name,
2469 signed,
2470 variants,
2471 comments,
2472 hole,
2473 generate_typescript,
2474 private,
2475 js_namespace,
2476 wasm_bindgen: program.wasm_bindgen.clone(),
2477 });
2478 Ok(())
2479 }
2480}
2481
2482impl MacroParse<BindgenAttrs> for syn::ItemConst {
2483 fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
2484 if opts.typescript_custom_section().is_none() {
2486 bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)].");
2487 }
2488
2489 let typescript_custom_section = match get_expr(&self.expr) {
2490 syn::Expr::Lit(syn::ExprLit {
2491 lit: syn::Lit::Str(litstr),
2492 ..
2493 }) => ast::LitOrExpr::Lit(litstr.value()),
2494 expr => ast::LitOrExpr::Expr(expr.clone()),
2495 };
2496
2497 program
2498 .typescript_custom_sections
2499 .push(typescript_custom_section);
2500
2501 opts.check_used();
2502
2503 Ok(())
2504 }
2505}
2506
2507impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
2508 fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
2509 let mut errors = Vec::new();
2510 if let Some(other) = self.abi.name.filter(|l| l.value() != "C") {
2511 errors.push(err_span!(
2512 other,
2513 "only foreign mods with the `C` ABI are allowed"
2514 ));
2515 }
2516 let js_namespace = opts.js_namespace().map(|(s, _)| s);
2517 let module = module_from_opts(program, &opts)
2518 .map_err(|e| errors.push(e))
2519 .unwrap_or_default();
2520 let slice_to_array = opts.slice_to_array().is_some();
2521 for item in self.items.into_iter() {
2522 let ctx = ForeignItemCtx {
2523 module: module.clone(),
2524 js_namespace: js_namespace.clone(),
2525 slice_to_array,
2526 };
2527 if let Err(e) = item.macro_parse(program, ctx) {
2528 errors.push(e);
2529 }
2530 }
2531 Diagnostic::from_vec(errors)?;
2532 opts.check_used();
2533 Ok(())
2534 }
2535}
2536
2537struct ForeignItemCtx {
2538 module: Option<ast::ImportModule>,
2539 js_namespace: Option<JsNamespace>,
2540 slice_to_array: bool,
2544}
2545
2546impl MacroParse<ForeignItemCtx> for syn::ForeignItem {
2547 fn macro_parse(
2548 mut self,
2549 program: &mut ast::Program,
2550 ctx: ForeignItemCtx,
2551 ) -> Result<(), Diagnostic> {
2552 let item_opts = {
2553 let attrs = match self {
2554 syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
2555 syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
2556 syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
2557 syn::ForeignItem::Verbatim(v) => {
2558 let mut item: syn::ItemStatic =
2559 syn::parse(v.into()).expect("only foreign functions/types allowed for now");
2560 let item_opts = BindgenAttrs::find(&mut item.attrs)?;
2561 let reexport = item_opts.reexport().cloned();
2562 let kind = item.convert((program, item_opts, &ctx.module))?;
2563
2564 program.imports.push(ast::Import {
2565 module: None,
2566 js_namespace: None,
2567 reexport,
2568 kind,
2569 });
2570
2571 return Ok(());
2572 }
2573 _ => panic!("only foreign functions/types allowed for now"),
2574 };
2575 BindgenAttrs::find(attrs)?
2576 };
2577
2578 let js_namespace = item_opts
2579 .js_namespace()
2580 .map(|(s, _)| s)
2581 .or(ctx.js_namespace)
2582 .map(|s| s.0);
2583 let module = ctx.module;
2584 let block_slice_to_array = ctx.slice_to_array;
2585 let reexport = item_opts.reexport().cloned();
2586
2587 if let syn::ForeignItem::Fn(_) = &self {
2593 let is_method_like = item_opts.method().is_some()
2594 || item_opts.constructor().is_some()
2595 || item_opts.static_method_of().is_some()
2596 || item_opts.getter().is_some()
2597 || item_opts.setter().is_some();
2598 if !is_method_like && js_namespace.is_none() {
2599 if let Some((value, span)) = item_opts.js_name() {
2600 if is_computed_key(value) {
2601 return Err(Diagnostic::span_error(
2602 span,
2603 "free imported functions do not support symbols in js_name unless a js_namespace is specified",
2604 ));
2605 }
2606 }
2607 }
2608 }
2609
2610 let kind = match self {
2611 syn::ForeignItem::Fn(f) => {
2612 f.convert((program, item_opts, &module, block_slice_to_array))?
2613 }
2614 syn::ForeignItem::Type(t) => t.convert((program, item_opts))?,
2615 syn::ForeignItem::Static(s) => s.convert((program, item_opts, &module))?,
2616 _ => panic!("only foreign functions/types allowed for now"),
2617 };
2618
2619 let needs_check = js_namespace.is_none() && module.is_none();
2626 if needs_check {
2627 match &kind {
2628 ast::ImportKind::Function(import_function) => {
2629 if matches!(import_function.kind, ast::ImportFunctionKind::Normal)
2630 && is_non_value_js_keyword(&import_function.function.name)
2631 {
2632 bail_span!(
2633 import_function.rust_name,
2634 "Imported function cannot use the JS keyword `{}` as its name.",
2635 import_function.function.name
2636 );
2637 }
2638 }
2639 ast::ImportKind::Static(import_static) => {
2640 if is_non_value_js_keyword(&import_static.js_name) {
2641 bail_span!(
2642 import_static.rust_name,
2643 "Imported static cannot use the JS keyword `{}` as its name.",
2644 import_static.js_name
2645 );
2646 }
2647 }
2648 ast::ImportKind::String(_) => {
2649 }
2651 ast::ImportKind::Type(import_type) => {
2652 if is_non_value_js_keyword(&import_type.js_name) {
2653 bail_span!(
2654 import_type.rust_name,
2655 "Imported type cannot use the JS keyword `{}` as its name.",
2656 import_type.js_name
2657 );
2658 }
2659 }
2660 ast::ImportKind::Enum(_) => {
2661 }
2663 ast::ImportKind::DynamicUnion(_) => {
2664 }
2666 }
2667 }
2668
2669 program.imports.push(ast::Import {
2670 module,
2671 js_namespace,
2672 reexport,
2673 kind,
2674 });
2675
2676 Ok(())
2677 }
2678}
2679
2680pub fn module_from_opts(
2681 program: &mut ast::Program,
2682 opts: &BindgenAttrs,
2683) -> Result<Option<ast::ImportModule>, Diagnostic> {
2684 if let Some(path) = opts.wasm_bindgen() {
2685 program.wasm_bindgen = path.clone();
2686 }
2687
2688 if let Some(path) = opts.js_sys() {
2689 program.js_sys = path.clone();
2690 }
2691
2692 if let Some(path) = opts.wasm_bindgen_futures() {
2693 program.wasm_bindgen_futures = path.clone();
2694 }
2695
2696 let mut errors = Vec::new();
2697 let module = if let Some((name, span)) = opts.module() {
2698 if opts.inline_js().is_some() {
2699 let msg = "cannot specify both `module` and `inline_js`";
2700 errors.push(Diagnostic::span_error(span, msg));
2701 }
2702 if opts.raw_module().is_some() {
2703 let msg = "cannot specify both `module` and `raw_module`";
2704 errors.push(Diagnostic::span_error(span, msg));
2705 }
2706 Some(ast::ImportModule::Named(name.to_string(), span))
2707 } else if let Some((name, span)) = opts.raw_module() {
2708 if opts.inline_js().is_some() {
2709 let msg = "cannot specify both `raw_module` and `inline_js`";
2710 errors.push(Diagnostic::span_error(span, msg));
2711 }
2712 Some(ast::ImportModule::RawNamed(name.to_string(), span))
2713 } else if let Some((js, _span)) = opts.inline_js() {
2714 let i = program.inline_js.len();
2715 program.inline_js.push(js.to_string());
2716 Some(ast::ImportModule::Inline(i))
2717 } else {
2718 None
2719 };
2720 Diagnostic::from_vec(errors)?;
2721 Ok(module)
2722}
2723
2724fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, Diagnostic> {
2726 let t = match ty {
2727 Some(t) => t,
2728 None => return Ok(None),
2729 };
2730 let path = match *get_ty(t) {
2731 syn::Type::Path(syn::TypePath {
2732 qself: None,
2733 ref path,
2734 }) => path,
2735 _ => bail_span!(t, "must be Result<...>"),
2736 };
2737 let seg = path
2738 .segments
2739 .last()
2740 .ok_or_else(|| err_span!(t, "must have at least one segment"))?;
2741 let generics = match seg.arguments {
2742 syn::PathArguments::AngleBracketed(ref t) => t,
2743 _ => bail_span!(t, "must be Result<...>"),
2744 };
2745 let generic = generics
2746 .args
2747 .first()
2748 .ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?;
2749 let ty = match generic {
2750 syn::GenericArgument::Type(t) => t,
2751 other => bail_span!(other, "must be a type parameter"),
2752 };
2753 match get_ty(ty) {
2754 syn::Type::Tuple(t) if t.elems.is_empty() => return Ok(None),
2755 _ => {}
2756 }
2757 Ok(Some(ty.clone()))
2758}
2759
2760fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
2762 attrs
2763 .iter()
2764 .filter_map(|a| {
2765 if a.path().segments.iter().any(|s| s.ident == "doc") {
2768 let tokens = match &a.meta {
2769 syn::Meta::Path(_) => None,
2770 syn::Meta::List(list) => Some(list.tokens.clone()),
2771 syn::Meta::NameValue(name_value) => Some(name_value.value.to_token_stream()),
2772 };
2773
2774 Some(
2775 tokens.into_iter().flatten().filter_map(|t| match t {
2777 TokenTree::Literal(lit) => {
2778 let quoted = lit.to_string();
2779 Some(try_unescape("ed).unwrap_or(quoted))
2780 }
2781 _ => None,
2782 }),
2783 )
2784 } else {
2785 None
2786 }
2787 })
2788 .fold(vec![], |mut acc, a| {
2790 acc.extend(a);
2791 acc
2792 })
2793}
2794
2795fn try_unescape(mut s: &str) -> Option<String> {
2797 s = s.strip_prefix('"').unwrap_or(s);
2798 s = s.strip_suffix('"').unwrap_or(s);
2799 let mut result = String::with_capacity(s.len());
2800 let mut chars = s.chars();
2801 while let Some(c) = chars.next() {
2802 if c == '\\' {
2803 let c = chars.next()?;
2804 match c {
2805 't' => result.push('\t'),
2806 'r' => result.push('\r'),
2807 'n' => result.push('\n'),
2808 '\\' | '\'' | '"' => result.push(c),
2809 'u' => {
2810 if chars.next() != Some('{') {
2811 return None;
2812 }
2813 let (c, next) = unescape_unicode(&mut chars)?;
2814 result.push(c);
2815 if next != '}' {
2816 return None;
2817 }
2818 }
2819 _ => return None,
2820 }
2821 } else {
2822 result.push(c);
2823 }
2824 }
2825 Some(result)
2826}
2827
2828fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
2829 let mut value = 0;
2830 for (i, c) in chars.enumerate() {
2831 match (i, c.to_digit(16)) {
2832 (0..=5, Some(num)) => value = (value << 4) | num,
2833 (1.., None) => return Some((char::from_u32(value)?, c)),
2834 _ => break,
2835 }
2836 }
2837 None
2838}
2839
2840fn extract_path_ident(path: &syn::Path, allow_generics: bool) -> Result<Ident, Diagnostic> {
2843 for segment in path.segments.iter() {
2844 match &segment.arguments {
2845 syn::PathArguments::None => {}
2846 syn::PathArguments::AngleBracketed(_) => {
2847 if !allow_generics {
2848 bail_span!(
2849 path,
2850 "paths with type parameters are not supported in this position"
2851 )
2852 }
2853 }
2854 syn::PathArguments::Parenthesized(_) => {
2855 bail_span!(path, "parenthesized paths are not supported yet")
2856 }
2857 }
2858 }
2859
2860 match path.segments.last() {
2861 Some(value) => Ok(value.ident.clone()),
2862 None => {
2863 bail_span!(path, "empty idents are not supported");
2864 }
2865 }
2866}
2867
2868fn bail_generic_unsupported(span: impl Spanned + ToTokens) -> Result<(), Diagnostic> {
2869 bail_span!(span, "unsupported in wasm-bindgen generics");
2870}
2871
2872fn validate_generic_type_param_bound(bound: &syn::TypeParamBound) -> Result<(), Diagnostic> {
2873 match bound {
2874 syn::TypeParamBound::Trait(trait_bound) => {
2875 if let syn::TraitBoundModifier::Maybe(question) = trait_bound.modifier {
2877 bail_generic_unsupported(question)?;
2878 }
2879 }
2880 syn::TypeParamBound::Lifetime(_) => {
2881 }
2883 syn::TypeParamBound::Verbatim(_) => {}
2884 _ => {}
2885 }
2886 Ok(())
2887}
2888
2889fn validate_generics(generics: &syn::Generics) -> Result<(), Diagnostic> {
2892 if let Some(where_clause) = &generics.where_clause {
2893 for predicate in &where_clause.predicates {
2894 match predicate {
2895 syn::WherePredicate::Type(predicate_type) => {
2896 predicate_type
2898 .bounds
2899 .iter()
2900 .try_for_each(validate_generic_type_param_bound)?;
2901 }
2902 syn::WherePredicate::Lifetime(_) => {
2903 }
2905 _ => bail_generic_unsupported(predicate)?,
2906 }
2907 }
2908 }
2909
2910 for param in &generics.params {
2911 match param {
2912 syn::GenericParam::Lifetime(_) => {
2913 }
2915 syn::GenericParam::Type(type_param) => {
2916 type_param
2917 .bounds
2918 .iter()
2919 .try_for_each(validate_generic_type_param_bound)?;
2920 }
2921 syn::GenericParam::Const(const_param) => bail_generic_unsupported(const_param)?,
2922 }
2923 }
2924
2925 Ok(())
2926}
2927
2928pub fn reset_attrs_used() {
2929 ATTRS.with(|state| {
2930 state.parsed.set(0);
2931 state.checks.set(0);
2932 state.unused_attrs.borrow_mut().clear();
2933 })
2934}
2935
2936pub fn check_unused_attrs(tokens: &mut TokenStream) {
2937 ATTRS.with(|state| {
2938 assert_eq!(state.parsed.get(), state.checks.get());
2939 let unused_attrs = &*state.unused_attrs.borrow();
2940 if !unused_attrs.is_empty() {
2941 let unused_attrs = unused_attrs.iter().map(|UnusedState { error, ident }| {
2942 if *error {
2943 let text = format!("invalid attribute {ident} in this position");
2944 quote::quote_spanned! { ident.span() => ::core::compile_error!(#text); }
2945 } else {
2946 quote::quote! { let #ident: (); }
2947 }
2948 });
2949 tokens.extend(quote::quote! {
2950 const _: () = {
2952 #(#unused_attrs)*
2953 };
2954 });
2955 }
2956 })
2957}
2958
2959pub fn unused_attrs_diagnostic() -> Result<(), Diagnostic> {
2960 ATTRS.with(|state| {
2961 assert_eq!(state.parsed.get(), state.checks.get());
2962 let errors = state
2963 .unused_attrs
2964 .borrow()
2965 .iter()
2966 .filter_map(|UnusedState { error, ident }| {
2967 if *error {
2968 Some(Diagnostic::span_error(
2969 ident.span(),
2970 format!("invalid attribute {ident} in this position"),
2971 ))
2972 } else {
2973 None
2974 }
2975 })
2976 .collect();
2977 Diagnostic::from_vec(errors)
2978 })
2979}
2980
2981fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind {
2982 let mut operation_kind = ast::OperationKind::Regular;
2983 if opts.this().is_some() {
2984 operation_kind = ast::OperationKind::RegularThis;
2985 }
2986 if let Some(g) = opts.getter() {
2987 operation_kind = ast::OperationKind::Getter(g.clone());
2988 }
2989 if let Some(s) = opts.setter() {
2990 operation_kind = ast::OperationKind::Setter(s.clone());
2991 }
2992 if opts.indexing_getter().is_some() {
2993 operation_kind = ast::OperationKind::IndexingGetter;
2994 }
2995 if opts.indexing_setter().is_some() {
2996 operation_kind = ast::OperationKind::IndexingSetter;
2997 }
2998 if opts.indexing_deleter().is_some() {
2999 operation_kind = ast::OperationKind::IndexingDeleter;
3000 }
3001 operation_kind
3002}
3003
3004pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
3005 let mut program = ast::Program::default();
3006 let module = module_from_opts(&mut program, &opts)?.ok_or_else(|| {
3007 Diagnostic::span_error(Span::call_site(), "`link_to!` requires a module.")
3008 })?;
3009 if let ast::ImportModule::Named(p, s) | ast::ImportModule::RawNamed(p, s) = &module {
3010 if !p.starts_with("./") && !p.starts_with("../") && !p.starts_with('/') {
3011 return Err(Diagnostic::span_error(
3012 *s,
3013 "`link_to!` does not support module paths.",
3014 ));
3015 }
3016 }
3017 opts.enforce_used()?;
3018 program.linked_modules.push(module);
3019 Ok(ast::LinkToModule(program))
3020}
3021
3022fn main(program: &ast::Program, mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
3023 if f.sig.ident != "main" {
3024 bail_span!(&f.sig.ident, "the main function has to be called main");
3025 }
3026 if let Some(constness) = f.sig.constness {
3027 bail_span!(&constness, "the main function cannot be const");
3028 }
3029 if !f.sig.generics.params.is_empty() {
3030 bail_span!(&f.sig.generics, "the main function cannot have generics");
3031 }
3032 if !f.sig.inputs.is_empty() {
3033 bail_span!(&f.sig.inputs, "the main function cannot have arguments");
3034 }
3035
3036 let r#return = f.sig.output;
3037 f.sig.output = ReturnType::Default;
3038 let body = f.block.as_ref();
3039
3040 let wasm_bindgen = &program.wasm_bindgen;
3041 let wasm_bindgen_futures = &program.wasm_bindgen_futures;
3042 let js_sys = &program.js_sys;
3043 let futures = if ast::use_js_sys_futures() {
3044 quote::quote! { #js_sys::futures }
3045 } else {
3046 quote::quote! { #wasm_bindgen_futures }
3047 };
3048
3049 if f.sig.asyncness.take().is_some() {
3050 *f.block = syn::parse2(quote::quote! {
3051 {
3052 async fn __wasm_bindgen_generated_main() #r#return #body
3053 #futures::spawn_local(
3054 async move {
3055 use #wasm_bindgen::__rt::Main;
3056 let __ret = __wasm_bindgen_generated_main();
3057 (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main()
3058 },
3059 )
3060 }
3061 })
3062 .unwrap();
3063 } else {
3064 *f.block = syn::parse2(quote::quote! {
3065 {
3066 fn __wasm_bindgen_generated_main() #r#return #body
3067 use #wasm_bindgen::__rt::Main;
3068 let __ret = __wasm_bindgen_generated_main();
3069 (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main()
3070 }
3071 })
3072 .unwrap();
3073 }
3074
3075 f.to_tokens(tokens);
3076
3077 Ok(())
3078}
3079
3080#[cfg(test)]
3081mod tests {
3082 #[test]
3083 fn test_try_unescape() {
3084 use super::try_unescape;
3085 assert_eq!(try_unescape("hello").unwrap(), "hello");
3086 assert_eq!(try_unescape("\"hello").unwrap(), "hello");
3087 assert_eq!(try_unescape("hello\"").unwrap(), "hello");
3088 assert_eq!(try_unescape("\"hello\"").unwrap(), "hello");
3089 assert_eq!(try_unescape("hello\\\\").unwrap(), "hello\\");
3090 assert_eq!(try_unescape("hello\\n").unwrap(), "hello\n");
3091 assert_eq!(try_unescape("hello\\u"), None);
3092 assert_eq!(try_unescape("hello\\u{"), None);
3093 assert_eq!(try_unescape("hello\\u{}"), None);
3094 assert_eq!(try_unescape("hello\\u{0}").unwrap(), "hello\0");
3095 assert_eq!(try_unescape("hello\\u{000000}").unwrap(), "hello\0");
3096 assert_eq!(try_unescape("hello\\u{0000000}"), None);
3097 }
3098}