From 6702dd635c3ac191cb5a1a13445f8472f4c3181d Mon Sep 17 00:00:00 2001 From: su-fang Date: Thu, 9 Mar 2023 10:52:56 +0800 Subject: [PATCH] Import Upstream version 0.2.83 --- Cargo.toml | 47 ++ Cargo.toml.orig | 24 + LICENSE-APACHE | 201 ++++++ LICENSE-MIT | 25 + src/lib.rs | 105 +++ src/parser.rs | 1651 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 2053 insertions(+) create mode 100644 Cargo.toml create mode 100644 Cargo.toml.orig create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 src/lib.rs create mode 100644 src/parser.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..378973e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,47 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "wasm-bindgen-macro-support" +version = "0.2.83" +authors = ["The wasm-bindgen Developers"] +description = """ +The part of the implementation of the `#[wasm_bindgen]` attribute that is not in the shared backend crate +""" +homepage = "https://rustwasm.github.io/wasm-bindgen/" +documentation = "https://docs.rs/wasm-bindgen" +license = "MIT/Apache-2.0" +repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support" + +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "1.0.67" +features = [ + "visit", + "full", +] + +[dependencies.wasm-bindgen-backend] +version = "=0.2.83" + +[dependencies.wasm-bindgen-shared] +version = "=0.2.83" + +[features] +extra-traits = ["syn/extra-traits"] +spans = ["wasm-bindgen-backend/spans"] +strict-macro = [] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..39448b1 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,24 @@ +[package] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +authors = ["The wasm-bindgen Developers"] +license = "MIT/Apache-2.0" +repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support" +homepage = "https://rustwasm.github.io/wasm-bindgen/" +documentation = "https://docs.rs/wasm-bindgen" +description = """ +The part of the implementation of the `#[wasm_bindgen]` attribute that is not in the shared backend crate +""" +edition = '2018' + +[features] +spans = ["wasm-bindgen-backend/spans"] +extra-traits = ["syn/extra-traits"] +strict-macro = [] + +[dependencies] +syn = { version = '1.0.67', features = ['visit', 'full'] } +quote = '1.0' +proc-macro2 = "1.0" +wasm-bindgen-backend = { path = "../backend", version = "=0.2.83" } +wasm-bindgen-shared = { path = "../shared", version = "=0.2.83" } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..39e0ed6 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b7a35ae --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,105 @@ +//! This crate contains the part of the implementation of the `#[wasm_bindgen]` optsibute that is +//! not in the shared backend crate. + +#![doc(html_root_url = "https://docs.rs/wasm-bindgen-macro-support/0.2")] + +extern crate proc_macro2; +extern crate quote; +#[macro_use] +extern crate syn; +#[macro_use] +extern crate wasm_bindgen_backend as backend; +extern crate wasm_bindgen_shared as shared; + +pub use crate::parser::BindgenAttrs; +use crate::parser::MacroParse; +use backend::{Diagnostic, TryToTokens}; +use proc_macro2::TokenStream; +use quote::ToTokens; +use quote::TokenStreamExt; +use syn::parse::{Parse, ParseStream, Result as SynResult}; + +mod parser; + +/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings +pub fn expand(attr: TokenStream, input: TokenStream) -> Result { + parser::reset_attrs_used(); + let item = syn::parse2::(input)?; + let opts = syn::parse2(attr)?; + + let mut tokens = proc_macro2::TokenStream::new(); + let mut program = backend::ast::Program::default(); + item.macro_parse(&mut program, (Some(opts), &mut tokens))?; + program.try_to_tokens(&mut tokens)?; + + // If we successfully got here then we should have used up all attributes + // and considered all of them to see if they were used. If one was forgotten + // that's a bug on our end, so sanity check here. + parser::check_unused_attrs(&mut tokens); + + Ok(tokens) +} + +/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings +pub fn expand_class_marker( + attr: TokenStream, + input: TokenStream, +) -> Result { + parser::reset_attrs_used(); + let mut item = syn::parse2::(input)?; + let opts: ClassMarker = syn::parse2(attr)?; + + let mut program = backend::ast::Program::default(); + item.macro_parse(&mut program, (&opts.class, &opts.js_class))?; + + // This is where things are slightly different, we are being expanded in the + // context of an impl so we can't inject arbitrary item-like tokens into the + // output stream. If we were to do that then it wouldn't parse! + // + // Instead what we want to do is to generate the tokens for `program` into + // the header of the function. This'll inject some no_mangle functions and + // statics and such, and they should all be valid in the context of the + // start of a function. + // + // We manually implement `ToTokens for ImplItemMethod` here, injecting our + // program's tokens before the actual method's inner body tokens. + let mut tokens = proc_macro2::TokenStream::new(); + tokens.append_all(item.attrs.iter().filter(|attr| match attr.style { + syn::AttrStyle::Outer => true, + _ => false, + })); + item.vis.to_tokens(&mut tokens); + item.sig.to_tokens(&mut tokens); + let mut err = None; + item.block.brace_token.surround(&mut tokens, |tokens| { + if let Err(e) = program.try_to_tokens(tokens) { + err = Some(e); + } + parser::check_unused_attrs(tokens); // same as above + tokens.append_all(item.attrs.iter().filter(|attr| match attr.style { + syn::AttrStyle::Inner(_) => true, + _ => false, + })); + tokens.append_all(&item.block.stmts); + }); + + if let Some(err) = err { + return Err(err); + } + + Ok(tokens) +} + +struct ClassMarker { + class: syn::Ident, + js_class: String, +} + +impl Parse for ClassMarker { + fn parse(input: ParseStream) -> SynResult { + let class = input.parse::()?; + input.parse::()?; + let js_class = input.parse::()?.value(); + Ok(ClassMarker { class, js_class }) + } +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..d05f139 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,1651 @@ +use std::cell::{Cell, RefCell}; +use std::char; +use std::str::Chars; + +use ast::OperationKind; +use backend::ast; +use backend::util::{ident_ty, ShortHash}; +use backend::Diagnostic; +use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree}; +use quote::ToTokens; +use syn::parse::{Parse, ParseStream, Result as SynResult}; +use syn::spanned::Spanned; +use syn::Lit; + +thread_local!(static ATTRS: AttributeParseState = Default::default()); + +/// Javascript keywords which are not keywords in Rust. +const JS_KEYWORDS: [&str; 20] = [ + "class", + "case", + "catch", + "debugger", + "default", + "delete", + "export", + "extends", + "finally", + "function", + "import", + "instanceof", + "new", + "null", + "switch", + "this", + "throw", + "var", + "void", + "with", +]; +#[derive(Default)] +struct AttributeParseState { + parsed: Cell, + checks: Cell, + unused_attrs: RefCell>, +} + +/// Parsed attributes from a `#[wasm_bindgen(..)]`. +#[cfg_attr(feature = "extra-traits", derive(Debug))] +pub struct BindgenAttrs { + /// List of parsed attributes + pub attrs: Vec<(Cell, BindgenAttr)>, +} + +macro_rules! attrgen { + ($mac:ident) => { + $mac! { + (catch, Catch(Span)), + (constructor, Constructor(Span)), + (method, Method(Span)), + (static_method_of, StaticMethodOf(Span, Ident)), + (js_namespace, JsNamespace(Span, Vec, Vec)), + (module, Module(Span, String, Span)), + (raw_module, RawModule(Span, String, Span)), + (inline_js, InlineJs(Span, String, Span)), + (getter, Getter(Span, Option)), + (setter, Setter(Span, Option)), + (indexing_getter, IndexingGetter(Span)), + (indexing_setter, IndexingSetter(Span)), + (indexing_deleter, IndexingDeleter(Span)), + (structural, Structural(Span)), + (r#final, Final(Span)), + (readonly, Readonly(Span)), + (js_name, JsName(Span, String, Span)), + (js_class, JsClass(Span, String, Span)), + (inspectable, Inspectable(Span)), + (is_type_of, IsTypeOf(Span, syn::Expr)), + (extends, Extends(Span, syn::Path)), + (no_deref, NoDeref(Span)), + (vendor_prefix, VendorPrefix(Span, Ident)), + (variadic, Variadic(Span)), + (typescript_custom_section, TypescriptCustomSection(Span)), + (skip_typescript, SkipTypescript(Span)), + (start, Start(Span)), + (skip, Skip(Span)), + (typescript_type, TypeScriptType(Span, String, Span)), + (getter_with_clone, GetterWithClone(Span)), + + // For testing purposes only. + (assert_no_shim, AssertNoShim(Span)), + } + }; +} + +macro_rules! methods { + ($(($name:ident, $variant:ident($($contents:tt)*)),)*) => { + $(methods!(@method $name, $variant($($contents)*));)* + + fn check_used(self) { + // Account for the fact this method was called + ATTRS.with(|state| { + state.checks.set(state.checks.get() + 1); + + state.unused_attrs.borrow_mut().extend( + self.attrs + .iter() + .filter_map(|(used, attr)| if used.get() { None } else { Some(attr) }) + .map(|attr| { + match attr { + $(BindgenAttr::$variant(span, ..) => { + syn::parse_quote_spanned!(*span => $name) + })* + } + }) + ); + }); + } + }; + + (@method $name:ident, $variant:ident(Span, String, Span)) => { + fn $name(&self) -> Option<(&str, Span)> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(_, s, span) => { + a.0.set(true); + Some((&s[..], *span)) + } + _ => None, + }) + .next() + } + }; + + (@method $name:ident, $variant:ident(Span, Vec, Vec)) => { + fn $name(&self) -> Option<(&[String], &[Span])> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(_, ss, spans) => { + a.0.set(true); + Some((&ss[..], &spans[..])) + } + _ => None, + }) + .next() + } + }; + + (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => { + #[allow(unused)] + fn $name(&self) -> Option<&$($other)*> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(_, s) => { + a.0.set(true); + Some(s) + } + _ => None, + }) + .next() + } + }; + + (@method $name:ident, $variant:ident($($other:tt)*)) => { + #[allow(unused)] + fn $name(&self) -> Option<&$($other)*> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(s) => { + a.0.set(true); + Some(s) + } + _ => None, + }) + .next() + } + }; +} + +impl BindgenAttrs { + /// Find and parse the wasm_bindgen attributes. + fn find(attrs: &mut Vec) -> Result { + let mut ret = BindgenAttrs::default(); + loop { + let pos = attrs + .iter() + .enumerate() + .find(|&(_, ref m)| m.path.segments[0].ident == "wasm_bindgen") + .map(|a| a.0); + let pos = match pos { + Some(i) => i, + None => return Ok(ret), + }; + let attr = attrs.remove(pos); + let mut tts = attr.tokens.clone().into_iter(); + let group = match tts.next() { + Some(TokenTree::Group(d)) => d, + Some(_) => bail_span!(attr, "malformed #[wasm_bindgen] attribute"), + None => continue, + }; + if tts.next().is_some() { + bail_span!(attr, "malformed #[wasm_bindgen] attribute"); + } + if group.delimiter() != Delimiter::Parenthesis { + bail_span!(attr, "malformed #[wasm_bindgen] attribute"); + } + let mut attrs: BindgenAttrs = syn::parse2(group.stream())?; + ret.attrs.extend(attrs.attrs.drain(..)); + attrs.check_used(); + } + } + + attrgen!(methods); +} + +impl Default for BindgenAttrs { + fn default() -> BindgenAttrs { + // Add 1 to the list of parsed attribute sets. We'll use this counter to + // sanity check that we call `check_used` an appropriate number of + // times. + ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1)); + BindgenAttrs { attrs: Vec::new() } + } +} + +impl Parse for BindgenAttrs { + fn parse(input: ParseStream) -> SynResult { + let mut attrs = BindgenAttrs::default(); + if input.is_empty() { + return Ok(attrs); + } + + let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?; + attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect(); + Ok(attrs) + } +} + +macro_rules! gen_bindgen_attr { + ($(($method:ident, $($variants:tt)*),)*) => { + /// The possible attributes in the `#[wasm_bindgen]`. + #[cfg_attr(feature = "extra-traits", derive(Debug))] + pub enum BindgenAttr { + $($($variants)*,)* + } + } +} +attrgen!(gen_bindgen_attr); + +impl Parse for BindgenAttr { + fn parse(input: ParseStream) -> SynResult { + let original = input.fork(); + let attr: AnyIdent = input.parse()?; + let attr = attr.0; + let attr_span = attr.span(); + let attr_string = attr.to_string(); + let raw_attr_string = format!("r#{}", attr_string); + + macro_rules! parsers { + ($(($name:ident, $($contents:tt)*),)*) => { + $( + if attr_string == stringify!($name) || raw_attr_string == stringify!($name) { + parsers!( + @parser + $($contents)* + ); + } + )* + }; + + (@parser $variant:ident(Span)) => ({ + return Ok(BindgenAttr::$variant(attr_span)); + }); + + (@parser $variant:ident(Span, Ident)) => ({ + input.parse::()?; + let ident = input.parse::()?.0; + return Ok(BindgenAttr::$variant(attr_span, ident)) + }); + + (@parser $variant:ident(Span, Option)) => ({ + if input.parse::().is_ok() { + let ident = input.parse::()?.0; + return Ok(BindgenAttr::$variant(attr_span, Some(ident))) + } else { + return Ok(BindgenAttr::$variant(attr_span, None)); + } + }); + + (@parser $variant:ident(Span, syn::Path)) => ({ + input.parse::()?; + return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); + }); + + (@parser $variant:ident(Span, syn::Expr)) => ({ + input.parse::()?; + return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); + }); + + (@parser $variant:ident(Span, String, Span)) => ({ + input.parse::()?; + let (val, span) = match input.parse::() { + Ok(str) => (str.value(), str.span()), + Err(_) => { + let ident = input.parse::()?.0; + (ident.to_string(), ident.span()) + } + }; + return Ok(BindgenAttr::$variant(attr_span, val, span)) + }); + + (@parser $variant:ident(Span, Vec, Vec)) => ({ + input.parse::()?; + let (vals, spans) = match input.parse::() { + Ok(exprs) => { + let mut vals = vec![]; + let mut spans = vec![]; + + for expr in exprs.elems.iter() { + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(ref str), + .. + }) = expr { + vals.push(str.value()); + spans.push(str.span()); + } else { + return Err(syn::Error::new(expr.span(), "expected string literals")); + } + } + + (vals, spans) + }, + Err(_) => { + let ident = input.parse::()?.0; + (vec![ident.to_string()], vec![ident.span()]) + } + }; + return Ok(BindgenAttr::$variant(attr_span, vals, spans)) + }); + } + + attrgen!(parsers); + + return Err(original.error(if attr_string.starts_with("_") { + "unknown attribute: it's safe to remove unused attributes entirely." + } else { + "unknown attribute" + })); + } +} + +struct AnyIdent(Ident); + +impl Parse for AnyIdent { + fn parse(input: ParseStream) -> SynResult { + input.step(|cursor| match cursor.ident() { + Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)), + None => Err(cursor.error("expected an identifier")), + }) + } +} + +/// Conversion trait with context. +/// +/// Used to convert syn tokens into an AST, that we can then use to generate glue code. The context +/// (`Ctx`) is used to pass in the attributes from the `#[wasm_bindgen]`, if needed. +trait ConvertToAst { + /// What we are converting to. + type Target; + /// Convert into our target. + /// + /// Since this is used in a procedural macro, use panic to fail. + fn convert(self, context: Ctx) -> Result; +} + +impl<'a> ConvertToAst for &'a mut syn::ItemStruct { + type Target = ast::Struct; + + fn convert(self, attrs: BindgenAttrs) -> Result { + if self.generics.params.len() > 0 { + bail_span!( + self.generics, + "structs with #[wasm_bindgen] cannot have lifetime or \ + type parameters currently" + ); + } + let mut fields = Vec::new(); + let js_name = attrs + .js_name() + .map(|s| s.0.to_string()) + .unwrap_or(self.ident.to_string()); + let is_inspectable = attrs.inspectable().is_some(); + let getter_with_clone = attrs.getter_with_clone().is_some(); + for (i, field) in self.fields.iter_mut().enumerate() { + match field.vis { + syn::Visibility::Public(..) => {} + _ => continue, + } + let (js_field_name, member) = match &field.ident { + Some(ident) => (ident.to_string(), syn::Member::Named(ident.clone())), + None => (i.to_string(), syn::Member::Unnamed(i.into())), + }; + + let attrs = BindgenAttrs::find(&mut field.attrs)?; + if attrs.skip().is_some() { + attrs.check_used(); + continue; + } + + let js_field_name = match attrs.js_name() { + Some((name, _)) => name.to_string(), + None => js_field_name, + }; + + let comments = extract_doc_comments(&field.attrs); + let getter = shared::struct_field_get(&js_name, &js_field_name); + let setter = shared::struct_field_set(&js_name, &js_field_name); + + fields.push(ast::StructField { + rust_name: member, + js_name: js_field_name, + struct_name: self.ident.clone(), + readonly: attrs.readonly().is_some(), + ty: field.ty.clone(), + getter: Ident::new(&getter, Span::call_site()), + setter: Ident::new(&setter, Span::call_site()), + comments, + generate_typescript: attrs.skip_typescript().is_none(), + getter_with_clone: getter_with_clone || attrs.getter_with_clone().is_some(), + }); + attrs.check_used(); + } + let generate_typescript = attrs.skip_typescript().is_none(); + let comments: Vec = extract_doc_comments(&self.attrs); + attrs.check_used(); + Ok(ast::Struct { + rust_name: self.ident.clone(), + js_name, + fields, + comments, + is_inspectable, + generate_typescript, + }) + } +} + +fn get_ty(mut ty: &syn::Type) -> &syn::Type { + while let syn::Type::Group(g) = ty { + ty = &g.elem; + } + + ty +} + +fn get_expr(mut expr: &syn::Expr) -> &syn::Expr { + while let syn::Expr::Group(g) = expr { + expr = &g.expr; + } + + expr +} + +impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn { + type Target = ast::ImportKind; + + fn convert( + self, + (opts, module): (BindgenAttrs, &'a Option), + ) -> Result { + let mut wasm = function_from_decl( + &self.sig.ident, + &opts, + self.sig.clone(), + self.attrs.clone(), + self.vis.clone(), + false, + None, + false, + )? + .0; + let catch = opts.catch().is_some(); + let variadic = opts.variadic().is_some(); + let js_ret = if catch { + // TODO: this assumes a whole bunch: + // + // * The outer type is actually a `Result` + // * The error type is a `JsValue` + // * The actual type is the first type parameter + // + // should probably fix this one day... + extract_first_ty_param(wasm.ret.as_ref())? + } else { + wasm.ret.clone() + }; + + let operation_kind = operation_kind(&opts); + + let kind = if opts.method().is_some() { + let class = wasm.arguments.get(0).ok_or_else(|| { + err_span!(self, "imported methods must have at least one argument") + })?; + let class = match get_ty(&class.ty) { + syn::Type::Reference(syn::TypeReference { + mutability: None, + elem, + .. + }) => &**elem, + _ => bail_span!( + class.ty, + "first argument of method must be a shared reference" + ), + }; + let class_name = match get_ty(class) { + syn::Type::Path(syn::TypePath { + qself: None, + ref path, + }) => path, + _ => bail_span!(class, "first argument of method must be a path"), + }; + let class_name = extract_path_ident(class_name)?; + let class_name = opts + .js_class() + .map(|p| p.0.into()) + .unwrap_or_else(|| class_name.to_string()); + + let kind = ast::MethodKind::Operation(ast::Operation { + is_static: false, + kind: operation_kind, + }); + + ast::ImportFunctionKind::Method { + class: class_name, + ty: class.clone(), + kind, + } + } else if let Some(cls) = opts.static_method_of() { + let class = opts + .js_class() + .map(|p| p.0.into()) + .unwrap_or_else(|| cls.to_string()); + let ty = ident_ty(cls.clone()); + + let kind = ast::MethodKind::Operation(ast::Operation { + is_static: true, + kind: operation_kind, + }); + + ast::ImportFunctionKind::Method { class, ty, kind } + } else if opts.constructor().is_some() { + let class = match js_ret { + Some(ref ty) => ty, + _ => bail_span!(self, "constructor returns must be bare types"), + }; + let class_name = match get_ty(class) { + syn::Type::Path(syn::TypePath { + qself: None, + ref path, + }) => path, + _ => bail_span!(self, "return value of constructor must be a bare path"), + }; + let class_name = extract_path_ident(class_name)?; + let class_name = opts + .js_class() + .map(|p| p.0.into()) + .unwrap_or_else(|| class_name.to_string()); + + ast::ImportFunctionKind::Method { + class: class_name.to_string(), + ty: class.clone(), + kind: ast::MethodKind::Constructor, + } + } else { + ast::ImportFunctionKind::Normal + }; + + let shim = { + let ns = match kind { + ast::ImportFunctionKind::Normal => (0, "n"), + ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]), + }; + let data = (ns, &self.sig.ident, module); + format!( + "__wbg_{}_{}", + wasm.name + .chars() + .filter(|c| c.is_ascii_alphanumeric()) + .collect::(), + ShortHash(data) + ) + }; + if let Some(span) = opts.r#final() { + if opts.structural().is_some() { + let msg = "cannot specify both `structural` and `final`"; + return Err(Diagnostic::span_error(*span, msg)); + } + } + let assert_no_shim = opts.assert_no_shim().is_some(); + + let mut doc_comment = String::new(); + // Extract the doc comments from our list of attributes. + wasm.rust_attrs.retain(|attr| { + struct DocContents { + contents: String, + } + + impl Parse for DocContents { + fn parse(input: ParseStream) -> SynResult { + ::parse(input)?; + match Lit::parse(input)? { + Lit::Str(str) => Ok(Self { + contents: str.value(), + }), + other => Err(syn::Error::new_spanned(other, "expected a string literal")), + } + } + } + + /// Returns the contents of the passed `#[doc = "..."]` attribute, + /// or `None` if it isn't one. + fn get_docs(attr: &syn::Attribute) -> Option { + if attr.path.is_ident("doc") { + syn::parse2::(attr.tokens.clone()) + .ok() + .map(|doc| doc.contents) + } else { + None + } + } + + if let Some(docs) = get_docs(attr) { + if !doc_comment.is_empty() { + // Add newlines between the doc comments + doc_comment.push('\n'); + } + // Add this doc comment to the complete docs + doc_comment.push_str(&docs); + + // Remove it from the list of regular attributes + false + } else { + true + } + }); + + let ret = ast::ImportKind::Function(ast::ImportFunction { + function: wasm, + assert_no_shim, + kind, + js_ret, + catch, + variadic, + structural: opts.structural().is_some() || opts.r#final().is_none(), + rust_name: self.sig.ident.clone(), + shim: Ident::new(&shim, Span::call_site()), + doc_comment, + }); + opts.check_used(); + + Ok(ret) + } +} + +impl ConvertToAst for syn::ForeignItemType { + type Target = ast::ImportKind; + + fn convert(self, attrs: BindgenAttrs) -> Result { + let js_name = attrs + .js_name() + .map(|s| s.0) + .map_or_else(|| self.ident.to_string(), |s| s.to_string()); + let typescript_type = attrs.typescript_type().map(|s| s.0.to_string()); + let is_type_of = attrs.is_type_of().cloned(); + let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident)); + let mut extends = Vec::new(); + let mut vendor_prefixes = Vec::new(); + let no_deref = attrs.no_deref().is_some(); + for (used, attr) in attrs.attrs.iter() { + match attr { + BindgenAttr::Extends(_, e) => { + extends.push(e.clone()); + used.set(true); + } + BindgenAttr::VendorPrefix(_, e) => { + vendor_prefixes.push(e.clone()); + used.set(true); + } + _ => {} + } + } + attrs.check_used(); + Ok(ast::ImportKind::Type(ast::ImportType { + vis: self.vis, + attrs: self.attrs, + doc_comment: None, + instanceof_shim: shim, + is_type_of, + rust_name: self.ident, + typescript_type, + js_name, + extends, + vendor_prefixes, + no_deref, + })) + } +} + +impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemStatic { + type Target = ast::ImportKind; + + fn convert( + self, + (opts, module): (BindgenAttrs, &'a Option), + ) -> Result { + if self.mutability.is_some() { + bail_span!(self.mutability, "cannot import mutable globals yet") + } + + let default_name = self.ident.to_string(); + let js_name = opts + .js_name() + .map(|p| p.0) + .unwrap_or(&default_name) + .to_string(); + let shim = format!( + "__wbg_static_accessor_{}_{}", + self.ident, + ShortHash((&js_name, module, &self.ident)), + ); + opts.check_used(); + Ok(ast::ImportKind::Static(ast::ImportStatic { + ty: *self.ty, + vis: self.vis, + rust_name: self.ident.clone(), + js_name, + shim: Ident::new(&shim, Span::call_site()), + })) + } +} + +impl ConvertToAst for syn::ItemFn { + type Target = ast::Function; + + fn convert(self, attrs: BindgenAttrs) -> Result { + match self.vis { + syn::Visibility::Public(_) => {} + _ => bail_span!(self, "can only #[wasm_bindgen] public functions"), + } + if self.sig.constness.is_some() { + bail_span!( + self.sig.constness, + "can only #[wasm_bindgen] non-const functions" + ); + } + + let ret = function_from_decl( + &self.sig.ident, + &attrs, + self.sig.clone(), + self.attrs, + self.vis, + false, + None, + false, + )?; + attrs.check_used(); + Ok(ret.0) + } +} + +pub(crate) fn is_js_keyword(keyword: &str) -> bool { + JS_KEYWORDS.contains(&keyword) +} + +/// Construct a function (and gets the self type if appropriate) for our AST from a syn function. +#[allow(clippy::too_many_arguments)] +fn function_from_decl( + decl_name: &syn::Ident, + opts: &BindgenAttrs, + sig: syn::Signature, + attrs: Vec, + vis: syn::Visibility, + allow_self: bool, + self_ty: Option<&Ident>, + is_from_impl: bool, +) -> Result<(ast::Function, Option), Diagnostic> { + if sig.variadic.is_some() { + bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions"); + } + if sig.generics.params.len() > 0 { + bail_span!( + sig.generics, + "can't #[wasm_bindgen] functions with lifetime or type parameters", + ); + } + + assert_no_lifetimes(&sig)?; + + let syn::Signature { inputs, output, .. } = sig; + + let replace_self = |t: syn::Type| { + let self_ty = match self_ty { + Some(i) => i, + None => return t, + }; + let path = match get_ty(&t) { + syn::Type::Path(syn::TypePath { qself: None, path }) => path.clone(), + other => return other.clone(), + }; + let new_path = if path.segments.len() == 1 && path.segments[0].ident == "Self" { + self_ty.clone().into() + } else { + path + }; + syn::Type::Path(syn::TypePath { + qself: None, + path: new_path, + }) + }; + + let replace_colliding_arg = |i: &mut syn::PatType| { + if let syn::Pat::Ident(ref mut i) = *i.pat { + let ident = i.ident.to_string(); + if is_js_keyword(ident.as_str()) { + i.ident = Ident::new(format!("_{}", ident).as_str(), i.ident.span()); + } + } + }; + + let mut method_self = None; + let arguments = inputs + .into_iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(mut c) => { + replace_colliding_arg(&mut c); + c.ty = Box::new(replace_self(*c.ty)); + Some(c) + } + syn::FnArg::Receiver(r) => { + if !allow_self { + panic!("arguments cannot be `self`") + } + assert!(method_self.is_none()); + if r.reference.is_none() { + method_self = Some(ast::MethodSelf::ByValue); + } else if r.mutability.is_some() { + method_self = Some(ast::MethodSelf::RefMutable); + } else { + method_self = Some(ast::MethodSelf::RefShared); + } + None + } + }) + .collect::>(); + + let ret = match output { + syn::ReturnType::Default => None, + syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)), + }; + + let (name, name_span, renamed_via_js_name) = if let Some((js_name, js_name_span)) = + opts.js_name() + { + let kind = operation_kind(opts); + let prefix = match kind { + OperationKind::Setter(_) => "set_", + _ => "", + }; + let name = if prefix.is_empty() && opts.method().is_none() && is_js_keyword(js_name) { + format!("_{}", js_name) + } else { + format!("{}{}", prefix, js_name) + }; + (name, js_name_span, true) + } else { + let name = + if !is_from_impl && opts.method().is_none() && is_js_keyword(&decl_name.to_string()) { + format!("_{}", decl_name) + } else { + decl_name.to_string() + }; + (name, decl_name.span(), false) + }; + Ok(( + ast::Function { + arguments, + name_span, + name, + renamed_via_js_name, + ret, + rust_attrs: attrs, + rust_vis: vis, + r#async: sig.asyncness.is_some(), + generate_typescript: opts.skip_typescript().is_none(), + variadic: opts.variadic().is_some(), + }, + method_self, + )) +} + +pub(crate) trait MacroParse { + /// Parse the contents of an object into our AST, with a context if necessary. + /// + /// The context is used to have access to the attributes on `#[wasm_bindgen]`, and to allow + /// writing to the output `TokenStream`. + fn macro_parse(self, program: &mut ast::Program, context: Ctx) -> Result<(), Diagnostic>; +} + +impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { + fn macro_parse( + self, + program: &mut ast::Program, + (opts, tokens): (Option, &'a mut TokenStream), + ) -> Result<(), Diagnostic> { + match self { + syn::Item::Fn(mut f) => { + let no_mangle = f + .attrs + .iter() + .enumerate() + .filter_map(|(i, m)| m.parse_meta().ok().map(|m| (i, m))) + .find(|(_, m)| m.path().is_ident("no_mangle")); + match no_mangle { + Some((i, _)) => { + f.attrs.remove(i); + } + _ => {} + } + let comments = extract_doc_comments(&f.attrs); + f.to_tokens(tokens); + let opts = opts.unwrap_or_default(); + if opts.start().is_some() { + if f.sig.generics.params.len() > 0 { + bail_span!(&f.sig.generics, "the start function cannot have generics",); + } + if f.sig.inputs.len() > 0 { + bail_span!(&f.sig.inputs, "the start function cannot have arguments",); + } + } + let method_kind = ast::MethodKind::Operation(ast::Operation { + is_static: true, + kind: operation_kind(&opts), + }); + let rust_name = f.sig.ident.clone(); + let start = opts.start().is_some(); + program.exports.push(ast::Export { + comments, + function: f.convert(opts)?, + js_class: None, + method_kind, + method_self: None, + rust_class: None, + rust_name, + start, + }); + } + syn::Item::Struct(mut s) => { + let opts = opts.unwrap_or_default(); + program.structs.push((&mut s).convert(opts)?); + s.to_tokens(tokens); + } + syn::Item::Impl(mut i) => { + let opts = opts.unwrap_or_default(); + (&mut i).macro_parse(program, opts)?; + i.to_tokens(tokens); + } + syn::Item::ForeignMod(mut f) => { + let opts = match opts { + Some(opts) => opts, + None => BindgenAttrs::find(&mut f.attrs)?, + }; + f.macro_parse(program, opts)?; + } + syn::Item::Enum(mut e) => { + let opts = match opts { + Some(opts) => opts, + None => BindgenAttrs::find(&mut e.attrs)?, + }; + e.macro_parse(program, (tokens, opts))?; + } + syn::Item::Const(mut c) => { + let opts = match opts { + Some(opts) => opts, + None => BindgenAttrs::find(&mut c.attrs)?, + }; + c.macro_parse(program, opts)?; + } + _ => { + bail_span!( + self, + "#[wasm_bindgen] can only be applied to a function, \ + struct, enum, impl, or extern block", + ); + } + } + + Ok(()) + } +} + +impl<'a> MacroParse for &'a mut syn::ItemImpl { + fn macro_parse( + self, + _program: &mut ast::Program, + opts: BindgenAttrs, + ) -> Result<(), Diagnostic> { + if self.defaultness.is_some() { + bail_span!( + self.defaultness, + "#[wasm_bindgen] default impls are not supported" + ); + } + if self.unsafety.is_some() { + bail_span!( + self.unsafety, + "#[wasm_bindgen] unsafe impls are not supported" + ); + } + if let Some((_, path, _)) = &self.trait_ { + bail_span!(path, "#[wasm_bindgen] trait impls are not supported"); + } + if self.generics.params.len() > 0 { + bail_span!( + self.generics, + "#[wasm_bindgen] generic impls aren't supported" + ); + } + let name = match get_ty(&self.self_ty) { + syn::Type::Path(syn::TypePath { + qself: None, + ref path, + }) => path, + _ => bail_span!( + self.self_ty, + "unsupported self type in #[wasm_bindgen] impl" + ), + }; + let mut errors = Vec::new(); + for item in self.items.iter_mut() { + if let Err(e) = prepare_for_impl_recursion(item, &name, &opts) { + errors.push(e); + } + } + Diagnostic::from_vec(errors)?; + opts.check_used(); + Ok(()) + } +} + +// Prepare for recursion into an `impl` block. Here we want to attach an +// internal attribute, `__wasm_bindgen_class_marker`, with any metadata we need +// to pass from the impl to the impl item. Recursive macro expansion will then +// expand the `__wasm_bindgen_class_marker` attribute. +// +// Note that we currently do this because inner items may have things like cfgs +// on them, so we want to expand the impl first, let the insides get cfg'd, and +// then go for the rest. +fn prepare_for_impl_recursion( + item: &mut syn::ImplItem, + class: &syn::Path, + impl_opts: &BindgenAttrs, +) -> Result<(), Diagnostic> { + let method = match item { + syn::ImplItem::Method(m) => m, + syn::ImplItem::Const(_) => { + bail_span!( + &*item, + "const definitions aren't supported with #[wasm_bindgen]" + ); + } + syn::ImplItem::Type(_) => bail_span!( + &*item, + "type definitions in impls aren't supported with #[wasm_bindgen]" + ), + syn::ImplItem::Macro(_) => { + // In theory we want to allow this, but we have no way of expanding + // the macro and then placing our magical attributes on the expanded + // functions. As a result, just disallow it for now to hopefully + // ward off buggy results from this macro. + bail_span!(&*item, "macros in impls aren't supported"); + } + syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"), + other => bail_span!(other, "failed to parse this item as a known item"), + }; + + let ident = extract_path_ident(class)?; + + let js_class = impl_opts + .js_class() + .map(|s| s.0.to_string()) + .unwrap_or(ident.to_string()); + + method.attrs.insert( + 0, + syn::Attribute { + pound_token: Default::default(), + style: syn::AttrStyle::Outer, + bracket_token: Default::default(), + path: syn::parse_quote! { wasm_bindgen::prelude::__wasm_bindgen_class_marker }, + tokens: quote::quote! { (#class = #js_class) }.into(), + }, + ); + + Ok(()) +} + +impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod { + fn macro_parse( + self, + program: &mut ast::Program, + (class, js_class): (&'a Ident, &'a str), + ) -> Result<(), Diagnostic> { + match self.vis { + syn::Visibility::Public(_) => {} + _ => return Ok(()), + } + if self.defaultness.is_some() { + panic!("default methods are not supported"); + } + if self.sig.constness.is_some() { + bail_span!( + self.sig.constness, + "can only #[wasm_bindgen] non-const functions", + ); + } + + let opts = BindgenAttrs::find(&mut self.attrs)?; + let comments = extract_doc_comments(&self.attrs); + let (function, method_self) = function_from_decl( + &self.sig.ident, + &opts, + self.sig.clone(), + self.attrs.clone(), + self.vis.clone(), + true, + Some(class), + true, + )?; + let method_kind = if opts.constructor().is_some() { + ast::MethodKind::Constructor + } else { + let is_static = method_self.is_none(); + let kind = operation_kind(&opts); + ast::MethodKind::Operation(ast::Operation { is_static, kind }) + }; + program.exports.push(ast::Export { + comments, + function, + js_class: Some(js_class.to_string()), + method_kind, + method_self, + rust_class: Some(class.clone()), + rust_name: self.sig.ident.clone(), + start: false, + }); + opts.check_used(); + Ok(()) + } +} + +fn import_enum(enum_: syn::ItemEnum, program: &mut ast::Program) -> Result<(), Diagnostic> { + let mut variants = vec![]; + let mut variant_values = vec![]; + + for v in enum_.variants.iter() { + match v.fields { + syn::Fields::Unit => (), + _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"), + } + + let (_, expr) = match &v.discriminant { + Some(pair) => pair, + None => { + bail_span!(v, "all variants must have a value"); + } + }; + match get_expr(expr) { + syn::Expr::Lit(syn::ExprLit { + attrs: _, + lit: syn::Lit::Str(str_lit), + }) => { + variants.push(v.ident.clone()); + variant_values.push(str_lit.value()); + } + expr => bail_span!( + expr, + "enums with #[wasm_bindgen] cannot mix string and non-string values", + ), + } + } + + program.imports.push(ast::Import { + module: None, + js_namespace: None, + kind: ast::ImportKind::Enum(ast::ImportEnum { + vis: enum_.vis, + name: enum_.ident, + variants, + variant_values, + rust_attrs: enum_.attrs, + }), + }); + + Ok(()) +} + +impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { + fn macro_parse( + self, + program: &mut ast::Program, + (tokens, opts): (&'a mut TokenStream, BindgenAttrs), + ) -> Result<(), Diagnostic> { + if self.variants.len() == 0 { + bail_span!(self, "cannot export empty enums to JS"); + } + let generate_typescript = opts.skip_typescript().is_none(); + + // Check if the first value is a string literal + if let Some((_, expr)) = &self.variants[0].discriminant { + match get_expr(expr) { + syn::Expr::Lit(syn::ExprLit { + attrs: _, + lit: syn::Lit::Str(_), + }) => { + opts.check_used(); + return import_enum(self, program); + } + _ => {} + } + } + let js_name = opts + .js_name() + .map(|s| s.0) + .map_or_else(|| self.ident.to_string(), |s| s.to_string()); + opts.check_used(); + + let has_discriminant = self.variants[0].discriminant.is_some(); + + match self.vis { + syn::Visibility::Public(_) => {} + _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"), + } + + let variants = self + .variants + .iter() + .enumerate() + .map(|(i, v)| { + match v.fields { + syn::Fields::Unit => (), + _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"), + } + + // Require that everything either has a discriminant or doesn't. + // We don't really want to get in the business of emulating how + // rustc assigns values to enums. + if v.discriminant.is_some() != has_discriminant { + bail_span!( + v, + "must either annotate discriminant of all variants or none" + ); + } + + let value = match &v.discriminant { + Some((_, expr)) => match get_expr(expr) { + syn::Expr::Lit(syn::ExprLit { + attrs: _, + lit: syn::Lit::Int(int_lit), + }) => match int_lit.base10_digits().parse::() { + Ok(v) => v, + Err(_) => { + bail_span!( + int_lit, + "enums with #[wasm_bindgen] can only support \ + numbers that can be represented as u32" + ); + } + }, + expr => bail_span!( + expr, + "enums with #[wasm_bindgen] may only have \ + number literal values", + ), + }, + None => i as u32, + }; + + let comments = extract_doc_comments(&v.attrs); + Ok(ast::Variant { + name: v.ident.clone(), + value, + comments, + }) + }) + .collect::, Diagnostic>>()?; + + let mut values = variants.iter().map(|v| v.value).collect::>(); + values.sort(); + let hole = values + .windows(2) + .filter_map(|window| { + if window[0] + 1 != window[1] { + Some(window[0] + 1) + } else { + None + } + }) + .next() + .unwrap_or(*values.last().unwrap() + 1); + for value in values { + assert!(hole != value); + } + + let comments = extract_doc_comments(&self.attrs); + + self.to_tokens(tokens); + + program.enums.push(ast::Enum { + rust_name: self.ident, + js_name, + variants, + comments, + hole, + generate_typescript, + }); + Ok(()) + } +} + +impl MacroParse for syn::ItemConst { + fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> { + // Shortcut + if opts.typescript_custom_section().is_none() { + bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)]."); + } + + match get_expr(&self.expr) { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(litstr), + .. + }) => { + program.typescript_custom_sections.push(litstr.value()); + } + expr => { + bail_span!(expr, "Expected a string literal to be used with #[wasm_bindgen(typescript_custom_section)]."); + } + } + + opts.check_used(); + + Ok(()) + } +} + +impl MacroParse for syn::ItemForeignMod { + fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> { + let mut errors = Vec::new(); + match self.abi.name { + Some(ref l) if l.value() == "C" => {} + None => {} + Some(ref other) => { + errors.push(err_span!( + other, + "only foreign mods with the `C` ABI are allowed" + )); + } + } + let module = if let Some((name, span)) = opts.module() { + if opts.inline_js().is_some() { + let msg = "cannot specify both `module` and `inline_js`"; + errors.push(Diagnostic::span_error(span, msg)); + } + if opts.raw_module().is_some() { + let msg = "cannot specify both `module` and `raw_module`"; + errors.push(Diagnostic::span_error(span, msg)); + } + Some(ast::ImportModule::Named(name.to_string(), span)) + } else if let Some((name, span)) = opts.raw_module() { + if opts.inline_js().is_some() { + let msg = "cannot specify both `raw_module` and `inline_js`"; + errors.push(Diagnostic::span_error(span, msg)); + } + Some(ast::ImportModule::RawNamed(name.to_string(), span)) + } else if let Some((js, span)) = opts.inline_js() { + let i = program.inline_js.len(); + program.inline_js.push(js.to_string()); + Some(ast::ImportModule::Inline(i, span)) + } else { + None + }; + for item in self.items.into_iter() { + if let Err(e) = item.macro_parse(program, module.clone()) { + errors.push(e); + } + } + Diagnostic::from_vec(errors)?; + opts.check_used(); + Ok(()) + } +} + +impl MacroParse> for syn::ForeignItem { + fn macro_parse( + mut self, + program: &mut ast::Program, + module: Option, + ) -> Result<(), Diagnostic> { + let item_opts = { + let attrs = match self { + syn::ForeignItem::Fn(ref mut f) => &mut f.attrs, + syn::ForeignItem::Type(ref mut t) => &mut t.attrs, + syn::ForeignItem::Static(ref mut s) => &mut s.attrs, + _ => panic!("only foreign functions/types allowed for now"), + }; + BindgenAttrs::find(attrs)? + }; + let js_namespace = item_opts.js_namespace().map(|(s, _)| s.to_owned()); + let kind = match self { + syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?, + syn::ForeignItem::Type(t) => t.convert(item_opts)?, + syn::ForeignItem::Static(s) => s.convert((item_opts, &module))?, + _ => panic!("only foreign functions/types allowed for now"), + }; + + program.imports.push(ast::Import { + module, + js_namespace, + kind, + }); + + Ok(()) + } +} + +/// Get the first type parameter of a generic type, errors on incorrect input. +fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result, Diagnostic> { + let t = match ty { + Some(t) => t, + None => return Ok(None), + }; + let path = match *get_ty(&t) { + syn::Type::Path(syn::TypePath { + qself: None, + ref path, + }) => path, + _ => bail_span!(t, "must be Result<...>"), + }; + let seg = path + .segments + .last() + .ok_or_else(|| err_span!(t, "must have at least one segment"))?; + let generics = match seg.arguments { + syn::PathArguments::AngleBracketed(ref t) => t, + _ => bail_span!(t, "must be Result<...>"), + }; + let generic = generics + .args + .first() + .ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?; + let ty = match generic { + syn::GenericArgument::Type(t) => t, + other => bail_span!(other, "must be a type parameter"), + }; + match get_ty(&ty) { + syn::Type::Tuple(t) if t.elems.len() == 0 => return Ok(None), + _ => {} + } + Ok(Some(ty.clone())) +} + +/// Extract the documentation comments from a Vec of attributes +fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec { + attrs + .iter() + .filter_map(|a| { + // if the path segments include an ident of "doc" we know this + // this is a doc comment + if a.path.segments.iter().any(|s| s.ident.to_string() == "doc") { + Some( + // We want to filter out any Puncts so just grab the Literals + a.tokens.clone().into_iter().filter_map(|t| match t { + TokenTree::Literal(lit) => { + let quoted = lit.to_string(); + Some(try_unescape("ed).unwrap_or_else(|| quoted)) + } + _ => None, + }), + ) + } else { + None + } + }) + //Fold up the [[String]] iter we created into Vec + .fold(vec![], |mut acc, a| { + acc.extend(a); + acc + }) +} + +// Unescapes a quoted string. char::escape_debug() was used to escape the text. +fn try_unescape(s: &str) -> Option { + if s.is_empty() { + return Some(String::new()); + } + let mut result = String::with_capacity(s.len()); + let mut chars = s.chars(); + for i in 0.. { + let c = match chars.next() { + Some(c) => c, + None => { + if result.ends_with('"') { + result.pop(); + } + return Some(result); + } + }; + if i == 0 && c == '"' { + // ignore it + } else if c == '\\' { + let c = chars.next()?; + match c { + 't' => result.push('\t'), + 'r' => result.push('\r'), + 'n' => result.push('\n'), + '\\' | '\'' | '"' => result.push(c), + 'u' => { + if chars.next() != Some('{') { + return None; + } + let (c, next) = unescape_unicode(&mut chars)?; + result.push(c); + if next != '}' { + return None; + } + } + _ => return None, + } + } else { + result.push(c); + } + } + None +} + +fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> { + let mut value = 0; + for i in 0..7 { + let c = chars.next()?; + let num = if c >= '0' && c <= '9' { + c as u32 - '0' as u32 + } else if c >= 'a' && c <= 'f' { + c as u32 - 'a' as u32 + 10 + } else if c >= 'A' && c <= 'F' { + c as u32 - 'A' as u32 + 10 + } else { + if i == 0 { + return None; + } + let decoded = char::from_u32(value)?; + return Some((decoded, c)); + }; + if i >= 6 { + return None; + } + value = (value << 4) | num; + } + None +} + +/// Check there are no lifetimes on the function. +fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> { + struct Walk { + diagnostics: Vec, + } + + impl<'ast> syn::visit::Visit<'ast> for Walk { + fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) { + self.diagnostics.push(err_span!( + &*i, + "it is currently not sound to use lifetimes in function \ + signatures" + )); + } + } + let mut walk = Walk { + diagnostics: Vec::new(), + }; + syn::visit::Visit::visit_signature(&mut walk, sig); + Diagnostic::from_vec(walk.diagnostics) +} + +/// Extracts the last ident from the path +fn extract_path_ident(path: &syn::Path) -> Result { + for segment in path.segments.iter() { + match segment.arguments { + syn::PathArguments::None => {} + _ => bail_span!(path, "paths with type parameters are not supported yet"), + } + } + + match path.segments.last() { + Some(value) => Ok(value.ident.clone()), + None => { + bail_span!(path, "empty idents are not supported"); + } + } +} + +pub fn reset_attrs_used() { + ATTRS.with(|state| { + state.parsed.set(0); + state.checks.set(0); + state.unused_attrs.borrow_mut().clear(); + }) +} + +pub fn check_unused_attrs(tokens: &mut TokenStream) { + ATTRS.with(|state| { + assert_eq!(state.parsed.get(), state.checks.get()); + let unused_attrs = &*state.unused_attrs.borrow(); + if !unused_attrs.is_empty() { + tokens.extend(quote::quote! { + // Anonymous scope to prevent name clashes. + const _: () = { + #(let #unused_attrs: ();)* + }; + }); + } + }) +} + +fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind { + let mut operation_kind = ast::OperationKind::Regular; + if let Some(g) = opts.getter() { + operation_kind = ast::OperationKind::Getter(g.clone()); + } + if let Some(s) = opts.setter() { + operation_kind = ast::OperationKind::Setter(s.clone()); + } + if opts.indexing_getter().is_some() { + operation_kind = ast::OperationKind::IndexingGetter; + } + if opts.indexing_setter().is_some() { + operation_kind = ast::OperationKind::IndexingSetter; + } + if opts.indexing_deleter().is_some() { + operation_kind = ast::OperationKind::IndexingDeleter; + } + operation_kind +}