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