1use std::cell::{Cell, RefCell};
2use std::char;
3use std::str::Chars;
4
5use ast::OperationKind;
6use backend::ast;
7use backend::util::{ident_ty, ShortHash};
8use backend::Diagnostic;
9use proc_macro2::{Ident, Span, TokenStream, TokenTree};
10use quote::ToTokens;
11use syn::ext::IdentExt;
12use syn::parse::{Parse, ParseStream, Result as SynResult};
13use syn::spanned::Spanned;
14use syn::{ItemFn, Lit, MacroDelimiter, ReturnType};
15
16use crate::ClassMarker;
17
18thread_local!(static ATTRS: AttributeParseState = Default::default());
19
20const JS_KEYWORDS: [&str; 20] = [
22 "class",
23 "case",
24 "catch",
25 "debugger",
26 "default",
27 "delete",
28 "export",
29 "extends",
30 "finally",
31 "function",
32 "import",
33 "instanceof",
34 "new",
35 "null",
36 "switch",
37 "this",
38 "throw",
39 "var",
40 "void",
41 "with",
42];
43#[derive(Default)]
44struct AttributeParseState {
45 parsed: Cell<usize>,
46 checks: Cell<usize>,
47 unused_attrs: RefCell<Vec<Ident>>,
48}
49
50#[cfg_attr(feature = "extra-traits", derive(Debug))]
52pub struct BindgenAttrs {
53 pub attrs: Vec<(Cell<bool>, BindgenAttr)>,
55}
56
57macro_rules! attrgen {
58 ($mac:ident) => {
59 $mac! {
60 (catch, Catch(Span)),
61 (constructor, Constructor(Span)),
62 (method, Method(Span)),
63 (static_method_of, StaticMethodOf(Span, Ident)),
64 (js_namespace, JsNamespace(Span, Vec<String>, Vec<Span>)),
65 (module, Module(Span, String, Span)),
66 (raw_module, RawModule(Span, String, Span)),
67 (inline_js, InlineJs(Span, String, Span)),
68 (getter, Getter(Span, Option<String>)),
69 (setter, Setter(Span, Option<String>)),
70 (indexing_getter, IndexingGetter(Span)),
71 (indexing_setter, IndexingSetter(Span)),
72 (indexing_deleter, IndexingDeleter(Span)),
73 (structural, Structural(Span)),
74 (r#final, Final(Span)),
75 (readonly, Readonly(Span)),
76 (js_name, JsName(Span, String, Span)),
77 (js_class, JsClass(Span, String, Span)),
78 (inspectable, Inspectable(Span)),
79 (is_type_of, IsTypeOf(Span, syn::Expr)),
80 (extends, Extends(Span, syn::Path)),
81 (no_deref, NoDeref(Span)),
82 (vendor_prefix, VendorPrefix(Span, Ident)),
83 (variadic, Variadic(Span)),
84 (typescript_custom_section, TypescriptCustomSection(Span)),
85 (skip_typescript, SkipTypescript(Span)),
86 (skip_jsdoc, SkipJsDoc(Span)),
87 (main, Main(Span)),
88 (start, Start(Span)),
89 (wasm_bindgen, WasmBindgen(Span, syn::Path)),
90 (js_sys, JsSys(Span, syn::Path)),
91 (wasm_bindgen_futures, WasmBindgenFutures(Span, syn::Path)),
92 (skip, Skip(Span)),
93 (typescript_type, TypeScriptType(Span, String, Span)),
94 (getter_with_clone, GetterWithClone(Span)),
95 (static_string, StaticString(Span)),
96 (thread_local, ThreadLocal(Span)),
97
98 (assert_no_shim, AssertNoShim(Span)),
100 }
101 };
102}
103
104macro_rules! methods {
105 ($(($name:ident, $variant:ident($($contents:tt)*)),)*) => {
106 $(methods!(@method $name, $variant($($contents)*));)*
107
108 fn enforce_used(self) -> Result<(), Diagnostic> {
109 ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
111
112 let mut errors = Vec::new();
113 for (used, attr) in self.attrs.iter() {
114 if used.get() {
115 continue
116 }
117 let span = match attr {
118 $(BindgenAttr::$variant(span, ..) => span,)*
119 };
120 errors.push(Diagnostic::span_error(*span, "unused wasm_bindgen attribute"));
121 }
122 Diagnostic::from_vec(errors)
123 }
124
125 fn check_used(self) {
126 ATTRS.with(|state| {
128 state.checks.set(state.checks.get() + 1);
129
130 state.unused_attrs.borrow_mut().extend(
131 self.attrs
132 .iter()
133 .filter_map(|(used, attr)| if used.get() { None } else { Some(attr) })
134 .map(|attr| {
135 match attr {
136 $(BindgenAttr::$variant(span, ..) => {
137 syn::parse_quote_spanned!(*span => $name)
138 })*
139 }
140 })
141 );
142 });
143 }
144 };
145
146 (@method $name:ident, $variant:ident(Span, String, Span)) => {
147 fn $name(&self) -> Option<(&str, Span)> {
148 self.attrs
149 .iter()
150 .find_map(|a| match &a.1 {
151 BindgenAttr::$variant(_, s, span) => {
152 a.0.set(true);
153 Some((&s[..], *span))
154 }
155 _ => None,
156 })
157 }
158 };
159
160 (@method $name:ident, $variant:ident(Span, Vec<String>, Vec<Span>)) => {
161 fn $name(&self) -> Option<(&[String], &[Span])> {
162 self.attrs
163 .iter()
164 .find_map(|a| match &a.1 {
165 BindgenAttr::$variant(_, ss, spans) => {
166 a.0.set(true);
167 Some((&ss[..], &spans[..]))
168 }
169 _ => None,
170 })
171 }
172 };
173
174 (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
175 #[allow(unused)]
176 fn $name(&self) -> Option<&$($other)*> {
177 self.attrs
178 .iter()
179 .find_map(|a| match &a.1 {
180 BindgenAttr::$variant(_, s) => {
181 a.0.set(true);
182 Some(s)
183 }
184 _ => None,
185 })
186 }
187 };
188
189 (@method $name:ident, $variant:ident($($other:tt)*)) => {
190 #[allow(unused)]
191 fn $name(&self) -> Option<&$($other)*> {
192 self.attrs
193 .iter()
194 .find_map(|a| match &a.1 {
195 BindgenAttr::$variant(s) => {
196 a.0.set(true);
197 Some(s)
198 }
199 _ => None,
200 })
201 }
202 };
203}
204
205impl BindgenAttrs {
206 fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
208 let mut ret = BindgenAttrs::default();
209 loop {
210 let pos = attrs
211 .iter()
212 .enumerate()
213 .find(|&(_, m)| m.path().segments[0].ident == "wasm_bindgen")
214 .map(|a| a.0);
215 let pos = match pos {
216 Some(i) => i,
217 None => return Ok(ret),
218 };
219 let attr = attrs.remove(pos);
220 let tokens = match attr.meta {
221 syn::Meta::Path(_) => continue,
222 syn::Meta::List(syn::MetaList {
223 delimiter: MacroDelimiter::Paren(_),
224 tokens,
225 ..
226 }) => tokens,
227 syn::Meta::List(_) | syn::Meta::NameValue(_) => {
228 bail_span!(attr, "malformed #[wasm_bindgen] attribute")
229 }
230 };
231 let mut attrs: BindgenAttrs = syn::parse2(tokens)?;
232 ret.attrs.append(&mut attrs.attrs);
233 attrs.check_used();
234 }
235 }
236
237 attrgen!(methods);
238}
239
240impl Default for BindgenAttrs {
241 fn default() -> BindgenAttrs {
242 ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1));
246 BindgenAttrs { attrs: Vec::new() }
247 }
248}
249
250impl Parse for BindgenAttrs {
251 fn parse(input: ParseStream) -> SynResult<Self> {
252 let mut attrs = BindgenAttrs::default();
253 if input.is_empty() {
254 return Ok(attrs);
255 }
256
257 let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
258 attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect();
259 Ok(attrs)
260 }
261}
262
263macro_rules! gen_bindgen_attr {
264 ($(($method:ident, $($variants:tt)*),)*) => {
265 #[cfg_attr(feature = "extra-traits", derive(Debug))]
267 pub enum BindgenAttr {
268 $($($variants)*,)*
269 }
270 }
271}
272attrgen!(gen_bindgen_attr);
273
274impl Parse for BindgenAttr {
275 fn parse(input: ParseStream) -> SynResult<Self> {
276 let original = input.fork();
277 let attr: AnyIdent = input.parse()?;
278 let attr = attr.0;
279 let attr_span = attr.span();
280 let attr_string = attr.to_string();
281 let raw_attr_string = format!("r#{}", attr_string);
282
283 macro_rules! parsers {
284 ($(($name:ident, $($contents:tt)*),)*) => {
285 $(
286 if attr_string == stringify!($name) || raw_attr_string == stringify!($name) {
287 parsers!(
288 @parser
289 $($contents)*
290 );
291 }
292 )*
293 };
294
295 (@parser $variant:ident(Span)) => ({
296 return Ok(BindgenAttr::$variant(attr_span));
297 });
298
299 (@parser $variant:ident(Span, Ident)) => ({
300 input.parse::<Token![=]>()?;
301 let ident = input.parse::<AnyIdent>()?.0;
302 return Ok(BindgenAttr::$variant(attr_span, ident))
303 });
304
305 (@parser $variant:ident(Span, Option<String>)) => ({
306 if input.parse::<Token![=]>().is_ok() {
307 if input.peek(syn::LitStr) {
308 let litstr = input.parse::<syn::LitStr>()?;
309 return Ok(BindgenAttr::$variant(attr_span, Some(litstr.value())))
310 }
311
312 let ident = input.parse::<AnyIdent>()?.0;
313 return Ok(BindgenAttr::$variant(attr_span, Some(ident.to_string())))
314 } else {
315 return Ok(BindgenAttr::$variant(attr_span, None));
316 }
317 });
318
319 (@parser $variant:ident(Span, syn::Path)) => ({
320 input.parse::<Token![=]>()?;
321 return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
322 });
323
324 (@parser $variant:ident(Span, syn::Expr)) => ({
325 input.parse::<Token![=]>()?;
326 return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
327 });
328
329 (@parser $variant:ident(Span, String, Span)) => ({
330 input.parse::<Token![=]>()?;
331 let (val, span) = match input.parse::<syn::LitStr>() {
332 Ok(str) => (str.value(), str.span()),
333 Err(_) => {
334 let ident = input.parse::<AnyIdent>()?.0;
335 (ident.to_string(), ident.span())
336 }
337 };
338 return Ok(BindgenAttr::$variant(attr_span, val, span))
339 });
340
341 (@parser $variant:ident(Span, Vec<String>, Vec<Span>)) => ({
342 input.parse::<Token![=]>()?;
343 let (vals, spans) = match input.parse::<syn::ExprArray>() {
344 Ok(exprs) => {
345 let mut vals = vec![];
346 let mut spans = vec![];
347
348 for expr in exprs.elems.iter() {
349 if let syn::Expr::Lit(syn::ExprLit {
350 lit: syn::Lit::Str(ref str),
351 ..
352 }) = expr {
353 vals.push(str.value());
354 spans.push(str.span());
355 } else {
356 return Err(syn::Error::new(expr.span(), "expected string literals"));
357 }
358 }
359
360 (vals, spans)
361 },
362 Err(_) => {
363 let ident = input.parse::<AnyIdent>()?.0;
364 (vec![ident.to_string()], vec![ident.span()])
365 }
366 };
367 return Ok(BindgenAttr::$variant(attr_span, vals, spans))
368 });
369 }
370
371 attrgen!(parsers);
372
373 Err(original.error(if attr_string.starts_with('_') {
374 "unknown attribute: it's safe to remove unused attributes entirely."
375 } else {
376 "unknown attribute"
377 }))
378 }
379}
380
381struct AnyIdent(Ident);
382
383impl Parse for AnyIdent {
384 fn parse(input: ParseStream) -> SynResult<Self> {
385 input.step(|cursor| match cursor.ident() {
386 Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)),
387 None => Err(cursor.error("expected an identifier")),
388 })
389 }
390}
391
392trait ConvertToAst<Ctx> {
397 type Target;
399 fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
403}
404
405impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs)> for &'a mut syn::ItemStruct {
406 type Target = ast::Struct;
407
408 fn convert(
409 self,
410 (program, attrs): (&ast::Program, BindgenAttrs),
411 ) -> Result<Self::Target, Diagnostic> {
412 if !self.generics.params.is_empty() {
413 bail_span!(
414 self.generics,
415 "structs with #[wasm_bindgen] cannot have lifetime or \
416 type parameters currently"
417 );
418 }
419 let mut fields = Vec::new();
420 let js_name = attrs
421 .js_name()
422 .map(|s| s.0.to_string())
423 .unwrap_or(self.ident.unraw().to_string());
424 let is_inspectable = attrs.inspectable().is_some();
425 let getter_with_clone = attrs.getter_with_clone();
426 for (i, field) in self.fields.iter_mut().enumerate() {
427 match field.vis {
428 syn::Visibility::Public(..) => {}
429 _ => continue,
430 }
431 let (js_field_name, member) = match &field.ident {
432 Some(ident) => (ident.unraw().to_string(), syn::Member::Named(ident.clone())),
433 None => (i.to_string(), syn::Member::Unnamed(i.into())),
434 };
435
436 let attrs = BindgenAttrs::find(&mut field.attrs)?;
437 if attrs.skip().is_some() {
438 attrs.check_used();
439 continue;
440 }
441
442 let js_field_name = match attrs.js_name() {
443 Some((name, _)) => name.to_string(),
444 None => js_field_name,
445 };
446
447 let comments = extract_doc_comments(&field.attrs);
448 let getter = shared::struct_field_get(&js_name, &js_field_name);
449 let setter = shared::struct_field_set(&js_name, &js_field_name);
450
451 fields.push(ast::StructField {
452 rust_name: member,
453 js_name: js_field_name,
454 struct_name: self.ident.clone(),
455 readonly: attrs.readonly().is_some(),
456 ty: field.ty.clone(),
457 getter: Ident::new(&getter, Span::call_site()),
458 setter: Ident::new(&setter, Span::call_site()),
459 comments,
460 generate_typescript: attrs.skip_typescript().is_none(),
461 generate_jsdoc: attrs.skip_jsdoc().is_none(),
462 getter_with_clone: attrs.getter_with_clone().or(getter_with_clone).copied(),
463 wasm_bindgen: program.wasm_bindgen.clone(),
464 });
465 attrs.check_used();
466 }
467 let generate_typescript = attrs.skip_typescript().is_none();
468 let comments: Vec<String> = extract_doc_comments(&self.attrs);
469 attrs.check_used();
470 Ok(ast::Struct {
471 rust_name: self.ident.clone(),
472 js_name,
473 fields,
474 comments,
475 is_inspectable,
476 generate_typescript,
477 wasm_bindgen: program.wasm_bindgen.clone(),
478 })
479 }
480}
481
482fn get_ty(mut ty: &syn::Type) -> &syn::Type {
483 while let syn::Type::Group(g) = ty {
484 ty = &g.elem;
485 }
486
487 ty
488}
489
490fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
491 while let syn::Expr::Group(g) = expr {
492 expr = &g.expr;
493 }
494
495 expr
496}
497
498impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
499 for syn::ForeignItemFn
500{
501 type Target = ast::ImportKind;
502
503 fn convert(
504 self,
505 (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
506 ) -> Result<Self::Target, Diagnostic> {
507 let mut wasm = function_from_decl(
508 &self.sig.ident,
509 &opts,
510 self.sig.clone(),
511 self.attrs.clone(),
512 self.vis.clone(),
513 false,
514 None,
515 false,
516 Some(&["default"]),
517 )?
518 .0;
519 let catch = opts.catch().is_some();
520 let variadic = opts.variadic().is_some();
521 let js_ret = if catch {
522 extract_first_ty_param(wasm.ret.as_ref())?
530 } else {
531 wasm.ret.clone()
532 };
533
534 let operation_kind = operation_kind(&opts);
535
536 let kind = if opts.method().is_some() {
537 let class = wasm.arguments.first().ok_or_else(|| {
538 err_span!(self, "imported methods must have at least one argument")
539 })?;
540 let class = match get_ty(&class.ty) {
541 syn::Type::Reference(syn::TypeReference {
542 mutability: None,
543 elem,
544 ..
545 }) => &**elem,
546 _ => bail_span!(
547 class.ty,
548 "first argument of method must be a shared reference"
549 ),
550 };
551 let class_name = match get_ty(class) {
552 syn::Type::Path(syn::TypePath {
553 qself: None,
554 ref path,
555 }) => path,
556 _ => bail_span!(class, "first argument of method must be a path"),
557 };
558 let class_name = extract_path_ident(class_name)?;
559 let class_name = opts
560 .js_class()
561 .map(|p| p.0.into())
562 .unwrap_or_else(|| class_name.to_string());
563
564 let kind = ast::MethodKind::Operation(ast::Operation {
565 is_static: false,
566 kind: operation_kind,
567 });
568
569 ast::ImportFunctionKind::Method {
570 class: class_name,
571 ty: class.clone(),
572 kind,
573 }
574 } else if let Some(cls) = opts.static_method_of() {
575 let class = opts
576 .js_class()
577 .map(|p| p.0.into())
578 .unwrap_or_else(|| cls.to_string());
579 let ty = ident_ty(cls.clone());
580
581 let kind = ast::MethodKind::Operation(ast::Operation {
582 is_static: true,
583 kind: operation_kind,
584 });
585
586 ast::ImportFunctionKind::Method { class, ty, kind }
587 } else if opts.constructor().is_some() {
588 let class = match js_ret {
589 Some(ref ty) => ty,
590 _ => bail_span!(self, "constructor returns must be bare types"),
591 };
592 let class_name = match get_ty(class) {
593 syn::Type::Path(syn::TypePath {
594 qself: None,
595 ref path,
596 }) => path,
597 _ => bail_span!(self, "return value of constructor must be a bare path"),
598 };
599 let class_name = extract_path_ident(class_name)?;
600 let class_name = opts
601 .js_class()
602 .map(|p| p.0.into())
603 .unwrap_or_else(|| class_name.to_string());
604
605 ast::ImportFunctionKind::Method {
606 class: class_name,
607 ty: class.clone(),
608 kind: ast::MethodKind::Constructor,
609 }
610 } else {
611 ast::ImportFunctionKind::Normal
612 };
613
614 let shim = {
615 let ns = match kind {
616 ast::ImportFunctionKind::Normal => (0, "n"),
617 ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
618 };
619 let data = (ns, &self.sig.ident, module);
620 format!(
621 "__wbg_{}_{}",
622 wasm.name
623 .chars()
624 .filter(|c| c.is_ascii_alphanumeric())
625 .collect::<String>(),
626 ShortHash(data)
627 )
628 };
629 if let Some(span) = opts.r#final() {
630 if opts.structural().is_some() {
631 let msg = "cannot specify both `structural` and `final`";
632 return Err(Diagnostic::span_error(*span, msg));
633 }
634 }
635 let assert_no_shim = opts.assert_no_shim().is_some();
636
637 let mut doc_comment = String::new();
638 wasm.rust_attrs.retain(|attr| {
640 fn get_docs(attr: &syn::Attribute) -> Option<String> {
643 if attr.path().is_ident("doc") {
644 if let syn::Meta::NameValue(syn::MetaNameValue {
645 value:
646 syn::Expr::Lit(syn::ExprLit {
647 lit: Lit::Str(str), ..
648 }),
649 ..
650 }) = &attr.meta
651 {
652 Some(str.value())
653 } else {
654 None
655 }
656 } else {
657 None
658 }
659 }
660
661 if let Some(docs) = get_docs(attr) {
662 if !doc_comment.is_empty() {
663 doc_comment.push('\n');
665 }
666 doc_comment.push_str(&docs);
668
669 false
671 } else {
672 true
673 }
674 });
675
676 let ret = ast::ImportKind::Function(ast::ImportFunction {
677 function: wasm,
678 assert_no_shim,
679 kind,
680 js_ret,
681 catch,
682 variadic,
683 structural: opts.structural().is_some() || opts.r#final().is_none(),
684 rust_name: self.sig.ident,
685 shim: Ident::new(&shim, Span::call_site()),
686 doc_comment,
687 wasm_bindgen: program.wasm_bindgen.clone(),
688 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
689 });
690 opts.check_used();
691
692 Ok(ret)
693 }
694}
695
696impl ConvertToAst<(&ast::Program, BindgenAttrs)> for syn::ForeignItemType {
697 type Target = ast::ImportKind;
698
699 fn convert(
700 self,
701 (program, attrs): (&ast::Program, BindgenAttrs),
702 ) -> Result<Self::Target, Diagnostic> {
703 let js_name = attrs
704 .js_name()
705 .map(|s| s.0)
706 .map_or_else(|| self.ident.to_string(), |s| s.to_string());
707 let typescript_type = attrs.typescript_type().map(|s| s.0.to_string());
708 let is_type_of = attrs.is_type_of().cloned();
709 let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
710 let mut extends = Vec::new();
711 let mut vendor_prefixes = Vec::new();
712 let no_deref = attrs.no_deref().is_some();
713 for (used, attr) in attrs.attrs.iter() {
714 match attr {
715 BindgenAttr::Extends(_, e) => {
716 extends.push(e.clone());
717 used.set(true);
718 }
719 BindgenAttr::VendorPrefix(_, e) => {
720 vendor_prefixes.push(e.clone());
721 used.set(true);
722 }
723 _ => {}
724 }
725 }
726 attrs.check_used();
727 Ok(ast::ImportKind::Type(ast::ImportType {
728 vis: self.vis,
729 attrs: self.attrs,
730 doc_comment: None,
731 instanceof_shim: shim,
732 is_type_of,
733 rust_name: self.ident,
734 typescript_type,
735 js_name,
736 extends,
737 vendor_prefixes,
738 no_deref,
739 wasm_bindgen: program.wasm_bindgen.clone(),
740 }))
741 }
742}
743
744impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
745 for syn::ForeignItemStatic
746{
747 type Target = ast::ImportKind;
748
749 fn convert(
750 self,
751 (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
752 ) -> Result<Self::Target, Diagnostic> {
753 if let syn::StaticMutability::Mut(_) = self.mutability {
754 bail_span!(self.mutability, "cannot import mutable globals yet")
755 }
756
757 if let Some(span) = opts.static_string() {
758 return Err(Diagnostic::span_error(
759 *span,
760 "static strings require a string literal",
761 ));
762 }
763
764 let default_name = self.ident.to_string();
765 let js_name = opts
766 .js_name()
767 .map(|p| p.0)
768 .unwrap_or(&default_name)
769 .to_string();
770 let shim = format!(
771 "__wbg_static_accessor_{}_{}",
772 self.ident,
773 ShortHash((&js_name, module, &self.ident)),
774 );
775 let thread_local = opts.thread_local().is_some();
776
777 opts.check_used();
778 Ok(ast::ImportKind::Static(ast::ImportStatic {
779 ty: *self.ty,
780 vis: self.vis,
781 rust_name: self.ident.clone(),
782 js_name,
783 shim: Ident::new(&shim, Span::call_site()),
784 wasm_bindgen: program.wasm_bindgen.clone(),
785 thread_local,
786 }))
787 }
788}
789
790impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)>
791 for syn::ItemStatic
792{
793 type Target = ast::ImportKind;
794
795 fn convert(
796 self,
797 (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>),
798 ) -> Result<Self::Target, Diagnostic> {
799 if let syn::StaticMutability::Mut(_) = self.mutability {
800 bail_span!(self.mutability, "cannot import mutable globals yet")
801 }
802
803 let string = if let syn::Expr::Lit(syn::ExprLit {
804 lit: syn::Lit::Str(string),
805 ..
806 }) = *self.expr.clone()
807 {
808 string.value()
809 } else {
810 bail_span!(
811 self.expr,
812 "statics with a value can only be string literals"
813 )
814 };
815
816 if opts.static_string().is_none() {
817 bail_span!(
818 self,
819 "static strings require `#[wasm_bindgen(static_string)]`"
820 )
821 }
822
823 if opts.thread_local().is_none() {
824 bail_span!(
825 self,
826 "static strings require `#[wasm_bindgen(thread_local)]`"
827 )
828 }
829
830 let shim = format!(
831 "__wbg_string_{}_{}",
832 self.ident,
833 ShortHash((&module, &self.ident)),
834 );
835 opts.check_used();
836 Ok(ast::ImportKind::String(ast::ImportString {
837 ty: *self.ty,
838 vis: self.vis,
839 rust_name: self.ident.clone(),
840 shim: Ident::new(&shim, Span::call_site()),
841 wasm_bindgen: program.wasm_bindgen.clone(),
842 js_sys: program.js_sys.clone(),
843 string,
844 }))
845 }
846}
847
848impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
849 type Target = ast::Function;
850
851 fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
852 match self.vis {
853 syn::Visibility::Public(_) => {}
854 _ if attrs.start().is_some() => {}
855 _ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
856 }
857 if self.sig.constness.is_some() {
858 bail_span!(
859 self.sig.constness,
860 "can only #[wasm_bindgen] non-const functions"
861 );
862 }
863
864 let ret = function_from_decl(
865 &self.sig.ident,
866 &attrs,
867 self.sig.clone(),
868 self.attrs,
869 self.vis,
870 false,
871 None,
872 false,
873 Some(&["default"]),
874 )?;
875 attrs.check_used();
876 Ok(ret.0)
877 }
878}
879
880pub(crate) fn is_js_keyword(keyword: &str, skip: Option<&[&str]>) -> bool {
881 JS_KEYWORDS
882 .iter()
883 .filter(|keyword| skip.filter(|skip| skip.contains(keyword)).is_none())
884 .any(|this| *this == keyword)
885}
886
887#[allow(clippy::too_many_arguments)]
889fn function_from_decl(
890 decl_name: &syn::Ident,
891 opts: &BindgenAttrs,
892 sig: syn::Signature,
893 attrs: Vec<syn::Attribute>,
894 vis: syn::Visibility,
895 allow_self: bool,
896 self_ty: Option<&Ident>,
897 is_from_impl: bool,
898 skip_keywords: Option<&[&str]>,
899) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> {
900 if sig.variadic.is_some() {
901 bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions");
902 }
903 if !sig.generics.params.is_empty() {
904 bail_span!(
905 sig.generics,
906 "can't #[wasm_bindgen] functions with lifetime or type parameters",
907 );
908 }
909
910 assert_no_lifetimes(&sig)?;
911
912 let syn::Signature { inputs, output, .. } = sig;
913
914 let replace_self = |t: syn::Type| {
915 let self_ty = match self_ty {
916 Some(i) => i,
917 None => return t,
918 };
919 let path = match get_ty(&t) {
920 syn::Type::Path(syn::TypePath { qself: None, path }) => path.clone(),
921 other => return other.clone(),
922 };
923 let new_path = if path.segments.len() == 1 && path.segments[0].ident == "Self" {
924 self_ty.clone().into()
925 } else {
926 path
927 };
928 syn::Type::Path(syn::TypePath {
929 qself: None,
930 path: new_path,
931 })
932 };
933
934 let replace_colliding_arg = |i: &mut syn::PatType| {
935 if let syn::Pat::Ident(ref mut i) = *i.pat {
936 let ident = i.ident.to_string();
937 if is_js_keyword(ident.as_str(), skip_keywords) {
938 i.ident = Ident::new(format!("_{}", ident).as_str(), i.ident.span());
939 }
940 }
941 };
942
943 let mut method_self = None;
944 let arguments = inputs
945 .into_iter()
946 .filter_map(|arg| match arg {
947 syn::FnArg::Typed(mut c) => {
948 replace_colliding_arg(&mut c);
949 c.ty = Box::new(replace_self(*c.ty));
950 Some(c)
951 }
952 syn::FnArg::Receiver(r) => {
953 if !allow_self {
954 panic!("arguments cannot be `self`")
955 }
956 assert!(method_self.is_none());
957 if r.reference.is_none() {
958 method_self = Some(ast::MethodSelf::ByValue);
959 } else if r.mutability.is_some() {
960 method_self = Some(ast::MethodSelf::RefMutable);
961 } else {
962 method_self = Some(ast::MethodSelf::RefShared);
963 }
964 None
965 }
966 })
967 .collect::<Vec<_>>();
968
969 let ret = match output {
970 syn::ReturnType::Default => None,
971 syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)),
972 };
973
974 let (name, name_span, renamed_via_js_name) =
975 if let Some((js_name, js_name_span)) = opts.js_name() {
976 let kind = operation_kind(opts);
977 let prefix = match kind {
978 OperationKind::Setter(_) => "set_",
979 _ => "",
980 };
981 let name = if prefix.is_empty()
982 && opts.method().is_none()
983 && is_js_keyword(js_name, skip_keywords)
984 {
985 format!("_{}", js_name)
986 } else {
987 format!("{}{}", prefix, js_name)
988 };
989 (name, js_name_span, true)
990 } else {
991 let name = if !is_from_impl
992 && opts.method().is_none()
993 && is_js_keyword(&decl_name.to_string(), skip_keywords)
994 {
995 format!("_{}", decl_name.unraw())
996 } else {
997 decl_name.unraw().to_string()
998 };
999 (name, decl_name.span(), false)
1000 };
1001 Ok((
1002 ast::Function {
1003 arguments,
1004 name_span,
1005 name,
1006 renamed_via_js_name,
1007 ret,
1008 rust_attrs: attrs,
1009 rust_vis: vis,
1010 r#unsafe: sig.unsafety.is_some(),
1011 r#async: sig.asyncness.is_some(),
1012 generate_typescript: opts.skip_typescript().is_none(),
1013 generate_jsdoc: opts.skip_jsdoc().is_none(),
1014 variadic: opts.variadic().is_some(),
1015 },
1016 method_self,
1017 ))
1018}
1019
1020pub(crate) trait MacroParse<Ctx> {
1021 fn macro_parse(self, program: &mut ast::Program, context: Ctx) -> Result<(), Diagnostic>;
1026}
1027
1028impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
1029 fn macro_parse(
1030 self,
1031 program: &mut ast::Program,
1032 (opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
1033 ) -> Result<(), Diagnostic> {
1034 match self {
1035 syn::Item::Fn(mut f) => {
1036 let opts = opts.unwrap_or_default();
1037 if let Some(path) = opts.wasm_bindgen() {
1038 program.wasm_bindgen = path.clone();
1039 }
1040 if let Some(path) = opts.js_sys() {
1041 program.js_sys = path.clone();
1042 }
1043 if let Some(path) = opts.wasm_bindgen_futures() {
1044 program.wasm_bindgen_futures = path.clone();
1045 }
1046
1047 if opts.main().is_some() {
1048 opts.check_used();
1049 return main(program, f, tokens);
1050 }
1051
1052 let no_mangle = f
1053 .attrs
1054 .iter()
1055 .enumerate()
1056 .find(|(_, m)| m.path().is_ident("no_mangle"));
1057 if let Some((i, _)) = no_mangle {
1058 f.attrs.remove(i);
1059 }
1060 let comments = extract_doc_comments(&f.attrs);
1061 tokens.extend(quote::quote! { #[allow(dead_code)] });
1065 f.to_tokens(tokens);
1066 if opts.start().is_some() {
1067 if !f.sig.generics.params.is_empty() {
1068 bail_span!(&f.sig.generics, "the start function cannot have generics",);
1069 }
1070 if !f.sig.inputs.is_empty() {
1071 bail_span!(&f.sig.inputs, "the start function cannot have arguments",);
1072 }
1073 }
1074 let method_kind = ast::MethodKind::Operation(ast::Operation {
1075 is_static: true,
1076 kind: operation_kind(&opts),
1077 });
1078 let rust_name = f.sig.ident.clone();
1079 let start = opts.start().is_some();
1080 program.exports.push(ast::Export {
1081 comments,
1082 function: f.convert(opts)?,
1083 js_class: None,
1084 method_kind,
1085 method_self: None,
1086 rust_class: None,
1087 rust_name,
1088 start,
1089 wasm_bindgen: program.wasm_bindgen.clone(),
1090 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1091 });
1092 }
1093 syn::Item::Struct(mut s) => {
1094 let opts = opts.unwrap_or_default();
1095 program.structs.push((&mut s).convert((program, opts))?);
1096 s.to_tokens(tokens);
1097 }
1098 syn::Item::Impl(mut i) => {
1099 let opts = opts.unwrap_or_default();
1100 (&mut i).macro_parse(program, opts)?;
1101 i.to_tokens(tokens);
1102 }
1103 syn::Item::ForeignMod(mut f) => {
1104 let opts = match opts {
1105 Some(opts) => opts,
1106 None => BindgenAttrs::find(&mut f.attrs)?,
1107 };
1108 f.macro_parse(program, opts)?;
1109 }
1110 syn::Item::Enum(mut e) => {
1111 let opts = match opts {
1112 Some(opts) => opts,
1113 None => BindgenAttrs::find(&mut e.attrs)?,
1114 };
1115 e.macro_parse(program, (tokens, opts))?;
1116 }
1117 syn::Item::Const(mut c) => {
1118 let opts = match opts {
1119 Some(opts) => opts,
1120 None => BindgenAttrs::find(&mut c.attrs)?,
1121 };
1122 c.macro_parse(program, opts)?;
1123 }
1124 _ => {
1125 bail_span!(
1126 self,
1127 "#[wasm_bindgen] can only be applied to a function, \
1128 struct, enum, impl, or extern block",
1129 );
1130 }
1131 }
1132
1133 Ok(())
1134 }
1135}
1136
1137impl<'a> MacroParse<BindgenAttrs> for &'a mut syn::ItemImpl {
1138 fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1139 if self.defaultness.is_some() {
1140 bail_span!(
1141 self.defaultness,
1142 "#[wasm_bindgen] default impls are not supported"
1143 );
1144 }
1145 if self.unsafety.is_some() {
1146 bail_span!(
1147 self.unsafety,
1148 "#[wasm_bindgen] unsafe impls are not supported"
1149 );
1150 }
1151 if let Some((_, path, _)) = &self.trait_ {
1152 bail_span!(path, "#[wasm_bindgen] trait impls are not supported");
1153 }
1154 if !self.generics.params.is_empty() {
1155 bail_span!(
1156 self.generics,
1157 "#[wasm_bindgen] generic impls aren't supported"
1158 );
1159 }
1160 let name = match get_ty(&self.self_ty) {
1161 syn::Type::Path(syn::TypePath {
1162 qself: None,
1163 ref path,
1164 }) => path,
1165 _ => bail_span!(
1166 self.self_ty,
1167 "unsupported self type in #[wasm_bindgen] impl"
1168 ),
1169 };
1170 let mut errors = Vec::new();
1171 for item in self.items.iter_mut() {
1172 if let Err(e) = prepare_for_impl_recursion(item, name, program, &opts) {
1173 errors.push(e);
1174 }
1175 }
1176 Diagnostic::from_vec(errors)?;
1177 opts.check_used();
1178 Ok(())
1179 }
1180}
1181
1182fn prepare_for_impl_recursion(
1191 item: &mut syn::ImplItem,
1192 class: &syn::Path,
1193 program: &ast::Program,
1194 impl_opts: &BindgenAttrs,
1195) -> Result<(), Diagnostic> {
1196 let method = match item {
1197 syn::ImplItem::Fn(m) => m,
1198 syn::ImplItem::Const(_) => {
1199 bail_span!(
1200 &*item,
1201 "const definitions aren't supported with #[wasm_bindgen]"
1202 );
1203 }
1204 syn::ImplItem::Type(_) => bail_span!(
1205 &*item,
1206 "type definitions in impls aren't supported with #[wasm_bindgen]"
1207 ),
1208 syn::ImplItem::Macro(_) => {
1209 bail_span!(&*item, "macros in impls aren't supported");
1214 }
1215 syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
1216 other => bail_span!(other, "failed to parse this item as a known item"),
1217 };
1218
1219 let ident = extract_path_ident(class)?;
1220
1221 let js_class = impl_opts
1222 .js_class()
1223 .map(|s| s.0.to_string())
1224 .unwrap_or(ident.to_string());
1225
1226 let wasm_bindgen = &program.wasm_bindgen;
1227 let wasm_bindgen_futures = &program.wasm_bindgen_futures;
1228 method.attrs.insert(
1229 0,
1230 syn::Attribute {
1231 pound_token: Default::default(),
1232 style: syn::AttrStyle::Outer,
1233 bracket_token: Default::default(),
1234 meta: syn::parse_quote! { #wasm_bindgen::prelude::__wasm_bindgen_class_marker(#class = #js_class, wasm_bindgen = #wasm_bindgen, wasm_bindgen_futures = #wasm_bindgen_futures) },
1235 },
1236 );
1237
1238 Ok(())
1239}
1240
1241impl<'a> MacroParse<&ClassMarker> for &'a mut syn::ImplItemFn {
1242 fn macro_parse(
1243 self,
1244 program: &mut ast::Program,
1245 ClassMarker {
1246 class,
1247 js_class,
1248 wasm_bindgen,
1249 wasm_bindgen_futures,
1250 }: &ClassMarker,
1251 ) -> Result<(), Diagnostic> {
1252 program.wasm_bindgen = wasm_bindgen.clone();
1253 program.wasm_bindgen_futures = wasm_bindgen_futures.clone();
1254
1255 match self.vis {
1256 syn::Visibility::Public(_) => {}
1257 _ => return Ok(()),
1258 }
1259 if self.defaultness.is_some() {
1260 panic!("default methods are not supported");
1261 }
1262 if self.sig.constness.is_some() {
1263 bail_span!(
1264 self.sig.constness,
1265 "can only #[wasm_bindgen] non-const functions",
1266 );
1267 }
1268
1269 let opts = BindgenAttrs::find(&mut self.attrs)?;
1270 let comments = extract_doc_comments(&self.attrs);
1271 let (function, method_self) = function_from_decl(
1272 &self.sig.ident,
1273 &opts,
1274 self.sig.clone(),
1275 self.attrs.clone(),
1276 self.vis.clone(),
1277 true,
1278 Some(class),
1279 true,
1280 None,
1281 )?;
1282 let method_kind = if opts.constructor().is_some() {
1283 ast::MethodKind::Constructor
1284 } else {
1285 let is_static = method_self.is_none();
1286 let kind = operation_kind(&opts);
1287 ast::MethodKind::Operation(ast::Operation { is_static, kind })
1288 };
1289 program.exports.push(ast::Export {
1290 comments,
1291 function,
1292 js_class: Some(js_class.to_string()),
1293 method_kind,
1294 method_self,
1295 rust_class: Some(class.clone()),
1296 rust_name: self.sig.ident.clone(),
1297 start: false,
1298 wasm_bindgen: program.wasm_bindgen.clone(),
1299 wasm_bindgen_futures: program.wasm_bindgen_futures.clone(),
1300 });
1301 opts.check_used();
1302 Ok(())
1303 }
1304}
1305
1306fn string_enum(enum_: syn::ItemEnum, program: &mut ast::Program) -> Result<(), Diagnostic> {
1307 let mut variants = vec![];
1308 let mut variant_values = vec![];
1309
1310 for v in enum_.variants.iter() {
1311 match v.fields {
1312 syn::Fields::Unit => (),
1313 _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"),
1314 }
1315
1316 let (_, expr) = match &v.discriminant {
1317 Some(pair) => pair,
1318 None => {
1319 bail_span!(v, "all variants must have a value");
1320 }
1321 };
1322 match get_expr(expr) {
1323 syn::Expr::Lit(syn::ExprLit {
1324 attrs: _,
1325 lit: syn::Lit::Str(str_lit),
1326 }) => {
1327 variants.push(v.ident.clone());
1328 variant_values.push(str_lit.value());
1329 }
1330 expr => bail_span!(
1331 expr,
1332 "enums with #[wasm_bindgen] cannot mix string and non-string values",
1333 ),
1334 }
1335 }
1336
1337 program.imports.push(ast::Import {
1338 module: None,
1339 js_namespace: None,
1340 kind: ast::ImportKind::Enum(ast::StringEnum {
1341 vis: enum_.vis,
1342 name: enum_.ident,
1343 variants,
1344 variant_values,
1345 rust_attrs: enum_.attrs,
1346 wasm_bindgen: program.wasm_bindgen.clone(),
1347 }),
1348 });
1349
1350 Ok(())
1351}
1352
1353impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
1354 fn macro_parse(
1355 self,
1356 program: &mut ast::Program,
1357 (tokens, opts): (&'a mut TokenStream, BindgenAttrs),
1358 ) -> Result<(), Diagnostic> {
1359 if self.variants.is_empty() {
1360 bail_span!(self, "cannot export empty enums to JS");
1361 }
1362 let generate_typescript = opts.skip_typescript().is_none();
1363
1364 if let Some((_, expr)) = &self.variants[0].discriminant {
1366 if let syn::Expr::Lit(syn::ExprLit {
1367 lit: syn::Lit::Str(_),
1368 ..
1369 }) = get_expr(expr)
1370 {
1371 opts.check_used();
1372 return string_enum(self, program);
1373 }
1374 }
1375 let js_name = opts
1376 .js_name()
1377 .map(|s| s.0)
1378 .map_or_else(|| self.ident.to_string(), |s| s.to_string());
1379 opts.check_used();
1380
1381 let has_discriminant = self.variants[0].discriminant.is_some();
1382
1383 match self.vis {
1384 syn::Visibility::Public(_) => {}
1385 _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
1386 }
1387
1388 let variants = self
1389 .variants
1390 .iter()
1391 .enumerate()
1392 .map(|(i, v)| {
1393 match v.fields {
1394 syn::Fields::Unit => (),
1395 _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"),
1396 }
1397
1398 if v.discriminant.is_some() != has_discriminant {
1402 bail_span!(
1403 v,
1404 "must either annotate discriminant of all variants or none"
1405 );
1406 }
1407
1408 let value = match &v.discriminant {
1409 Some((_, expr)) => match get_expr(expr) {
1410 syn::Expr::Lit(syn::ExprLit {
1411 attrs: _,
1412 lit: syn::Lit::Int(int_lit),
1413 }) => match int_lit.base10_digits().parse::<u32>() {
1414 Ok(v) => v,
1415 Err(_) => {
1416 bail_span!(
1417 int_lit,
1418 "enums with #[wasm_bindgen] can only support \
1419 numbers that can be represented as u32"
1420 );
1421 }
1422 },
1423 expr => bail_span!(
1424 expr,
1425 "enums with #[wasm_bindgen] may only have \
1426 number literal values",
1427 ),
1428 },
1429 None => i as u32,
1430 };
1431
1432 let comments = extract_doc_comments(&v.attrs);
1433 Ok(ast::Variant {
1434 name: v.ident.clone(),
1435 value,
1436 comments,
1437 })
1438 })
1439 .collect::<Result<Vec<_>, Diagnostic>>()?;
1440
1441 let mut values = variants.iter().map(|v| v.value).collect::<Vec<_>>();
1442 values.sort();
1443 let hole = values
1444 .windows(2)
1445 .find_map(|window| {
1446 if window[0] + 1 != window[1] {
1447 Some(window[0] + 1)
1448 } else {
1449 None
1450 }
1451 })
1452 .unwrap_or(*values.last().unwrap() + 1);
1453 for value in values {
1454 assert!(hole != value);
1455 }
1456
1457 let comments = extract_doc_comments(&self.attrs);
1458
1459 self.to_tokens(tokens);
1460
1461 program.enums.push(ast::Enum {
1462 rust_name: self.ident,
1463 js_name,
1464 variants,
1465 comments,
1466 hole,
1467 generate_typescript,
1468 wasm_bindgen: program.wasm_bindgen.clone(),
1469 });
1470 Ok(())
1471 }
1472}
1473
1474impl MacroParse<BindgenAttrs> for syn::ItemConst {
1475 fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1476 if opts.typescript_custom_section().is_none() {
1478 bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)].");
1479 }
1480
1481 let typescript_custom_section = match get_expr(&self.expr) {
1482 syn::Expr::Lit(syn::ExprLit {
1483 lit: syn::Lit::Str(litstr),
1484 ..
1485 }) => ast::LitOrExpr::Lit(litstr.value()),
1486 expr => ast::LitOrExpr::Expr(expr.clone()),
1487 };
1488
1489 program
1490 .typescript_custom_sections
1491 .push(typescript_custom_section);
1492
1493 opts.check_used();
1494
1495 Ok(())
1496 }
1497}
1498
1499impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
1500 fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
1501 let mut errors = Vec::new();
1502 if let Some(other) = self.abi.name.filter(|l| l.value() != "C") {
1503 errors.push(err_span!(
1504 other,
1505 "only foreign mods with the `C` ABI are allowed"
1506 ));
1507 }
1508 let js_namespace = opts.js_namespace().map(|(s, _)| s.to_owned());
1509 let module = module_from_opts(program, &opts)
1510 .map_err(|e| errors.push(e))
1511 .unwrap_or_default();
1512 for item in self.items.into_iter() {
1513 let ctx = ForeignItemCtx {
1514 module: module.clone(),
1515 js_namespace: js_namespace.clone(),
1516 };
1517 if let Err(e) = item.macro_parse(program, ctx) {
1518 errors.push(e);
1519 }
1520 }
1521 Diagnostic::from_vec(errors)?;
1522 opts.check_used();
1523 Ok(())
1524 }
1525}
1526
1527struct ForeignItemCtx {
1528 module: Option<ast::ImportModule>,
1529 js_namespace: Option<Vec<String>>,
1530}
1531
1532impl MacroParse<ForeignItemCtx> for syn::ForeignItem {
1533 fn macro_parse(
1534 mut self,
1535 program: &mut ast::Program,
1536 ctx: ForeignItemCtx,
1537 ) -> Result<(), Diagnostic> {
1538 let item_opts = {
1539 let attrs = match self {
1540 syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
1541 syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
1542 syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
1543 syn::ForeignItem::Verbatim(v) => {
1544 let mut item: syn::ItemStatic =
1545 syn::parse(v.into()).expect("only foreign functions/types allowed for now");
1546 let item_opts = BindgenAttrs::find(&mut item.attrs)?;
1547 let kind = item.convert((program, item_opts, &ctx.module))?;
1548
1549 program.imports.push(ast::Import {
1550 module: None,
1551 js_namespace: None,
1552 kind,
1553 });
1554
1555 return Ok(());
1556 }
1557 _ => panic!("only foreign functions/types allowed for now"),
1558 };
1559 BindgenAttrs::find(attrs)?
1560 };
1561
1562 let js_namespace = item_opts
1563 .js_namespace()
1564 .map(|(s, _)| s.to_owned())
1565 .or(ctx.js_namespace);
1566 let module = ctx.module;
1567
1568 let kind = match self {
1569 syn::ForeignItem::Fn(f) => f.convert((program, item_opts, &module))?,
1570 syn::ForeignItem::Type(t) => t.convert((program, item_opts))?,
1571 syn::ForeignItem::Static(s) => s.convert((program, item_opts, &module))?,
1572 _ => panic!("only foreign functions/types allowed for now"),
1573 };
1574
1575 program.imports.push(ast::Import {
1576 module,
1577 js_namespace,
1578 kind,
1579 });
1580
1581 Ok(())
1582 }
1583}
1584
1585pub fn module_from_opts(
1586 program: &mut ast::Program,
1587 opts: &BindgenAttrs,
1588) -> Result<Option<ast::ImportModule>, Diagnostic> {
1589 if let Some(path) = opts.wasm_bindgen() {
1590 program.wasm_bindgen = path.clone();
1591 }
1592
1593 if let Some(path) = opts.js_sys() {
1594 program.js_sys = path.clone();
1595 }
1596
1597 if let Some(path) = opts.wasm_bindgen_futures() {
1598 program.wasm_bindgen_futures = path.clone();
1599 }
1600
1601 let mut errors = Vec::new();
1602 let module = if let Some((name, span)) = opts.module() {
1603 if opts.inline_js().is_some() {
1604 let msg = "cannot specify both `module` and `inline_js`";
1605 errors.push(Diagnostic::span_error(span, msg));
1606 }
1607 if opts.raw_module().is_some() {
1608 let msg = "cannot specify both `module` and `raw_module`";
1609 errors.push(Diagnostic::span_error(span, msg));
1610 }
1611 Some(ast::ImportModule::Named(name.to_string(), span))
1612 } else if let Some((name, span)) = opts.raw_module() {
1613 if opts.inline_js().is_some() {
1614 let msg = "cannot specify both `raw_module` and `inline_js`";
1615 errors.push(Diagnostic::span_error(span, msg));
1616 }
1617 Some(ast::ImportModule::RawNamed(name.to_string(), span))
1618 } else if let Some((js, span)) = opts.inline_js() {
1619 let i = program.inline_js.len();
1620 program.inline_js.push(js.to_string());
1621 Some(ast::ImportModule::Inline(i, span))
1622 } else {
1623 None
1624 };
1625 Diagnostic::from_vec(errors)?;
1626 Ok(module)
1627}
1628
1629fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, Diagnostic> {
1631 let t = match ty {
1632 Some(t) => t,
1633 None => return Ok(None),
1634 };
1635 let path = match *get_ty(t) {
1636 syn::Type::Path(syn::TypePath {
1637 qself: None,
1638 ref path,
1639 }) => path,
1640 _ => bail_span!(t, "must be Result<...>"),
1641 };
1642 let seg = path
1643 .segments
1644 .last()
1645 .ok_or_else(|| err_span!(t, "must have at least one segment"))?;
1646 let generics = match seg.arguments {
1647 syn::PathArguments::AngleBracketed(ref t) => t,
1648 _ => bail_span!(t, "must be Result<...>"),
1649 };
1650 let generic = generics
1651 .args
1652 .first()
1653 .ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?;
1654 let ty = match generic {
1655 syn::GenericArgument::Type(t) => t,
1656 other => bail_span!(other, "must be a type parameter"),
1657 };
1658 match get_ty(ty) {
1659 syn::Type::Tuple(t) if t.elems.is_empty() => return Ok(None),
1660 _ => {}
1661 }
1662 Ok(Some(ty.clone()))
1663}
1664
1665fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
1667 attrs
1668 .iter()
1669 .filter_map(|a| {
1670 if a.path().segments.iter().any(|s| s.ident == "doc") {
1673 let tokens = match &a.meta {
1674 syn::Meta::Path(_) => None,
1675 syn::Meta::List(list) => Some(list.tokens.clone()),
1676 syn::Meta::NameValue(name_value) => Some(name_value.value.to_token_stream()),
1677 };
1678
1679 Some(
1680 tokens.into_iter().flatten().filter_map(|t| match t {
1682 TokenTree::Literal(lit) => {
1683 let quoted = lit.to_string();
1684 Some(try_unescape("ed).unwrap_or(quoted))
1685 }
1686 _ => None,
1687 }),
1688 )
1689 } else {
1690 None
1691 }
1692 })
1693 .fold(vec![], |mut acc, a| {
1695 acc.extend(a);
1696 acc
1697 })
1698}
1699
1700fn try_unescape(mut s: &str) -> Option<String> {
1702 s = s.strip_prefix('"').unwrap_or(s);
1703 s = s.strip_suffix('"').unwrap_or(s);
1704 let mut result = String::with_capacity(s.len());
1705 let mut chars = s.chars();
1706 while let Some(c) = chars.next() {
1707 if c == '\\' {
1708 let c = chars.next()?;
1709 match c {
1710 't' => result.push('\t'),
1711 'r' => result.push('\r'),
1712 'n' => result.push('\n'),
1713 '\\' | '\'' | '"' => result.push(c),
1714 'u' => {
1715 if chars.next() != Some('{') {
1716 return None;
1717 }
1718 let (c, next) = unescape_unicode(&mut chars)?;
1719 result.push(c);
1720 if next != '}' {
1721 return None;
1722 }
1723 }
1724 _ => return None,
1725 }
1726 } else {
1727 result.push(c);
1728 }
1729 }
1730 Some(result)
1731}
1732
1733fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
1734 let mut value = 0;
1735 for (i, c) in chars.enumerate() {
1736 match (i, c.to_digit(16)) {
1737 (0..=5, Some(num)) => value = (value << 4) | num,
1738 (1.., None) => return Some((char::from_u32(value)?, c)),
1739 _ => break,
1740 }
1741 }
1742 None
1743}
1744
1745fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> {
1747 struct Walk {
1748 diagnostics: Vec<Diagnostic>,
1749 }
1750
1751 impl<'ast> syn::visit::Visit<'ast> for Walk {
1752 fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
1753 self.diagnostics.push(err_span!(
1754 i,
1755 "it is currently not sound to use lifetimes in function \
1756 signatures"
1757 ));
1758 }
1759 }
1760 let mut walk = Walk {
1761 diagnostics: Vec::new(),
1762 };
1763 syn::visit::Visit::visit_signature(&mut walk, sig);
1764 Diagnostic::from_vec(walk.diagnostics)
1765}
1766
1767fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> {
1769 for segment in path.segments.iter() {
1770 match segment.arguments {
1771 syn::PathArguments::None => {}
1772 _ => bail_span!(path, "paths with type parameters are not supported yet"),
1773 }
1774 }
1775
1776 match path.segments.last() {
1777 Some(value) => Ok(value.ident.clone()),
1778 None => {
1779 bail_span!(path, "empty idents are not supported");
1780 }
1781 }
1782}
1783
1784pub fn reset_attrs_used() {
1785 ATTRS.with(|state| {
1786 state.parsed.set(0);
1787 state.checks.set(0);
1788 state.unused_attrs.borrow_mut().clear();
1789 })
1790}
1791
1792pub fn check_unused_attrs(tokens: &mut TokenStream) {
1793 ATTRS.with(|state| {
1794 assert_eq!(state.parsed.get(), state.checks.get());
1795 let unused_attrs = &*state.unused_attrs.borrow();
1796 if !unused_attrs.is_empty() {
1797 tokens.extend(quote::quote! {
1798 const _: () = {
1800 #(let #unused_attrs: ();)*
1801 };
1802 });
1803 }
1804 })
1805}
1806
1807fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind {
1808 let mut operation_kind = ast::OperationKind::Regular;
1809 if let Some(g) = opts.getter() {
1810 operation_kind = ast::OperationKind::Getter(g.clone());
1811 }
1812 if let Some(s) = opts.setter() {
1813 operation_kind = ast::OperationKind::Setter(s.clone());
1814 }
1815 if opts.indexing_getter().is_some() {
1816 operation_kind = ast::OperationKind::IndexingGetter;
1817 }
1818 if opts.indexing_setter().is_some() {
1819 operation_kind = ast::OperationKind::IndexingSetter;
1820 }
1821 if opts.indexing_deleter().is_some() {
1822 operation_kind = ast::OperationKind::IndexingDeleter;
1823 }
1824 operation_kind
1825}
1826
1827pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
1828 let mut program = ast::Program::default();
1829 let module = module_from_opts(&mut program, &opts)?.ok_or_else(|| {
1830 Diagnostic::span_error(Span::call_site(), "`link_to!` requires a module.")
1831 })?;
1832 if let ast::ImportModule::Named(p, s) | ast::ImportModule::RawNamed(p, s) = &module {
1833 if !p.starts_with("./") && !p.starts_with("../") && !p.starts_with('/') {
1834 return Err(Diagnostic::span_error(
1835 *s,
1836 "`link_to!` does not support module paths.",
1837 ));
1838 }
1839 }
1840 opts.enforce_used()?;
1841 program.linked_modules.push(module);
1842 Ok(ast::LinkToModule(program))
1843}
1844
1845fn main(program: &ast::Program, mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
1846 if f.sig.ident != "main" {
1847 bail_span!(&f.sig.ident, "the main function has to be called main");
1848 }
1849 if let Some(constness) = f.sig.constness {
1850 bail_span!(&constness, "the main function cannot be const");
1851 }
1852 if !f.sig.generics.params.is_empty() {
1853 bail_span!(&f.sig.generics, "the main function cannot have generics");
1854 }
1855 if !f.sig.inputs.is_empty() {
1856 bail_span!(&f.sig.inputs, "the main function cannot have arguments");
1857 }
1858
1859 let r#return = f.sig.output;
1860 f.sig.output = ReturnType::Default;
1861 let body = f.block;
1862
1863 let wasm_bindgen = &program.wasm_bindgen;
1864 let wasm_bindgen_futures = &program.wasm_bindgen_futures;
1865
1866 if f.sig.asyncness.take().is_some() {
1867 f.block = Box::new(
1868 syn::parse2(quote::quote! {
1869 {
1870 async fn __wasm_bindgen_generated_main() #r#return #body
1871 #wasm_bindgen_futures::spawn_local(
1872 async move {
1873 use #wasm_bindgen::__rt::Main;
1874 let __ret = __wasm_bindgen_generated_main();
1875 (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main()
1876 },
1877 )
1878 }
1879 })
1880 .unwrap(),
1881 );
1882 } else {
1883 f.block = Box::new(
1884 syn::parse2(quote::quote! {
1885 {
1886 fn __wasm_bindgen_generated_main() #r#return #body
1887 use #wasm_bindgen::__rt::Main;
1888 let __ret = __wasm_bindgen_generated_main();
1889 (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main()
1890 }
1891 })
1892 .unwrap(),
1893 );
1894 }
1895
1896 f.to_tokens(tokens);
1897
1898 Ok(())
1899}
1900
1901#[cfg(test)]
1902mod tests {
1903 #[test]
1904 fn test_try_unescape() {
1905 use super::try_unescape;
1906 assert_eq!(try_unescape("hello").unwrap(), "hello");
1907 assert_eq!(try_unescape("\"hello").unwrap(), "hello");
1908 assert_eq!(try_unescape("hello\"").unwrap(), "hello");
1909 assert_eq!(try_unescape("\"hello\"").unwrap(), "hello");
1910 assert_eq!(try_unescape("hello\\\\").unwrap(), "hello\\");
1911 assert_eq!(try_unescape("hello\\n").unwrap(), "hello\n");
1912 assert_eq!(try_unescape("hello\\u"), None);
1913 assert_eq!(try_unescape("hello\\u{"), None);
1914 assert_eq!(try_unescape("hello\\u{}"), None);
1915 assert_eq!(try_unescape("hello\\u{0}").unwrap(), "hello\0");
1916 assert_eq!(try_unescape("hello\\u{000000}").unwrap(), "hello\0");
1917 assert_eq!(try_unescape("hello\\u{0000000}"), None);
1918 }
1919}