chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"25a853068e2102dde18557cf6f967674c2e1661ce53868b61221d139f922931e","Cargo.lock":"571525059b72e8d2e70e209bd482f9a577d05b6b6ea5eafa934d487806b4d292","Cargo.toml":"d3735b5e5b45eb43e7dbf0e185fe6dc903514861670440b263af7002210b6a70","Cargo.toml.orig":"7bc6f7190b480e5b462f606e04fcc1fee7b43d8264d6ef31603b358c50b37ec7","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","src/ast.rs":"8d563110d9f2841ff350ce7ead69fd80c732d62cf62b247bf1581f1e95998160","src/codegen.rs":"30e021b0905213490dba8a50a878fd4c9b59411e4390547eb3cee699d189e064","src/encode.rs":"b68eb50ca8119b2e0c80fba10e64625848d83c2eaa6bdb6ffd4cf413b1227656","src/error.rs":"3ce79c73e831c77cd72dd140cce670a47bf3de362e9554fda03bfea9eb5fdee3","src/generics.rs":"f0a50750ae1c994d04c1daf0d3b68f6ec8896d5ae1d87c88196f52d8c1d07591","src/hash.rs":"6db19dbfbc0891d051bafe3c109e50a4f51bcd75d3eaf149d5999ac3f238cc2f","src/lib.rs":"16641765023d2ef429450372249c91d80329273f1909102da6c0a0d8b0c1ce8d","src/parser.rs":"6ad78d88f2e8ed2e3d1daf43843748ed989cd38ed7e70a8928d2d2997b87dcfd"},"package":"03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"}

View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "22cfd556870fa897d0b2db4c84603c1a9643298c"
},
"path_in_vcs": "crates/macro-support"
}

64
vendor/wasm-bindgen-macro-support/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,64 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.114"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
dependencies = [
"unicode-ident",
]

View File

