wasm-bindgen-macro-support-0.2.108/.cargo_vcs_info.json0000644000000001620000000000100163670ustar { "git": { "sha1": "a788f58f95cb6603ad2cbdf87571b27665d6cbd9" }, "path_in_vcs": "crates/macro-support" }wasm-bindgen-macro-support-0.2.108/Cargo.lock0000644000000031040000000000100143410ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "proc-macro2" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "wasm-bindgen-macro-support" version = "0.2.108" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] wasm-bindgen-macro-support-0.2.108/Cargo.toml0000644000000034700000000000100143720ustar # 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 = "2021" rust-version = "1.71" name = "wasm-bindgen-macro-support" version = "0.2.108" authors = ["The wasm-bindgen Developers"] build = false include = [ "/LICENSE-*", "/src", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Implementation APIs for the `#[wasm_bindgen]` attribute" homepage = "https://wasm-bindgen.github.io/wasm-bindgen/" documentation = "https://docs.rs/wasm-bindgen" readme = false license = "MIT OR Apache-2.0" repository = "https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro-support" [features] extra-traits = ["syn/extra-traits"] strict-macro = [] [lib] name = "wasm_bindgen_macro_support" path = "src/lib.rs" [dependencies.bumpalo] version = "3.0.0" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "2.0" features = [ "visit", "visit-mut", "full", ] [dependencies.wasm-bindgen-shared] version = "=0.2.108" [lints.clippy] large_enum_variant = "allow" new_without_default = "allow" overly_complex_bool_expr = "allow" too_many_arguments = "allow" type_complexity = "allow" uninlined_format_args = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = [ "cfg(wasm_bindgen_unstable_test_coverage)", "cfg(xxx_debug_only_print_generated_code)", ] wasm-bindgen-macro-support-0.2.108/Cargo.toml.orig000064400000000000000000000014101046102023000200430ustar 00000000000000[package] authors = ["The wasm-bindgen Developers"] description = "Implementation APIs for the `#[wasm_bindgen]` attribute" documentation = "https://docs.rs/wasm-bindgen" edition = "2021" homepage = "https://wasm-bindgen.github.io/wasm-bindgen/" include = ["/LICENSE-*", "/src"] license = "MIT OR Apache-2.0" name = "wasm-bindgen-macro-support" repository = "https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro-support" rust-version = "1.71" version = "0.2.108" [features] extra-traits = ["syn/extra-traits"] strict-macro = [] [dependencies] bumpalo = "3.0.0" proc-macro2 = "1.0" quote = '1.0' syn = { version = '2.0', features = ['visit', 'visit-mut', 'full'] } wasm-bindgen-shared = { path = "../shared", version = "=0.2.108" } [lints] workspace = true wasm-bindgen-macro-support-0.2.108/LICENSE-APACHE000064400000000000000000000251371046102023000171140ustar 00000000000000 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. wasm-bindgen-macro-support-0.2.108/LICENSE-MIT000064400000000000000000000020411046102023000166110ustar 00000000000000Copyright (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. wasm-bindgen-macro-support-0.2.108/src/ast.rs000064400000000000000000000502511046102023000171070ustar 00000000000000//! A representation of the Abstract Syntax Tree of a Rust program, //! with all the added metadata necessary to generate Wasm bindings //! for it. use crate::{hash::ShortHash, Diagnostic}; use proc_macro2::{Ident, Span}; use std::hash::{Hash, Hasher}; use syn::Path; use wasm_bindgen_shared as shared; /// An abstract syntax tree representing a rust program. Contains /// extra information for joining up this rust code with javascript. #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct Program { /// rust -> js interfaces pub exports: Vec, /// js -> rust interfaces pub imports: Vec, /// linked-to modules pub linked_modules: Vec, /// rust enums pub enums: Vec, /// rust structs pub structs: Vec, /// custom typescript sections to be included in the definition file pub typescript_custom_sections: Vec, /// Inline JS snippets pub inline_js: Vec, /// Path to wasm_bindgen pub wasm_bindgen: Path, /// Path to js_sys pub js_sys: Path, /// Path to wasm_bindgen_futures pub wasm_bindgen_futures: Path, } impl Default for Program { fn default() -> Self { Self { exports: Default::default(), imports: Default::default(), linked_modules: Default::default(), enums: Default::default(), structs: Default::default(), typescript_custom_sections: Default::default(), inline_js: Default::default(), wasm_bindgen: syn::parse_quote! { wasm_bindgen }, js_sys: syn::parse_quote! { js_sys }, wasm_bindgen_futures: syn::parse_quote! { wasm_bindgen_futures }, } } } impl Program { /// Name of the link function for a specific linked module pub fn link_function_name(&self, idx: usize) -> String { let hash = match &self.linked_modules[idx] { ImportModule::Inline(idx) => ShortHash((1, &self.inline_js[*idx])).to_string(), other => ShortHash((0, other)).to_string(), }; format!("__wbindgen_link_{hash}") } } /// An abstract syntax tree representing a link to a module in Rust. /// In contrast to Program, LinkToModule must expand to an expression. /// linked_modules of the inner Program must contain exactly one element /// whose link is produced by the expression. #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct LinkToModule(pub Program); /// A rust to js interface. Allows interaction with rust objects/functions /// from javascript. #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct Export { /// Comments extracted from the rust source. pub comments: Vec, /// The rust function pub function: Function, /// The class name in JS this is attached to pub js_class: Option, /// The namespace to export the item through, if any pub js_namespace: Option>, /// The kind (static, named, regular) pub method_kind: MethodKind, /// The type of `self` (either `self`, `&self`, or `&mut self`) pub method_self: Option, /// The struct name, in Rust, this is attached to pub rust_class: Option, /// The name of the rust function/method on the rust side. pub rust_name: Ident, /// Whether or not this function should be flagged as the Wasm start /// function. pub start: bool, /// Path to wasm_bindgen pub wasm_bindgen: Path, /// Path to wasm_bindgen_futures pub wasm_bindgen_futures: Path, } /// The 3 types variations of `self`. #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Copy, Clone)] pub enum MethodSelf { /// `self` ByValue, /// `&mut self` RefMutable, /// `&self` RefShared, } /// Things imported from a JS module (in an `extern` block) #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct Import { /// The type of module being imported from, if any pub module: Option, /// The namespace to access the item through, if any pub js_namespace: Option>, /// If Some, this import should be re-exported with the optional given name pub reexport: Option>, /// The type of item being imported pub kind: ImportKind, } /// The possible types of module to import from #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub enum ImportModule { /// Import from the named module, with relative paths interpreted Named(String, Span), /// Import from the named module, without interpreting paths RawNamed(String, Span), /// Import from an inline JS snippet Inline(usize), } impl Hash for ImportModule { fn hash(&self, h: &mut H) { match self { ImportModule::Named(name, _) => (1u8, name).hash(h), ImportModule::Inline(idx) => (2u8, idx).hash(h), ImportModule::RawNamed(name, _) => (3u8, name).hash(h), } } } /// The type of item being imported #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub enum ImportKind { /// Importing a function Function(ImportFunction), /// Importing a static value Static(ImportStatic), /// Importing a static string String(ImportString), /// Importing a type/class Type(ImportType), /// Importing a JS enum Enum(StringEnum), } /// A function being imported from JS #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct ImportFunction { /// The full signature of the function pub function: Function, /// The name rust code will use pub rust_name: Ident, /// The type being returned pub js_ret: Option, /// Whether to catch JS exceptions pub catch: bool, /// Whether the function is variadic on the JS side pub variadic: bool, /// Whether the function should use structural type checking pub structural: bool, /// Causes the Builder (See cli-support::js::binding::Builder) to error out if /// it finds itself generating code for a function with this signature pub assert_no_shim: bool, /// The kind of function being imported pub kind: ImportFunctionKind, /// The shim name to use in the generated code. The 'shim' is a function that appears in /// the generated JS as a wrapper around the actual function to import, performing any /// necessary conversions (EG adding a try/catch to change a thrown error into a Result) pub shim: Ident, /// The doc comment on this import, if one is provided pub doc_comment: String, /// Path to wasm_bindgen pub wasm_bindgen: Path, /// Path to wasm_bindgen_futures pub wasm_bindgen_futures: Path, } /// The type of a function being imported #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub enum ImportFunctionKind { /// A class method Method { /// The name of the class for this method, in JS class: String, /// The type of the class for this method, in Rust ty: syn::Type, /// The kind of method this is kind: MethodKind, }, /// A standard function Normal, } /// The type of a method #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub enum MethodKind { /// A class constructor Constructor, /// Any other kind of method Operation(Operation), } /// The operation performed by a class method #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub struct Operation { /// Whether this method is static pub is_static: bool, /// The internal kind of this Operation pub kind: OperationKind, } /// The kind of operation performed by a method #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub enum OperationKind { /// A standard method, nothing special Regular, /// A free function that receives JS `this` as its first parameter RegularThis, /// A method for getting the value of the provided Ident or String Getter(Option), /// A method for setting the value of the provided Ident or String Setter(Option), /// A dynamically intercepted getter IndexingGetter, /// A dynamically intercepted setter IndexingSetter, /// A dynamically intercepted deleter IndexingDeleter, } /// The type of a static being imported #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub struct ImportStatic { /// The visibility of this static in Rust pub vis: syn::Visibility, /// The type of static being imported pub ty: syn::Type, /// The name of the shim function used to access this static pub shim: Ident, /// The name of this static on the Rust side pub rust_name: Ident, /// The name of this static on the JS side pub js_name: String, /// Path to wasm_bindgen pub wasm_bindgen: Path, /// Version of `thread_local`, if any. pub thread_local: Option, } /// Which version of the `thread_local` attribute is enabled. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ThreadLocal { /// V1. V1, /// V2. V2, } /// The type of a static string being imported #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub struct ImportString { /// The visibility of this static string in Rust pub vis: syn::Visibility, /// The type specified by the user, which we only use to show an error if the wrong type is used. pub ty: syn::Type, /// The name of the shim function used to access this static pub shim: Ident, /// The name of this static on the Rust side pub rust_name: Ident, /// Path to wasm_bindgen pub wasm_bindgen: Path, /// Path to js_sys pub js_sys: Path, /// The string to export. pub string: String, /// Version of `thread_local`. pub thread_local: ThreadLocal, } /// The metadata for a type being imported #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub struct ImportType { /// The visibility of this type in Rust pub vis: syn::Visibility, /// The name of this type on the Rust side pub rust_name: Ident, /// The name of this type on the JS side pub js_name: String, /// The custom attributes to apply to this type pub attrs: Vec, /// The TS definition to generate for this type pub typescript_type: Option, /// The doc comment applied to this type, if one exists pub doc_comment: Option, /// The name of the shim to check instanceof for this type pub instanceof_shim: String, /// The name of the remote function to use for the generated is_type_of pub is_type_of: Option, /// The list of classes this extends, if any pub extends: Vec, /// A custom prefix to add and attempt to fall back to, if the type isn't found pub vendor_prefixes: Vec, /// If present, don't generate a `Deref` impl pub no_deref: bool, /// Path to wasm_bindgen pub wasm_bindgen: Path, } /// The metadata for a String Enum #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub struct StringEnum { /// The Rust enum's visibility pub vis: syn::Visibility, /// The Rust enum's identifiers pub name: Ident, /// The export name of this string enum in JS/TS code pub export_name: String, /// The Rust identifiers for the variants pub variants: Vec, /// The JS string values of the variants pub variant_values: Vec, /// The doc comments on this enum, if any pub comments: Vec, /// Attributes to apply to the Rust enum pub rust_attrs: Vec, /// Whether to generate a typescript definition for this enum pub generate_typescript: bool, /// The namespace to export the enum through, if any pub js_namespace: Option>, /// Path to wasm_bindgen pub wasm_bindgen: Path, } /// Information about a function being imported or exported #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct Function { /// The exported name of the function pub name: String, /// The span of the function's name in Rust code pub name_span: Span, /// The arguments to the function pub arguments: Vec, /// The data of return type of the function pub ret: Option, /// Any custom attributes being applied to the function pub rust_attrs: Vec, /// The visibility of this function in Rust pub rust_vis: syn::Visibility, /// Whether this is an `unsafe` function pub r#unsafe: bool, /// Whether this is an `async` function pub r#async: bool, /// Whether to generate a typescript definition for this function pub generate_typescript: bool, /// Whether to generate jsdoc documentation for this function pub generate_jsdoc: bool, /// Whether this is a function with a variadict parameter pub variadic: bool, } /// Information about a function's return #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct FunctionReturnData { /// Specifies the type of the function's return pub r#type: syn::Type, /// Specifies the JS return type override pub js_type: Option, /// Specifies the return description pub desc: Option, } /// Information about a function's argument #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct FunctionArgumentData { /// Specifies the type of the function's argument pub pat_type: syn::PatType, /// Specifies the JS argument name override pub js_name: Option, /// Specifies the JS function argument type override pub js_type: Option, /// Specifies the argument description pub desc: Option, } /// Information about a Struct being exported #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct Struct { /// The name of the struct in Rust code pub rust_name: Ident, /// The export name of the struct in JS code pub js_name: String, /// All the fields of this struct to export pub fields: Vec, /// The doc comments on this struct, if provided pub comments: Vec, /// Whether this struct is inspectable (provides toJSON/toString properties to JS) pub is_inspectable: bool, /// Whether to generate a typescript definition for this struct pub generate_typescript: bool, /// Whether to skip exporting this struct from the module exports pub private: bool, /// The namespace to export the struct through, if any pub js_namespace: Option>, /// Path to wasm_bindgen pub wasm_bindgen: Path, } /// The field of a struct #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct StructField { /// The name of the field in Rust code pub rust_name: syn::Member, /// The name of the field in JS code pub js_name: String, /// The name of the struct this field is part of pub struct_name: Ident, /// Whether this value is read-only to JS pub readonly: bool, /// The type of this field pub ty: syn::Type, /// The name of the getter shim for this field pub getter: Ident, /// The name of the setter shim for this field pub setter: Ident, /// The doc comments on this field, if any pub comments: Vec, /// Whether to generate a typescript definition for this field pub generate_typescript: bool, /// Whether to generate jsdoc documentation for this field pub generate_jsdoc: bool, /// The span of the `#[wasm_bindgen(getter_with_clone)]` attribute applied /// to this field, if any. /// /// If this is `Some`, the auto-generated getter for this field must clone /// the field instead of copying it. pub getter_with_clone: Option, /// Path to wasm_bindgen pub wasm_bindgen: Path, } /// The metadata for an Enum #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub struct Enum { /// The name of this enum in Rust code pub rust_name: Ident, /// The export name of this enum in JS code pub js_name: String, /// Whether the variant values and hole are signed, meaning that they /// represent the bits of a `i32` value. pub signed: bool, /// The variants provided by this enum pub variants: Vec, /// The doc comments on this enum, if any pub comments: Vec, /// The value to use for a `none` variant of the enum pub hole: u32, /// Whether to generate a typescript definition for this enum pub generate_typescript: bool, /// Whether to hide this enum from the module exports pub private: bool, /// The namespace to export the enum through, if any pub js_namespace: Option>, /// Path to wasm_bindgen pub wasm_bindgen: Path, } /// The variant of an enum #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub struct Variant { /// The name of this variant pub name: Ident, /// The backing value of this variant pub value: u32, /// The doc comments on this variant, if any pub comments: Vec, } /// An enum representing either a literal value (`Lit`) or an expression (`syn::Expr`). #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub enum LitOrExpr { /// Represents an expression that needs to be evaluated before it can be encoded Expr(syn::Expr), /// Represents a literal string that can be directly encoded. Lit(String), } impl Export { /// Mangles a rust -> javascript export, so that the created Ident will be unique over function /// name and class name, if the function belongs to a javascript class. pub(crate) fn rust_symbol(&self) -> Ident { let mut generated_name = String::from("__wasm_bindgen_generated"); if let Some(class) = &self.js_class { generated_name.push('_'); generated_name.push_str(class); } generated_name.push('_'); generated_name.push_str(&self.function.name.to_string()); Ident::new(&generated_name, Span::call_site()) } /// This is the name of the shim function that gets exported and takes the raw /// ABI form of its arguments and converts them back into their normal, /// "high level" form before calling the actual function. pub(crate) fn export_name(&self) -> String { let fn_name = self.function.name.to_string(); let base_name = match &self.js_class { Some(class) => shared::struct_function_export_name(class, &fn_name), None => shared::free_function_export_name(&fn_name), }; if let Some(ns) = &self.js_namespace { format!("{}_{base_name}", ns.join("_")) } else { base_name } } } impl ImportKind { /// Whether this type can be inside an `impl` block. pub fn fits_on_impl(&self) -> bool { match *self { ImportKind::Function(_) => true, ImportKind::Static(_) => false, ImportKind::String(_) => false, ImportKind::Type(_) => false, ImportKind::Enum(_) => false, } } } impl Function { /// If the rust object has a `fn xxx(&self) -> MyType` method, get the name for a getter in /// javascript (in this case `xxx`, so you can write `val = obj.xxx`) pub fn infer_getter_property(&self) -> &str { &self.name } /// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name /// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`) pub fn infer_setter_property(&self) -> Result { let name = self.name.to_string(); // Otherwise we infer names based on the Rust function name. if !name.starts_with("set_") { bail_span!( syn::token::Pub(self.name_span), "setters must start with `set_`, found: {}", name, ); } Ok(name[4..].to_string()) } } wasm-bindgen-macro-support-0.2.108/src/codegen.rs000064400000000000000000002233241046102023000177270ustar 00000000000000use crate::ast; use crate::encode; use crate::encode::EncodeChunk; use crate::Diagnostic; use proc_macro2::{Ident, Span, TokenStream}; use quote::format_ident; use quote::quote_spanned; use quote::{quote, ToTokens}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use syn::parse_quote; use syn::spanned::Spanned; use wasm_bindgen_shared as shared; /// A trait for converting AST structs into Tokens and adding them to a TokenStream, /// or providing a diagnostic if conversion fails. pub trait TryToTokens { /// Attempt to convert a `Self` into tokens and add it to the `TokenStream` fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic>; /// Attempt to convert a `Self` into a new `TokenStream` fn try_to_token_stream(&self) -> Result { let mut tokens = TokenStream::new(); self.try_to_tokens(&mut tokens)?; Ok(tokens) } } impl TryToTokens for ast::Program { // Generate wrappers for all the items that we've found fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { let mut errors = Vec::new(); for export in self.exports.iter() { if let Err(e) = export.try_to_tokens(tokens) { errors.push(e); } } for s in self.structs.iter() { s.to_tokens(tokens); } let mut types = HashMap::new(); for i in self.imports.iter() { if let ast::ImportKind::Type(t) = &i.kind { types.insert(t.rust_name.to_string(), t.rust_name.clone()); } } for i in self.imports.iter() { DescribeImport { kind: &i.kind, wasm_bindgen: &self.wasm_bindgen, } .to_tokens(tokens); // If there is a js namespace, check that name isn't a type. If it is, // this import might be a method on that type. if let Some(nss) = &i.js_namespace { // When the namespace is `A.B`, the type name should be `B`. if let Some(ns) = nss.last().and_then(|t| types.get(t)) { if i.kind.fits_on_impl() { let kind = match i.kind.try_to_token_stream() { Ok(kind) => kind, Err(e) => { errors.push(e); continue; } }; (quote! { #[automatically_derived] impl #ns { #kind } }) .to_tokens(tokens); continue; } } } if let Err(e) = i.kind.try_to_tokens(tokens) { errors.push(e); } } for e in self.enums.iter() { e.to_tokens(tokens); } Diagnostic::from_vec(errors)?; // Generate a static which will eventually be what lives in a custom section // of the Wasm executable. For now it's just a plain old static, but we'll // eventually have it actually in its own section. // See comments in `crates/cli-support/src/lib.rs` about what this // `schema_version` is. let prefix_json = format!( r#"{{"schema_version":"{}","version":"{}"}}"#, shared::SCHEMA_VERSION, shared::version() ); let wasm_bindgen = &self.wasm_bindgen; let encoded = encode::encode(self)?; let encoded_chunks: Vec<_> = encoded .custom_section .iter() .map(|chunk| match chunk { EncodeChunk::EncodedBuf(buf) => { let buf = syn::LitByteStr::new(buf.as_slice(), Span::call_site()); quote!(#buf) } EncodeChunk::StrExpr(expr) => { // encode expr as str quote!({ use #wasm_bindgen::__rt::{encode_u32_to_fixed_len_bytes}; const _STR_EXPR: &str = #expr; const _STR_EXPR_BYTES: &[u8] = _STR_EXPR.as_bytes(); const _STR_EXPR_BYTES_LEN: usize = _STR_EXPR_BYTES.len() + 5; const _ENCODED_BYTES: [u8; _STR_EXPR_BYTES_LEN] = flat_byte_slices([ &encode_u32_to_fixed_len_bytes(_STR_EXPR_BYTES.len() as u32), _STR_EXPR_BYTES, ]); &_ENCODED_BYTES }) } }) .collect(); let chunk_len = encoded_chunks.len(); // concatenate all encoded chunks and write the length in front of the chunk; let encode_bytes = quote!({ const _CHUNK_SLICES: [&[u8]; #chunk_len] = [ #(#encoded_chunks,)* ]; #[allow(long_running_const_eval)] const _CHUNK_LEN: usize = flat_len(_CHUNK_SLICES); #[allow(long_running_const_eval)] const _CHUNKS: [u8; _CHUNK_LEN] = flat_byte_slices(_CHUNK_SLICES); const _LEN_BYTES: [u8; 4] = (_CHUNK_LEN as u32).to_le_bytes(); const _ENCODED_BYTES_LEN: usize = _CHUNK_LEN + 4; #[allow(long_running_const_eval)] const _ENCODED_BYTES: [u8; _ENCODED_BYTES_LEN] = flat_byte_slices([&_LEN_BYTES, &_CHUNKS]); &_ENCODED_BYTES }); // We already consumed the contents of included files when generating // the custom section, but we want to make sure that updates to the // generated files will cause this macro to rerun incrementally. To do // that we use `include_str!` to force rustc to think it has a // dependency on these files. That way when the file changes Cargo will // automatically rerun rustc which will rerun this macro. Other than // this we don't actually need the results of the `include_str!`, so // it's just shoved into an anonymous static. let file_dependencies = encoded.included_files.iter().map(|file| { let file = file.to_str().unwrap(); quote! { include_str!(#file) } }); let len = prefix_json.len() as u32; let prefix_json_bytes = [&len.to_le_bytes()[..], prefix_json.as_bytes()].concat(); let prefix_json_bytes = syn::LitByteStr::new(&prefix_json_bytes, Span::call_site()); (quote! { #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] #[automatically_derived] const _: () = { use #wasm_bindgen::__rt::{flat_len, flat_byte_slices}; static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*]; const _ENCODED_BYTES: &[u8] = #encode_bytes; const _PREFIX_JSON_BYTES: &[u8] = #prefix_json_bytes; const _ENCODED_BYTES_LEN: usize = _ENCODED_BYTES.len(); const _PREFIX_JSON_BYTES_LEN: usize = _PREFIX_JSON_BYTES.len(); const _LEN: usize = _PREFIX_JSON_BYTES_LEN + _ENCODED_BYTES_LEN; #[link_section = "__wasm_bindgen_unstable"] #[allow(long_running_const_eval)] static _GENERATED: [u8; _LEN] = flat_byte_slices([_PREFIX_JSON_BYTES, _ENCODED_BYTES]); }; }) .to_tokens(tokens); Ok(()) } } impl TryToTokens for ast::LinkToModule { fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { let mut program = TokenStream::new(); self.0.try_to_tokens(&mut program)?; let link_function_name = self.0.link_function_name(0); let name = Ident::new(&link_function_name, Span::call_site()); let wasm_bindgen = &self.0.wasm_bindgen; let abi_ret = quote! { #wasm_bindgen::convert::WasmRet<<#wasm_bindgen::__rt::alloc::string::String as #wasm_bindgen::convert::FromWasmAbi>::Abi> }; let extern_fn = extern_fn(&name, &[], &[], &[], abi_ret); (quote! { { #program #extern_fn static __VAL: #wasm_bindgen::__rt::LazyLock<#wasm_bindgen::__rt::alloc::string::String> = #wasm_bindgen::__rt::LazyLock::new(|| unsafe { <#wasm_bindgen::__rt::alloc::string::String as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#name().join()) }); #wasm_bindgen::__rt::alloc::string::String::clone(&__VAL) } }) .to_tokens(tokens); Ok(()) } } impl ToTokens for ast::Struct { fn to_tokens(&self, tokens: &mut TokenStream) { let name = &self.rust_name; let name_str = self.js_name.to_string(); let name_len = name_str.len() as u32; let name_chars: Vec = name_str.chars().map(|c| c as u32).collect(); let new_fn = Ident::new(&shared::new_function(&name_str), Span::call_site()); let free_fn = Ident::new(&shared::free_function(&name_str), Span::call_site()); let unwrap_fn = Ident::new(&shared::unwrap_function(&name_str), Span::call_site()); let wasm_bindgen = &self.wasm_bindgen; (quote! { #[automatically_derived] impl #wasm_bindgen::__rt::marker::SupportsConstructor for #name {} #[automatically_derived] impl #wasm_bindgen::__rt::marker::SupportsInstanceProperty for #name {} #[automatically_derived] impl #wasm_bindgen::__rt::marker::SupportsStaticProperty for #name {} #[automatically_derived] impl #wasm_bindgen::describe::WasmDescribe for #name { fn describe() { use #wasm_bindgen::describe::*; inform(RUST_STRUCT); inform(#name_len); #(inform(#name_chars);)* } } #[automatically_derived] impl #wasm_bindgen::convert::IntoWasmAbi for #name { type Abi = u32; fn into_abi(self) -> u32 { use #wasm_bindgen::__rt::alloc::rc::Rc; use #wasm_bindgen::__rt::WasmRefCell; Rc::into_raw(Rc::new(WasmRefCell::new(self))) as u32 } } #[automatically_derived] impl #wasm_bindgen::convert::FromWasmAbi for #name { type Abi = u32; unsafe fn from_abi(js: u32) -> Self { use #wasm_bindgen::__rt::alloc::rc::Rc; use #wasm_bindgen::__rt::core::result::Result::{Ok, Err}; use #wasm_bindgen::__rt::{assert_not_null, WasmRefCell}; let ptr = js as *mut WasmRefCell<#name>; assert_not_null(ptr); let rc = Rc::from_raw(ptr); match Rc::try_unwrap(rc) { Ok(cell) => cell.into_inner(), Err(_) => #wasm_bindgen::throw_str( "attempted to take ownership of Rust value while it was borrowed" ), } } } #[automatically_derived] impl #wasm_bindgen::__rt::core::convert::From<#name> for #wasm_bindgen::JsValue { fn from(value: #name) -> Self { let ptr = #wasm_bindgen::convert::IntoWasmAbi::into_abi(value); #[link(wasm_import_module = "__wbindgen_placeholder__")] #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] extern "C" { fn #new_fn(ptr: u32) -> u32; } #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] unsafe fn #new_fn(_: u32) -> u32 { panic!("cannot convert to JsValue outside of the Wasm target") } unsafe { <#wasm_bindgen::JsValue as #wasm_bindgen::convert::FromWasmAbi> ::from_abi(#new_fn(ptr)) } } } #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] #[automatically_derived] const _: () = { #wasm_bindgen::__wbindgen_coverage! { #[no_mangle] #[doc(hidden)] // `allow_delayed` is whether it's ok to not actually free the `ptr` immediately // if it's still borrowed. pub unsafe extern "C-unwind" fn #free_fn(ptr: u32, allow_delayed: u32) { use #wasm_bindgen::__rt::alloc::rc::Rc; if allow_delayed != 0 { // Just drop the implicit `Rc` owned by JS, and then if the value is still // referenced it'll be kept alive by its other `Rc`s. let ptr = ptr as *mut #wasm_bindgen::__rt::WasmRefCell<#name>; #wasm_bindgen::__rt::assert_not_null(ptr); drop(Rc::from_raw(ptr)); } else { // Claim ownership of the value, which will panic if it's borrowed. let _ = <#name as #wasm_bindgen::convert::FromWasmAbi>::from_abi(ptr); } } } }; #[automatically_derived] impl #wasm_bindgen::convert::RefFromWasmAbi for #name { type Abi = u32; type Anchor = #wasm_bindgen::__rt::RcRef<#name>; unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor { use #wasm_bindgen::__rt::alloc::rc::Rc; let js = js as *mut #wasm_bindgen::__rt::WasmRefCell<#name>; #wasm_bindgen::__rt::assert_not_null(js); Rc::increment_strong_count(js); let rc = Rc::from_raw(js); #wasm_bindgen::__rt::RcRef::new(rc) } } #[automatically_derived] impl #wasm_bindgen::convert::RefMutFromWasmAbi for #name { type Abi = u32; type Anchor = #wasm_bindgen::__rt::RcRefMut<#name>; unsafe fn ref_mut_from_abi(js: Self::Abi) -> Self::Anchor { use #wasm_bindgen::__rt::alloc::rc::Rc; let js = js as *mut #wasm_bindgen::__rt::WasmRefCell<#name>; #wasm_bindgen::__rt::assert_not_null(js); Rc::increment_strong_count(js); let rc = Rc::from_raw(js); #wasm_bindgen::__rt::RcRefMut::new(rc) } } #[automatically_derived] impl #wasm_bindgen::convert::LongRefFromWasmAbi for #name { type Abi = u32; type Anchor = #wasm_bindgen::__rt::RcRef<#name>; unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor { ::ref_from_abi(js) } } #[automatically_derived] impl #wasm_bindgen::convert::OptionIntoWasmAbi for #name { #[inline] fn none() -> Self::Abi { 0 } } #[automatically_derived] impl #wasm_bindgen::convert::OptionFromWasmAbi for #name { #[inline] fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } } #[automatically_derived] impl #wasm_bindgen::convert::TryFromJsValue for #name { fn try_from_js_value(value: #wasm_bindgen::JsValue) -> #wasm_bindgen::__rt::core::result::Result { Self::try_from_js_value_ref(&value).ok_or(value) } fn try_from_js_value_ref(value: &#wasm_bindgen::JsValue) -> #wasm_bindgen::__rt::core::option::Option { let idx = #wasm_bindgen::convert::IntoWasmAbi::into_abi(value); #[link(wasm_import_module = "__wbindgen_placeholder__")] #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] extern "C" { fn #unwrap_fn(ptr: u32) -> u32; } #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] unsafe fn #unwrap_fn(_: u32) -> u32 { panic!("cannot convert from JsValue outside of the Wasm target") } let ptr = unsafe { #unwrap_fn(idx) }; if ptr == 0 { #wasm_bindgen::__rt::core::option::Option::None } else { unsafe { #wasm_bindgen::__rt::core::option::Option::Some( ::from_abi(ptr) ) } } } } #[automatically_derived] impl #wasm_bindgen::describe::WasmDescribeVector for #name { fn describe_vector() { use #wasm_bindgen::describe::*; inform(VECTOR); inform(NAMED_EXTERNREF); inform(#name_len); #(inform(#name_chars);)* } } #[automatically_derived] impl #wasm_bindgen::convert::VectorIntoWasmAbi for #name { type Abi = < #wasm_bindgen::__rt::alloc::boxed::Box<[#wasm_bindgen::JsValue]> as #wasm_bindgen::convert::IntoWasmAbi >::Abi; fn vector_into_abi( vector: #wasm_bindgen::__rt::alloc::boxed::Box<[#name]> ) -> Self::Abi { #wasm_bindgen::convert::js_value_vector_into_abi(vector) } } #[automatically_derived] impl #wasm_bindgen::convert::VectorFromWasmAbi for #name { type Abi = < #wasm_bindgen::__rt::alloc::boxed::Box<[#wasm_bindgen::JsValue]> as #wasm_bindgen::convert::FromWasmAbi >::Abi; unsafe fn vector_from_abi( js: Self::Abi ) -> #wasm_bindgen::__rt::alloc::boxed::Box<[#name]> { #wasm_bindgen::convert::js_value_vector_from_abi(js) } } }) .to_tokens(tokens); for field in self.fields.iter() { field.to_tokens(tokens); } } } impl ToTokens for ast::StructField { fn to_tokens(&self, tokens: &mut TokenStream) { let rust_name = &self.rust_name; let struct_name = &self.struct_name; let ty = &self.ty; let getter = &self.getter; let setter = &self.setter; let maybe_assert_copy = if self.getter_with_clone.is_some() { quote! {} } else { quote! { assert_copy::<#ty>() } }; let maybe_assert_copy = respan(maybe_assert_copy, ty); // Split this out so that it isn't affected by `quote_spanned!`. // // If we don't do this, it might end up being unable to reference `js` // properly because it doesn't have the same span. // // See https://github.com/wasm-bindgen/wasm-bindgen/pull/3725. let js_token = quote! { js }; let mut val = quote_spanned!(self.rust_name.span()=> (*#js_token).borrow().#rust_name); if let Some(span) = self.getter_with_clone { val = quote_spanned!(span=> <#ty as Clone>::clone(&#val) ); } let wasm_bindgen = &self.wasm_bindgen; (quote! { #[automatically_derived] const _: () = { #wasm_bindgen::__wbindgen_coverage! { #[cfg_attr(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), no_mangle)] #[doc(hidden)] pub unsafe extern "C-unwind" fn #getter(js: u32) -> #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi> { use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null}; use #wasm_bindgen::convert::IntoWasmAbi; fn assert_copy(){} #maybe_assert_copy; let js = js as *mut WasmRefCell<#struct_name>; assert_not_null(js); let val = #val; <#ty as IntoWasmAbi>::into_abi(val).into() } } }; }) .to_tokens(tokens); Descriptor { ident: getter, inner: quote! { <#ty as WasmDescribe>::describe(); }, attrs: vec![], wasm_bindgen: &self.wasm_bindgen, } .to_tokens(tokens); if self.readonly { return; } let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi }; let (args, names) = splat(wasm_bindgen, &Ident::new("val", rust_name.span()), &abi); (quote! { #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] #[automatically_derived] const _: () = { #wasm_bindgen::__wbindgen_coverage! { #[no_mangle] #[doc(hidden)] pub unsafe extern "C-unwind" fn #setter( js: u32, #(#args,)* ) { use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null}; use #wasm_bindgen::convert::FromWasmAbi; let js = js as *mut WasmRefCell<#struct_name>; assert_not_null(js); let val = <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#names),*); let val = <#ty as FromWasmAbi>::from_abi(val); (*js).borrow_mut().#rust_name = val; } } }; }) .to_tokens(tokens); } } impl TryToTokens for ast::Export { fn try_to_tokens(self: &ast::Export, into: &mut TokenStream) -> Result<(), Diagnostic> { let generated_name = self.rust_symbol(); let export_name = self.export_name(); let mut args = vec![]; let mut arg_conversions = vec![]; let mut converted_arguments = vec![]; let ret = Ident::new("_ret", Span::call_site()); let offset = if self.method_self.is_some() { args.push(quote! { me: u32 }); 1 } else { 0 }; let name = &self.rust_name; let wasm_bindgen = &self.wasm_bindgen; let wasm_bindgen_futures = &self.wasm_bindgen_futures; let receiver = match self.method_self { Some(ast::MethodSelf::ByValue) => { let class = self.rust_class.as_ref().unwrap(); arg_conversions.push(quote! { let me = unsafe { <#class as #wasm_bindgen::convert::FromWasmAbi>::from_abi(me) }; }); quote! { me.#name } } Some(ast::MethodSelf::RefMutable) => { let class = self.rust_class.as_ref().unwrap(); arg_conversions.push(quote! { let mut me = unsafe { <#class as #wasm_bindgen::convert::RefMutFromWasmAbi> ::ref_mut_from_abi(me) }; let me = &mut *me; }); quote! { me.#name } } Some(ast::MethodSelf::RefShared) => { let class = self.rust_class.as_ref().unwrap(); let (trait_, func, borrow) = if self.function.r#async { ( quote!(LongRefFromWasmAbi), quote!(long_ref_from_abi), quote!( <<#class as #wasm_bindgen::convert::LongRefFromWasmAbi> ::Anchor as #wasm_bindgen::__rt::core::borrow::Borrow<#class>> ::borrow(&me) ), ) } else { (quote!(RefFromWasmAbi), quote!(ref_from_abi), quote!(&*me)) }; arg_conversions.push(quote! { let me = unsafe { <#class as #wasm_bindgen::convert::#trait_>::#func(me) }; let me = #borrow; }); quote! { me.#name } } None => match &self.rust_class { Some(class) => quote! { #class::#name }, None => quote! { #name }, }, }; let mut argtys = Vec::new(); for (i, arg) in self.function.arguments.iter().enumerate() { argtys.push(&*arg.pat_type.ty); let i = i + offset; let ident = Ident::new(&format!("arg{i}"), Span::call_site()); fn unwrap_nested_types(ty: &syn::Type) -> &syn::Type { match &ty { syn::Type::Group(syn::TypeGroup { ref elem, .. }) => unwrap_nested_types(elem), syn::Type::Paren(syn::TypeParen { ref elem, .. }) => unwrap_nested_types(elem), _ => ty, } } let ty = unwrap_nested_types(&arg.pat_type.ty); match &ty { syn::Type::Reference(syn::TypeReference { mutability: Some(_), elem, .. }) => { let abi = quote! { <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>::Abi }; let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi); args.extend(prim_args); arg_conversions.push(quote! { let mut #ident = unsafe { <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi> ::ref_mut_from_abi( <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*) ) }; let #ident = &mut *#ident; }); } syn::Type::Reference(syn::TypeReference { elem, .. }) => { if self.function.r#async { let abi = quote! { <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>::Abi }; let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi); args.extend(prim_args); arg_conversions.push(quote! { let #ident = unsafe { <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi> ::long_ref_from_abi( <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*) ) }; let #ident = <<#elem as #wasm_bindgen::convert::LongRefFromWasmAbi> ::Anchor as core::borrow::Borrow<#elem>> ::borrow(&#ident); }); } else { let abi = quote! { <#elem as #wasm_bindgen::convert::RefFromWasmAbi>::Abi }; let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi); args.extend(prim_args); arg_conversions.push(quote! { let #ident = unsafe { <#elem as #wasm_bindgen::convert::RefFromWasmAbi> ::ref_from_abi( <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*) ) }; let #ident = &*#ident; }); } } _ => { let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi }; let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi); args.extend(prim_args); arg_conversions.push(quote! { let #ident = unsafe { <#ty as #wasm_bindgen::convert::FromWasmAbi> ::from_abi( <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*) ) }; }); } } converted_arguments.push(quote! { #ident }); } let syn_unit = syn::Type::Tuple(syn::TypeTuple { elems: Default::default(), paren_token: Default::default(), }); let syn_ret = self .function .ret .as_ref() .map(|ret| &ret.r#type) .unwrap_or(&syn_unit); if let syn::Type::Reference(_) = syn_ret { bail_span!(syn_ret, "cannot return a borrowed ref with #[wasm_bindgen]",) } // For an `async` function we always run it through `future_to_promise` // since we're returning a promise to JS, and this will implicitly // require that the function returns a `Future>` let (ret_ty, inner_ret_ty, ret_expr) = if self.function.r#async { if self.start { ( quote! { () }, quote! { () }, quote! { <#syn_ret as #wasm_bindgen::__rt::Start>::start(#ret.await) }, ) } else { ( quote! { #wasm_bindgen::JsValue }, quote! { #syn_ret }, quote! { <#syn_ret as #wasm_bindgen::__rt::IntoJsResult>::into_js_result(#ret.await) }, ) } } else if self.start { ( quote! { () }, quote! { () }, quote! { <#syn_ret as #wasm_bindgen::__rt::Start>::start(#ret) }, ) } else { (quote! { #syn_ret }, quote! { #syn_ret }, quote! { #ret }) }; let mut call = quote! { { #(#arg_conversions)* let #ret = #receiver(#(#converted_arguments),*); #ret_expr } }; if self.function.r#async { if self.start { call = quote! { #wasm_bindgen_futures::spawn_local(async move { #call }) } } else { call = quote! { #wasm_bindgen_futures::future_to_promise(async move { #call }).into() } } } else { call = quote! { #wasm_bindgen::__rt::maybe_catch_unwind(|| { #call }) }; } let projection = quote! { <#ret_ty as #wasm_bindgen::convert::ReturnWasmAbi> }; let convert_ret = quote! { #projection::return_abi(#ret).into() }; let describe_ret = quote! { <#ret_ty as WasmDescribe>::describe(); <#inner_ret_ty as WasmDescribe>::describe(); }; let nargs = self.function.arguments.len() as u32; let attrs = &self.function.rust_attrs; let mut checks = Vec::new(); if self.start { checks.push(quote! { const _ASSERT: fn() = || -> #projection::Abi { loop {} }; }); }; if let Some(class) = self.rust_class.as_ref() { // little helper function to make sure the check points to the // location of the function causing the assert to fail let mut add_check = |token_stream| { checks.push(respan(token_stream, &self.rust_name)); }; match &self.method_kind { ast::MethodKind::Constructor => { add_check(quote! { let _: #wasm_bindgen::__rt::marker::CheckSupportsConstructor<#class>; }); if self.function.r#async { (quote_spanned! { self.function.name_span => const _: () = { #[deprecated(note = "async constructors produce invalid TS code and support will be removed in the future")] const fn constructor() {} constructor(); }; }) .to_tokens(into); } } ast::MethodKind::Operation(operation) => match operation.kind { ast::OperationKind::Getter(_) | ast::OperationKind::Setter(_) => { if operation.is_static { add_check(quote! { let _: #wasm_bindgen::__rt::marker::CheckSupportsStaticProperty<#class>; }); } else { add_check(quote! { let _: #wasm_bindgen::__rt::marker::CheckSupportsInstanceProperty<#class>; }); } } _ => {} }, } } (quote! { #[automatically_derived] const _: () = { #wasm_bindgen::__wbindgen_coverage! { #(#attrs)* #[cfg_attr( all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), export_name = #export_name, )] pub unsafe extern "C-unwind" fn #generated_name(#(#args),*) -> #wasm_bindgen::convert::WasmRet<#projection::Abi> { const _: () = { #(#checks)* }; let #ret = #call; #convert_ret } } }; }) .to_tokens(into); let describe_args: TokenStream = argtys .iter() .map(|ty| match ty { syn::Type::Reference(reference) if self.function.r#async && reference.mutability.is_none() => { let inner = &reference.elem; quote! { inform(LONGREF); <#inner as WasmDescribe>::describe(); } } _ => quote! { <#ty as WasmDescribe>::describe(); }, }) .collect(); // In addition to generating the shim function above which is what // our generated JS will invoke, we *also* generate a "descriptor" // shim. This descriptor shim uses the `WasmDescribe` trait to // programmatically describe the type signature of the generated // shim above. This in turn is then used to inform the // `wasm-bindgen` CLI tool exactly what types and such it should be // using in JS. // // Note that this descriptor function is a purely an internal detail // of `#[wasm_bindgen]` and isn't intended to be exported to anyone // or actually part of the final was binary. Additionally, this is // literally executed when the `wasm-bindgen` tool executes. // // In any case, there's complications in `wasm-bindgen` to handle // this, but the tl;dr; is that this is stripped from the final wasm // binary along with anything it references. let export = Ident::new(&export_name, Span::call_site()); Descriptor { ident: &export, inner: quote! { inform(FUNCTION); inform(0); inform(#nargs); #describe_args #describe_ret }, attrs: attrs.clone(), wasm_bindgen: &self.wasm_bindgen, } .to_tokens(into); Ok(()) } } impl TryToTokens for ast::ImportKind { fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { match *self { ast::ImportKind::Function(ref f) => f.try_to_tokens(tokens)?, ast::ImportKind::Static(ref s) => s.to_tokens(tokens), ast::ImportKind::String(ref s) => s.to_tokens(tokens), ast::ImportKind::Type(ref t) => t.to_tokens(tokens), ast::ImportKind::Enum(ref e) => e.to_tokens(tokens), } Ok(()) } } impl ToTokens for ast::ImportType { fn to_tokens(&self, tokens: &mut TokenStream) { let vis = &self.vis; let rust_name = &self.rust_name; let attrs = &self.attrs; let doc_comment = match &self.doc_comment { None => "", Some(comment) => comment, }; let instanceof_shim = Ident::new(&self.instanceof_shim, Span::call_site()); let wasm_bindgen = &self.wasm_bindgen; let internal_obj = match self.extends.first() { Some(target) => { quote! { #target } } None => { quote! { #wasm_bindgen::JsValue } } }; let description = if let Some(typescript_type) = &self.typescript_type { let typescript_type_len = typescript_type.len() as u32; let typescript_type_chars = typescript_type.chars().map(|c| c as u32); quote! { use #wasm_bindgen::describe::*; inform(NAMED_EXTERNREF); inform(#typescript_type_len); #(inform(#typescript_type_chars);)* } } else { quote! { JsValue::describe() } }; let is_type_of = self.is_type_of.as_ref().map(|is_type_of| { quote! { #[inline] fn is_type_of(val: &JsValue) -> bool { let is_type_of: fn(&JsValue) -> bool = #is_type_of; is_type_of(val) } } }); let no_deref = self.no_deref; let doc = if doc_comment.is_empty() { quote! {} } else { quote! { #[doc = #doc_comment] } }; (quote! { #[automatically_derived] #(#attrs)* #doc #[repr(transparent)] #vis struct #rust_name { obj: #internal_obj } #[automatically_derived] const _: () = { use #wasm_bindgen::convert::TryFromJsValue; use #wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi}; use #wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; use #wasm_bindgen::convert::{RefFromWasmAbi, LongRefFromWasmAbi}; use #wasm_bindgen::describe::WasmDescribe; use #wasm_bindgen::{JsValue, JsCast}; use #wasm_bindgen::__rt::core; #[automatically_derived] impl WasmDescribe for #rust_name { fn describe() { #description } } #[automatically_derived] impl IntoWasmAbi for #rust_name { type Abi = ::Abi; #[inline] fn into_abi(self) -> Self::Abi { self.obj.into_abi() } } #[automatically_derived] impl OptionIntoWasmAbi for #rust_name { #[inline] fn none() -> Self::Abi { 0 } } #[automatically_derived] impl<'a> OptionIntoWasmAbi for &'a #rust_name { #[inline] fn none() -> Self::Abi { 0 } } #[automatically_derived] impl FromWasmAbi for #rust_name { type Abi = ::Abi; #[inline] unsafe fn from_abi(js: Self::Abi) -> Self { #rust_name { obj: JsValue::from_abi(js).into(), } } } #[automatically_derived] impl OptionFromWasmAbi for #rust_name { #[inline] fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } } #[automatically_derived] impl<'a> IntoWasmAbi for &'a #rust_name { type Abi = <&'a JsValue as IntoWasmAbi>::Abi; #[inline] fn into_abi(self) -> Self::Abi { (&self.obj).into_abi() } } #[automatically_derived] impl RefFromWasmAbi for #rust_name { type Abi = ::Abi; type Anchor = core::mem::ManuallyDrop<#rust_name>; #[inline] unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor { let tmp = ::ref_from_abi(js); core::mem::ManuallyDrop::new(#rust_name { obj: core::mem::ManuallyDrop::into_inner(tmp).into(), }) } } #[automatically_derived] impl LongRefFromWasmAbi for #rust_name { type Abi = ::Abi; type Anchor = #rust_name; #[inline] unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor { let tmp = ::long_ref_from_abi(js); #rust_name { obj: tmp.into() } } } // TODO: remove this on the next major version #[automatically_derived] impl From for #rust_name { #[inline] fn from(obj: JsValue) -> #rust_name { #rust_name { obj: obj.into() } } } #[automatically_derived] impl AsRef for #rust_name { #[inline] fn as_ref(&self) -> &JsValue { self.obj.as_ref() } } #[automatically_derived] impl AsRef<#rust_name> for #rust_name { #[inline] fn as_ref(&self) -> &#rust_name { self } } #[automatically_derived] impl From<#rust_name> for JsValue { #[inline] fn from(obj: #rust_name) -> JsValue { obj.obj.into() } } #[automatically_derived] impl JsCast for #rust_name { fn instanceof(val: &JsValue) -> bool { #[link(wasm_import_module = "__wbindgen_placeholder__")] #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] extern "C" { fn #instanceof_shim(val: u32) -> u32; } #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] unsafe fn #instanceof_shim(_: u32) -> u32 { panic!("cannot check instanceof on non-wasm targets"); } unsafe { let idx = val.into_abi(); #instanceof_shim(idx) != 0 } } #is_type_of #[inline] fn unchecked_from_js(val: JsValue) -> Self { #rust_name { obj: val.into() } } #[inline] fn unchecked_from_js_ref(val: &JsValue) -> &Self { // Should be safe because `#rust_name` is a transparent // wrapper around `val` unsafe { &*(val as *const JsValue as *const #rust_name) } } } }; }) .to_tokens(tokens); if !no_deref { (quote! { #[automatically_derived] impl #wasm_bindgen::__rt::core::ops::Deref for #rust_name { type Target = #internal_obj; #[inline] fn deref(&self) -> &#internal_obj { &self.obj } } }) .to_tokens(tokens); } for superclass in self.extends.iter() { (quote! { #[automatically_derived] impl From<#rust_name> for #superclass { #[inline] fn from(obj: #rust_name) -> #superclass { use #wasm_bindgen::JsCast; #superclass::unchecked_from_js(obj.into()) } } #[automatically_derived] impl AsRef<#superclass> for #rust_name { #[inline] fn as_ref(&self) -> &#superclass { use #wasm_bindgen::JsCast; #superclass::unchecked_from_js_ref(self.as_ref()) } } }) .to_tokens(tokens); } } } impl ToTokens for ast::StringEnum { fn to_tokens(&self, tokens: &mut TokenStream) { let vis = &self.vis; let enum_name = &self.name; let name_str = &self.export_name; let name_len = name_str.len() as u32; let name_chars = name_str.chars().map(u32::from); let variants = &self.variants; let variant_count = self.variant_values.len() as u32; let variant_values = &self.variant_values; let variant_indices = (0..variant_count).collect::>(); let invalid = variant_count; let hole = variant_count + 1; let attrs = &self.rust_attrs; let invalid_to_str_msg = format!( "Converting an invalid string enum ({enum_name}) back to a string is currently not supported" ); // A vector of EnumName::VariantName tokens for this enum let variant_paths: Vec = self .variants .iter() .map(|v| quote!(#enum_name::#v).into_token_stream()) .collect(); // Borrow variant_paths because we need to use it multiple times inside the quote! macro let variant_paths_ref = &variant_paths; let wasm_bindgen = &self.wasm_bindgen; (quote! { #(#attrs)* #[non_exhaustive] #[repr(u32)] #vis enum #enum_name { #(#variants = #variant_indices,)* #[automatically_derived] #[doc(hidden)] __Invalid } #[automatically_derived] impl #enum_name { fn from_str(s: &str) -> Option<#enum_name> { match s { #(#variant_values => Some(#variant_paths_ref),)* _ => None, } } fn to_str(&self) -> &'static str { match self { #(#variant_paths_ref => #variant_values,)* #enum_name::__Invalid => panic!(#invalid_to_str_msg), } } #vis fn from_js_value(obj: &#wasm_bindgen::JsValue) -> Option<#enum_name> { obj.as_string().and_then(|obj_str| Self::from_str(obj_str.as_str())) } } #[automatically_derived] impl #wasm_bindgen::convert::IntoWasmAbi for #enum_name { type Abi = u32; #[inline] fn into_abi(self) -> u32 { self as u32 } } #[automatically_derived] impl #wasm_bindgen::convert::FromWasmAbi for #enum_name { type Abi = u32; unsafe fn from_abi(val: u32) -> Self { match val { #(#variant_indices => #variant_paths_ref,)* #invalid => #enum_name::__Invalid, _ => unreachable!("The JS binding should only ever produce a valid value or the specific 'invalid' value"), } } } #[automatically_derived] impl #wasm_bindgen::convert::OptionFromWasmAbi for #enum_name { #[inline] fn is_none(val: &u32) -> bool { *val == #hole } } #[automatically_derived] impl #wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name { #[inline] fn none() -> Self::Abi { #hole } } #[automatically_derived] impl #wasm_bindgen::describe::WasmDescribe for #enum_name { fn describe() { use #wasm_bindgen::describe::*; inform(STRING_ENUM); inform(#name_len); #(inform(#name_chars);)* inform(#variant_count); } } #[automatically_derived] impl #wasm_bindgen::__rt::core::convert::From<#enum_name> for #wasm_bindgen::JsValue { fn from(val: #enum_name) -> Self { #wasm_bindgen::JsValue::from_str(val.to_str()) } } }) .to_tokens(tokens); } } impl TryToTokens for ast::ImportFunction { fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { let mut class_ty = None; let mut is_method = false; match self.kind { ast::ImportFunctionKind::Method { ref ty, ref kind, .. } => { if let ast::MethodKind::Operation(ast::Operation { is_static: false, .. }) = kind { is_method = true; } class_ty = Some(ty); } ast::ImportFunctionKind::Normal => {} } let vis = &self.function.rust_vis; let ret = match self.function.ret.as_ref().map(|ret| &ret.r#type) { Some(ty) => quote! { -> #ty }, None => quote!(), }; let mut abi_argument_names = Vec::new(); let mut abi_arguments = Vec::new(); let mut arg_conversions = Vec::new(); let mut arguments = Vec::new(); let ret_ident = Ident::new("_ret", Span::call_site()); let wasm_bindgen = &self.wasm_bindgen; let wasm_bindgen_futures = &self.wasm_bindgen_futures; for (i, arg) in self.function.arguments.iter().enumerate() { let ty = &arg.pat_type.ty; let name = match &*arg.pat_type.pat { syn::Pat::Ident(syn::PatIdent { by_ref: None, ident, subpat: None, .. }) => ident.clone(), syn::Pat::Wild(_) => syn::Ident::new(&format!("__genarg_{i}"), Span::call_site()), _ => bail_span!( arg.pat_type.pat, "unsupported pattern in #[wasm_bindgen] imported function", ), }; let abi = quote! { <#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi }; let (prim_args, prim_names) = splat(wasm_bindgen, &name, &abi); abi_arguments.extend(prim_args); abi_argument_names.extend(prim_names.iter().cloned()); let var = if i == 0 && is_method { quote! { self } } else { arguments.push(quote! { #name: #ty }); quote! { #name } }; arg_conversions.push(quote! { let #name = <#ty as #wasm_bindgen::convert::IntoWasmAbi> ::into_abi(#var); let (#(#prim_names),*) = <#abi as #wasm_bindgen::convert::WasmAbi>::split(#name); }); } let abi_ret; let mut convert_ret; match &self.js_ret { Some(syn::Type::Reference(_)) => { bail_span!( self.js_ret, "cannot return references in #[wasm_bindgen] imports yet" ); } Some(ref ty) => { if self.function.r#async { abi_ret = quote! { #wasm_bindgen::convert::WasmRet<<#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi> }; let future = quote! { #wasm_bindgen_futures::JsFuture::from( <#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi> ::from_abi(#ret_ident.join()) ).await }; convert_ret = if self.catch { quote! { Ok(#wasm_bindgen::JsCast::unchecked_from_js(#future?)) } } else { quote! { #wasm_bindgen::JsCast::unchecked_from_js(#future.expect("unexpected exception")) } }; } else { abi_ret = quote! { #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi> }; convert_ret = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi> ::from_abi(#ret_ident.join()) }; } } None => { if self.function.r#async { abi_ret = quote! { #wasm_bindgen::convert::WasmRet<<#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi> }; let future = quote! { #wasm_bindgen_futures::JsFuture::from( <#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi> ::from_abi(#ret_ident.join()) ).await }; convert_ret = if self.catch { quote! { #future?; Ok(()) } } else { quote! { #future.expect("uncaught exception"); } }; } else { abi_ret = quote! { () }; convert_ret = quote! { () }; } } } let mut exceptional_ret = quote!(); if self.catch && !self.function.r#async { convert_ret = quote! { Ok(#convert_ret) }; exceptional_ret = quote! { #wasm_bindgen::__rt::take_last_exception()?; }; } let rust_name = &self.rust_name; let import_name = &self.shim; let attrs = &self.function.rust_attrs; let arguments = &arguments; let abi_arguments = &abi_arguments[..]; let abi_argument_names = &abi_argument_names[..]; let doc = if self.doc_comment.is_empty() { quote! {} } else { let doc_comment = &self.doc_comment; quote! { #[doc = #doc_comment] } }; let me = if is_method { quote! { &self, } } else { quote!() }; // Route any errors pointing to this imported function to the identifier // of the function we're imported from so we at least know what function // is causing issues. // // Note that this is where type errors like "doesn't implement // FromWasmAbi" or "doesn't implement IntoWasmAbi" currently get routed. // I suspect that's because they show up in the signature via trait // projections as types of arguments, and all that needs to typecheck // before the body can be typechecked. Due to rust-lang/rust#60980 (and // probably related issues) we can't really get a precise span. // // Ideally what we want is to point errors for particular types back to // the specific argument/type that generated the error, but it looks // like rustc itself doesn't do great in that regard so let's just do // the best we can in the meantime. let extern_fn = respan( extern_fn( import_name, attrs, abi_arguments, abi_argument_names, abi_ret, ), &self.rust_name, ); let maybe_unsafe = if self.function.r#unsafe { Some(quote! {unsafe}) } else { None }; let maybe_async = if self.function.r#async { Some(quote! {async}) } else { None }; let invocation = quote! { // This is due to `#[automatically_derived]` attribute cannot be // placed onto bare functions. #[allow(nonstandard_style)] #[allow(clippy::all, clippy::nursery, clippy::pedantic, clippy::restriction)] #(#attrs)* #doc #vis #maybe_async #maybe_unsafe fn #rust_name(#me #(#arguments),*) #ret { #extern_fn unsafe { let #ret_ident = { #(#arg_conversions)* #import_name(#(#abi_argument_names),*) }; #exceptional_ret #convert_ret } } }; if let Some(class) = class_ty { (quote! { #[automatically_derived] impl #class { #invocation } }) .to_tokens(tokens); } else { invocation.to_tokens(tokens); } Ok(()) } } // See comment above in ast::Export for what's going on here. struct DescribeImport<'a> { kind: &'a ast::ImportKind, wasm_bindgen: &'a syn::Path, } impl ToTokens for DescribeImport<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let f = match *self.kind { ast::ImportKind::Function(ref f) => f, ast::ImportKind::Static(_) => return, ast::ImportKind::String(_) => return, ast::ImportKind::Type(_) => return, ast::ImportKind::Enum(_) => return, }; let argtys = f.function.arguments.iter().map(|arg| &arg.pat_type.ty); let nargs = f.function.arguments.len() as u32; let inform_ret = match &f.js_ret { Some(ref t) => quote! { <#t as WasmDescribe>::describe(); }, // async functions always return a JsValue, even if they say to return () None if f.function.r#async => quote! { ::describe(); }, None => quote! { <() as WasmDescribe>::describe(); }, }; Descriptor { ident: &f.shim, inner: quote! { inform(FUNCTION); inform(0); inform(#nargs); #(<#argtys as WasmDescribe>::describe();)* #inform_ret #inform_ret }, attrs: f.function.rust_attrs.clone(), wasm_bindgen: self.wasm_bindgen, } .to_tokens(tokens); } } impl ToTokens for ast::Enum { fn to_tokens(&self, into: &mut TokenStream) { let enum_name = &self.rust_name; let name_str = self.js_name.to_string(); let name_len = name_str.len() as u32; let name_chars = name_str.chars().map(|c| c as u32); let hole = &self.hole; let underlying = if self.signed { quote! { i32 } } else { quote! { u32 } }; let cast_clauses = self.variants.iter().map(|variant| { let variant_name = &variant.name; quote! { if js == #enum_name::#variant_name as #underlying { #enum_name::#variant_name } } }); let try_from_cast_clauses = cast_clauses.clone(); let wasm_bindgen = &self.wasm_bindgen; (quote! { #[automatically_derived] impl #wasm_bindgen::convert::IntoWasmAbi for #enum_name { type Abi = #underlying; #[inline] fn into_abi(self) -> #underlying { self as #underlying } } #[automatically_derived] impl #wasm_bindgen::convert::FromWasmAbi for #enum_name { type Abi = #underlying; #[inline] unsafe fn from_abi(js: #underlying) -> Self { #(#cast_clauses else)* { #wasm_bindgen::throw_str("invalid enum value passed") } } } #[automatically_derived] impl #wasm_bindgen::convert::OptionFromWasmAbi for #enum_name { #[inline] fn is_none(val: &Self::Abi) -> bool { *val == #hole as #underlying } } #[automatically_derived] impl #wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name { #[inline] fn none() -> Self::Abi { #hole as #underlying } } #[automatically_derived] impl #wasm_bindgen::describe::WasmDescribe for #enum_name { fn describe() { use #wasm_bindgen::describe::*; inform(ENUM); inform(#name_len); #(inform(#name_chars);)* inform(#hole); } } #[automatically_derived] impl #wasm_bindgen::__rt::core::convert::From<#enum_name> for #wasm_bindgen::JsValue { fn from(value: #enum_name) -> Self { #wasm_bindgen::JsValue::from_f64((value as #underlying).into()) } } #[automatically_derived] impl #wasm_bindgen::convert::TryFromJsValue for #enum_name { fn try_from_js_value_ref(value: &#wasm_bindgen::JsValue) -> #wasm_bindgen::__rt::core::option::Option { use #wasm_bindgen::__rt::core::convert::TryFrom; let js = f64::try_from(value).ok()? as #underlying; #wasm_bindgen::__rt::core::option::Option::Some( #(#try_from_cast_clauses else)* { return #wasm_bindgen::__rt::core::option::Option::None; } ) } } #[automatically_derived] impl #wasm_bindgen::describe::WasmDescribeVector for #enum_name { fn describe_vector() { use #wasm_bindgen::describe::*; inform(VECTOR); <#wasm_bindgen::JsValue as #wasm_bindgen::describe::WasmDescribe>::describe(); } } #[automatically_derived] impl #wasm_bindgen::convert::VectorIntoWasmAbi for #enum_name { type Abi = < #wasm_bindgen::__rt::alloc::boxed::Box<[#wasm_bindgen::JsValue]> as #wasm_bindgen::convert::IntoWasmAbi >::Abi; fn vector_into_abi( vector: #wasm_bindgen::__rt::alloc::boxed::Box<[#enum_name]> ) -> Self::Abi { #wasm_bindgen::convert::js_value_vector_into_abi(vector) } } #[automatically_derived] impl #wasm_bindgen::convert::VectorFromWasmAbi for #enum_name { type Abi = < #wasm_bindgen::__rt::alloc::boxed::Box<[#wasm_bindgen::JsValue]> as #wasm_bindgen::convert::FromWasmAbi >::Abi; unsafe fn vector_from_abi( js: Self::Abi ) -> #wasm_bindgen::__rt::alloc::boxed::Box<[#enum_name]> { #wasm_bindgen::convert::js_value_vector_from_abi(js) } } }) .to_tokens(into); } } impl ToTokens for ast::ImportStatic { fn to_tokens(&self, into: &mut TokenStream) { let ty = &self.ty; if let Some(thread_local) = self.thread_local { thread_local_import( &self.vis, &self.rust_name, &self.wasm_bindgen, ty, ty, &self.shim, thread_local, ) .to_tokens(into) } else { let vis = &self.vis; let name = &self.rust_name; let wasm_bindgen = &self.wasm_bindgen; let ty = &self.ty; let shim_name = &self.shim; let init = static_init(wasm_bindgen, ty, shim_name); into.extend(quote! { #[automatically_derived] #[deprecated = "use with `#[wasm_bindgen(thread_local_v2)]` instead"] }); into.extend( quote_spanned! { name.span() => #vis static #name: #wasm_bindgen::JsStatic<#ty> = { fn init() -> #ty { #init } #wasm_bindgen::__rt::std::thread_local!(static _VAL: #ty = init();); #wasm_bindgen::JsStatic { __inner: &_VAL, } }; }, ); } Descriptor { ident: &self.shim, inner: quote! { <#ty as WasmDescribe>::describe(); }, attrs: vec![], wasm_bindgen: &self.wasm_bindgen, } .to_tokens(into); } } impl ToTokens for ast::ImportString { fn to_tokens(&self, into: &mut TokenStream) { let js_sys = &self.js_sys; let actual_ty: syn::Type = parse_quote!(#js_sys::JsString); thread_local_import( &self.vis, &self.rust_name, &self.wasm_bindgen, &actual_ty, &self.ty, &self.shim, self.thread_local, ) .to_tokens(into); } } fn thread_local_import( vis: &syn::Visibility, name: &Ident, wasm_bindgen: &syn::Path, actual_ty: &syn::Type, ty: &syn::Type, shim_name: &Ident, thread_local: ast::ThreadLocal, ) -> TokenStream { let init = static_init(wasm_bindgen, ty, shim_name); match thread_local { ast::ThreadLocal::V1 => quote! { #wasm_bindgen::__rt::std::thread_local! { #[automatically_derived] #[deprecated = "use with `#[wasm_bindgen(thread_local_v2)]` instead"] #vis static #name: #actual_ty = { #init }; } }, ast::ThreadLocal::V2 => { quote! { #vis static #name: #wasm_bindgen::JsThreadLocal<#actual_ty> = { fn init() -> #actual_ty { #init } #wasm_bindgen::__wbindgen_thread_local!(#wasm_bindgen, #actual_ty) }; } } } } fn static_init(wasm_bindgen: &syn::Path, ty: &syn::Type, shim_name: &Ident) -> TokenStream { let abi_ret = quote! { #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi> }; quote! { #[link(wasm_import_module = "__wbindgen_placeholder__")] #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] extern "C" { fn #shim_name() -> #abi_ret; } #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] unsafe fn #shim_name() -> #abi_ret { panic!("cannot access imported statics on non-wasm targets") } unsafe { <#ty as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name().join()) } } } /// Emits the necessary glue tokens for "descriptor", generating an appropriate /// symbol name as well as attributes around the descriptor function itself. struct Descriptor<'a, T> { ident: &'a Ident, inner: T, attrs: Vec, wasm_bindgen: &'a syn::Path, } impl ToTokens for Descriptor<'_, T> { fn to_tokens(&self, tokens: &mut TokenStream) { // It's possible for the same descriptor to be emitted in two different // modules (aka a value imported twice in a crate, each in a separate // module). In this case no need to emit duplicate descriptors (which // leads to duplicate symbol errors), instead just emit one. // // It's up to the descriptors themselves to ensure they have unique // names for unique items imported, currently done via `ShortHash` and // hashing appropriate data into the symbol name. thread_local! { static DESCRIPTORS_EMITTED: RefCell> = RefCell::default(); } let ident = self.ident; if !DESCRIPTORS_EMITTED.with(|list| list.borrow_mut().insert(ident.to_string())) { return; } let name = Ident::new(&format!("__wbindgen_describe_{ident}"), ident.span()); let inner = &self.inner; let attrs = &self.attrs; let wasm_bindgen = &self.wasm_bindgen; (quote! { #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] #[automatically_derived] const _: () = { #wasm_bindgen::__wbindgen_coverage! { #(#attrs)* #[no_mangle] #[doc(hidden)] pub extern "C-unwind" fn #name() { use #wasm_bindgen::describe::*; // See definition of `link_mem_intrinsics` for what this is doing #wasm_bindgen::__rt::link_mem_intrinsics(); #inner } } }; }) .to_tokens(tokens); } } fn extern_fn( import_name: &Ident, attrs: &[syn::Attribute], abi_arguments: &[TokenStream], abi_argument_names: &[Ident], abi_ret: TokenStream, ) -> TokenStream { quote! { #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] #(#attrs)* #[link(wasm_import_module = "__wbindgen_placeholder__")] extern "C" { fn #import_name(#(#abi_arguments),*) -> #abi_ret; } #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] unsafe fn #import_name(#(#abi_arguments),*) -> #abi_ret { #( drop(#abi_argument_names); )* panic!("cannot call wasm-bindgen imported functions on \ non-wasm targets"); } } } /// Splats an argument with the given name and ABI type into 4 arguments, one /// for each primitive that the ABI type splits into. /// /// Returns an `(args, names)` pair, where `args` is the list of arguments to /// be inserted into the function signature, and `names` is a list of the names /// of those arguments. fn splat( wasm_bindgen: &syn::Path, name: &Ident, abi: &TokenStream, ) -> (Vec, Vec) { let mut args = Vec::new(); let mut names = Vec::new(); for n in 1_u32..=4 { let arg_name = format_ident!("{}_{}", name, n); let prim_name = format_ident!("Prim{}", n); args.push(quote! { #arg_name: <#abi as #wasm_bindgen::convert::WasmAbi>::#prim_name }); names.push(arg_name); } (args, names) } /// Converts `span` into a stream of tokens, and attempts to ensure that `input` /// has all the appropriate span information so errors in it point to `span`. fn respan(input: TokenStream, span: &dyn ToTokens) -> TokenStream { let mut first_span = Span::call_site(); let mut last_span = Span::call_site(); let mut spans = TokenStream::new(); span.to_tokens(&mut spans); for (i, token) in spans.into_iter().enumerate() { if i == 0 { first_span = Span::call_site().located_at(token.span()); } last_span = Span::call_site().located_at(token.span()); } let mut new_tokens = Vec::new(); for (i, mut token) in input.into_iter().enumerate() { if i == 0 { token.set_span(first_span); } else { token.set_span(last_span); } new_tokens.push(token); } new_tokens.into_iter().collect() } wasm-bindgen-macro-support-0.2.108/src/encode.rs000064400000000000000000000474311046102023000175630ustar 00000000000000use crate::hash::ShortHash; use proc_macro2::{Ident, Span}; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::env; use std::fs; use std::path::PathBuf; use syn::ext::IdentExt; use crate::ast; use crate::Diagnostic; #[derive(Clone)] pub enum EncodeChunk { EncodedBuf(Vec), StrExpr(syn::Expr), // TODO: support more expr type; } pub struct EncodeResult { pub custom_section: Vec, pub included_files: Vec, } pub fn encode(program: &ast::Program) -> Result { let mut e = Encoder::new(); let i = Interner::new(); shared_program(program, &i)?.encode(&mut e); let custom_section = e.finish(); let included_files = i .files .borrow() .values() .map(|p| &p.path) .cloned() .collect(); Ok(EncodeResult { custom_section, included_files, }) } struct Interner { bump: bumpalo::Bump, files: RefCell>, root: PathBuf, crate_name: String, has_package_json: Cell, } struct LocalFile { path: PathBuf, definition: Span, new_identifier: String, linked_module: bool, } impl Interner { fn new() -> Interner { let root = env::var_os("CARGO_MANIFEST_DIR") .expect("should have CARGO_MANIFEST_DIR env var") .into(); let crate_name = env::var("CARGO_PKG_NAME").expect("should have CARGO_PKG_NAME env var"); Interner { bump: bumpalo::Bump::new(), files: RefCell::new(HashMap::new()), root, crate_name, has_package_json: Cell::new(false), } } fn intern(&self, s: &Ident) -> &str { self.intern_str(&s.to_string()) } fn intern_str(&self, s: &str) -> &str { // NB: eventually this could be used to intern `s` to only allocate one // copy, but for now let's just "transmute" `s` to have the same // lifetime as this struct itself (which is our main goal here) self.bump.alloc_str(s) } /// Given an import to a local module `id` this generates a unique module id /// to assign to the contents of `id`. /// /// Note that repeated invocations of this function will be memoized, so the /// same `id` will always return the same resulting unique `id`. fn resolve_import_module( &self, id: &str, span: Span, linked_module: bool, ) -> Result, Diagnostic> { let mut files = self.files.borrow_mut(); if let Some(file) = files.get(id) { return Ok(ImportModule::Named(self.intern_str(&file.new_identifier))); } self.check_for_package_json(); let path = if let Some(id) = id.strip_prefix('/') { self.root.join(id) } else if id.starts_with("./") || id.starts_with("../") { let msg = "relative module paths aren't supported yet"; return Err(Diagnostic::span_error(span, msg)); } else { return Ok(ImportModule::RawNamed(self.intern_str(id))); }; // Generate a unique ID which is somewhat readable as well, so mix in // the crate name, hash to make it unique, and then the original path. let new_identifier = format!("{}{id}", self.unique_crate_identifier()); let file = LocalFile { path, definition: span, new_identifier, linked_module, }; files.insert(id.to_string(), file); drop(files); self.resolve_import_module(id, span, linked_module) } fn unique_crate_identifier(&self) -> String { format!("{}-{}", self.crate_name, ShortHash(0)) } fn check_for_package_json(&self) { if self.has_package_json.get() { return; } let path = self.root.join("package.json"); if path.exists() { self.has_package_json.set(true); } } } fn shared_program<'a>( prog: &'a ast::Program, intern: &'a Interner, ) -> Result, Diagnostic> { Ok(Program { exports: prog .exports .iter() .map(|a| shared_export(a, intern)) .collect::, _>>()?, structs: prog .structs .iter() .map(|a| shared_struct(a, intern)) .collect(), enums: prog.enums.iter().map(|a| shared_enum(a, intern)).collect(), imports: prog .imports .iter() .map(|a| shared_import(a, intern)) .collect::, _>>()?, typescript_custom_sections: prog .typescript_custom_sections .iter() .map(|x| shared_lit_or_expr(x, intern)) .collect(), linked_modules: prog .linked_modules .iter() .enumerate() .map(|(i, a)| shared_linked_module(&prog.link_function_name(i), a, intern)) .collect::, _>>()?, local_modules: intern .files .borrow() .values() .map(|file| { fs::read_to_string(&file.path) .map(|s| LocalModule { identifier: intern.intern_str(&file.new_identifier), contents: intern.intern_str(&s), linked_module: file.linked_module, }) .map_err(|e| { let msg = format!("failed to read file `{}`: {e}", file.path.display()); Diagnostic::span_error(file.definition, msg) }) }) .collect::, _>>()?, inline_js: prog .inline_js .iter() .map(|js| intern.intern_str(js)) .collect(), unique_crate_identifier: intern.intern_str(&intern.unique_crate_identifier()), package_json: if intern.has_package_json.get() { Some(intern.intern_str(intern.root.join("package.json").to_str().unwrap())) } else { None }, }) } fn shared_export<'a>( export: &'a ast::Export, intern: &'a Interner, ) -> Result, Diagnostic> { let consumed = matches!(export.method_self, Some(ast::MethodSelf::ByValue)); let method_kind = from_ast_method_kind(&export.function, intern, &export.method_kind)?; Ok(Export { class: export.js_class.as_deref(), comments: export.comments.iter().map(|s| &**s).collect(), consumed, function: shared_function(&export.function, intern), js_namespace: export .js_namespace .as_ref() .map(|ns| ns.iter().map(|s| &**s).collect()), method_kind, start: export.start, }) } fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> { let args = func.arguments .iter() .enumerate() .map(|(idx, arg)| FunctionArgumentData { // use argument's "js_name" if it was provided via attributes // if not use the original Rust argument ident name: arg.js_name.clone().unwrap_or( if let syn::Pat::Ident(x) = &*arg.pat_type.pat { x.ident.unraw().to_string() } else { format!("arg{idx}") }, ), ty_override: arg.js_type.as_deref(), desc: arg.desc.as_deref(), }) .collect::>(); Function { args, asyncness: func.r#async, name: &func.name, generate_typescript: func.generate_typescript, generate_jsdoc: func.generate_jsdoc, variadic: func.variadic, ret_ty_override: func.ret.as_ref().and_then(|v| v.js_type.as_deref()), ret_desc: func.ret.as_ref().and_then(|v| v.desc.as_deref()), } } fn shared_enum<'a>(e: &'a ast::Enum, intern: &'a Interner) -> Enum<'a> { Enum { name: &e.js_name, signed: e.signed, variants: e .variants .iter() .map(|v| shared_variant(v, intern)) .collect(), comments: e.comments.iter().map(|s| &**s).collect(), generate_typescript: e.generate_typescript, js_namespace: e .js_namespace .as_ref() .map(|ns| ns.iter().map(|s| &**s).collect()), private: e.private, } } fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<'a> { EnumVariant { name: intern.intern(&v.name), value: v.value, comments: v.comments.iter().map(|s| &**s).collect(), } } fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result, Diagnostic> { // Resolve reexport name: use explicit rename if provided, otherwise use the import's name let reexport = i.reexport.as_ref().map(|rename_opt| { rename_opt.clone().unwrap_or_else(|| { // Get the default name from the import kind match &i.kind { ast::ImportKind::Type(t) => t.js_name.clone(), ast::ImportKind::Function(f) => f.function.name.clone(), ast::ImportKind::Static(s) => s.js_name.clone(), _ => unreachable!("reexport only supported on types, functions, and statics"), } }) }); Ok(Import { module: i .module .as_ref() .map(|m| shared_module(m, intern, false)) .transpose()?, js_namespace: i.js_namespace.clone(), reexport, kind: shared_import_kind(&i.kind, intern)?, }) } fn shared_lit_or_expr<'a>(i: &'a ast::LitOrExpr, _intern: &'a Interner) -> LitOrExpr<'a> { match i { ast::LitOrExpr::Lit(lit) => LitOrExpr::Lit(lit), ast::LitOrExpr::Expr(expr) => LitOrExpr::Expr(expr), } } fn shared_linked_module<'a>( name: &str, i: &'a ast::ImportModule, intern: &'a Interner, ) -> Result, Diagnostic> { Ok(LinkedModule { module: shared_module(i, intern, true)?, link_function_name: intern.intern_str(name), }) } fn shared_module<'a>( m: &'a ast::ImportModule, intern: &'a Interner, linked_module: bool, ) -> Result, Diagnostic> { Ok(match m { ast::ImportModule::Named(m, span) => { intern.resolve_import_module(m, *span, linked_module)? } ast::ImportModule::RawNamed(m, _span) => ImportModule::RawNamed(intern.intern_str(m)), ast::ImportModule::Inline(idx) => ImportModule::Inline(*idx as u32), }) } fn shared_import_kind<'a>( i: &'a ast::ImportKind, intern: &'a Interner, ) -> Result, Diagnostic> { Ok(match i { ast::ImportKind::Function(f) => ImportKind::Function(shared_import_function(f, intern)?), ast::ImportKind::Static(f) => ImportKind::Static(shared_import_static(f, intern)), ast::ImportKind::String(f) => ImportKind::String(shared_import_string(f, intern)), ast::ImportKind::Type(f) => ImportKind::Type(shared_import_type(f, intern)), ast::ImportKind::Enum(f) => ImportKind::Enum(shared_import_enum(f, intern)), }) } fn shared_import_function<'a>( i: &'a ast::ImportFunction, intern: &'a Interner, ) -> Result, Diagnostic> { let method = match &i.kind { ast::ImportFunctionKind::Method { class, kind, .. } => { let kind = from_ast_method_kind(&i.function, intern, kind)?; Some(MethodData { class, kind }) } ast::ImportFunctionKind::Normal => None, }; Ok(ImportFunction { shim: intern.intern(&i.shim), catch: i.catch, method, assert_no_shim: i.assert_no_shim, structural: i.structural, function: shared_function(&i.function, intern), variadic: i.variadic, }) } fn shared_import_static<'a>(i: &'a ast::ImportStatic, intern: &'a Interner) -> ImportStatic<'a> { ImportStatic { name: &i.js_name, shim: intern.intern(&i.shim), } } fn shared_import_string<'a>(i: &'a ast::ImportString, intern: &'a Interner) -> ImportString<'a> { ImportString { shim: intern.intern(&i.shim), string: &i.string, } } fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> ImportType<'a> { ImportType { name: &i.js_name, instanceof_shim: &i.instanceof_shim, vendor_prefixes: i.vendor_prefixes.iter().map(|x| intern.intern(x)).collect(), } } fn shared_import_enum<'a>(i: &'a ast::StringEnum, _intern: &'a Interner) -> StringEnum<'a> { StringEnum { name: &i.export_name, generate_typescript: i.generate_typescript, variant_values: i.variant_values.iter().map(|x| &**x).collect(), comments: i.comments.iter().map(|s| &**s).collect(), js_namespace: i .js_namespace .as_ref() .map(|ns| ns.iter().map(|s| &**s).collect()), } } fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> { Struct { name: &s.js_name, fields: s .fields .iter() .map(|s| shared_struct_field(s, intern)) .collect(), comments: s.comments.iter().map(|s| &**s).collect(), is_inspectable: s.is_inspectable, generate_typescript: s.generate_typescript, js_namespace: s .js_namespace .as_ref() .map(|ns| ns.iter().map(|s| &**s).collect()), private: s.private, } } fn shared_struct_field<'a>(s: &'a ast::StructField, _intern: &'a Interner) -> StructField<'a> { StructField { name: &s.js_name, readonly: s.readonly, comments: s.comments.iter().map(|s| &**s).collect(), generate_typescript: s.generate_typescript, generate_jsdoc: s.generate_jsdoc, } } trait Encode { fn encode(&self, dst: &mut Encoder); } struct Encoder { dst: Vec, } enum LitOrExpr<'a> { Expr(&'a syn::Expr), Lit(&'a str), } impl Encode for LitOrExpr<'_> { fn encode(&self, dst: &mut Encoder) { match self { LitOrExpr::Expr(expr) => { dst.dst.push(EncodeChunk::StrExpr((*expr).clone())); } LitOrExpr::Lit(s) => s.encode(dst), } } } impl Encoder { fn new() -> Encoder { Encoder { dst: vec![] } } fn finish(self) -> Vec { self.dst } fn byte(&mut self, byte: u8) { if let Some(EncodeChunk::EncodedBuf(buf)) = self.dst.last_mut() { buf.push(byte); } else { self.dst.push(EncodeChunk::EncodedBuf(vec![byte])); } } fn extend_from_slice(&mut self, slice: &[u8]) { if let Some(EncodeChunk::EncodedBuf(buf)) = self.dst.last_mut() { buf.extend_from_slice(slice); } else { self.dst.push(EncodeChunk::EncodedBuf(slice.to_owned())); } } } impl Encode for bool { fn encode(&self, dst: &mut Encoder) { dst.byte(*self as u8); } } impl Encode for u32 { fn encode(&self, dst: &mut Encoder) { let mut val = *self; while (val >> 7) != 0 { dst.byte((val as u8) | 0x80); val >>= 7; } assert_eq!(val >> 7, 0); dst.byte(val as u8); } } impl Encode for usize { fn encode(&self, dst: &mut Encoder) { assert!(*self <= u32::MAX as usize); (*self as u32).encode(dst); } } impl Encode for &[u8] { fn encode(&self, dst: &mut Encoder) { self.len().encode(dst); dst.extend_from_slice(self); } } impl Encode for &str { fn encode(&self, dst: &mut Encoder) { self.as_bytes().encode(dst); } } impl Encode for String { fn encode(&self, dst: &mut Encoder) { self.as_bytes().encode(dst); } } impl Encode for Vec { fn encode(&self, dst: &mut Encoder) { self.len().encode(dst); for item in self { item.encode(dst); } } } impl Encode for Option { fn encode(&self, dst: &mut Encoder) { match self { None => dst.byte(0), Some(val) => { dst.byte(1); val.encode(dst) } } } } macro_rules! encode_struct { ($name:ident ($($lt:tt)*) $($field:ident: $ty:ty,)*) => { struct $name $($lt)* { $($field: $ty,)* } impl $($lt)* Encode for $name $($lt)* { fn encode(&self, _dst: &mut Encoder) { $(self.$field.encode(_dst);)* } } } } macro_rules! encode_enum { ($name:ident ($($lt:tt)*) $($fields:tt)*) => ( enum $name $($lt)* { $($fields)* } impl$($lt)* Encode for $name $($lt)* { fn encode(&self, dst: &mut Encoder) { use self::$name::*; encode_enum!(@arms self dst (0) () $($fields)*) } } ); (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*)) => ( encode_enum!(@expr match $me { $($arms)* }) ); (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident, $($rest:tt)*) => ( encode_enum!( @arms $me $dst ($cnt+1) ($($arms)* $name => $dst.byte($cnt),) $($rest)* ) ); (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident($t:ty), $($rest:tt)*) => ( encode_enum!( @arms $me $dst ($cnt+1) ($($arms)* $name(val) => { $dst.byte($cnt); val.encode($dst) }) $($rest)* ) ); (@expr $e:expr) => ($e); } macro_rules! encode_api { () => (); (struct $name:ident<'a> { $($fields:tt)* } $($rest:tt)*) => ( encode_struct!($name (<'a>) $($fields)*); encode_api!($($rest)*); ); (struct $name:ident { $($fields:tt)* } $($rest:tt)*) => ( encode_struct!($name () $($fields)*); encode_api!($($rest)*); ); (enum $name:ident<'a> { $($variants:tt)* } $($rest:tt)*) => ( encode_enum!($name (<'a>) $($variants)*); encode_api!($($rest)*); ); (enum $name:ident { $($variants:tt)* } $($rest:tt)*) => ( encode_enum!($name () $($variants)*); encode_api!($($rest)*); ); } wasm_bindgen_shared::shared_api!(encode_api); fn from_ast_method_kind<'a>( function: &'a ast::Function, intern: &'a Interner, method_kind: &'a ast::MethodKind, ) -> Result, Diagnostic> { Ok(match method_kind { ast::MethodKind::Constructor => MethodKind::Constructor, ast::MethodKind::Operation(ast::Operation { is_static, kind }) => { let is_static = *is_static; let kind = match kind { ast::OperationKind::Getter(g) => { let g = g.as_ref().map(|g| intern.intern_str(g)); OperationKind::Getter(g.unwrap_or_else(|| function.infer_getter_property())) } ast::OperationKind::Regular => OperationKind::Regular, ast::OperationKind::RegularThis => OperationKind::RegularThis, ast::OperationKind::Setter(s) => { let s = s.as_ref().map(|s| intern.intern_str(s)); OperationKind::Setter(match s { Some(s) => s, None => intern.intern_str(&function.infer_setter_property()?), }) } ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter, ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter, ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter, }; MethodKind::Operation(Operation { is_static, kind }) } }) } wasm-bindgen-macro-support-0.2.108/src/error.rs000064400000000000000000000076011046102023000174520ustar 00000000000000use proc_macro2::*; use quote::{ToTokens, TokenStreamExt}; use syn::parse::Error; /// Provide a Diagnostic with the given span and message macro_rules! err_span { ($span:expr, $($msg:tt)*) => ( $crate::Diagnostic::spanned_error(&$span, format!($($msg)*)) ) } /// Immediately fail and return an Err, with the arguments passed to err_span! macro_rules! bail_span { ($($t:tt)*) => ( return Err(err_span!($($t)*).into()) ) } /// A struct representing a diagnostic to emit to the end-user as an error. #[derive(Debug)] pub struct Diagnostic { inner: Repr, } #[derive(Debug)] enum Repr { Single { text: String, span: Option<(Span, Span)>, }, SynError(Error), Multi { diagnostics: Vec, }, } impl Diagnostic { /// Generate a `Diagnostic` from an informational message with no Span pub fn error>(text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), span: None, }, } } /// Generate a `Diagnostic` from a Span and an informational message pub fn span_error>(span: Span, text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), span: Some((span, span)), }, } } /// Generate a `Diagnostic` from the span of any tokenizable object and a message pub fn spanned_error>(node: &dyn ToTokens, text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), span: extract_spans(node), }, } } /// Attempt to generate a `Diagnostic` from a vector of other `Diagnostic` instances. /// If the `Vec` is empty, returns `Ok(())`, otherwise returns the new `Diagnostic` pub fn from_vec(diagnostics: Vec) -> Result<(), Diagnostic> { if diagnostics.is_empty() { Ok(()) } else { Err(Diagnostic { inner: Repr::Multi { diagnostics }, }) } } /// Immediately trigger a panic from this `Diagnostic` #[allow(unconditional_recursion)] pub fn panic(&self) -> ! { match &self.inner { Repr::Single { text, .. } => panic!("{}", text), Repr::SynError(error) => panic!("{}", error), Repr::Multi { diagnostics } => diagnostics[0].panic(), } } } impl From for Diagnostic { fn from(err: Error) -> Diagnostic { Diagnostic { inner: Repr::SynError(err), } } } fn extract_spans(node: &dyn ToTokens) -> Option<(Span, Span)> { let mut t = TokenStream::new(); node.to_tokens(&mut t); let mut tokens = t.into_iter(); let start = tokens.next().map(|t| t.span()); let end = tokens.last().map(|t| t.span()); start.map(|start| (start, end.unwrap_or(start))) } impl ToTokens for Diagnostic { fn to_tokens(&self, dst: &mut TokenStream) { match &self.inner { Repr::Single { text, span } => { let cs2 = (Span::call_site(), Span::call_site()); let (start, end) = span.unwrap_or(cs2); dst.append(Ident::new("compile_error", start)); dst.append(Punct::new('!', Spacing::Alone)); let mut message = TokenStream::new(); message.append(Literal::string(text)); let mut group = Group::new(Delimiter::Brace, message); group.set_span(end); dst.append(group); } Repr::Multi { diagnostics } => { for diagnostic in diagnostics { diagnostic.to_tokens(dst); } } Repr::SynError(err) => { err.to_compile_error().to_tokens(dst); } } } } wasm-bindgen-macro-support-0.2.108/src/hash.rs000064400000000000000000000031431046102023000172410ustar 00000000000000//! Common utility function for manipulating syn types and //! handling parsed values use std::collections::hash_map::DefaultHasher; use std::env; use std::fmt; use std::hash::{Hash, Hasher}; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering::SeqCst; /// Small utility used when generating symbol names. /// /// Hashes the public field here along with a few cargo-set env vars to /// distinguish between runs of the procedural macro. #[derive(Debug)] pub struct ShortHash(pub T); impl fmt::Display for ShortHash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { static HASHED: AtomicBool = AtomicBool::new(false); static HASH: AtomicUsize = AtomicUsize::new(0); // Try to amortize the cost of loading env vars a lot as we're gonna be // hashing for a lot of symbols. if !HASHED.load(SeqCst) { let mut h = DefaultHasher::new(); env::var("CARGO_PKG_NAME") .expect("should have CARGO_PKG_NAME env var") .hash(&mut h); env::var("CARGO_PKG_VERSION") .expect("should have CARGO_PKG_VERSION env var") .hash(&mut h); // This may chop off 32 bits on 32-bit platforms, but that's ok, we // just want something to mix in below anyway. HASH.store(h.finish() as usize, SeqCst); HASHED.store(true, SeqCst); } let mut h = DefaultHasher::new(); HASH.load(SeqCst).hash(&mut h); self.0.hash(&mut h); write!(f, "{:016x}", h.finish()) } } wasm-bindgen-macro-support-0.2.108/src/lib.rs000064400000000000000000000152501046102023000170660ustar 00000000000000//! This crate contains implementation APIs for the `#[wasm_bindgen]` attribute. #![doc(html_root_url = "https://docs.rs/wasm-bindgen-macro-support/0.2")] #[macro_use] mod error; mod ast; mod codegen; mod encode; mod hash; mod parser; use codegen::TryToTokens; use error::Diagnostic; pub use parser::BindgenAttrs; use parser::{ConvertToAst, MacroParse}; use proc_macro2::TokenStream; use quote::quote; use quote::ToTokens; use quote::TokenStreamExt; use syn::parse::{Parse, ParseStream, Result as SynResult}; use syn::Token; /// 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(); // if struct is encountered, add `derive` attribute and let everything happen there (workaround // to help parsing cfg_attr correctly). let item = syn::parse2::(input)?; if let syn::Item::Struct(s) = item { let opts: BindgenAttrs = syn::parse2(attr.clone())?; let wasm_bindgen = opts .wasm_bindgen() .cloned() .unwrap_or_else(|| syn::parse_quote! { ::wasm_bindgen }); let item = quote! { #[derive(#wasm_bindgen::__rt::BindgenedStruct)] #[wasm_bindgen(#attr)] #s }; return Ok(item); } let opts = syn::parse2(attr)?; let mut tokens = proc_macro2::TokenStream::new(); let mut program = 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::link_to` macro and returns the generated link pub fn expand_link_to(input: TokenStream) -> Result { parser::reset_attrs_used(); let opts = syn::parse2(input)?; let mut tokens = proc_macro2::TokenStream::new(); let link = parser::link_to(opts)?; link.try_to_tokens(&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 = ast::Program::default(); item.macro_parse(&mut program, &opts)?; // 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 ImplItemFn` 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| matches!(attr.style, syn::AttrStyle::Outer)), ); 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| matches!(attr.style, syn::AttrStyle::Inner(_))), ); tokens.append_all(&item.block.stmts); }); if let Some(err) = err { return Err(err); } Ok(tokens) } struct ClassMarker { class: syn::Ident, js_class: String, wasm_bindgen: syn::Path, wasm_bindgen_futures: syn::Path, } impl Parse for ClassMarker { fn parse(input: ParseStream) -> SynResult { let class = input.parse::()?; input.parse::()?; let mut js_class = input.parse::()?.value(); js_class = js_class .strip_prefix("r#") .map(String::from) .unwrap_or(js_class); let mut wasm_bindgen = None; let mut wasm_bindgen_futures = None; loop { if input.parse::>()?.is_some() { let ident = input.parse::()?; if ident == "wasm_bindgen" { if wasm_bindgen.is_some() { return Err(syn::Error::new( ident.span(), "found duplicate `wasm_bindgen`", )); } input.parse::()?; wasm_bindgen = Some(input.parse::()?); } else if ident == "wasm_bindgen_futures" { if wasm_bindgen_futures.is_some() { return Err(syn::Error::new( ident.span(), "found duplicate `wasm_bindgen_futures`", )); } input.parse::()?; wasm_bindgen_futures = Some(input.parse::()?); } else { return Err(syn::Error::new( ident.span(), "expected `wasm_bindgen` or `wasm_bindgen_futures`", )); } } else { break; } } Ok(ClassMarker { class, js_class, wasm_bindgen: wasm_bindgen.unwrap_or_else(|| syn::parse_quote! { wasm_bindgen }), wasm_bindgen_futures: wasm_bindgen_futures .unwrap_or_else(|| syn::parse_quote! { wasm_bindgen_futures }), }) } } pub fn expand_struct_marker(item: TokenStream) -> Result { parser::reset_attrs_used(); let mut s: syn::ItemStruct = syn::parse2(item)?; let mut program = ast::Program::default(); program.structs.push((&mut s).convert(&program)?); let mut tokens = proc_macro2::TokenStream::new(); program.try_to_tokens(&mut tokens)?; parser::check_unused_attrs(&mut tokens); Ok(tokens) } wasm-bindgen-macro-support-0.2.108/src/parser.rs000064400000000000000000002441021046102023000176140ustar 00000000000000use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::str::Chars; use std::{char, iter}; use ast::OperationKind; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::ToTokens; use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream, Result as SynResult}; use syn::spanned::Spanned; use syn::visit_mut::VisitMut; use syn::Token; use syn::{ItemFn, Lit, MacroDelimiter, ReturnType}; use wasm_bindgen_shared::identifier::{is_js_keyword, is_non_value_js_keyword, is_valid_ident}; use crate::ast::{self, ThreadLocal}; use crate::hash::ShortHash; use crate::ClassMarker; use crate::Diagnostic; thread_local!(static ATTRS: AttributeParseState = Default::default()); /// Return an [`Err`] if the given string contains a comment close syntax (`*/``). fn check_js_comment_close(str: &str, span: Span) -> Result<(), Diagnostic> { if str.contains("*/") { Err(Diagnostic::span_error( span, "contains comment close syntax", )) } else { Ok(()) } } /// Return an [`Err`] if the given string is a JS keyword or contains a comment close syntax (`*/``). fn check_invalid_type(str: &str, span: Span) -> Result<(), Diagnostic> { if is_js_keyword(str) { return Err(Diagnostic::span_error(span, "collides with JS keyword")); } check_js_comment_close(str, span)?; Ok(()) } #[derive(Default)] struct AttributeParseState { parsed: Cell, checks: Cell, unused_attrs: RefCell>, } struct UnusedState { error: bool, ident: Ident, } /// 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)>, } /// A list of identifiers representing the namespace prefix of an imported /// function or constant, or for exported types. /// /// The list is guaranteed to be non-empty and not start with a non-value JS keyword /// (except for "default", which is allowed as a special case). #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct JsNamespace(Vec); macro_rules! attrgen { ($mac:ident) => { $mac! { (catch, false, Catch(Span)), (constructor, false, Constructor(Span)), (method, false, Method(Span)), (r#this, false, This(Span)), (static_method_of, false, StaticMethodOf(Span, Ident)), (js_namespace, false, JsNamespace(Span, JsNamespace, Vec)), (module, true, Module(Span, String, Span)), (raw_module, true, RawModule(Span, String, Span)), (inline_js, true, InlineJs(Span, String, Span)), (getter, false, Getter(Span, Option)), (setter, false, Setter(Span, Option)), (indexing_getter, false, IndexingGetter(Span)), (indexing_setter, false, IndexingSetter(Span)), (indexing_deleter, false, IndexingDeleter(Span)), (structural, false, Structural(Span)), (r#final, false, Final(Span)), (readonly, false, Readonly(Span)), (js_name, false, JsName(Span, String, Span)), (js_class, false, JsClass(Span, String, Span)), (reexport, false, Reexport(Span, Option)), (inspectable, false, Inspectable(Span)), (is_type_of, false, IsTypeOf(Span, syn::Expr)), (extends, false, Extends(Span, syn::Path)), (no_deref, false, NoDeref(Span)), (vendor_prefix, false, VendorPrefix(Span, Ident)), (variadic, false, Variadic(Span)), (typescript_custom_section, false, TypescriptCustomSection(Span)), (skip_typescript, false, SkipTypescript(Span)), (skip_jsdoc, false, SkipJsDoc(Span)), (private, false, Hide(Span)), (main, false, Main(Span)), (start, false, Start(Span)), (wasm_bindgen, false, WasmBindgen(Span, syn::Path)), (js_sys, false, JsSys(Span, syn::Path)), (wasm_bindgen_futures, false, WasmBindgenFutures(Span, syn::Path)), (skip, false, Skip(Span)), (typescript_type, false, TypeScriptType(Span, String, Span)), (getter_with_clone, false, GetterWithClone(Span)), (static_string, false, StaticString(Span)), (thread_local, false, ThreadLocal(Span)), (thread_local_v2, false, ThreadLocalV2(Span)), (unchecked_return_type, true, ReturnType(Span, String, Span)), (return_description, true, ReturnDesc(Span, String, Span)), (unchecked_param_type, true, ParamType(Span, String, Span)), (param_description, true, ParamDesc(Span, String, Span)), // For testing purposes only. (assert_no_shim, false, AssertNoShim(Span)), } }; } macro_rules! methods { ($(($name:ident, $invalid_unused:literal, $variant:ident($($contents:tt)*)),)*) => { $(methods!(@method $name, $variant($($contents)*));)* fn enforce_used(self) -> Result<(), Diagnostic> { // Account for the fact this method was called ATTRS.with(|state| state.checks.set(state.checks.get() + 1)); let mut errors = Vec::new(); for (used, attr) in self.attrs.iter() { if used.get() { continue } let span = match attr { $(BindgenAttr::$variant(span, ..) => span,)* }; errors.push(Diagnostic::span_error(*span, "unused wasm_bindgen attribute")); } Diagnostic::from_vec(errors) } 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, ..) => { UnusedState { error: $invalid_unused, ident: syn::parse_quote_spanned!(*span => $name) } })* } }) ); }); } }; (@method $name:ident, $variant:ident(Span, String, Span)) => { pub(crate) fn $name(&self) -> Option<(&str, Span)> { self.attrs .iter() .find_map(|a| match &a.1 { BindgenAttr::$variant(_, s, span) => { a.0.set(true); Some((&s[..], *span)) } _ => None, }) } }; (@method $name:ident, $variant:ident(Span, JsNamespace, Vec)) => { pub(crate) fn $name(&self) -> Option<(JsNamespace, &[Span])> { self.attrs .iter() .find_map(|a| match &a.1 { BindgenAttr::$variant(_, ss, spans) => { a.0.set(true); Some((ss.clone(), &spans[..])) } _ => None, }) } }; (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => { #[allow(unused)] pub(crate) fn $name(&self) -> Option<&$($other)*> { self.attrs .iter() .find_map(|a| match &a.1 { BindgenAttr::$variant(_, s) => { a.0.set(true); Some(s) } _ => None, }) } }; (@method $name:ident, $variant:ident($($other:tt)*)) => { #[allow(unused)] pub(crate) fn $name(&self) -> Option<&$($other)*> { self.attrs .iter() .find_map(|a| match &a.1 { BindgenAttr::$variant(s) => { a.0.set(true); Some(s) } _ => None, }) } }; } 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(|&(_, 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 tokens = match attr.meta { syn::Meta::Path(_) => continue, syn::Meta::List(syn::MetaList { delimiter: MacroDelimiter::Paren(_), tokens, .. }) => tokens, syn::Meta::List(_) | syn::Meta::NameValue(_) => { bail_span!(attr, "malformed #[wasm_bindgen] attribute") } }; let mut attrs: BindgenAttrs = syn::parse2(tokens)?; ret.attrs.append(&mut attrs.attrs); attrs.check_used(); } } fn get_thread_local(&self) -> Result, Diagnostic> { let mut thread_local = self.thread_local_v2().map(|_| ThreadLocal::V2); if let Some(span) = self.thread_local() { if thread_local.is_some() { return Err(Diagnostic::span_error( *span, "`thread_local` can't be used with `thread_local_v2`", )); } else { thread_local = Some(ThreadLocal::V1) } } Ok(thread_local) } 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, $_:literal, $($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, $_:literal, $($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() { if input.peek(syn::LitStr) { let litstr = input.parse::()?; return Ok(BindgenAttr::$variant(attr_span, Some(litstr.value()))) } let ident = input.parse::()?.0; return Ok(BindgenAttr::$variant(attr_span, Some(ident.to_string()))) } 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, JsNamespace, 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")); } } if vals.is_empty() { return Err(syn::Error::new(exprs.span(), "Empty namespace lists are not allowed.")); } (vals, spans) }, // Try parsing as a string literal, then fall back to identifier Err(_) => match input.parse::() { Ok(str) => (vec![str.value()], vec![str.span()]), Err(_) => { let ident = input.parse::()?.0; (vec![ident.to_string()], vec![ident.span()]) } } }; let first = &vals[0]; if is_non_value_js_keyword(first) && first != "default" { let msg = format!("Namespace cannot start with the JS keyword `{}`", first); return Err(syn::Error::new(spans[0], msg)); } return Ok(BindgenAttr::$variant(attr_span, JsNamespace(vals), spans)) }); } attrgen!(parsers); 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. pub(crate) 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 ConvertToAst<&ast::Program> for &mut syn::ItemStruct { type Target = ast::Struct; fn convert(self, program: &ast::Program) -> Result { if !self.generics.params.is_empty() { bail_span!( self.generics, "structs with #[wasm_bindgen] cannot have lifetime or \ type parameters currently" ); } let attrs = BindgenAttrs::find(&mut self.attrs)?; // the `wasm_bindgen` option has been used before let _ = attrs.wasm_bindgen(); let mut fields = Vec::new(); let js_name = attrs .js_name() .map(|s| s.0.to_string()) .unwrap_or(self.ident.unraw().to_string()); if is_js_keyword(&js_name) && js_name != "default" { bail_span!( self.ident, "struct cannot use the JS keyword `{}` as its name", js_name ); } let is_inspectable = attrs.inspectable().is_some(); let getter_with_clone = attrs.getter_with_clone(); 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.unraw().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 = wasm_bindgen_shared::struct_field_get(&js_name, &js_field_name); let setter = wasm_bindgen_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(), generate_jsdoc: attrs.skip_jsdoc().is_none(), getter_with_clone: attrs.getter_with_clone().or(getter_with_clone).copied(), wasm_bindgen: program.wasm_bindgen.clone(), }); attrs.check_used(); } let generate_typescript = attrs.skip_typescript().is_none(); let private = attrs.private().is_some(); let comments: Vec = extract_doc_comments(&self.attrs); let js_namespace = attrs.js_namespace().map(|(ns, _)| ns.0); attrs.check_used(); Ok(ast::Struct { rust_name: self.ident.clone(), js_name, fields, comments, is_inspectable, generate_typescript, private, js_namespace, wasm_bindgen: program.wasm_bindgen.clone(), }) } } 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<(&ast::Program, BindgenAttrs, &'a Option)> for syn::ForeignItemFn { type Target = ast::ImportKind; fn convert( self, (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option), ) -> Result { let (mut wasm, _) = function_from_decl( &self.sig.ident, &opts, self.sig.clone(), self.attrs.clone(), self.vis.clone(), FunctionPosition::Extern, None, )?; 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().map(|ret| &ret.r#type))? } else { wasm.ret.as_ref().map(|ret| ret.r#type.clone()) }; let operation_kind = operation_kind(&opts); let kind = if opts.method().is_some() { let class = wasm.arguments.first().ok_or_else(|| { err_span!(self, "imported methods must have at least one argument") })?; let class = match get_ty(&class.pat_type.ty) { syn::Type::Reference(syn::TypeReference { mutability: None, elem, .. }) => &**elem, _ => bail_span!( class.pat_type.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 = syn::Type::Path(syn::TypePath { qself: None, path: syn::Path { leading_colon: None, segments: std::iter::once(syn::PathSegment { ident: cls.clone(), arguments: syn::PathArguments::None, }) .collect(), }, }); 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, ty: class.clone(), kind: ast::MethodKind::Constructor, } } else { ast::ImportFunctionKind::Normal }; // Validate that reexport is not used on methods/constructors/static methods if opts.reexport().is_some() && matches!(kind, ast::ImportFunctionKind::Method { .. }) { return Err(Diagnostic::span_error( self.sig.ident.span(), "`reexport` cannot be used on methods, constructors, or static methods. \ Use `reexport` on the type import instead.", )); } let shim = { let ns = match kind { ast::ImportFunctionKind::Normal => (0, "n"), ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]), }; let data = (ns, self.sig.to_token_stream().to_string(), module); format!( "__wbg_{}_{}", wasm.name .chars() .filter(|&c| c.is_ascii_alphanumeric() || c == '_') .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| { /// 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") { if let syn::Meta::NameValue(syn::MetaNameValue { value: syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(str), .. }), .. }) = &attr.meta { Some(str.value()) } else { None } } 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, shim: Ident::new(&shim, Span::call_site()), doc_comment, wasm_bindgen: program.wasm_bindgen.clone(), wasm_bindgen_futures: program.wasm_bindgen_futures.clone(), }); opts.check_used(); Ok(ret) } } impl ConvertToAst<(&ast::Program, BindgenAttrs)> for syn::ForeignItemType { type Target = ast::ImportKind; fn convert( self, (program, attrs): (&ast::Program, 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((attrs.js_namespace().map(|(ns, _)| ns.0), &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, wasm_bindgen: program.wasm_bindgen.clone(), })) } } impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option)> for syn::ForeignItemStatic { type Target = ast::ImportKind; fn convert( self, (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option), ) -> Result { if let syn::StaticMutability::Mut(_) = self.mutability { bail_span!(self.mutability, "cannot import mutable globals yet") } if let Some(span) = opts.static_string() { return Err(Diagnostic::span_error( *span, "static strings require a string literal", )); } 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)), ); let thread_local = opts.get_thread_local()?; 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()), wasm_bindgen: program.wasm_bindgen.clone(), thread_local, })) } } impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option)> for syn::ItemStatic { type Target = ast::ImportKind; fn convert( self, (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option), ) -> Result { if let syn::StaticMutability::Mut(_) = self.mutability { bail_span!(self.mutability, "cannot import mutable globals yet") } let string = if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(string), .. }) = *self.expr.clone() { string.value() } else { bail_span!( self.expr, "statics with a value can only be string literals" ) }; if opts.static_string().is_none() { bail_span!( self, "static strings require `#[wasm_bindgen(static_string)]`" ) } let thread_local = if let Some(thread_local) = opts.get_thread_local()? { thread_local } else { bail_span!( self, "static strings require `#[wasm_bindgen(thread_local_v2)]`" ) }; let shim = format!( "__wbg_string_{}_{}", self.ident, ShortHash((&module, &self.ident)), ); opts.check_used(); Ok(ast::ImportKind::String(ast::ImportString { ty: *self.ty, vis: self.vis, rust_name: self.ident.clone(), shim: Ident::new(&shim, Span::call_site()), wasm_bindgen: program.wasm_bindgen.clone(), js_sys: program.js_sys.clone(), string, thread_local, })) } } impl ConvertToAst<(BindgenAttrs, Vec)> for syn::ItemFn { type Target = ast::Function; fn convert( self, (attrs, args_attrs): (BindgenAttrs, Vec), ) -> Result { match self.vis { syn::Visibility::Public(_) => {} _ if attrs.start().is_some() => {} _ => 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 (mut ret, _) = function_from_decl( &self.sig.ident, &attrs, self.sig.clone(), self.attrs, self.vis, FunctionPosition::Free, Some(args_attrs), )?; attrs.check_used(); // TODO: Deprecate this for next major // Due to legacy behavior, we need to escape all keyword identifiers as // `_keyword`, except `default` if is_js_keyword(&ret.name) && ret.name != "default" { ret.name = format!("_{}", ret.name); } Ok(ret) } } /// Returns whether `self` is passed by reference or by value. fn get_self_method(r: syn::Receiver) -> ast::MethodSelf { // The tricky part here is that `r` can have many forms. E.g. `self`, // `&self`, `&mut self`, `self: Self`, `self: &Self`, `self: &mut Self`, // `self: Box`, `self: Rc`, etc. // Luckily, syn always populates the `ty` field with the type of `self`, so // e.g. `&self` gets the type `&Self`. So we only have check whether the // type is a reference or not. match &*r.ty { syn::Type::Reference(ty) => { if ty.mutability.is_some() { ast::MethodSelf::RefMutable } else { ast::MethodSelf::RefShared } } _ => ast::MethodSelf::ByValue, } } enum FunctionPosition<'a> { Extern, Free, Impl { self_ty: &'a Ident }, } /// 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, position: FunctionPosition, args_attrs: Option>, ) -> Result<(ast::Function, Option), Diagnostic> { if sig.variadic.is_some() { bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions"); } if !sig.generics.params.is_empty() { bail_span!( sig.generics, "can't #[wasm_bindgen] functions with lifetime or type parameters", ); } assert_no_lifetimes(&sig)?; let syn::Signature { inputs, output, .. } = sig; // A helper function to replace `Self` in the function signature of methods. // E.g. `fn get(&self) -> Option` to `fn get(&self) -> Option` // The following comment explains why this is necessary: // https://github.com/wasm-bindgen/wasm-bindgen/issues/3105#issuecomment-1275160744 let replace_self = |mut t: syn::Type| { if let FunctionPosition::Impl { self_ty } = position { // This uses a visitor to replace all occurrences of `Self` with // the actual type identifier. The visitor guarantees that we find // all occurrences of `Self`, even if deeply nested and even if // future Rust versions add more places where `Self` can appear. struct SelfReplace(Ident); impl VisitMut for SelfReplace { fn visit_ident_mut(&mut self, i: &mut proc_macro2::Ident) { if i == "Self" { *i = self.0.clone(); } } } let mut replace = SelfReplace(self_ty.clone()); replace.visit_type_mut(&mut t); } t }; // A helper function to replace argument names that are JS keywords. // E.g. this will replace `fn foo(class: u32)` to `fn foo(_class: u32)` let replace_colliding_arg = |i: &mut syn::PatType| { if let syn::Pat::Ident(ref mut i) = *i.pat { let ident = i.ident.unraw().to_string(); // JS keywords are NEVER allowed as argument names. Since argument // names are considered an implementation detail in JS, we can // safely rename them to avoid collisions. if is_js_keyword(&ident) { i.ident = Ident::new(format!("_{ident}").as_str(), i.ident.span()); } } }; let mut method_self = None; let mut arguments = Vec::new(); for arg in inputs.into_iter() { match arg { syn::FnArg::Typed(mut c) => { // typical arguments like foo: u32 replace_colliding_arg(&mut c); *c.ty = replace_self(*c.ty); arguments.push(c); } syn::FnArg::Receiver(r) => { // the self argument, so self, &self, &mut self, self: Box, etc. // `self` is only allowed for `fn`s inside an `impl` block. match position { FunctionPosition::Free => { bail_span!( r.self_token, "the `self` argument is only allowed for functions in `impl` blocks.\n\n\ If the function is already in an `impl` block, did you perhaps forget to add `#[wasm_bindgen]` to the `impl` block?" ); } FunctionPosition::Extern => { bail_span!( r.self_token, "the `self` argument is not allowed for `extern` functions.\n\n\ Did you perhaps mean `this`? For more information on importing JavaScript functions, see:\n\ https://wasm-bindgen.github.io/wasm-bindgen/examples/import-js.html" ); } FunctionPosition::Impl { .. } => {} } // We need to know *how* `self` is passed to the method (by // value or by reference) to generate the correct JS shim. assert!(method_self.is_none()); method_self = Some(get_self_method(r)); } } } // process function return data let ret_ty_override = opts.unchecked_return_type(); let ret_desc = opts.return_description(); let ret = match output { syn::ReturnType::Default => None, syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData { r#type: replace_self(*ty), js_type: ret_ty_override .as_ref() .map_or::, _>(Ok(None), |(ty, span)| { check_invalid_type(ty, *span)?; Ok(Some(ty.to_string())) })?, desc: ret_desc.as_ref().map_or::, _>( Ok(None), |(desc, span)| { check_js_comment_close(desc, *span)?; Ok(Some(desc.to_string())) }, )?, }), }; // error if there were description or type override specified for // function return while it doesn't actually return anything if ret.is_none() && (ret_ty_override.is_some() || ret_desc.is_some()) { if let Some((_, span)) = ret_ty_override { return Err(Diagnostic::span_error( span, "cannot specify return type for a function that doesn't return", )); } if let Some((_, span)) = ret_desc { return Err(Diagnostic::span_error( span, "cannot specify return description for a function that doesn't return", )); } } let (name, name_span) = if let Some((js_name, js_name_span)) = opts.js_name() { let kind = operation_kind(opts); let prefix = match kind { OperationKind::Setter(_) => "set_", _ => "", }; (format!("{prefix}{js_name}"), js_name_span) } else { (decl_name.unraw().to_string(), decl_name.span()) }; Ok(( ast::Function { name_span, name, rust_attrs: attrs, rust_vis: vis, r#unsafe: sig.unsafety.is_some(), r#async: sig.asyncness.is_some(), generate_typescript: opts.skip_typescript().is_none(), generate_jsdoc: opts.skip_jsdoc().is_none(), variadic: opts.variadic().is_some(), ret, arguments: arguments .into_iter() .zip( args_attrs .into_iter() .flatten() .chain(iter::repeat(FnArgAttrs::default())), ) .map(|(pat_type, attrs)| ast::FunctionArgumentData { pat_type, js_name: attrs.js_name, js_type: attrs.js_type, desc: attrs.desc, }) .collect(), }, method_self, )) } /// Helper struct to store extracted function argument attrs #[derive(Default, Clone)] struct FnArgAttrs { js_name: Option, js_type: Option, desc: Option, } /// Extracts function arguments attributes fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagnostic> { let mut args_attrs = vec![]; for input in sig.inputs.iter_mut() { if let syn::FnArg::Typed(pat_type) = input { let attrs = BindgenAttrs::find(&mut pat_type.attrs)?; let arg_attrs = FnArgAttrs { js_name: attrs .js_name() .map_or(Ok(None), |(js_name_override, span)| { if is_js_keyword(js_name_override) || !is_valid_ident(js_name_override) { return Err(Diagnostic::span_error(span, "invalid JS identifier")); } Ok(Some(js_name_override.to_string())) })?, js_type: attrs .unchecked_param_type() .map_or::, _>(Ok(None), |(ty, span)| { check_invalid_type(ty, span)?; Ok(Some(ty.to_string())) })?, desc: attrs .param_description() .map_or::, _>(Ok(None), |(description, span)| { check_js_comment_close(description, span)?; Ok(Some(description.to_string())) })?, }; // throw error for any unused attrs attrs.enforce_used()?; args_attrs.push(arg_attrs); } } Ok(args_attrs) } 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 opts = opts.unwrap_or_default(); if let Some(path) = opts.wasm_bindgen() { program.wasm_bindgen = path.clone(); } if let Some(path) = opts.js_sys() { program.js_sys = path.clone(); } if let Some(path) = opts.wasm_bindgen_futures() { program.wasm_bindgen_futures = path.clone(); } if opts.main().is_some() { opts.check_used(); return main(program, f, tokens); } let no_mangle = f .attrs .iter() .enumerate() .find(|(_, m)| m.path().is_ident("no_mangle")); if let Some((i, _)) = no_mangle { f.attrs.remove(i); } // extract fn args attributes before parsing to tokens stream let args_attrs = extract_args_attrs(&mut f.sig)?; let comments = extract_doc_comments(&f.attrs); // If the function isn't used for anything other than being exported to JS, // it'll be unused when not building for the Wasm target and produce a // `dead_code` warning. So, add `#[allow(dead_code)]` before it to avoid that. tokens.extend(quote::quote! { #[allow(dead_code)] }); f.to_tokens(tokens); if opts.start().is_some() { if !f.sig.generics.params.is_empty() { bail_span!(&f.sig.generics, "the start function cannot have generics",); } if !f.sig.inputs.is_empty() { 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(); if opts.this().is_some() && f.sig.inputs.is_empty() { bail_span!( &f.sig.inputs, "functions taking a 'this' argument must have at least one parameter" ); } let js_namespace = opts.js_namespace().map(|(ns, _)| ns.0); program.exports.push(ast::Export { comments, function: f.convert((opts, args_attrs))?, js_class: None, js_namespace, method_kind, method_self: None, rust_class: None, rust_name, start, wasm_bindgen: program.wasm_bindgen.clone(), wasm_bindgen_futures: program.wasm_bindgen_futures.clone(), }); } 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 MacroParse for &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.is_empty() { 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, program, &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, program: &ast::Program, impl_opts: &BindgenAttrs, ) -> Result<(), Diagnostic> { let method = match item { syn::ImplItem::Fn(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()); let wasm_bindgen = &program.wasm_bindgen; let wasm_bindgen_futures = &program.wasm_bindgen_futures; method.attrs.insert( 0, syn::Attribute { pound_token: Default::default(), style: syn::AttrStyle::Outer, bracket_token: Default::default(), meta: syn::parse_quote! { #wasm_bindgen::prelude::__wasm_bindgen_class_marker(#class = #js_class, wasm_bindgen = #wasm_bindgen, wasm_bindgen_futures = #wasm_bindgen_futures) }, }, ); Ok(()) } impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn { fn macro_parse( self, program: &mut ast::Program, ClassMarker { class, js_class, wasm_bindgen, wasm_bindgen_futures, }: &ClassMarker, ) -> Result<(), Diagnostic> { program.wasm_bindgen = wasm_bindgen.clone(); program.wasm_bindgen_futures = wasm_bindgen_futures.clone(); 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)?; if opts.this().is_some() { bail_span!( &self.sig.ident, "#[wasm_bindgen(this)] cannot be used on impl block methods; \ it is only valid on free functions" ); } let comments = extract_doc_comments(&self.attrs); let args_attrs: Vec = extract_args_attrs(&mut self.sig)?; let (function, method_self) = function_from_decl( &self.sig.ident, &opts, self.sig.clone(), self.attrs.clone(), self.vis.clone(), FunctionPosition::Impl { self_ty: class }, Some(args_attrs), )?; 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 }) }; // Validate that js_namespace is not used on methods if let Some((_, span)) = opts.js_namespace() { return Err(Diagnostic::span_error( span[0], "`js_namespace` cannot be used on methods, getters, setters, or static methods. \ Use `js_namespace` on the exported struct definition instead to put the entire class in a namespace.", )); } program.exports.push(ast::Export { comments, function, js_class: Some(js_class.to_string()), js_namespace: None, method_kind, method_self, rust_class: Some(class.clone()), rust_name: self.sig.ident.clone(), start: false, wasm_bindgen: program.wasm_bindgen.clone(), wasm_bindgen_futures: program.wasm_bindgen_futures.clone(), }); opts.check_used(); Ok(()) } } fn string_enum( enum_: syn::ItemEnum, program: &mut ast::Program, js_name: String, generate_typescript: bool, comments: Vec, js_namespace: Option>, ) -> Result<(), Diagnostic> { let mut variants = vec![]; let mut variant_values = vec![]; for v in enum_.variants.iter() { let (_, expr) = match &v.discriminant { Some(pair) => pair, None => { bail_span!(v, "all variants of a string enum must have a string 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, reexport: None, kind: ast::ImportKind::Enum(ast::StringEnum { vis: enum_.vis, name: enum_.ident, export_name: js_name, variants, variant_values, comments, rust_attrs: enum_.attrs, generate_typescript, js_namespace, wasm_bindgen: program.wasm_bindgen.clone(), }), }); Ok(()) } /// Represents a possibly negative numeric value as base 10 digits. struct NumericValue<'a> { negative: bool, base10_digits: &'a str, } impl<'a> NumericValue<'a> { fn from_expr(expr: &'a syn::Expr) -> Option { match get_expr(expr) { syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(int_lit), .. }) => Some(Self { negative: false, base10_digits: int_lit.base10_digits(), }), syn::Expr::Unary(syn::ExprUnary { op: syn::UnOp::Neg(_), expr, .. }) => Self::from_expr(expr).map(|n| n.neg()), _ => None, } } fn parse(&self) -> Option { let mut value = self.base10_digits.parse::().ok()?; if self.negative { value = -value; } Some(value) } fn neg(self) -> Self { Self { negative: !self.negative, base10_digits: self.base10_digits, } } } 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.is_empty() { bail_span!(self, "cannot export empty enums to JS"); } for variant in self.variants.iter() { match variant.fields { syn::Fields::Unit => (), _ => bail_span!( variant.fields, "enum variants with associated data are not supported with #[wasm_bindgen]" ), } } let generate_typescript = opts.skip_typescript().is_none(); let private = opts.private().is_some(); let comments = extract_doc_comments(&self.attrs); let js_name = opts .js_name() .map(|s| s.0) .map_or_else(|| self.ident.to_string(), |s| s.to_string()); if is_js_keyword(&js_name) && js_name != "default" { bail_span!( self.ident, "enum cannot use the JS keyword `{}` as its name", js_name ); } let js_namespace = opts.js_namespace().map(|(ns, _)| ns.0); opts.check_used(); // Check if the enum is a string enum, by checking whether any variant has a string discriminant. let is_string_enum = self.variants.iter().any(|v| { if let Some((_, expr)) = &v.discriminant { if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(_), .. }) = get_expr(expr) { return true; } } false }); if is_string_enum { return string_enum( self, program, js_name, generate_typescript, comments, js_namespace, ); } match self.vis { syn::Visibility::Public(_) => {} _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"), } // Go through all variants once first to determine whether the enum is // signed or unsigned. We don't need to actually parse the discriminant // values yet, we just need to know their sign. The actual parsing is // done in a second pass. let signed = self.variants.iter().any(|v| match &v.discriminant { Some((_, expr)) => NumericValue::from_expr(expr).is_some_and(|n| n.negative), None => false, }); let underlying_min = if signed { i32::MIN as i64 } else { 0 }; let underlying_max = if signed { i32::MAX as i64 } else { u32::MAX as i64 }; let mut last_discriminant: Option = None; let mut discriminant_map: HashMap = HashMap::new(); let variants = self .variants .iter() .map(|v| { let value: i64 = match &v.discriminant { Some((_, expr)) => match NumericValue::from_expr(expr).and_then(|n| n.parse()) { Some(value) => value, _ => bail_span!( expr, "C-style enums with #[wasm_bindgen] may only have \ numeric literal values that fit in a 32-bit integer as discriminants. \ Expressions or variables are not supported.", ), }, None => { // Use the same algorithm as rustc to determine the next discriminant // https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants last_discriminant.map_or(0, |last| last + 1) } }; last_discriminant = Some(value); // check that the value fits within the underlying type let underlying = if signed { "i32" } else { "u32" }; let numbers = if signed { "signed numbers" } else { "unsigned numbers" }; if value < underlying_min { bail_span!( v, "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \ but `{1}` is too small for `{2}`", numbers, value, underlying ); } if value > underlying_max { bail_span!( v, "C-style enums with #[wasm_bindgen] can only support {0} that can be represented by `{2}`, \ but `{1}` is too large for `{2}`", numbers, value, underlying ); } // detect duplicate discriminants if let Some(old) = discriminant_map.insert(value, v) { bail_span!( v, "discriminant value `{}` is already used by {} in this enum", value, old.ident ); } let comments = extract_doc_comments(&v.attrs); Ok(ast::Variant { name: v.ident.clone(), // due to the above checks, we know that the value fits // within 32 bits, so this cast doesn't lose any information value: value as u32, comments, }) }) .collect::, Diagnostic>>()?; // To make all the code handling holes simpler, we only consider // non-negative holes. This allows us to use `u32` to represent holes. let hole = (0..=underlying_max) .find(|v| !discriminant_map.contains_key(v)) .unwrap() as u32; self.to_tokens(tokens); program.enums.push(ast::Enum { rust_name: self.ident, js_name, signed, variants, comments, hole, generate_typescript, private, js_namespace, wasm_bindgen: program.wasm_bindgen.clone(), }); 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)]."); } let typescript_custom_section = match get_expr(&self.expr) { syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(litstr), .. }) => ast::LitOrExpr::Lit(litstr.value()), expr => ast::LitOrExpr::Expr(expr.clone()), }; program .typescript_custom_sections .push(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(); if let Some(other) = self.abi.name.filter(|l| l.value() != "C") { errors.push(err_span!( other, "only foreign mods with the `C` ABI are allowed" )); } let js_namespace = opts.js_namespace().map(|(s, _)| s); let module = module_from_opts(program, &opts) .map_err(|e| errors.push(e)) .unwrap_or_default(); for item in self.items.into_iter() { let ctx = ForeignItemCtx { module: module.clone(), js_namespace: js_namespace.clone(), }; if let Err(e) = item.macro_parse(program, ctx) { errors.push(e); } } Diagnostic::from_vec(errors)?; opts.check_used(); Ok(()) } } struct ForeignItemCtx { module: Option, js_namespace: Option, } impl MacroParse for syn::ForeignItem { fn macro_parse( mut self, program: &mut ast::Program, ctx: ForeignItemCtx, ) -> 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, syn::ForeignItem::Verbatim(v) => { let mut item: syn::ItemStatic = syn::parse(v.into()).expect("only foreign functions/types allowed for now"); let item_opts = BindgenAttrs::find(&mut item.attrs)?; let reexport = item_opts.reexport().cloned(); let kind = item.convert((program, item_opts, &ctx.module))?; program.imports.push(ast::Import { module: None, js_namespace: None, reexport, kind, }); return Ok(()); } _ => panic!("only foreign functions/types allowed for now"), }; BindgenAttrs::find(attrs)? }; let js_namespace = item_opts .js_namespace() .map(|(s, _)| s) .or(ctx.js_namespace) .map(|s| s.0); let module = ctx.module; let reexport = item_opts.reexport().cloned(); let kind = match self { syn::ForeignItem::Fn(f) => f.convert((program, item_opts, &module))?, syn::ForeignItem::Type(t) => t.convert((program, item_opts))?, syn::ForeignItem::Static(s) => s.convert((program, item_opts, &module))?, _ => panic!("only foreign functions/types allowed for now"), }; // check for JS keywords // We only need to check if there isn't a JS namespace or module. If // there is namespace, then we already checked the namespace while // parsing. If there is a module, we can rename the import symbol to // avoid using keywords. let needs_check = js_namespace.is_none() && module.is_none(); if needs_check { match &kind { ast::ImportKind::Function(import_function) => { if matches!(import_function.kind, ast::ImportFunctionKind::Normal) && is_non_value_js_keyword(&import_function.function.name) { bail_span!( import_function.rust_name, "Imported function cannot use the JS keyword `{}` as its name.", import_function.function.name ); } } ast::ImportKind::Static(import_static) => { if is_non_value_js_keyword(&import_static.js_name) { bail_span!( import_static.rust_name, "Imported static cannot use the JS keyword `{}` as its name.", import_static.js_name ); } } ast::ImportKind::String(_) => { // static strings don't have JS names, so we don't need to check for JS keywords } ast::ImportKind::Type(import_type) => { if is_non_value_js_keyword(&import_type.js_name) { bail_span!( import_type.rust_name, "Imported type cannot use the JS keyword `{}` as its name.", import_type.js_name ); } } ast::ImportKind::Enum(_) => { // string enums aren't possible here } } } program.imports.push(ast::Import { module, js_namespace, reexport, kind, }); Ok(()) } } pub fn module_from_opts( program: &mut ast::Program, opts: &BindgenAttrs, ) -> Result, Diagnostic> { if let Some(path) = opts.wasm_bindgen() { program.wasm_bindgen = path.clone(); } if let Some(path) = opts.js_sys() { program.js_sys = path.clone(); } if let Some(path) = opts.wasm_bindgen_futures() { program.wasm_bindgen_futures = path.clone(); } let mut errors = Vec::new(); 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)) } else { None }; Diagnostic::from_vec(errors)?; Ok(module) } /// 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.is_empty() => 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 == "doc") { let tokens = match &a.meta { syn::Meta::Path(_) => None, syn::Meta::List(list) => Some(list.tokens.clone()), syn::Meta::NameValue(name_value) => Some(name_value.value.to_token_stream()), }; Some( // We want to filter out any Puncts so just grab the Literals tokens.into_iter().flatten().filter_map(|t| match t { TokenTree::Literal(lit) => { let quoted = lit.to_string(); Some(try_unescape("ed).unwrap_or(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(mut s: &str) -> Option { s = s.strip_prefix('"').unwrap_or(s); s = s.strip_suffix('"').unwrap_or(s); let mut result = String::with_capacity(s.len()); let mut chars = s.chars(); while let Some(c) = chars.next() { 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); } } Some(result) } fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> { let mut value = 0; for (i, c) in chars.enumerate() { match (i, c.to_digit(16)) { (0..=5, Some(num)) => value = (value << 4) | num, (1.., None) => return Some((char::from_u32(value)?, c)), _ => break, } } 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() { let unused_attrs = unused_attrs.iter().map(|UnusedState { error, ident }| { if *error { let text = format!("invalid attribute {ident} in this position"); quote::quote_spanned! { ident.span() => ::core::compile_error!(#text); } } else { quote::quote! { let #ident: (); } } }); tokens.extend(quote::quote! { // Anonymous scope to prevent name clashes. const _: () = { #(#unused_attrs)* }; }); } }) } fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind { let mut operation_kind = ast::OperationKind::Regular; if opts.this().is_some() { operation_kind = ast::OperationKind::RegularThis; } 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 } pub fn link_to(opts: BindgenAttrs) -> Result { let mut program = ast::Program::default(); let module = module_from_opts(&mut program, &opts)?.ok_or_else(|| { Diagnostic::span_error(Span::call_site(), "`link_to!` requires a module.") })?; if let ast::ImportModule::Named(p, s) | ast::ImportModule::RawNamed(p, s) = &module { if !p.starts_with("./") && !p.starts_with("../") && !p.starts_with('/') { return Err(Diagnostic::span_error( *s, "`link_to!` does not support module paths.", )); } } opts.enforce_used()?; program.linked_modules.push(module); Ok(ast::LinkToModule(program)) } fn main(program: &ast::Program, mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> { if f.sig.ident != "main" { bail_span!(&f.sig.ident, "the main function has to be called main"); } if let Some(constness) = f.sig.constness { bail_span!(&constness, "the main function cannot be const"); } if !f.sig.generics.params.is_empty() { bail_span!(&f.sig.generics, "the main function cannot have generics"); } if !f.sig.inputs.is_empty() { bail_span!(&f.sig.inputs, "the main function cannot have arguments"); } let r#return = f.sig.output; f.sig.output = ReturnType::Default; let body = f.block.as_ref(); let wasm_bindgen = &program.wasm_bindgen; let wasm_bindgen_futures = &program.wasm_bindgen_futures; if f.sig.asyncness.take().is_some() { *f.block = syn::parse2(quote::quote! { { async fn __wasm_bindgen_generated_main() #r#return #body #wasm_bindgen_futures::spawn_local( async move { use #wasm_bindgen::__rt::Main; let __ret = __wasm_bindgen_generated_main(); (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main() }, ) } }) .unwrap(); } else { *f.block = syn::parse2(quote::quote! { { fn __wasm_bindgen_generated_main() #r#return #body use #wasm_bindgen::__rt::Main; let __ret = __wasm_bindgen_generated_main(); (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main() } }) .unwrap(); } f.to_tokens(tokens); Ok(()) } #[cfg(test)] mod tests { #[test] fn test_try_unescape() { use super::try_unescape; assert_eq!(try_unescape("hello").unwrap(), "hello"); assert_eq!(try_unescape("\"hello").unwrap(), "hello"); assert_eq!(try_unescape("hello\"").unwrap(), "hello"); assert_eq!(try_unescape("\"hello\"").unwrap(), "hello"); assert_eq!(try_unescape("hello\\\\").unwrap(), "hello\\"); assert_eq!(try_unescape("hello\\n").unwrap(), "hello\n"); assert_eq!(try_unescape("hello\\u"), None); assert_eq!(try_unescape("hello\\u{"), None); assert_eq!(try_unescape("hello\\u{}"), None); assert_eq!(try_unescape("hello\\u{0}").unwrap(), "hello\0"); assert_eq!(try_unescape("hello\\u{000000}").unwrap(), "hello\0"); assert_eq!(try_unescape("hello\\u{0000000}"), None); } }