From bf4eab3e106fdca3f05511b1fe41cc8660eb825e Mon Sep 17 00:00:00 2001 From: ImUrX Date: Wed, 20 Apr 2022 19:24:00 -0300 Subject: [PATCH 01/13] add Trait type --- crates/backend/src/ast.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 80af028f422..46835f67092 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -21,6 +21,8 @@ pub struct Program { pub enums: Vec, /// rust structs pub structs: Vec, + /// rust traits + pub traits: Vec, /// custom typescript sections to be included in the definition file pub typescript_custom_sections: Vec, /// Inline JS snippets @@ -36,6 +38,7 @@ impl Program { && self.structs.is_empty() && self.typescript_custom_sections.is_empty() && self.inline_js.is_empty() + && self.traits.is_empty() } } @@ -379,6 +382,42 @@ pub struct Variant { pub comments: Vec, } +/// Information about a Trait being exported +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +#[derive(Clone)] +pub struct Trait { + /// The name of the trait in Rust code + pub rust_name: Ident, + /// The name of the trait in JS code + pub js_name: String, + /// All the methods of this trait to export + pub methods: Vec, + /// The doc comments on this trait, if provided + pub comments: Vec, + /// Whether this trait is inspectable (provides toJSON/toString properties to JS) + pub is_inspectable: bool, + /// Whether to generate a typescript definition for this trait + pub generate_typescript: bool, +} + +/// The method signature of a trait +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +#[derive(Clone)] +pub struct TraitMethod { + /// Comments extracted from the rust source. + pub comments: Vec, + /// The rust method + pub function: Function, + /// The kind (static, named, regular) + pub method_kind: MethodKind, + /// The type of `self` (either `self`, `&self`, or `&mut self`) + pub method_self: Option, + /// The trait name, in Rust, this is attached to + pub rust_trait: Ident, + /// The name of the rust method on the rust side. + pub rust_name: Ident, +} + /// Unused, the type of an argument to / return from a function #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum TypeKind { From d080365ecdbf94d8c3c4828e574b0ec267c0979b Mon Sep 17 00:00:00 2001 From: ImUrX Date: Wed, 20 Apr 2022 21:47:04 -0300 Subject: [PATCH 02/13] add parser code --- crates/backend/src/ast.rs | 2 +- crates/macro-support/src/parser.rs | 75 +++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 46835f67092..711c4c00376 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -413,7 +413,7 @@ pub struct TraitMethod { /// The type of `self` (either `self`, `&self`, or `&mut self`) pub method_self: Option, /// The trait name, in Rust, this is attached to - pub rust_trait: Ident, + pub trait_name: Ident, /// The name of the rust method on the rust side. pub rust_name: Ident, } diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 4768b3d15de..82b08ae91fd 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -431,6 +431,74 @@ impl<'a> ConvertToAst for &'a mut syn::ItemStruct { } } +impl<'a> ConvertToAst for &'a mut syn::ItemTrait { + type Target = ast::Trait; + + fn convert(self, attrs: BindgenAttrs) -> Result { + if self.generics.params.len() > 0 { + bail_span!( + self.generics, + "traits with #[wasm_bindgen] cannot have lifetime or \ + type parameters currently" + ); + } + match self.vis { + syn::Visibility::Public(_) => {} + _ => bail_span!(self, "can only #[wasm_bindgen] public traits"), + } + let mut methods = Vec::new(); + let js_name = attrs + .js_name() + .map(|s| s.0.to_string()) + .unwrap_or(self.ident.to_string()); + let is_inspectable = attrs.inspectable().is_some(); + for item in self.items.iter_mut() { + match item { + syn::TraitItem::Method(method) => { + let (function, method_self) = function_from_decl( + &method.sig.ident, + &attrs, + method.sig.clone(), + method.attrs.clone(), + self.vis.clone(), + true, + Some(&self.ident), + )?; + let comments = extract_doc_comments(&method.attrs); + let method_kind = if attrs.constructor().is_some() { + ast::MethodKind::Constructor + } else { + let is_static = method_self.is_none(); + let kind = operation_kind(&attrs); + ast::MethodKind::Operation(ast::Operation { is_static, kind }) + }; + + methods.push(ast::TraitMethod { + rust_name: method.sig.ident.clone(), + trait_name: self.ident.clone(), + function, + method_self, + method_kind, + comments, + }); + } + _ => {} + } + } + let generate_typescript = attrs.skip_typescript().is_none(); + let comments: Vec = extract_doc_comments(&self.attrs); + attrs.check_used()?; + Ok(ast::Trait { + rust_name: self.ident.clone(), + js_name, + methods, + comments, + is_inspectable, + generate_typescript, + }) + } +} + fn get_ty(mut ty: &syn::Type) -> &syn::Type { while let syn::Type::Group(g) = ty { ty = &g.elem; @@ -902,11 +970,16 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { }; c.macro_parse(program, opts)?; } + syn::Item::Trait(mut t) => { + let opts = opts.unwrap_or_default(); + program.traits.push((&mut t).convert(opts)?); + t.to_tokens(tokens); + } _ => { bail_span!( self, "#[wasm_bindgen] can only be applied to a function, \ - struct, enum, impl, or extern block", + struct, enum, impl, trait, or extern block", ); } } From 309c04d55c9e17906a3ba5436eb3da415a7c98bf Mon Sep 17 00:00:00 2001 From: ImUrX Date: Wed, 20 Apr 2022 22:09:52 -0300 Subject: [PATCH 03/13] fix current tests --- crates/macro/ui-tests/invalid-items.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/macro/ui-tests/invalid-items.stderr b/crates/macro/ui-tests/invalid-items.stderr index 2df6351b1b4..c426464c7a3 100644 --- a/crates/macro/ui-tests/invalid-items.stderr +++ b/crates/macro/ui-tests/invalid-items.stderr @@ -58,7 +58,7 @@ error: can't #[wasm_bindgen] functions with lifetime or type parameters 31 | pub fn foo6<'a, T>() {} | ^^^^^^^ -error: #[wasm_bindgen] can only be applied to a function, struct, enum, impl, or extern block +error: can only #[wasm_bindgen] public traits --> $DIR/invalid-items.rs:34:1 | 34 | trait X {} From 3565091d13143661f14be7d66b1186e01df68938 Mon Sep 17 00:00:00 2001 From: ImUrX Date: Sun, 24 Apr 2022 18:31:58 -0300 Subject: [PATCH 04/13] add js code generation --- crates/backend/src/encode.rs | 36 +++++++++++++ crates/cli-support/src/js/mod.rs | 65 ++++++++++++++++++++++- crates/cli-support/src/wit/mod.rs | 64 ++++++++++++++++++++++ crates/cli-support/src/wit/nonstandard.rs | 34 ++++++++++++ crates/cli-support/src/wit/section.rs | 9 ++++ crates/shared/src/lib.rs | 16 ++++++ tests/wasm/traits.js | 2 + tests/wasm/traits.rs | 7 +++ 8 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 tests/wasm/traits.js create mode 100644 tests/wasm/traits.rs diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 6fa1245b32c..823498abde5 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -167,6 +167,11 @@ fn shared_program<'a>( .iter() .map(|js| intern.intern_str(js)) .collect(), + traits: prog + .traits + .iter() + .map(|a| shared_trait(a, intern)) + .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())) @@ -328,6 +333,37 @@ fn shared_struct_field<'a>(s: &'a ast::StructField, _intern: &'a Interner) -> St } } +fn shared_trait<'a>(t: &'a ast::Trait, intern: &'a Interner) -> Result, Diagnostic> { + Ok(Trait { + name: &t.js_name, + methods: t + .methods + .iter() + .map(|t| shared_trait_method(t, intern)) + .collect::, _>>()?, + comments: t.comments.iter().map(|t| &**t).collect(), + is_inspectable: t.is_inspectable, + generate_typescript: t.generate_typescript, + }) +} + +fn shared_trait_method<'a>( + method: &'a ast::TraitMethod, + intern: &'a Interner, +) -> Result, Diagnostic> { + let consumed = match method.method_self { + Some(ast::MethodSelf::ByValue) => true, + _ => false, + }; + let method_kind = from_ast_method_kind(&method.function, intern, &method.method_kind)?; + Ok(TraitMethod { + comments: method.comments.iter().map(|s| &**s).collect(), + consumed, + function: shared_function(&method.function, intern), + method_kind, + }) +} + trait Encode { fn encode(&self, dst: &mut Encoder); } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index ed540b0cfe3..f7995833ed4 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2,7 +2,7 @@ use crate::descriptor::VectorKind; use crate::intrinsic::Intrinsic; use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, AuxValue}; use crate::wit::{AdapterKind, Instruction, InstructionData}; -use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; +use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct, AuxTrait}; use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux}; use crate::{reset_indentation, Bindgen, EncodeInto, OutputMode, PLACEHOLDER_MODULE}; use anyhow::{anyhow, bail, Context as _, Error}; @@ -2265,6 +2265,10 @@ impl<'a> Context<'a> { self.generate_enum(e)?; } + for t in self.aux.traits.iter() { + self.generate_trait(t)?; + } + for s in self.aux.structs.iter() { self.generate_struct(s)?; } @@ -3327,6 +3331,65 @@ impl<'a> Context<'a> { Ok(()) } + fn generate_trait(&mut self, trait_: &AuxTrait) -> Result<(), Error> { + let docs = format_doc_comments(&trait_.comments, None); + let mut symbols = String::new(); + let mut interface = String::new(); + + if trait_.generate_typescript { + self.typescript.push_str(&docs); + self.typescript + .push_str(&format!("export interface {}Trait {{", trait_.name)); + interface.push_str(&docs); + interface + .push_str(&format!("export interface {} {{", trait_.name)); + } + for method in trait_.methods.iter() { + let method_docs = if method.comments.is_empty() { + String::new() + } else { + // How do I generate JSDoc for this + format_doc_comments(&method.comments, None) + }; + if !method_docs.is_empty() { + symbols.push_str("\n"); + symbols.push_str(&method_docs); + } + let name = match method.kind { + AuxExportKind::Function(_) + | AuxExportKind::Constructor(_) => bail!( + "this shouldn't be possible" + ), + AuxExportKind::Getter { field: name, .. } + | AuxExportKind::Setter { field: name, .. } + | AuxExportKind::StaticFunction { name, .. } + | AuxExportKind::Method { name, .. } => name, + }; + symbols.push_str(&format!("{name}:Symbol(\"{name}\"),")); + if trait_.generate_typescript { + self.typescript.push_str("\n"); + self.typescript.push_str(&format_doc_comments(format!("Symbol for the {} method", name), None)); + self.typescript.push_str(&format!(" readonly {}: unique symbol;", name)); + interface.push_str("\n"); + //How do I generate ts sig for this? + //interface.push_str() + } + } + if trait_.generate_typescript { + self.typescript.push_str("\n}\n"); + self.typescript.push_str(format!("declare var {name}: {name}Trait;\n", name = trait_.name)); + self.typescript.push_str(&interface); + self.typescript.push_str("\n}\n"); + } + self.export( + &trait_.name, + &format!("Object.freeze({{ {} }})", symbols), + Some(&docs), + )?; + + Ok(()) + } + fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> { let class = require_class(&mut self.exported_classes, &struct_.name); class.comments = format_doc_comments(&struct_.comments, None); diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index b041a77841c..0b0bb9c4d52 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -335,6 +335,7 @@ impl<'a> Context<'a> { enums, imports, structs, + traits, typescript_custom_sections, local_modules, inline_js, @@ -385,6 +386,9 @@ impl<'a> Context<'a> { for struct_ in structs { self.struct_(struct_)?; } + for trait_ in traits { + self.trait_(trait_)?; + } for section in typescript_custom_sections { self.aux.extra_typescript.push_str(section); self.aux.extra_typescript.push_str("\n\n"); @@ -876,6 +880,66 @@ impl<'a> Context<'a> { Ok(()) } + fn trait_(&mut self, trait_: decode::Trait<'_>) -> Result<(), Error> { + let mut methods = vec!(); + for export in trait_.methods { + let wasm_name = struct_function_export_name(trait_.name, export.function.name); + + let interface = trait_.name; + let kind = match export.method_kind { + decode::MethodKind::Constructor => bail!( + "traits can't have constructors" + ), + decode::MethodKind::Operation(op) => match op.kind { + decode::OperationKind::Getter(f) => { + AuxExportKind::Getter { + class: interface.to_string(), + field: f.to_string(), + consumed: export.consumed, + } + } + decode::OperationKind::Setter(f) => { + AuxExportKind::Setter { + class: interface.to_string(), + field: f.to_string(), + consumed: export.consumed, + } + } + _ if op.is_static => AuxExportKind::StaticFunction { + class: interface.to_string(), + name: export.function.name.to_string(), + }, + _ => { + AuxExportKind::Method { + class: interface.to_string(), + name: export.function.name.to_string(), + consumed: export.consumed, + } + } + }, + }; + + methods.push(AuxTraitMethod { + debug_name: wasm_name, + comments: concatenate_comments(&export.comments), + arg_names: Some(export.function.arg_names), + asyncness: export.function.asyncness, + kind, + generate_typescript: export.function.generate_typescript, + }); + } + + let aux = AuxTrait { + name: trait_.name.to_string(), + comments: concatenate_comments(&trait_.comments), + methods, + generate_typescript: trait_.generate_typescript, + }; + self.aux.traits.push(aux); + + Ok(()) + } + fn determine_import(&self, import: &decode::Import<'_>, item: &str) -> Result { let is_local_snippet = match import.module { decode::ImportModule::Named(s) => self.aux.local_modules.contains_key(s), diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index abae06daf9c..35d2aca9b6e 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -47,6 +47,10 @@ pub struct WasmBindgenAux { /// exported structs from Rust and their fields they've got exported. pub structs: Vec, + /// Auxiliary information to go into JS/TypeScript bindings describing the + /// exported traits from Rust and their method info + pub traits: Vec, + /// Information about various internal functions used to manage the `externref` /// table, later used to process JS bindings. pub externref_table: Option, @@ -165,6 +169,36 @@ pub struct AuxStruct { pub generate_typescript: bool, } +#[derive(Debug)] +pub struct AuxTrait { + /// The name of this trait + pub name: String, + /// The copied Rust comments to forward to JS + pub comments: String, + /// The method signatures the trait defines + pub methods: Vec, + /// Whether typescript bindings should be generated for this trait. + pub generate_typescript: bool, +} + +#[derive(Debug)] +pub struct AuxTraitMethod { + /// When generating errors about this method, a helpful name to remember it + /// by. + pub debug_name: String, + /// Comments parsed in Rust and forwarded here to show up in JS bindings. + pub comments: String, + /// Argument names in Rust forwarded here to configure the names that show + /// up in TypeScript bindings. + pub arg_names: Option>, + /// Whether this is an async function, to configure the TypeScript return value. + pub asyncness: bool, + /// What kind of function this is and where it shows up + pub kind: AuxExportKind, + /// Whether typescript bindings should be generated for this export. + pub generate_typescript: bool, +} + /// All possible types of imports that can be imported by a wasm module. /// /// This `enum` is intended to map out what an imported value is. For example diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index 30a6f82acc5..9057ace41fe 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -43,6 +43,7 @@ pub fn add(module: &mut Module) -> Result<(), Error> { imports_with_assert_no_shim: _, // not relevant for this purpose enums, structs, + traits, // irrelevant ids used to track various internal intrinsics and such externref_table: _, @@ -200,6 +201,14 @@ pub fn add(module: &mut Module) -> Result<(), Error> { ); } + if let Some(trait_) = traits.iter().next() { + bail!( + "generating a bindings section is currently incompatible with \ + exporting a `trait` from the wasm file, cannot export `{}`", + trait_.name, + ); + } + module.customs.add(section); Ok(()) } diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 2c2eecd33a8..4680c80cfae 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -17,6 +17,7 @@ macro_rules! shared_api { enums: Vec>, imports: Vec>, structs: Vec>, + traits: Vec>, typescript_custom_sections: Vec<&'a str>, local_modules: Vec>, inline_js: Vec<&'a str>, @@ -135,6 +136,21 @@ macro_rules! shared_api { generate_typescript: bool, } + struct Trait<'a> { + name: &'a str, + methods: Vec>, + comments: Vec<&'a str>, + is_inspectable: bool, + generate_typescript: bool, + } + + struct TraitMethod<'a> { + consumed: bool, + comments: Vec<&'a str>, + function: Function<'a>, + method_kind: MethodKind<'a>, + } + struct LocalModule<'a> { identifier: &'a str, contents: &'a str, diff --git a/tests/wasm/traits.js b/tests/wasm/traits.js new file mode 100644 index 00000000000..3c292c376c6 --- /dev/null +++ b/tests/wasm/traits.js @@ -0,0 +1,2 @@ +const wasm = require('wasm-bindgen-test.js'); +const assert = require('assert'); \ No newline at end of file diff --git a/tests/wasm/traits.rs b/tests/wasm/traits.rs new file mode 100644 index 00000000000..87cc65a448c --- /dev/null +++ b/tests/wasm/traits.rs @@ -0,0 +1,7 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; + +#[wasm_bindgen] +pub trait Testable { + +} \ No newline at end of file From 8428e71e02a33ba10f3575b0a92c9941757e9fab Mon Sep 17 00:00:00 2001 From: ImUrX Date: Sun, 24 Apr 2022 18:33:14 -0300 Subject: [PATCH 05/13] cargo fmt --- crates/cli-support/src/js/mod.rs | 23 +++++++++------ crates/cli-support/src/wit/mod.rs | 48 +++++++++++++------------------ 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index f7995833ed4..951899aa238 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -3341,8 +3341,7 @@ impl<'a> Context<'a> { self.typescript .push_str(&format!("export interface {}Trait {{", trait_.name)); interface.push_str(&docs); - interface - .push_str(&format!("export interface {} {{", trait_.name)); + interface.push_str(&format!("export interface {} {{", trait_.name)); } for method in trait_.methods.iter() { let method_docs = if method.comments.is_empty() { @@ -3356,10 +3355,9 @@ impl<'a> Context<'a> { symbols.push_str(&method_docs); } let name = match method.kind { - AuxExportKind::Function(_) - | AuxExportKind::Constructor(_) => bail!( - "this shouldn't be possible" - ), + AuxExportKind::Function(_) | AuxExportKind::Constructor(_) => { + bail!("this shouldn't be possible") + } AuxExportKind::Getter { field: name, .. } | AuxExportKind::Setter { field: name, .. } | AuxExportKind::StaticFunction { name, .. } @@ -3368,8 +3366,12 @@ impl<'a> Context<'a> { symbols.push_str(&format!("{name}:Symbol(\"{name}\"),")); if trait_.generate_typescript { self.typescript.push_str("\n"); - self.typescript.push_str(&format_doc_comments(format!("Symbol for the {} method", name), None)); - self.typescript.push_str(&format!(" readonly {}: unique symbol;", name)); + self.typescript.push_str(&format_doc_comments( + format!("Symbol for the {} method", name), + None, + )); + self.typescript + .push_str(&format!(" readonly {}: unique symbol;", name)); interface.push_str("\n"); //How do I generate ts sig for this? //interface.push_str() @@ -3377,7 +3379,10 @@ impl<'a> Context<'a> { } if trait_.generate_typescript { self.typescript.push_str("\n}\n"); - self.typescript.push_str(format!("declare var {name}: {name}Trait;\n", name = trait_.name)); + self.typescript.push_str(format!( + "declare var {name}: {name}Trait;\n", + name = trait_.name + )); self.typescript.push_str(&interface); self.typescript.push_str("\n}\n"); } diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 0b0bb9c4d52..0c27a37acf4 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -881,44 +881,36 @@ impl<'a> Context<'a> { } fn trait_(&mut self, trait_: decode::Trait<'_>) -> Result<(), Error> { - let mut methods = vec!(); + let mut methods = vec![]; for export in trait_.methods { let wasm_name = struct_function_export_name(trait_.name, export.function.name); - + let interface = trait_.name; let kind = match export.method_kind { - decode::MethodKind::Constructor => bail!( - "traits can't have constructors" - ), + decode::MethodKind::Constructor => bail!("traits can't have constructors"), decode::MethodKind::Operation(op) => match op.kind { - decode::OperationKind::Getter(f) => { - AuxExportKind::Getter { - class: interface.to_string(), - field: f.to_string(), - consumed: export.consumed, - } - } - decode::OperationKind::Setter(f) => { - AuxExportKind::Setter { - class: interface.to_string(), - field: f.to_string(), - consumed: export.consumed, - } - } + decode::OperationKind::Getter(f) => AuxExportKind::Getter { + class: interface.to_string(), + field: f.to_string(), + consumed: export.consumed, + }, + decode::OperationKind::Setter(f) => AuxExportKind::Setter { + class: interface.to_string(), + field: f.to_string(), + consumed: export.consumed, + }, _ if op.is_static => AuxExportKind::StaticFunction { class: interface.to_string(), name: export.function.name.to_string(), }, - _ => { - AuxExportKind::Method { - class: interface.to_string(), - name: export.function.name.to_string(), - consumed: export.consumed, - } - } + _ => AuxExportKind::Method { + class: interface.to_string(), + name: export.function.name.to_string(), + consumed: export.consumed, + }, }, }; - + methods.push(AuxTraitMethod { debug_name: wasm_name, comments: concatenate_comments(&export.comments), @@ -928,7 +920,7 @@ impl<'a> Context<'a> { generate_typescript: export.function.generate_typescript, }); } - + let aux = AuxTrait { name: trait_.name.to_string(), comments: concatenate_comments(&trait_.comments), From 3398aa7f8c706bd43f1c6ad2b958f2d5d42b4cbd Mon Sep 17 00:00:00 2001 From: ImUrX Date: Sun, 24 Apr 2022 18:39:25 -0300 Subject: [PATCH 06/13] fix errors --- crates/cli-support/src/js/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 951899aa238..57f8da26679 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -3361,13 +3361,13 @@ impl<'a> Context<'a> { AuxExportKind::Getter { field: name, .. } | AuxExportKind::Setter { field: name, .. } | AuxExportKind::StaticFunction { name, .. } - | AuxExportKind::Method { name, .. } => name, + | AuxExportKind::Method { name, .. } => name.clone(), }; symbols.push_str(&format!("{name}:Symbol(\"{name}\"),")); if trait_.generate_typescript { self.typescript.push_str("\n"); self.typescript.push_str(&format_doc_comments( - format!("Symbol for the {} method", name), + &format!("Symbol for the {} method", name), None, )); self.typescript @@ -3379,7 +3379,7 @@ impl<'a> Context<'a> { } if trait_.generate_typescript { self.typescript.push_str("\n}\n"); - self.typescript.push_str(format!( + self.typescript.push_str(&format!( "declare var {name}: {name}Trait;\n", name = trait_.name )); From 2e2634aab0a3ab224d6098141f56e9e36d444f42 Mon Sep 17 00:00:00 2001 From: ImUrX Date: Sun, 24 Apr 2022 18:45:19 -0300 Subject: [PATCH 07/13] reference kind --- crates/cli-support/src/js/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 57f8da26679..7e221922bbe 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -3354,7 +3354,7 @@ impl<'a> Context<'a> { symbols.push_str("\n"); symbols.push_str(&method_docs); } - let name = match method.kind { + let name = match &method.kind { AuxExportKind::Function(_) | AuxExportKind::Constructor(_) => { bail!("this shouldn't be possible") } From fe68d92690e6a25d776c1588151c103ade5db0a5 Mon Sep 17 00:00:00 2001 From: Uriel Date: Mon, 25 Apr 2022 12:39:30 -0300 Subject: [PATCH 08/13] dont use format! capture --- crates/cli-support/src/js/mod.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 7e221922bbe..7a87da0e6f6 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -3344,15 +3344,18 @@ impl<'a> Context<'a> { interface.push_str(&format!("export interface {} {{", trait_.name)); } for method in trait_.methods.iter() { - let method_docs = if method.comments.is_empty() { - String::new() + let (symbol_docs, method_docs) = if method.comments.is_empty() { + (String::new(), String::new()) } else { // How do I generate JSDoc for this - format_doc_comments(&method.comments, None) + ( + format_doc_comments(&method.comments, None), + format_doc_comments(&method.comments, None), + ) }; - if !method_docs.is_empty() { + if !symbol_docs.is_empty() { symbols.push_str("\n"); - symbols.push_str(&method_docs); + symbols.push_str(&symbol_docs); } let name = match &method.kind { AuxExportKind::Function(_) | AuxExportKind::Constructor(_) => { @@ -3363,7 +3366,7 @@ impl<'a> Context<'a> { | AuxExportKind::StaticFunction { name, .. } | AuxExportKind::Method { name, .. } => name.clone(), }; - symbols.push_str(&format!("{name}:Symbol(\"{name}\"),")); + symbols.push_str(&format!("{name}:Symbol(\"{name}\"),", name = name)); if trait_.generate_typescript { self.typescript.push_str("\n"); self.typescript.push_str(&format_doc_comments( From 75cac5ebfb85a416815a4ebf65c41036507a874b Mon Sep 17 00:00:00 2001 From: ImUrX Date: Fri, 6 May 2022 12:21:47 -0300 Subject: [PATCH 09/13] implement test --- crates/cli-support/src/js/mod.rs | 4 ++-- crates/shared/src/schema_hash_approval.rs | 2 +- tests/wasm/main.rs | 1 + tests/wasm/traits.js | 8 +++++++- tests/wasm/traits.rs | 14 +++++++++++++- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 7a87da0e6f6..f81e196b59e 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -3344,7 +3344,7 @@ impl<'a> Context<'a> { interface.push_str(&format!("export interface {} {{", trait_.name)); } for method in trait_.methods.iter() { - let (symbol_docs, method_docs) = if method.comments.is_empty() { + let (symbol_docs, _method_docs) = if method.comments.is_empty() { (String::new(), String::new()) } else { // How do I generate JSDoc for this @@ -3366,7 +3366,7 @@ impl<'a> Context<'a> { | AuxExportKind::StaticFunction { name, .. } | AuxExportKind::Method { name, .. } => name.clone(), }; - symbols.push_str(&format!("{name}:Symbol(\"{name}\"),", name = name)); + symbols.push_str(&format!("{name}:Symbol(\"{}.{name}\"),", trait_.name, name = name)); if trait_.generate_typescript { self.typescript.push_str("\n"); self.typescript.push_str(&format_doc_comments( diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index 32a0f51d419..6949c41f717 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &'static str = "3468290064813615840"; +const APPROVED_SCHEMA_FILE_HASH: &'static str = "7893674600188816948"; #[test] fn schema_version() { diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index a90f9f3afef..408755cb572 100644 --- a/tests/wasm/main.rs +++ b/tests/wasm/main.rs @@ -40,6 +40,7 @@ pub mod rethrow; pub mod simple; pub mod slice; pub mod structural; +pub mod traits; pub mod truthy_falsy; pub mod u64; pub mod validate_prt; diff --git a/tests/wasm/traits.js b/tests/wasm/traits.js index 3c292c376c6..5f5fb51e7aa 100644 --- a/tests/wasm/traits.js +++ b/tests/wasm/traits.js @@ -1,2 +1,8 @@ const wasm = require('wasm-bindgen-test.js'); -const assert = require('assert'); \ No newline at end of file +const assert = require('assert'); + +exports.js_works = () => { + assert(wasm.Testable.method); + assert(wasm.Testable.second); + assert.notDeepStrictEqual(wasm.Testable.method, wasm.Testable.second); +} diff --git a/tests/wasm/traits.rs b/tests/wasm/traits.rs index 87cc65a448c..9c1c912d348 100644 --- a/tests/wasm/traits.rs +++ b/tests/wasm/traits.rs @@ -1,7 +1,19 @@ use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; +#[wasm_bindgen(module = "tests/wasm/traits.js")] +extern "C" { + fn js_works(); +} + #[wasm_bindgen] pub trait Testable { + /// test + fn method(n: i32) -> u32; + fn second(&self, n: i32) -> u32; +} -} \ No newline at end of file +#[wasm_bindgen_test] +fn works() { + js_works(); +} From 92a1d82e854a31a768f87f87b92b9da871bc7ba3 Mon Sep 17 00:00:00 2001 From: ImUrX Date: Mon, 9 May 2022 09:44:45 -0300 Subject: [PATCH 10/13] change pc --- crates/backend/src/ast.rs | 4 ++-- crates/backend/src/encode.rs | 1 - crates/macro-support/src/parser.rs | 7 +++++-- crates/shared/src/lib.rs | 1 - 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 711c4c00376..45b71df37c2 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -394,10 +394,10 @@ pub struct Trait { pub methods: Vec, /// The doc comments on this trait, if provided pub comments: Vec, - /// Whether this trait is inspectable (provides toJSON/toString properties to JS) - pub is_inspectable: bool, /// Whether to generate a typescript definition for this trait pub generate_typescript: bool, + /// The supertraits of this trait + pub supertraits: Vec, } /// The method signature of a trait diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 823498abde5..9754604ba43 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -342,7 +342,6 @@ fn shared_trait<'a>(t: &'a ast::Trait, intern: &'a Interner) -> Result .map(|t| shared_trait_method(t, intern)) .collect::, _>>()?, comments: t.comments.iter().map(|t| &**t).collect(), - is_inspectable: t.is_inspectable, generate_typescript: t.generate_typescript, }) } diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 82b08ae91fd..4e0111012b9 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -446,12 +446,16 @@ impl<'a> ConvertToAst for &'a mut syn::ItemTrait { syn::Visibility::Public(_) => {} _ => bail_span!(self, "can only #[wasm_bindgen] public traits"), } + + for supertrait in self.supertraits.iter() { + + } + let mut methods = Vec::new(); let js_name = attrs .js_name() .map(|s| s.0.to_string()) .unwrap_or(self.ident.to_string()); - let is_inspectable = attrs.inspectable().is_some(); for item in self.items.iter_mut() { match item { syn::TraitItem::Method(method) => { @@ -493,7 +497,6 @@ impl<'a> ConvertToAst for &'a mut syn::ItemTrait { js_name, methods, comments, - is_inspectable, generate_typescript, }) } diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 4680c80cfae..bd641b1c143 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -140,7 +140,6 @@ macro_rules! shared_api { name: &'a str, methods: Vec>, comments: Vec<&'a str>, - is_inspectable: bool, generate_typescript: bool, } From 1881822f3835407cce7126a5fa83a79247690e9e Mon Sep 17 00:00:00 2001 From: ImUrX Date: Sat, 30 Jul 2022 20:26:25 -0300 Subject: [PATCH 11/13] implement typescript test --- crates/typescript-tests/src/lib.rs | 1 + crates/typescript-tests/src/simple_trait.rs | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 crates/typescript-tests/src/simple_trait.rs diff --git a/crates/typescript-tests/src/lib.rs b/crates/typescript-tests/src/lib.rs index ad8a1c3fbf8..b696ad3d094 100644 --- a/crates/typescript-tests/src/lib.rs +++ b/crates/typescript-tests/src/lib.rs @@ -6,5 +6,6 @@ pub mod optional_fields; pub mod simple_async_fn; pub mod simple_fn; pub mod simple_struct; +pub mod simple_trait; pub mod typescript_type; pub mod web_sys; diff --git a/crates/typescript-tests/src/simple_trait.rs b/crates/typescript-tests/src/simple_trait.rs new file mode 100644 index 00000000000..f5b8c334150 --- /dev/null +++ b/crates/typescript-tests/src/simple_trait.rs @@ -0,0 +1,7 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub trait B { + fn greet(_: &str); + fn take_and_return(b: bool) -> bool; +} From 841babfa66b2178176594efd54f444c4e754d5bd Mon Sep 17 00:00:00 2001 From: ImUrX Date: Sat, 30 Jul 2022 20:27:00 -0300 Subject: [PATCH 12/13] impl supertraits --- crates/backend/src/ast.rs | 8 ++++-- crates/cli-support/src/js/mod.rs | 9 +++--- crates/macro-support/src/parser.rs | 44 +++++++++++++++++++++++++----- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 45b71df37c2..3ddfe5a656d 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -396,8 +396,8 @@ pub struct Trait { pub comments: Vec, /// Whether to generate a typescript definition for this trait pub generate_typescript: bool, - /// The supertraits of this trait - pub supertraits: Vec, + /// The supertraits of this trait, if provided + pub supertraits: Vec, } /// The method signature of a trait @@ -414,8 +414,10 @@ pub struct TraitMethod { pub method_self: Option, /// The trait name, in Rust, this is attached to pub trait_name: Ident, - /// The name of the rust method on the rust side. + /// The name of the method on the rust side. pub rust_name: Ident, + /// The name of the method in JS code + pub js_name: String, } /// Unused, the type of an argument to / return from a function diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index f81e196b59e..23d38cb5c0f 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,4 +1,4 @@ -use crate::descriptor::VectorKind; +use crate::descriptor::{VectorKind, Function}; use crate::intrinsic::Intrinsic; use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, AuxValue}; use crate::wit::{AdapterKind, Instruction, InstructionData}; @@ -3339,7 +3339,7 @@ impl<'a> Context<'a> { if trait_.generate_typescript { self.typescript.push_str(&docs); self.typescript - .push_str(&format!("export interface {}Trait {{", trait_.name)); + .push_str(&format!("interface __wbindgen_{}Trait {{", trait_.name)); interface.push_str(&docs); interface.push_str(&format!("export interface {} {{", trait_.name)); } @@ -3370,12 +3370,13 @@ impl<'a> Context<'a> { if trait_.generate_typescript { self.typescript.push_str("\n"); self.typescript.push_str(&format_doc_comments( - &format!("Symbol for the {} method", name), + &format!(" Symbol for the {} method", name), None, )); self.typescript .push_str(&format!(" readonly {}: unique symbol;", name)); interface.push_str("\n"); + //How do I generate ts sig for this? //interface.push_str() } @@ -3383,7 +3384,7 @@ impl<'a> Context<'a> { if trait_.generate_typescript { self.typescript.push_str("\n}\n"); self.typescript.push_str(&format!( - "declare var {name}: {name}Trait;\n", + "export const {name}: __wbindgen_{name}Trait;\n", name = trait_.name )); self.typescript.push_str(&interface); diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 4e0111012b9..caaecad2359 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -447,18 +447,47 @@ impl<'a> ConvertToAst for &'a mut syn::ItemTrait { _ => bail_span!(self, "can only #[wasm_bindgen] public traits"), } + let mut supertraits = Vec::new(); for supertrait in self.supertraits.iter() { - + match supertrait { + syn::TypeParamBound::Trait(t) => supertraits.push(extract_path_ident(&t.path)?), + _ => panic!("Why is the type parameter on the supertrait not a trait?") + } } - let mut methods = Vec::new(); let js_name = attrs .js_name() .map(|s| s.0.to_string()) .unwrap_or(self.ident.to_string()); + + let mut methods = Vec::new(); for item in self.items.iter_mut() { match item { syn::TraitItem::Method(method) => { + let attrs = BindgenAttrs::find(&mut method.attrs)?; + assert_not_variadic(&attrs)?; + if attrs.skip().is_some() { + attrs.check_used()?; + continue; + } + + if method.default.is_some() { + bail_span!(method.default, "default methods are not supported"); + } + if method.sig.unsafety.is_some() { + bail_span!(method.sig.unsafety, "can only bindgen safe functions",); + } + if attrs.constructor().is_some() { + panic!("traits can't define constructors") + } + + let (js_name, rust_name) = (method.sig.ident.to_string(), method.sig.ident.clone()); + let js_name = match attrs.js_name() { + Some((name, _)) => name.to_string(), + None => js_name, + }; + + let comments = extract_doc_comments(&method.attrs); let (function, method_self) = function_from_decl( &method.sig.ident, &attrs, @@ -468,23 +497,23 @@ impl<'a> ConvertToAst for &'a mut syn::ItemTrait { true, Some(&self.ident), )?; - let comments = extract_doc_comments(&method.attrs); - let method_kind = if attrs.constructor().is_some() { - ast::MethodKind::Constructor - } else { + + let method_kind = { let is_static = method_self.is_none(); let kind = operation_kind(&attrs); ast::MethodKind::Operation(ast::Operation { is_static, kind }) }; methods.push(ast::TraitMethod { - rust_name: method.sig.ident.clone(), + rust_name, + js_name, trait_name: self.ident.clone(), function, method_self, method_kind, comments, }); + attrs.check_used()?; } _ => {} } @@ -498,6 +527,7 @@ impl<'a> ConvertToAst for &'a mut syn::ItemTrait { methods, comments, generate_typescript, + supertraits }) } } From 3e9ebdcaf4bcd0a80de7b950ff56205554adc927 Mon Sep 17 00:00:00 2001 From: ImUrX Date: Wed, 3 Aug 2022 23:59:25 -0300 Subject: [PATCH 13/13] partial support for impl trait --- crates/typescript-tests/src/lib.rs | 1 + crates/typescript-tests/src/simple_trait.rs | 18 ++++++++++++++++-- crates/typescript-tests/src/simple_trait.ts | 6 ++++++ crates/typescript-tests/src/supertrait.rs | 8 ++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 crates/typescript-tests/src/simple_trait.ts create mode 100644 crates/typescript-tests/src/supertrait.rs diff --git a/crates/typescript-tests/src/lib.rs b/crates/typescript-tests/src/lib.rs index b696ad3d094..62a565fdd46 100644 --- a/crates/typescript-tests/src/lib.rs +++ b/crates/typescript-tests/src/lib.rs @@ -7,5 +7,6 @@ pub mod simple_async_fn; pub mod simple_fn; pub mod simple_struct; pub mod simple_trait; +pub mod supertrait; pub mod typescript_type; pub mod web_sys; diff --git a/crates/typescript-tests/src/simple_trait.rs b/crates/typescript-tests/src/simple_trait.rs index f5b8c334150..e5ccefc690e 100644 --- a/crates/typescript-tests/src/simple_trait.rs +++ b/crates/typescript-tests/src/simple_trait.rs @@ -1,7 +1,21 @@ use wasm_bindgen::prelude::*; +use crate::simple_struct::A; + #[wasm_bindgen] pub trait B { - fn greet(_: &str); - fn take_and_return(b: bool) -> bool; + fn other(); + fn greet(&self, c: &str); + fn take_and_return(&self, c: bool) -> bool; +} + +#[wasm_bindgen] +impl B for A { + fn other() {} + + fn greet(&self, _: &str) {} + + fn take_and_return(&self, _: bool) -> bool { + true + } } diff --git a/crates/typescript-tests/src/simple_trait.ts b/crates/typescript-tests/src/simple_trait.ts new file mode 100644 index 00000000000..f2616f4ae00 --- /dev/null +++ b/crates/typescript-tests/src/simple_trait.ts @@ -0,0 +1,6 @@ +import * as wbg from '../pkg/typescript_tests'; + +const a2 = new wbg.A(); +wbg.A[wbg.B.other](); +a2[wbg.B.greet]("Hello World!"); +const b2: boolean = a2[wbg.B.take_and_return](true) \ No newline at end of file diff --git a/crates/typescript-tests/src/supertrait.rs b/crates/typescript-tests/src/supertrait.rs new file mode 100644 index 00000000000..597ddcdc0df --- /dev/null +++ b/crates/typescript-tests/src/supertrait.rs @@ -0,0 +1,8 @@ +use wasm_bindgen::prelude::*; + +use crate::simple_trait::B; + +#[wasm_bindgen] +pub trait C { + fn take_bool(_: bool); +}