@@ -0,0 +1,81 @@
# 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.114"
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",
"extra-traits",
]
[dependencies.wasm-bindgen-shared]
version = "=0.2.114"
[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]
unused_lifetimes = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = [
"cfg(wasm_bindgen_unstable_test_coverage)",
"cfg(xxx_debug_only_print_generated_code)",
]

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,25 @@
Copyright (c) 2014 Alex Crichton
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,606 @@
//! 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<Export>,
/// js -> rust interfaces
pub imports: Vec<Import>,
/// linked-to modules
pub linked_modules: Vec<ImportModule>,
/// rust enums
pub enums: Vec<Enum>,
/// rust structs
pub structs: Vec<Struct>,
/// custom typescript sections to be included in the definition file
pub typescript_custom_sections: Vec<LitOrExpr>,
/// Inline JS snippets
pub inline_js: Vec<String>,
/// 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<String>,
/// The rust function
pub function: Function,
/// The class name in JS this is attached to
pub js_class: Option<String>,
/// The namespace to export the item through, if any
pub js_namespace: Option<Vec<String>>,
/// The kind (static, named, regular)
pub method_kind: MethodKind,
/// The type of `self` (either `self`, `&self`, or `&mut self`)
pub method_self: Option<MethodSelf>,
/// The struct name, in Rust, this is attached to
pub rust_class: Option<Ident>,
/// 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<ImportModule>,
/// The namespace to access the item through, if any
pub js_namespace: Option<Vec<String>>,
/// If Some, this import should be re-exported with the optional given name
pub reexport: Option<Option<String>>,
/// 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<H: Hasher>(&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<syn::Type>,
/// 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,
/// Generic parameters as validated simple type parameters for this function
pub generics: syn::Generics,
}
/// 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<String>),
/// A method for setting the value of the provided Ident or String
Setter(Option<String>),
/// 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<ThreadLocal>,
}
/// 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<syn::Attribute>,
/// The TS definition to generate for this type
pub typescript_type: Option<String>,
/// The doc comment applied to this type, if one exists
pub doc_comment: Option<String>,
/// 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<syn::Expr>,
/// The list of classes this extends, if any
pub extends: Vec<syn::Path>,
/// A custom prefix to add and attempt to fall back to, if the type isn't found
pub vendor_prefixes: Vec<Ident>,
/// If present, don't generate a `Deref` impl
pub no_deref: bool,
/// If present, don't generate `Upcast` impls
pub no_upcast: bool,
/// If present, don't generate a `Promising` impl
pub no_promising: bool,
/// Path to wasm_bindgen
pub wasm_bindgen: Path,
/// Validated generics
pub generics: syn::Generics,
}
/// 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<Ident>,
/// The JS string values of the variants
pub variant_values: Vec<String>,
/// The doc comments on this enum, if any
pub comments: Vec<String>,
/// Attributes to apply to the Rust enum
pub rust_attrs: Vec<syn::Attribute>,
/// 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<Vec<String>>,
/// 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<FunctionArgumentData>,
/// The data of return type of the function
pub ret: Option<FunctionReturnData>,
/// Any custom attributes being applied to the function
pub rust_attrs: Vec<syn::Attribute>,
/// 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<String>,
/// Specifies the return description
pub desc: Option<String>,
}
/// 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<String>,
/// Specifies the JS function argument type override
pub js_type: Option<String>,
/// Specifies whether the parameter is optional
pub optional: bool,
/// Specifies the argument description
pub desc: Option<String>,
}
/// 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,
/// The namespace-qualified internal name used for wasm symbol generation.
/// When a namespace is present, this is `ns1_ns2_JsName`; otherwise it equals `js_name`.
pub qualified_name: String,
/// All the fields of this struct to export
pub fields: Vec<StructField>,
/// The doc comments on this struct, if provided
pub comments: Vec<String>,
/// 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<Vec<String>>,
/// 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<String>,
/// 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<Span>,
/// 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<Variant>,
/// The doc comments on this enum, if any
pub comments: Vec<String>,
/// 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<Vec<String>>,
/// 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<String>,
}
/// 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<String, Diagnostic> {
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())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,661 @@
use 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<u8>),
StrExpr(syn::Expr),
// TODO: support more expr type;
}
pub struct EncodeResult {
pub custom_section: Vec<EncodeChunk>,
pub included_files: Vec<PathBuf>,
}
pub fn encode(program: &ast::Program) -> Result<EncodeResult, Diagnostic> {
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<HashMap<String, LocalFile>>,
root: PathBuf,
crate_name: String,
has_package_json: Cell<bool>,
}
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<ImportModule<'_>, 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<Program<'a>, Diagnostic> {
Ok(Program {
exports: prog
.exports
.iter()
.map(|a| shared_export(a, intern))
.collect::<Result<Vec<_>, _>>()?,
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::<Result<Vec<_>, _>>()?,
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::<Result<Vec<_>, _>>()?,
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::<Result<Vec<_>, _>>()?,
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<Export<'a>, 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(),
optional: arg.optional,
desc: arg.desc.as_deref(),
})
.collect::<Vec<_>>();
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<Import<'a>, 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<LinkedModule<'a>, 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<ImportModule<'a>, 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<ImportKind<'a>, 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<ImportFunction<'a>, 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,
rust_name: intern.intern(&s.rust_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<EncodeChunk>,
}
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<EncodeChunk> {
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<T: Encode> Encode for Vec<T> {
fn encode(&self, dst: &mut Encoder) {
self.len().encode(dst);
for item in self {
item.encode(dst);
}
}
}
impl<T: Encode> Encode for Option<T> {
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<MethodKind<'a>, 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 })
}
})
}

View File

@@ -0,0 +1,132 @@
use 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<Diagnostic>,
},
}
impl Diagnostic {
/// Generate a `Diagnostic` from an informational message with no Span
pub fn error<T: Into<String>>(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<T: Into<String>>(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<T: Into<String>>(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<Diagnostic>) -> 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<Error> 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);
}
}
}
}

View File

