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