From 0c4cdefd07df46b7bf7912a293a77cac6afe54b6 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 29 Apr 2019 09:45:23 -0300 Subject: [PATCH 1/4] Rustfmt --- crates/js-sys/tests/wasm/JsString.rs | 21 ++++++++----------- crates/js-sys/tests/wasm/SharedArrayBuffer.rs | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/js-sys/tests/wasm/JsString.rs b/crates/js-sys/tests/wasm/JsString.rs index 652cb7d01fa..f2a3d48a51a 100644 --- a/crates/js-sys/tests/wasm/JsString.rs +++ b/crates/js-sys/tests/wasm/JsString.rs @@ -92,15 +92,15 @@ fn from_char_code() { "½+¾=" ); - let codes_u16: Vec = codes.into_iter().map(|code| { - assert!(code <= u32::from(u16::max_value())); - code as u16 - }).collect(); + let codes_u16: Vec = codes + .into_iter() + .map(|code| { + assert!(code <= u32::from(u16::max_value())); + code as u16 + }) + .collect(); - assert_eq!( - JsString::from_char_code(&codes_u16), - "½+¾=" - ); + assert_eq!(JsString::from_char_code(&codes_u16), "½+¾="); } #[wasm_bindgen_test] @@ -121,10 +121,7 @@ fn from_code_point() { JsString::from_code_point4(codes[0], codes[1], codes[2], codes[3]).unwrap(), "☃★♲你" ); - assert_eq!( - JsString::from_code_point(&codes).unwrap(), - "☃★♲你" - ); + assert_eq!(JsString::from_code_point(&codes).unwrap(), "☃★♲你"); assert!(!JsString::from_code_point1(0x10FFFF).is_err()); assert!(JsString::from_code_point1(0x110000).is_err()); diff --git a/crates/js-sys/tests/wasm/SharedArrayBuffer.rs b/crates/js-sys/tests/wasm/SharedArrayBuffer.rs index 0509f4906ad..fdfd55d9bbb 100644 --- a/crates/js-sys/tests/wasm/SharedArrayBuffer.rs +++ b/crates/js-sys/tests/wasm/SharedArrayBuffer.rs @@ -1,6 +1,6 @@ use js_sys::*; -use wasm_bindgen::JsCast; use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; use wasm_bindgen_test::*; #[wasm_bindgen(module = "tests/wasm/SharedArrayBuffer.js")] From 470eea9fb0249b1bd5da97e5c68795796f65d268 Mon Sep 17 00:00:00 2001 From: Caio Date: Tue, 30 Apr 2019 10:26:03 -0300 Subject: [PATCH 2/4] Getters/Setters for fields --- crates/backend/src/ast.rs | 27 ++- crates/backend/src/encode.rs | 82 ++++---- crates/cli-support/src/js/js2rust.rs | 38 ++-- crates/cli-support/src/js/mod.rs | 176 ++++++++++-------- crates/macro-support/src/parser.rs | 91 +++++---- crates/shared/src/lib.rs | 5 +- guide/src/SUMMARY.md | 1 + .../on-rust-exports/getter-and-setter.md | 45 +++++ tests/wasm/getters_and_setters.js | 50 +++++ tests/wasm/getters_and_setters.rs | 125 +++++++++++++ tests/wasm/main.rs | 1 + 11 files changed, 453 insertions(+), 188 deletions(-) create mode 100644 guide/src/reference/attributes/on-rust-exports/getter-and-setter.md create mode 100644 tests/wasm/getters_and_setters.js create mode 100644 tests/wasm/getters_and_setters.rs diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index c54a27be021..78e26af4a20 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -34,19 +34,18 @@ pub struct Program { #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct Export { - /// The struct name, in Rust, this is attached to - pub rust_class: Option, + /// Comments extracted from the rust source. + pub comments: Vec, + /// The rust function + pub function: Function, /// The class name in JS this is attached to pub js_class: Option, + /// The kind (static, named, regular) + pub method_kind: MethodKind, /// The type of `self` (either `self`, `&self`, or `&mut self`) pub method_self: Option, - /// Whether or not this export is flagged as a constructor, returning an - /// instance of the `impl` type - pub is_constructor: bool, - /// The rust function - pub function: Function, - /// Comments extracted from the rust source. - pub comments: Vec, + /// The struct name, in Rust, this is attached to + pub rust_class: Option, /// The name of the rust function/method on the rust side. pub rust_name: Ident, /// Whether or not this function should be flagged as the wasm start @@ -342,28 +341,28 @@ impl ImportKind { } } -impl ImportFunction { +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.function.name + &self.name } /// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name /// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`) pub fn infer_setter_property(&self) -> Result { - let name = self.function.name.to_string(); + let name = self.name.to_string(); // if `#[wasm_bindgen(js_name = "...")]` is used then that explicitly // because it was hand-written anyway. - if self.function.renamed_via_js_name { + if self.renamed_via_js_name { return Ok(name); } // Otherwise we infer names based on the Rust function name. if !name.starts_with("set_") { bail_span!( - syn::token::Pub(self.function.name_span), + syn::token::Pub(self.name_span), "setters must start with `set_`, found: {}", name, ); diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index ae8d4cd396c..66711f1fb7c 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -125,7 +125,7 @@ fn shared_program<'a>( .exports .iter() .map(|a| shared_export(a, intern)) - .collect(), + .collect::, _>>()?, structs: prog .structs .iter() @@ -172,21 +172,23 @@ fn shared_program<'a>( }) } -fn shared_export<'a>(export: &'a ast::Export, intern: &'a Interner) -> Export<'a> { - let (method, consumed) = match export.method_self { - Some(ast::MethodSelf::ByValue) => (true, true), - Some(_) => (true, false), - None => (false, false), +fn shared_export<'a>( + export: &'a ast::Export, + intern: &'a Interner, +) -> Result, Diagnostic> { + let consumed = match export.method_self { + Some(ast::MethodSelf::ByValue) => true, + _ => false, }; - Export { + let method_kind = from_ast_method_kind(&export.function, intern, &export.method_kind)?; + Ok(Export { class: export.js_class.as_ref().map(|s| &**s), - method, + comments: export.comments.iter().map(|s| &**s).collect(), consumed, - is_constructor: export.is_constructor, function: shared_function(&export.function, intern), - comments: export.comments.iter().map(|s| &**s).collect(), + method_kind, start: export.start, - } + }) } fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> { @@ -203,8 +205,8 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi }) .collect::>(); Function { - name: &func.name, arg_names, + name: &func.name, } } @@ -260,30 +262,7 @@ fn shared_import_function<'a>( ) -> Result, Diagnostic> { let method = match &i.kind { ast::ImportFunctionKind::Method { class, kind, .. } => { - let kind = match 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::Regular => OperationKind::Regular, - ast::OperationKind::Getter(g) => { - let g = g.as_ref().map(|g| intern.intern(g)); - OperationKind::Getter(g.unwrap_or_else(|| i.infer_getter_property())) - } - ast::OperationKind::Setter(s) => { - let s = s.as_ref().map(|s| intern.intern(s)); - OperationKind::Setter(match s { - Some(s) => s, - None => intern.intern_str(&i.infer_setter_property()?), - }) - } - ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter, - ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter, - ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter, - }; - MethodKind::Operation(Operation { is_static, kind }) - } - }; + let kind = from_ast_method_kind(&i.function, intern, kind)?; Some(MethodData { class, kind }) } ast::ImportFunctionKind::Normal => None, @@ -510,3 +489,34 @@ macro_rules! encode_api { ); } wasm_bindgen_shared::shared_api!(encode_api); + +fn from_ast_method_kind<'a>( + function: &'a ast::Function, + intern: &'a Interner, + method_kind: &'a ast::MethodKind, +) -> Result, Diagnostic> { + Ok(match method_kind { + ast::MethodKind::Constructor => MethodKind::Constructor, + ast::MethodKind::Operation(ast::Operation { is_static, kind }) => { + let is_static = *is_static; + let kind = match kind { + ast::OperationKind::Getter(g) => { + let g = g.as_ref().map(|g| intern.intern(g)); + OperationKind::Getter(g.unwrap_or_else(|| function.infer_getter_property())) + } + ast::OperationKind::Regular => OperationKind::Regular, + ast::OperationKind::Setter(s) => { + let s = s.as_ref().map(|s| intern.intern(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 }) + } + }) +} diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index de6d17bb359..c75b0e7a8a4 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -103,26 +103,24 @@ impl<'a, 'b> Js2Rust<'a, 'b> { /// Flag this shim as a method call into Rust, so the first Rust argument /// passed should be `this.ptr`. - pub fn method(&mut self, method: bool, consumed: bool) -> &mut Self { - if method { - if self.cx.config.debug { - self.prelude( - "if (this.ptr === 0) { - throw new Error('Attempt to use a moved value'); - }", - ); - } - if consumed { - self.prelude( - "\ - const ptr = this.ptr;\n\ - this.ptr = 0;\n\ - ", - ); - self.rust_arguments.insert(0, "ptr".to_string()); - } else { - self.rust_arguments.insert(0, "this.ptr".to_string()); - } + pub fn method(&mut self, consumed: bool) -> &mut Self { + if self.cx.config.debug { + self.prelude( + "if (this.ptr === 0) { + throw new Error('Attempt to use a moved value'); + }", + ); + } + if consumed { + self.prelude( + "\ + const ptr = this.ptr;\n\ + this.ptr = 0;\n\ + ", + ); + self.rust_arguments.insert(0, "ptr".to_string()); + } else { + self.rust_arguments.insert(0, "this.ptr".to_string()); } self } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 42f6a6e29b4..c00185516ef 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,19 +1,25 @@ -use crate::decode; -use crate::descriptor::{Descriptor, VectorKind}; -use crate::{Bindgen, EncodeInto, OutputMode}; +mod closures; +mod js2rust; +mod rust2js; + +use self::{ + js2rust::{ExportedShim, Js2Rust}, + rust2js::Rust2Js, +}; +use crate::{ + decode, + descriptor::{Descriptor, VectorKind}, + Bindgen, EncodeInto, OutputMode, +}; use failure::{bail, Error, ResultExt}; -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::env; -use std::fs; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + env, fs, +}; use walrus::{MemoryId, Module}; +use wasm_bindgen_shared::struct_function_export_name; use wasm_bindgen_wasm_interpreter::Interpreter; -mod js2rust; -use self::js2rust::{ExportedShim, Js2Rust}; -mod rust2js; -use self::rust2js::Rust2Js; -mod closures; - pub struct Context<'a> { pub globals: String, pub imports: String, @@ -2189,13 +2195,8 @@ impl<'a> Context<'a> { } } - fn require_class_wrap(&mut self, class: &str) { - self.exported_classes - .as_mut() - .expect("classes already written") - .entry(class.to_string()) - .or_insert_with(ExportedClass::default) - .wrap_needed = true; + fn require_class_wrap(&mut self, name: &str) { + require_class(&mut self.exported_classes, name).wrap_needed = true; } fn import_identifier(&mut self, import: Import<'a>) -> String { @@ -2634,63 +2635,75 @@ impl<'a, 'b> SubContext<'a, 'b> { class_name: &'b str, export: &decode::Export, ) -> Result<(), Error> { - let wasm_name = - wasm_bindgen_shared::struct_function_export_name(class_name, &export.function.name); - + let mut fn_name = export.function.name; + let wasm_name = struct_function_export_name(class_name, fn_name); let descriptor = match self.cx.describe(&wasm_name) { None => return Ok(()), Some(d) => d, }; - - let function_name = if export.is_constructor { - "constructor" - } else { - &export.function.name + let docs = |raw_docs| format_doc_comments(&export.comments, Some(raw_docs)); + let method = |class: &mut ExportedClass, docs, fn_name, fn_prfx, js, ts| { + class.contents.push_str(docs); + class.contents.push_str(fn_prfx); + class.contents.push_str(fn_name); + class.contents.push_str(js); + class.contents.push_str("\n"); + class.typescript.push_str(docs); + class.typescript.push_str(" "); // Indentation + class.typescript.push_str(fn_prfx); + class.typescript.push_str(ts); + class.typescript.push_str("\n"); }; - let (js, ts, js_doc) = Js2Rust::new(function_name, self.cx) - .method(export.method, export.consumed) - .constructor(if export.is_constructor { - Some(class_name) - } else { - None - }) - .process(descriptor.unwrap_function(), &export.function.arg_names)? - .finish( - "", - &format!("wasm.{}", wasm_name), - ExportedShim::Named(&wasm_name), - ); - - let class = self - .cx - .exported_classes - .as_mut() - .expect("classes already written") - .entry(class_name.to_string()) - .or_insert(ExportedClass::default()); - - let doc_comments = format_doc_comments(&export.comments, Some(js_doc)); - class.contents.push_str(&doc_comments); - class.typescript.push_str(&doc_comments); - - class.typescript.push_str(" "); // Indentation - - if export.is_constructor { - if class.has_constructor { - bail!("found duplicate constructor `{}`", export.function.name); + let finish_j2r = |mut j2r: Js2Rust| -> Result<(_, _, _), Error> { + Ok(j2r + .process(descriptor.unwrap_function(), &export.function.arg_names)? + .finish( + "", + &format!("wasm.{}", wasm_name), + ExportedShim::Named(&wasm_name), + )) + }; + match &export.method_kind { + decode::MethodKind::Constructor => { + fn_name = "constructor"; + let mut j2r = Js2Rust::new(fn_name, self.cx); + j2r.constructor(Some(class_name)); + let (js, ts, raw_docs) = finish_j2r(j2r)?; + let class = require_class(&mut self.cx.exported_classes, class_name); + if class.has_constructor { + bail!("found duplicate constructor `{}`", export.function.name); + } + class.has_constructor = true; + let docs = docs(raw_docs); + method(class, &docs, fn_name, "", &js, &ts); + Ok(()) + } + decode::MethodKind::Operation(operation) => { + let mut j2r = Js2Rust::new(fn_name, self.cx); + let mut fn_prfx = ""; + if operation.is_static { + fn_prfx = "static "; + } else { + j2r.method(export.consumed); + } + let (js, ts, raw_docs) = finish_j2r(j2r)?; + let class = require_class(&mut self.cx.exported_classes, class_name); + let docs = docs(raw_docs); + match operation.kind { + decode::OperationKind::Getter(getter_name) => { + fn_name = getter_name; + fn_prfx = "get "; + } + decode::OperationKind::Setter(setter_name) => { + fn_name = setter_name; + fn_prfx = "set "; + } + _ => {} + } + method(class, &docs, fn_name, fn_prfx, &js, &ts); + Ok(()) } - class.has_constructor = true; - } else if !export.method { - class.contents.push_str("static "); - class.typescript.push_str("static "); } - - class.contents.push_str(function_name); - class.contents.push_str(&js); - class.contents.push_str("\n"); - class.typescript.push_str(&ts); - class.typescript.push_str("\n"); - Ok(()) } fn generate_import(&mut self, import: &decode::Import<'b>) -> Result<(), Error> { @@ -2764,7 +2777,7 @@ impl<'a, 'b> SubContext<'a, 'b> { // the class if there's a method call. let name = match &import.method { Some(data) => self.determine_import(info, &data.class)?, - None => self.determine_import(info, &import.function.name)?, + None => self.determine_import(info, import.function.name)?, }; // Build up our shim's state, and we'll use that to guide whether we @@ -2872,7 +2885,7 @@ impl<'a, 'b> SubContext<'a, 'b> { let set = { let setter = ExportedShim::Named(&wasm_setter); let mut cx = Js2Rust::new(&field.name, self.cx); - cx.method(true, false) + cx.method(false) .argument(&descriptor, None)? .ret(&Descriptor::Unit)?; ts_dst.push_str(&format!( @@ -2885,7 +2898,7 @@ impl<'a, 'b> SubContext<'a, 'b> { }; let getter = ExportedShim::Named(&wasm_getter); let (get, _ts, js_doc) = Js2Rust::new(&field.name, self.cx) - .method(true, false) + .method(false) .ret(&descriptor)? .finish("", &format!("wasm.{}", wasm_getter), getter); if !dst.ends_with("\n") { @@ -2903,13 +2916,7 @@ impl<'a, 'b> SubContext<'a, 'b> { } } - let class = self - .cx - .exported_classes - .as_mut() - .expect("classes already written") - .entry(struct_.name.to_string()) - .or_insert_with(Default::default); + let class = require_class(&mut self.cx.exported_classes, struct_.name); class.comments = format_doc_comments(&struct_.comments, None); class.contents.push_str(&dst); class.contents.push_str("\n"); @@ -3183,6 +3190,17 @@ fn format_doc_comments(comments: &[&str], js_doc_comments: Option) -> St format!("/**\n{}{}*/\n", body, doc) } +fn require_class<'a>( + exported_classes: &'a mut Option>, + name: &str, +) -> &'a mut ExportedClass { + exported_classes + .as_mut() + .expect("classes already written") + .entry(name.to_string()) + .or_insert_with(ExportedClass::default) +} + #[test] fn test_generate_identifier() { let mut used_names: HashMap = HashMap::new(); diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 165e76f4703..d00802ce80e 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -384,22 +384,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a ast::ImportModule)> for syn::ForeignIte wasm.ret.clone() }; - let mut operation_kind = ast::OperationKind::Regular; - if let Some(g) = opts.getter() { - operation_kind = ast::OperationKind::Getter(g.clone()); - } - if let Some(s) = opts.setter() { - operation_kind = ast::OperationKind::Setter(s.clone()); - } - if opts.indexing_getter().is_some() { - operation_kind = ast::OperationKind::IndexingGetter; - } - if opts.indexing_setter().is_some() { - operation_kind = ast::OperationKind::IndexingSetter; - } - if opts.indexing_deleter().is_some() { - operation_kind = ast::OperationKind::IndexingDeleter; - } + let operation_kind = operation_kind(&opts)?; let kind = if opts.method().is_some() { let class = wasm.arguments.get(0).ok_or_else(|| { @@ -699,18 +684,21 @@ fn function_from_decl( syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)), }; - let js_name = opts.js_name(); + let (name, name_span, renamed_via_js_name) = + if let Some((js_name, js_name_span)) = opts.js_name() { + (js_name.to_string(), js_name_span, true) + } else { + (decl_name.to_string(), decl_name.span(), false) + }; Ok(( ast::Function { - name: js_name - .map(|s| s.0.to_string()) - .unwrap_or(decl_name.to_string()), - name_span: js_name.map(|s| s.1).unwrap_or(decl_name.span()), - renamed_via_js_name: js_name.is_some(), arguments, + name_span, + name, + renamed_via_js_name, ret, - rust_vis: vis, rust_attrs: attrs, + rust_vis: vis, }, method_self, )) @@ -755,15 +743,21 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { bail_span!(&f.decl.inputs, "the start function cannot have arguments",); } } + let method_kind = ast::MethodKind::Operation(ast::Operation { + is_static: true, + kind: operation_kind(&opts)?, + }); + let rust_name = f.ident.clone(); + let start = opts.start().is_some(); program.exports.push(ast::Export { - rust_class: None, - js_class: None, - method_self: None, - is_constructor: false, comments, - rust_name: f.ident.clone(), - start: opts.start().is_some(), function: f.convert(opts)?, + js_class: None, + method_kind, + method_self: None, + rust_class: None, + rust_name, + start, }); } syn::Item::Struct(mut s) => { @@ -942,7 +936,6 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod { let opts = BindgenAttrs::find(&mut self.attrs)?; let comments = extract_doc_comments(&self.attrs); - let is_constructor = opts.constructor().is_some(); let (function, method_self) = function_from_decl( &self.sig.ident, &opts, @@ -952,16 +945,22 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod { true, Some(class), )?; - + let method_kind = if opts.constructor().is_some() { + ast::MethodKind::Constructor + } else { + let is_static = method_self.is_none(); + let kind = operation_kind(&opts)?; + ast::MethodKind::Operation(ast::Operation { is_static, kind }) + }; program.exports.push(ast::Export { - rust_class: Some(class.clone()), + comments, + function, js_class: Some(js_class.to_string()), + method_kind, method_self, - is_constructor, - function, - comments, - start: false, + rust_class: Some(class.clone()), rust_name: self.sig.ident.clone(), + start: false, }); opts.check_used()?; Ok(()) @@ -1294,3 +1293,23 @@ pub fn assert_all_attrs_checked() { assert_eq!(state.parsed.get(), state.checks.get()); }) } + +fn operation_kind(opts: &BindgenAttrs) -> Result { + let mut operation_kind = ast::OperationKind::Regular; + if let Some(g) = opts.getter() { + operation_kind = ast::OperationKind::Getter(g.clone()); + } + if let Some(s) = opts.setter() { + operation_kind = ast::OperationKind::Setter(s.clone()); + } + if opts.indexing_getter().is_some() { + operation_kind = ast::OperationKind::IndexingGetter; + } + if opts.indexing_setter().is_some() { + operation_kind = ast::OperationKind::IndexingSetter; + } + if opts.indexing_deleter().is_some() { + operation_kind = ast::OperationKind::IndexingDeleter; + } + Ok(operation_kind) +} diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 6bbfefb6a56..845b384f90d 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -88,11 +88,10 @@ macro_rules! shared_api { struct Export<'a> { class: Option<&'a str>, - method: bool, + comments: Vec<&'a str>, consumed: bool, - is_constructor: bool, function: Function<'a>, - comments: Vec<&'a str>, + method_kind: MethodKind<'a>, start: bool, } diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 11c3d18d4ce..fe32f7c4695 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -78,6 +78,7 @@ - [`skip`](./reference/attributes/on-rust-exports/skip.md) - [`start`](./reference/attributes/on-rust-exports/start.md) - [`typescript_custom_section`](./reference/attributes/on-rust-exports/typescript_custom_section.md) + - [`getter` and `setter`](./reference/attributes/on-rust-exports/getter-and-setter.md) - [`web-sys`](./web-sys/index.md) - [Using `web-sys`](./web-sys/using-web-sys.md) diff --git a/guide/src/reference/attributes/on-rust-exports/getter-and-setter.md b/guide/src/reference/attributes/on-rust-exports/getter-and-setter.md new file mode 100644 index 00000000000..40a0dae7177 --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/getter-and-setter.md @@ -0,0 +1,45 @@ +# `getter` and `setter` + +It is also possible to interact with `Rust` types either by using fields accessors. For example, the following: + +```rust +#[wasm_bindgen] +extern "C" { + fn check_modify_and_return_baz_in_js_fields(baz: Baz) -> Baz; +} + +#[wasm_bindgen_test] +fn create_and_check_baz_in_rust() { + let baz = check_modify_and_return_baz_in_js_fields(Baz { field: 123 }); + assert_eq!(baz.field.unwrap(), 456); +} + +#[wasm_bindgen] +#[derive(Default)] +pub struct Baz { + field: i32, +} + +#[wasm_bindgen] +impl Baz { + #[wasm_bindgen(getter)] + pub fn field(&self) -> i32 { + self.field + } + + #[wasm_bindgen(setter = field)] + pub fn set_field(&mut self, field: i32) { + self.field = field; + } +} +``` + +Can be combined in `JavaScript` like in this snippet: + +```js +check_modify_and_return_baz_in_js_fields = (baz) => { + console.log(baz.field, 123); + baz.field = 456; + return baz; +}; +``` \ No newline at end of file diff --git a/tests/wasm/getters_and_setters.js b/tests/wasm/getters_and_setters.js new file mode 100644 index 00000000000..5f9becf4894 --- /dev/null +++ b/tests/wasm/getters_and_setters.js @@ -0,0 +1,50 @@ +const wasm = require('wasm-bindgen-test.js'); +const assert = require('assert'); + +exports._1_js = (rules) => { + assert.equal(rules.field, 1); + rules.field *= 2; + return rules; +} + +exports._2_js = (rules) => { + let value = rules.no_js_name__no_getter_with_name__no_getter_without_name(); + assert.equal(value, 2); + rules.set_no_js_name__no_setter_with_name__no_setter_without_name(value * 2); + return rules; +} + +exports._3_js = (rules) => { + let value = rules.no_js_name__no_getter_with_name__getter_without_name; + assert.equal(value, 3); + rules.no_js_name__no_setter_with_name__setter_without_name = value * 2; + return rules; +} + +exports._4_js = (rules) => { + let value = rules.new_no_js_name__getter_with_name__getter_without_name; + assert.equal(value, 4); + rules.new_no_js_name__setter_with_name__setter_without_name = value * 2; + return rules; +} + +exports._5_js = (rules) => { + let value = rules.new_js_name__no_getter_with_name__no_getter_without_name(); + assert.equal(value, 5); + rules.new_js_name__no_setter_with_name__no_setter_without_name(value * 2); + return rules; +} + +exports._6_js = (rules) => { + let value = rules.new_js_name__no_getter_with_name__getter_without_name; + assert.equal(value, 6); + rules.new_js_name__no_setter_with_name__setter_without_name = value * 2; + return rules; +} + +exports._7_js = (rules) => { + let value = rules.new_js_name__getter_with_name__no_getter_without_name_for_field; + assert.equal(value, 7); + rules.new_js_name__setter_with_name__no_setter_without_name_for_field = value * 2; + return rules; +} diff --git a/tests/wasm/getters_and_setters.rs b/tests/wasm/getters_and_setters.rs new file mode 100644 index 00000000000..ffb8e00adcc --- /dev/null +++ b/tests/wasm/getters_and_setters.rs @@ -0,0 +1,125 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; + +#[wasm_bindgen(module = "tests/wasm/getters_and_setters.js")] +extern "C" { + fn _1_js(rules: Rules) -> Rules; + fn _2_js(rules: Rules) -> Rules; + fn _3_js(rules: Rules) -> Rules; + fn _4_js(rules: Rules) -> Rules; + fn _5_js(rules: Rules) -> Rules; + fn _6_js(rules: Rules) -> Rules; + fn _7_js(rules: Rules) -> Rules; +} + +// Each getter/setter combination is derived +// from https://github.com/rustwasm/wasm-bindgen/pull/1440#issuecomment-487113564 +#[wasm_bindgen] +pub struct Rules { + pub field: i32, +} + +#[wasm_bindgen] +impl Rules { + #[wasm_bindgen] + pub fn no_js_name__no_getter_with_name__no_getter_without_name(&self) -> i32 { + self.field + } + #[wasm_bindgen] + pub fn set_no_js_name__no_setter_with_name__no_setter_without_name(&mut self, field: i32) { + self.field = field; + } + + #[wasm_bindgen(getter)] + pub fn no_js_name__no_getter_with_name__getter_without_name(&self) -> i32 { + self.field + } + #[wasm_bindgen(setter)] + pub fn set_no_js_name__no_setter_with_name__setter_without_name(&mut self, field: i32) { + self.field = field; + } + + #[wasm_bindgen(getter = new_no_js_name__getter_with_name__getter_without_name)] + pub fn no_js_name__getter_with_name__getter_without_name(&self) -> i32 { + self.field + } + #[wasm_bindgen(setter = new_no_js_name__setter_with_name__setter_without_name)] + pub fn set_no_js_name__setter_with_name__setter_without_name(&mut self, field: i32) { + self.field = field; + } + + #[wasm_bindgen(js_name = new_js_name__no_getter_with_name__no_getter_without_name)] + pub fn js_name__no_getter_with_name__no_getter_without_name(&self) -> i32 { + self.field + } + #[wasm_bindgen(js_name = new_js_name__no_setter_with_name__no_setter_without_name)] + pub fn set_js_name__no_setter_with_name__no_setter_without_name(&mut self, field: i32) { + self.field = field; + } + + #[wasm_bindgen(getter, js_name = new_js_name__no_getter_with_name__getter_without_name)] + pub fn js_name__no_getter_with_name__getter_without_name(&self) -> i32 { + self.field + } + #[wasm_bindgen(js_name = new_js_name__no_setter_with_name__setter_without_name, setter)] + pub fn set_js_name__no_setter_with_name__setter_without_name(&mut self, field: i32) { + self.field = field; + } + + #[wasm_bindgen( + getter = new_js_name__getter_with_name__no_getter_without_name_for_field, + js_name = new_js_name__getter_with_name__no_getter_without_name_for_method + )] + pub fn js_name__getter_with_name__no_getter_without_name(&self) -> i32 { + self.field + } + #[wasm_bindgen( + js_name = new_js_name__setter_with_name__no_setter_without_name_for_method, + setter = new_js_name__setter_with_name__no_setter_without_name_for_field + )] + pub fn set_js_name__setter_with_name__no_setter_without_name_for_field(&mut self, field: i32) { + self.field = field; + } +} + +#[wasm_bindgen_test] +fn _1_rust() { + let rules = _1_js(Rules { field: 1 }); + assert_eq!(rules.field, 2); +} + +#[wasm_bindgen_test] +fn _2_rust() { + let rules = _2_js(Rules { field: 2 }); + assert_eq!(rules.field, 4); +} + +#[wasm_bindgen_test] +fn _3_rust() { + let rules = _3_js(Rules { field: 3 }); + assert_eq!(rules.field, 6); +} + +#[wasm_bindgen_test] +fn _4_rust() { + let rules = _4_js(Rules { field: 4 }); + assert_eq!(rules.field, 8); +} + +#[wasm_bindgen_test] +fn _5_rust() { + let rules = _5_js(Rules { field: 5 }); + assert_eq!(rules.field, 10); +} + +#[wasm_bindgen_test] +fn _6_rust() { + let rules = _6_js(Rules { field: 6 }); + assert_eq!(rules.field, 12); +} + +#[wasm_bindgen_test] +fn _7_rust() { + let rules = _7_js(Rules { field: 7 }); + assert_eq!(rules.field, 14); +} diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index fff18228416..65f47f861be 100644 --- a/tests/wasm/main.rs +++ b/tests/wasm/main.rs @@ -23,6 +23,7 @@ pub mod duplicates; pub mod enums; #[path = "final.rs"] pub mod final_; +pub mod getters_and_setters; pub mod import_class; pub mod imports; pub mod js_objects; From c70ed36be43938184b6fb0698509aa0a9c8d7731 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 30 Apr 2019 10:02:11 -0700 Subject: [PATCH 3/4] Add some tests which do computation in getters/setters --- tests/wasm/getters_and_setters.js | 8 +++++++ tests/wasm/getters_and_setters.rs | 38 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/tests/wasm/getters_and_setters.js b/tests/wasm/getters_and_setters.js index 5f9becf4894..def62b39fe5 100644 --- a/tests/wasm/getters_and_setters.js +++ b/tests/wasm/getters_and_setters.js @@ -48,3 +48,11 @@ exports._7_js = (rules) => { rules.new_js_name__setter_with_name__no_setter_without_name_for_field = value * 2; return rules; } + +exports.test_getter_compute = x => { + assert.equal(x.foo, 3) +}; + +exports.test_setter_compute = x => { + x.foo = 97; +}; diff --git a/tests/wasm/getters_and_setters.rs b/tests/wasm/getters_and_setters.rs index ffb8e00adcc..cb693e11653 100644 --- a/tests/wasm/getters_and_setters.rs +++ b/tests/wasm/getters_and_setters.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; +use std::cell::Cell; use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; @@ -10,6 +12,9 @@ extern "C" { fn _5_js(rules: Rules) -> Rules; fn _6_js(rules: Rules) -> Rules; fn _7_js(rules: Rules) -> Rules; + + fn test_getter_compute(x: GetterCompute); + fn test_setter_compute(x: SetterCompute); } // Each getter/setter combination is derived @@ -20,6 +25,7 @@ pub struct Rules { } #[wasm_bindgen] +#[allow(non_snake_case)] impl Rules { #[wasm_bindgen] pub fn no_js_name__no_getter_with_name__no_getter_without_name(&self) -> i32 { @@ -123,3 +129,35 @@ fn _7_rust() { let rules = _7_js(Rules { field: 7 }); assert_eq!(rules.field, 14); } + +#[wasm_bindgen] +struct GetterCompute; + +#[wasm_bindgen] +impl GetterCompute { + #[wasm_bindgen(getter)] + pub fn foo(&self) -> u32 { 3 } +} + +#[wasm_bindgen_test] +fn getter_compute() { + test_getter_compute(GetterCompute); +} + +#[wasm_bindgen] +struct SetterCompute(Rc>); + +#[wasm_bindgen] +impl SetterCompute { + #[wasm_bindgen(setter)] + pub fn set_foo(&self, x: u32) { + self.0.set(x + 3); + } +} + +#[wasm_bindgen_test] +fn setter_compute() { + let r = Rc::new(Cell::new(3)); + test_setter_compute(SetterCompute(r.clone())); + assert_eq!(r.get(), 100); +} From 3031c15bf6bcdcec4603738d682daa8e57b6c915 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 30 Apr 2019 10:06:38 -0700 Subject: [PATCH 4/4] Rewrite docs for getters/setters --- .../on-rust-exports/getter-and-setter.md | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/guide/src/reference/attributes/on-rust-exports/getter-and-setter.md b/guide/src/reference/attributes/on-rust-exports/getter-and-setter.md index 40a0dae7177..79da30a0ec8 100644 --- a/guide/src/reference/attributes/on-rust-exports/getter-and-setter.md +++ b/guide/src/reference/attributes/on-rust-exports/getter-and-setter.md @@ -1,33 +1,27 @@ # `getter` and `setter` -It is also possible to interact with `Rust` types either by using fields accessors. For example, the following: +The `getter` and `setter` attributes can be used in Rust `impl` blocks to define +properties in JS that act like getters and setters of a field. For example: ```rust #[wasm_bindgen] -extern "C" { - fn check_modify_and_return_baz_in_js_fields(baz: Baz) -> Baz; -} - -#[wasm_bindgen_test] -fn create_and_check_baz_in_rust() { - let baz = check_modify_and_return_baz_in_js_fields(Baz { field: 123 }); - assert_eq!(baz.field.unwrap(), 456); -} - -#[wasm_bindgen] -#[derive(Default)] pub struct Baz { field: i32, } #[wasm_bindgen] impl Baz { + #[wasm_bindgen(constructor)] + pub fn new(field: i32) -> Baz { + Baz { field } + } + #[wasm_bindgen(getter)] pub fn field(&self) -> i32 { self.field } - #[wasm_bindgen(setter = field)] + #[wasm_bindgen(setter)] pub fn set_field(&mut self, field: i32) { self.field = field; } @@ -37,9 +31,34 @@ impl Baz { Can be combined in `JavaScript` like in this snippet: ```js -check_modify_and_return_baz_in_js_fields = (baz) => { - console.log(baz.field, 123); - baz.field = 456; - return baz; -}; -``` \ No newline at end of file +const obj = new Baz(3); +assert.equal(obj.field, 3); +obj.field = 4; +assert.equal(obj.field, 4); +``` + +You can also configure the name of the property that is exported in JS like so: + +```rust +#[wasm_bindgen] +impl Baz { + #[wasm_bindgen(getter = anotherName)] + pub fn field(&self) -> i32 { + self.field + } + + #[wasm_bindgen(setter = anotherName)] + pub fn set_field(&mut self, field: i32) { + self.field = field; + } +} +``` + +Getters are expected to take no arguments other than `&self` and return the +field's type. Setters are expected to take one argument other than `&mut self` +(or `&self`) and return no values. + +The name for a `getter` is by default inferred from the function name it's +attached to. The default name for a `setter` is the function's name minus the +`set_` prefix, and if `set_` isn't a prefix of the function it's an error to not +provide the name explicitly.