@@ -0,0 +1,805 @@
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use syn::parse_quote;
use syn::visit_mut::{self, VisitMut};
use syn::{visit::Visit, Ident, Type};
use crate::error::Diagnostic;
/// Visitor to replace wasm bindgen generics with their concrete types
/// The concrete type is the default type on the import if specified when it was defined.
struct GenericRenameVisitor<'a> {
renames: &'a BTreeMap<&'a Ident, Option<Cow<'a, syn::Type>>>,
err: Option<Diagnostic>,
}
impl<'a> VisitMut for GenericRenameVisitor<'a> {
fn visit_type_mut(&mut self, ty: &mut Type) {
if self.err.is_some() {
return;
}
if let Type::Path(type_path) = ty {
// Handle <T as Trait>::AssocType
if let Some(qself) = &mut type_path.qself {
if let Type::Path(qself_path) = &mut *qself.ty {
if qself_path.qself.is_none() && qself_path.path.segments.len() == 1 {
let ident = &qself_path.path.segments[0].ident;
if let Some((_, concrete)) = self.renames.get_key_value(ident) {
*qself.ty = if let Some(concrete) = concrete {
concrete.clone().into_owned()
} else {
parse_quote! { JsValue }
};
return;
}
}
}
}
// Normal T::...
if type_path.qself.is_none() && !type_path.path.segments.is_empty() {
let first_seg = &type_path.path.segments[0];
if let Some((_, concrete)) = self.renames.get_key_value(&first_seg.ident) {
if let Some(concrete) = concrete {
if type_path.path.segments.len() == 1 {
*ty = concrete.clone().into_owned();
} else if let Type::Path(concrete_path) = concrete.as_ref() {
let remaining: Vec<_> =
type_path.path.segments.iter().skip(1).cloned().collect();
type_path.path.segments = concrete_path.path.segments.clone();
type_path.path.segments.extend(remaining);
}
} else {
*ty = parse_quote! { JsValue };
}
return;
}
}
}
visit_mut::visit_type_mut(self, ty);
}
}
/// Helper visitor for generic parameter usage
#[derive(Debug)]
pub struct GenericNameVisitor<'a, 'b> {
generic_params: &'a Vec<&'a Ident>,
/// The generic params that were found
found_set: &'b mut BTreeSet<Ident>,
}
/// Helper visitor for generic parameter usage
impl<'a, 'b> GenericNameVisitor<'a, 'b> {
/// Construct a new generic name visitors with a param search set,
/// and optionally a second parameter search set.
pub fn new(generic_params: &'a Vec<&'a Ident>, found_set: &'b mut BTreeSet<Ident>) -> Self {
Self {
generic_params,
found_set,
}
}
}
impl<'a, 'b> Visit<'a> for GenericNameVisitor<'a, 'b> {
fn visit_type_reference(&mut self, type_ref: &'a syn::TypeReference) {
if let syn::Type::Path(type_path) = &*type_ref.elem {
// Handle <T as Trait>::AssocType - visit the qself type
if let Some(qself) = &type_path.qself {
syn::visit::visit_type(self, &qself.ty);
// Also visit the path segments for any generic args
for segment in &type_path.path.segments {
syn::visit::visit_path_segment(self, segment);
}
return;
}
if let Some(first_segment) = type_path.path.segments.first() {
if type_path.path.segments.len() == 1 && first_segment.arguments.is_empty() {
if self.generic_params.contains(&&first_segment.ident) {
self.found_set.insert(first_segment.ident.clone());
return;
}
} else {
if self.generic_params.contains(&&first_segment.ident) {
self.found_set.insert(first_segment.ident.clone());
}
syn::visit::visit_path_arguments(self, &first_segment.arguments);
for segment in type_path.path.segments.iter().skip(1) {
syn::visit::visit_path_segment(self, segment);
}
return;
}
}
}
// For other cases, continue normal visiting
syn::visit::visit_type_reference(self, type_ref);
}
fn visit_path(&mut self, path: &'a syn::Path) {
if let Some(first_segment) = path.segments.first() {
if self.generic_params.contains(&&first_segment.ident) {
self.found_set.insert(first_segment.ident.clone());
}
}
for segment in &path.segments {
match &segment.arguments {
syn::PathArguments::AngleBracketed(args) => {
for arg in &args.args {
match arg {
syn::GenericArgument::Type(ty) => {
syn::visit::visit_type(self, ty);
}
syn::GenericArgument::AssocType(binding) => {
// Don't visit binding.ident, only visit binding.ty
syn::visit::visit_type(self, &binding.ty);
}
_ => {
syn::visit::visit_generic_argument(self, arg);
}
}
}
}
syn::PathArguments::Parenthesized(args) => {
// Handle function syntax like FnMut(T) -> Result<R, JsValue>
for input in &args.inputs {
syn::visit::visit_type(self, input);
}
if let syn::ReturnType::Type(_, return_type) = &args.output {
syn::visit::visit_type(self, return_type);
}
}
syn::PathArguments::None => {}
}
}
}
}
/// Obtain the generic parameters and their optional defaults
pub(crate) fn generic_params(generics: &syn::Generics) -> Vec<(&Ident, Option<&syn::Type>)> {
generics
.type_params()
.map(|tp| (&tp.ident, tp.default.as_ref()))
.collect()
}
/// Returns a vector of token streams representing generic type parameters with their bounds.
/// For example, `<T: Clone, U: Display>` returns `[quote!(T: Clone), quote!(U: Display)]`.
/// This is useful for constructing impl blocks that need to add lifetimes while preserving bounds.
pub(crate) fn type_params_with_bounds(generics: &syn::Generics) -> Vec<proc_macro2::TokenStream> {
generics
.type_params()
.map(|tp| {
let ident = &tp.ident;
let bounds = &tp.bounds;
if bounds.is_empty() {
quote::quote! { #ident }
} else {
quote::quote! { #ident: #bounds }
}
})
.collect()
}
/// Obtain the generic bounds, both inline and where clauses together
pub(crate) fn generic_bounds<'a>(generics: &'a syn::Generics) -> Vec<Cow<'a, syn::WherePredicate>> {
let mut bounds = Vec::new();
for param in &generics.params {
if let syn::GenericParam::Type(type_param) = param {
if !type_param.bounds.is_empty() {
let ident = &type_param.ident;
let predicate = syn::WherePredicate::Type(syn::PredicateType {
lifetimes: None,
bounded_ty: syn::parse_quote!(#ident),
colon_token: syn::Token![:](proc_macro2::Span::call_site()),
bounds: type_param.bounds.clone(),
});
bounds.push(Cow::Owned(predicate));
}
}
}
if let Some(where_clause) = &generics.where_clause {
bounds.extend(where_clause.predicates.iter().map(Cow::Borrowed));
}
bounds
}
/// Replace specified lifetime parameters with 'static.
/// This is used when generating concrete ABI types for extern blocks,
/// which cannot have lifetime parameters from the outer scope.
/// Only the lifetimes in `lifetimes_to_staticize` are replaced.
pub(crate) fn staticize_lifetimes(
mut ty: syn::Type,
lifetimes_to_staticize: &[&syn::Lifetime],
) -> syn::Type {
struct LifetimeStaticizer<'a> {
lifetimes: &'a [&'a syn::Lifetime],
}
impl VisitMut for LifetimeStaticizer<'_> {
fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) {
if self.lifetimes.iter().any(|lt| lt.ident == lifetime.ident) {
*lifetime = syn::Lifetime::new("'static", lifetime.span());
}
}
}
LifetimeStaticizer {
lifetimes: lifetimes_to_staticize,
}
.visit_type_mut(&mut ty);
ty
}
/// Obtain the generic type parameter names
pub(crate) fn generic_param_names(generics: &syn::Generics) -> Vec<&Ident> {
generics.type_params().map(|tp| &tp.ident).collect()
}
/// Obtain all lifetime parameters from generics
pub(crate) fn lifetime_params(generics: &syn::Generics) -> Vec<&syn::Lifetime> {
generics.lifetimes().map(|lp| &lp.lifetime).collect()
}
/// Obtain both lifetime and type parameter names from generics
pub(crate) fn all_param_names(generics: &syn::Generics) -> (Vec<&syn::Lifetime>, Vec<&Ident>) {
(lifetime_params(generics), generic_param_names(generics))
}
/// Helper visitor for lifetime usage detection in types
pub struct LifetimeVisitor<'a> {
lifetime_params: &'a [&'a syn::Lifetime],
found_set: BTreeSet<syn::Lifetime>,
}
impl<'a> LifetimeVisitor<'a> {
pub fn new(lifetime_params: &'a [&'a syn::Lifetime]) -> Self {
Self {
lifetime_params,
found_set: BTreeSet::new(),
}
}
pub fn into_found(self) -> BTreeSet<syn::Lifetime> {
self.found_set
}
}
impl<'ast> syn::visit::Visit<'ast> for LifetimeVisitor<'_> {
fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
if self.lifetime_params.contains(&lifetime) {
self.found_set.insert(lifetime.clone());
}
}
}
/// Find all lifetimes from the given set that are used in a type
pub(crate) fn used_lifetimes_in_type<'a>(
ty: &syn::Type,
lifetime_params: &'a [&'a syn::Lifetime],
) -> BTreeSet<syn::Lifetime> {
let mut visitor = LifetimeVisitor::new(lifetime_params);
syn::visit::Visit::visit_type(&mut visitor, ty);
visitor.into_found()
}
pub(crate) fn uses_generic_params(ty: &syn::Type, generic_names: &Vec<&Ident>) -> bool {
let mut found_set = Default::default();
let mut visitor = GenericNameVisitor::new(generic_names, &mut found_set);
visitor.visit_type(ty);
!found_set.is_empty()
}
pub(crate) fn uses_lifetime_params(ty: &syn::Type, lifetime_params: &[&syn::Lifetime]) -> bool {
!used_lifetimes_in_type(ty, lifetime_params).is_empty()
}
/// Find all lifetimes from the given set that are used in type param bounds
pub(crate) fn used_lifetimes_in_bounds<'a>(
bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus>,
lifetime_params: &'a [&'a syn::Lifetime],
) -> BTreeSet<syn::Lifetime> {
let mut visitor = LifetimeVisitor::new(lifetime_params);
for bound in bounds {
syn::visit::Visit::visit_type_param_bound(&mut visitor, bound);
}
visitor.into_found()
}
pub(crate) fn used_generic_params<'a>(
ty: &'a syn::Type,
generic_names: &'a Vec<&Ident>,
mut used_params: BTreeSet<Ident>,
) -> BTreeSet<Ident> {
let mut visitor = GenericNameVisitor::new(generic_names, &mut used_params);
visitor.visit_type(ty);
used_params
}
/// Usage visitor for generic bounds
pub(crate) fn generics_predicate_uses(
predicate: &syn::WherePredicate,
generic_names: &Vec<&Ident>,
) -> bool {
let mut found_set = Default::default();
let mut visitor = GenericNameVisitor::new(generic_names, &mut found_set);
visitor.visit_where_predicate(predicate);
!found_set.is_empty()
}
/// Concrete type replacement visitor application.
/// Replaces generic type parameters with their concrete types (or JsValue if no default),
/// and replaces specified lifetime parameters with 'static (since extern blocks cannot have
/// lifetime parameters from the outer scope).
pub(crate) fn generic_to_concrete<'a>(
mut ty: syn::Type,
generic_names: &BTreeMap<&'a Ident, Option<Cow<'a, syn::Type>>>,
lifetimes_to_staticize: &[&syn::Lifetime],
) -> Result<syn::Type, Diagnostic> {
// First, replace type parameters with their concrete types
if !generic_names.is_empty() {
let mut visitor = GenericRenameVisitor {
renames: generic_names,
err: None,
};
visitor.visit_type_mut(&mut ty);
if let Some(err) = visitor.err {
return Err(err);
}
}
// Then, replace specified lifetimes with 'static for ABI compatibility
Ok(staticize_lifetimes(ty, lifetimes_to_staticize))
}
#[cfg(test)]
mod tests {
#[test]
fn test_generic_name_visitor() {
let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
let u_ident = syn::Ident::new("U", proc_macro2::Span::call_site());
let generic_params = vec![&t_ident, &u_ident];
// Test T as value
let ty: syn::Type = syn::parse_quote!(T);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
// Test &T as reference
let ty: syn::Type = syn::parse_quote!(&T);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
// Test T<U> - both found
let ty: syn::Type = syn::parse_quote!(T<U>);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
assert!(visitor.found_set.contains(&u_ident));
// Test &T<U> - both found
let ty: syn::Type = syn::parse_quote!(&T<U>);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
assert!(visitor.found_set.contains(&u_ident));
// Test T::<U>::Foo - T and U found, Foo ignored
let ty: syn::Type = syn::parse_quote!(T::<U>::Foo);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
assert!(visitor.found_set.contains(&u_ident));
// Test Vec<T> - T found, Vec ignored
let ty: syn::Type = syn::parse_quote!(Vec<T>);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
assert!(!visitor.found_set.contains(&u_ident));
}
#[test]
fn test_associated_type_binding() {
let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
let u_ident = syn::Ident::new("U", proc_macro2::Span::call_site());
let generic_params = vec![&t_ident, &u_ident];
// Test SomeTrait<T = U> - should find U (RHS) but NOT T (LHS assoc type name)
let ty: syn::Type = syn::parse_quote!(SomeTrait<T = U>);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(!visitor.found_set.contains(&t_ident)); // T is LHS assoc type name
assert!(visitor.found_set.contains(&u_ident)); // U is RHS generic parameter
// Test SomeTrait<U = T> - should find T (RHS) but NOT U (LHS assoc type name)
let ty: syn::Type = syn::parse_quote!(SomeTrait<U = T>);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident)); // T is RHS generic parameter
assert!(!visitor.found_set.contains(&u_ident)); // U is LHS assoc type name
}
#[test]
fn test_nested_references() {
let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
let u_ident = syn::Ident::new("U", proc_macro2::Span::call_site());
let generic_params = vec![&t_ident, &u_ident];
// Test &T
let ty: syn::Type = syn::parse_quote!(&T);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
// Test &&T
let ty: syn::Type = syn::parse_quote!(&&T);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
// Test &&&T
let ty: syn::Type = syn::parse_quote!(&&&T);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
// Test &T<U>
let ty: syn::Type = syn::parse_quote!(&T<U>);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
assert!(visitor.found_set.contains(&u_ident));
}
#[test]
fn test_mixed_usage() {
let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
let generic_params = vec![&t_ident];
// Test T appearing in multiple places
let ty: syn::Type = syn::parse_quote!(SomeTrait<Item = T> + OtherTrait<Ref = &T>);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
}
#[test]
fn test_ref_qself_trait_assoc_type() {
let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
let generic_params = vec![&t_ident];
// Test &<T as JsFunction1>::Arg1 - T should be found
let ty: syn::Type = syn::parse_quote!(&<T as JsFunction1>::Arg1);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(
visitor.found_set.contains(&t_ident),
"T should be found in &<T as JsFunction1>::Arg1"
);
}
#[test]
fn test_complex_reference_with_closure() {
let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
let r_ident = syn::Ident::new("R", proc_macro2::Span::call_site());
let generic_params = vec![&t_ident, &r_ident];
let ty: syn::Type = syn::parse_quote!(&Closure<dyn FnMut(T) -> Result<R, JsValue>>);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_type(&mut visitor, &ty);
assert!(visitor.found_set.contains(&t_ident));
assert!(visitor.found_set.contains(&r_ident));
}
#[test]
fn test_generic_args_to_concrete() {
use std::borrow::Cow;
use std::collections::BTreeMap;
// T -> String replacement
let t = syn::parse_quote!(T);
let str: syn::Type = syn::parse_quote!(String);
let generic_names: BTreeMap<&syn::Ident, Option<Cow<syn::Type>>> = {
let mut map = BTreeMap::new();
map.insert(&t, Some(Cow::Borrowed(&str)));
map
};
// T gets replaced with String
let generic_type: syn::Type = syn::parse_quote!(Promise<T>);
let result =
crate::generics::generic_to_concrete(generic_type, &generic_names, &[]).unwrap();
let expected: syn::Type = syn::parse_quote!(Promise<String>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// Mixed: i32 stays, T becomes String
let mixed_type: syn::Type = syn::parse_quote!(Promise<i32, T>);
let result = crate::generics::generic_to_concrete(mixed_type, &generic_names, &[]).unwrap();
let expected: syn::Type = syn::parse_quote!(Promise<i32, String>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// No generics to replace - unchanged
let concrete_type: syn::Type = syn::parse_quote!(Promise<i32, bool>);
let result =
crate::generics::generic_to_concrete(concrete_type, &generic_names, &[]).unwrap();
let expected: syn::Type = syn::parse_quote!(Promise<i32, bool>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
}
#[test]
fn test_generic_associated_type_replacement() {
use std::borrow::Cow;
use std::collections::BTreeMap;
let t: syn::Ident = syn::parse_quote!(T);
let concrete: syn::Type = syn::parse_quote!(MyConcreteType);
let generic_names: BTreeMap<&syn::Ident, Option<Cow<syn::Type>>> = {
let mut map = BTreeMap::new();
map.insert(&t, Some(Cow::Borrowed(&concrete)));
map
};
// T::DurableObjectStub -> MyConcreteType::DurableObjectStub
let assoc_type: syn::Type = syn::parse_quote!(T::DurableObjectStub);
let result = crate::generics::generic_to_concrete(assoc_type, &generic_names, &[]).unwrap();
let expected: syn::Type = syn::parse_quote!(MyConcreteType::DurableObjectStub);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// Nested: Vec<T::Item> -> Vec<MyConcreteType::Item>
let nested: syn::Type = syn::parse_quote!(Vec<T::Item>);
let result = crate::generics::generic_to_concrete(nested, &generic_names, &[]).unwrap();
let expected: syn::Type = syn::parse_quote!(Vec<MyConcreteType::Item>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// Complex: WasmRet<<T::Stub as FromWasmAbi>::Abi>
let complex: syn::Type = syn::parse_quote!(WasmRet<<T::Stub as FromWasmAbi>::Abi>);
let result = crate::generics::generic_to_concrete(complex, &generic_names, &[]).unwrap();
let expected: syn::Type =
syn::parse_quote!(WasmRet<<MyConcreteType::Stub as FromWasmAbi>::Abi>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// T<Foo> gets fully replaced, args discarded
let with_args: syn::Type = syn::parse_quote!(T<SomeArg>);
let result = crate::generics::generic_to_concrete(with_args, &generic_names, &[]).unwrap();
let expected: syn::Type = syn::parse_quote!(MyConcreteType);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// QSelf: <T::DurableObjectStub as FromWasmAbi>::Abi
let qself_type: syn::Type = syn::parse_quote!(<T::DurableObjectStub as FromWasmAbi>::Abi);
let result = crate::generics::generic_to_concrete(qself_type, &generic_names, &[]).unwrap();
let expected: syn::Type =
syn::parse_quote!(<MyConcreteType::DurableObjectStub as FromWasmAbi>::Abi);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// QSelf with trait: <T as DurableObject>::DurableObjectStub
let qself_trait: syn::Type = syn::parse_quote!(<T as DurableObject>::DurableObjectStub);
let result =
crate::generics::generic_to_concrete(qself_trait, &generic_names, &[]).unwrap();
let expected: syn::Type =
syn::parse_quote!(<MyConcreteType as DurableObject>::DurableObjectStub);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// Reference to QSelf with trait: &<T as DurableObject>::DurableObjectStub
let ref_qself_trait: syn::Type =
syn::parse_quote!(&<T as DurableObject>::DurableObjectStub);
let result =
crate::generics::generic_to_concrete(ref_qself_trait, &generic_names, &[]).unwrap();
let expected: syn::Type =
syn::parse_quote!(&<MyConcreteType as DurableObject>::DurableObjectStub);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
}
#[test]
fn test_where_predicate_assoc_type_binding() {
// Test that generics_predicate_uses finds generic params in associated type bindings
// This is the pattern: F: JsFunction<Ret = Ret>
// Both F and Ret should be detected as used
let f_ident = syn::Ident::new("F", proc_macro2::Span::call_site());
let ret_ident = syn::Ident::new("Ret", proc_macro2::Span::call_site());
// Test with both F and Ret in the search set
let generic_params = vec![&f_ident, &ret_ident];
let predicate: syn::WherePredicate = syn::parse_quote!(F: JsFunction<Ret = Ret>);
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_where_predicate(&mut visitor, &predicate);
assert!(
found_set.contains(&f_ident),
"F should be found in 'F: JsFunction<Ret = Ret>'"
);
assert!(
found_set.contains(&ret_ident),
"Ret should be found in 'F: JsFunction<Ret = Ret>' (RHS of assoc type binding)"
);
}
#[test]
fn test_where_predicate_assoc_type_binding_only_rhs() {
let f_ident = syn::Ident::new("F", proc_macro2::Span::call_site());
let ret_ident = syn::Ident::new("Ret", proc_macro2::Span::call_site());
// Ret in the search set
let generic_params = vec![&ret_ident];
let predicate: syn::WherePredicate = syn::parse_quote!(F: JsFunction<Ret = Ret>);
let uses = crate::generics::generics_predicate_uses(&predicate, &generic_params);
assert!(
uses,
"Ret should be detected as used in 'F: JsFunction<Ret = Ret>'"
);
// F in the search set
let not_generic_params = vec![&f_ident];
let uses = crate::generics::generics_predicate_uses(&predicate, &not_generic_params);
assert!(
uses,
"F should not be detected as used in 'F: JsFunction<Ret = Ret>'"
);
}
#[test]
fn test_where_predicate_assoc_type_binding_only_bounded() {
// Test that only F (not Ret) is found when Ret is not in the search set
let f_ident = syn::Ident::new("F", proc_macro2::Span::call_site());
let ret_ident = syn::Ident::new("Ret", proc_macro2::Span::call_site());
// Only F in the search set
let generic_params = vec![&f_ident];
let predicate: syn::WherePredicate = syn::parse_quote!(F: JsFunction<Ret = Ret>);
let uses = crate::generics::generics_predicate_uses(&predicate, &generic_params);
assert!(
uses,
"F should be detected as used in 'F: JsFunction<Ret = Ret>'"
);
// Also verify Ret is NOT found when not in the search set
let mut found_set = Default::default();
let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
syn::visit::visit_where_predicate(&mut visitor, &predicate);
assert!(found_set.contains(&f_ident), "F should be found");
assert!(
!found_set.contains(&ret_ident),
"Ret should NOT be found when not in search set"
);
}
#[test]
fn test_staticize_specific_lifetimes() {
// Test that specified lifetimes in types are replaced with 'static
let lifetime_a: syn::Lifetime = syn::parse_quote!('a);
let lifetimes = [&lifetime_a];
let ty: syn::Type = syn::parse_quote!(ImmediateClosure<'a, dyn FnMut(T) -> R>);
let result = crate::generics::staticize_lifetimes(ty, &lifetimes);
let expected: syn::Type = syn::parse_quote!(ImmediateClosure<'static, dyn FnMut(T) -> R>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// Test multiple lifetimes - only staticize specified ones
let lifetime_b: syn::Lifetime = syn::parse_quote!('b);
let lifetimes_both = [&lifetime_a, &lifetime_b];
let ty: syn::Type = syn::parse_quote!(&'a SomeType<'b, T>);
let result = crate::generics::staticize_lifetimes(ty, &lifetimes_both);
let expected: syn::Type = syn::parse_quote!(&'static SomeType<'static, T>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// Test selective staticization - only 'a, not 'b
let ty: syn::Type = syn::parse_quote!(&'a SomeType<'b, T>);
let result = crate::generics::staticize_lifetimes(ty, &[&lifetime_a]);
let expected: syn::Type = syn::parse_quote!(&'static SomeType<'b, T>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// Test no lifetimes to staticize (should be unchanged)
let ty: syn::Type = syn::parse_quote!(Vec<T>);
let result = crate::generics::staticize_lifetimes(ty, &[]);
let expected: syn::Type = syn::parse_quote!(Vec<T>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
}
#[test]
fn test_generic_to_concrete_with_lifetimes() {
use std::borrow::Cow;
use std::collections::BTreeMap;
// Test that generic_to_concrete replaces both type params AND specified lifetimes
let t: syn::Ident = syn::parse_quote!(T);
let concrete: syn::Type = syn::parse_quote!(JsValue);
let generic_names: BTreeMap<&syn::Ident, Option<Cow<syn::Type>>> = {
let mut map = BTreeMap::new();
map.insert(&t, Some(Cow::Borrowed(&concrete)));
map
};
// Create the lifetime 'a that we want to staticize
let lifetime_a: syn::Lifetime = syn::parse_quote!('a);
let lifetimes_to_staticize = [&lifetime_a];
// ImmediateClosure<'a, dyn FnMut(T)> -> ImmediateClosure<'static, dyn FnMut(JsValue)>
let ty: syn::Type = syn::parse_quote!(ImmediateClosure<'a, dyn FnMut(T)>);
let result =
crate::generics::generic_to_concrete(ty, &generic_names, &lifetimes_to_staticize)
.unwrap();
let expected: syn::Type = syn::parse_quote!(ImmediateClosure<'static, dyn FnMut(JsValue)>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
// Test that lifetimes NOT in the list are preserved
let _lifetime_b: syn::Lifetime = syn::parse_quote!('b);
let lifetimes_only_a = [&lifetime_a];
let ty: syn::Type = syn::parse_quote!(Foo<'a, 'b>);
let result =
crate::generics::generic_to_concrete(ty, &BTreeMap::new(), &lifetimes_only_a).unwrap();
let expected: syn::Type = syn::parse_quote!(Foo<'static, 'b>);
assert_eq!(
quote::quote!(#result).to_string(),
quote::quote!(#expected).to_string()
);
}
}

View File

@@ -0,0 +1,45 @@
//! 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<T>(pub T);
impl<T: Hash> fmt::Display for ShortHash<T> {
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())
}
}

View File

@@ -0,0 +1,204 @@
//! 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 generics;
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<TokenStream, Diagnostic> {
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::<syn::Item>(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<TokenStream, Diagnostic> {
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<TokenStream, Diagnostic> {
parser::reset_attrs_used();
let mut item = syn::parse2::<syn::ImplItemFn>(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<Self> {
let class = input.parse::<syn::Ident>()?;
input.parse::<Token![=]>()?;
let mut js_class = input.parse::<syn::LitStr>()?.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::<Option<Token![,]>>()?.is_some() {
let ident = input.parse::<syn::Ident>()?;
if ident == "wasm_bindgen" {
if wasm_bindgen.is_some() {
return Err(syn::Error::new(
ident.span(),
"found duplicate `wasm_bindgen`",
));
}
input.parse::<Token![=]>()?;
wasm_bindgen = Some(input.parse::<syn::Path>()?);
} 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::<Token![=]>()?;
wasm_bindgen_futures = Some(input.parse::<syn::Path>()?);
} 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<TokenStream, Diagnostic> {
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)
}

File diff suppressed because it is too large Load Diff