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