Getters/Setters for fields

This commit is contained in:
Caio
2019-04-30 10:26:03 -03:00
parent 0c4cdefd07
commit 470eea9fb0
11 changed files with 460 additions and 195 deletions

View File

@ -34,19 +34,18 @@ pub struct Program {
#[cfg_attr(feature = "extra-traits", derive(Debug))] #[cfg_attr(feature = "extra-traits", derive(Debug))]
#[derive(Clone)] #[derive(Clone)]
pub struct Export { pub struct Export {
/// The struct name, in Rust, this is attached to
pub rust_class: Option<Ident>,
/// The class name in JS this is attached to
pub js_class: Option<String>,
/// The type of `self` (either `self`, `&self`, or `&mut self`)
pub method_self: Option<MethodSelf>,
/// 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. /// Comments extracted from the rust source.
pub comments: Vec<String>, 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 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. /// The name of the rust function/method on the rust side.
pub rust_name: Ident, pub rust_name: Ident,
/// Whether or not this function should be flagged as the wasm start /// 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 /// 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`) /// javascript (in this case `xxx`, so you can write `val = obj.xxx`)
pub fn infer_getter_property(&self) -> &str { 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 /// 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`) /// 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> { pub fn infer_setter_property(&self) -> Result<String, Diagnostic> {
let name = self.function.name.to_string(); let name = self.name.to_string();
// if `#[wasm_bindgen(js_name = "...")]` is used then that explicitly // if `#[wasm_bindgen(js_name = "...")]` is used then that explicitly
// because it was hand-written anyway. // because it was hand-written anyway.
if self.function.renamed_via_js_name { if self.renamed_via_js_name {
return Ok(name); return Ok(name);
} }
// Otherwise we infer names based on the Rust function name. // Otherwise we infer names based on the Rust function name.
if !name.starts_with("set_") { if !name.starts_with("set_") {
bail_span!( bail_span!(
syn::token::Pub(self.function.name_span), syn::token::Pub(self.name_span),
"setters must start with `set_`, found: {}", "setters must start with `set_`, found: {}",
name, name,
); );

View File

@ -125,7 +125,7 @@ fn shared_program<'a>(
.exports .exports
.iter() .iter()
.map(|a| shared_export(a, intern)) .map(|a| shared_export(a, intern))
.collect(), .collect::<Result<Vec<_>, _>>()?,
structs: prog structs: prog
.structs .structs
.iter() .iter()
@ -172,21 +172,23 @@ fn shared_program<'a>(
}) })
} }
fn shared_export<'a>(export: &'a ast::Export, intern: &'a Interner) -> Export<'a> { fn shared_export<'a>(
let (method, consumed) = match export.method_self { export: &'a ast::Export,
Some(ast::MethodSelf::ByValue) => (true, true), intern: &'a Interner,
Some(_) => (true, false), ) -> Result<Export<'a>, Diagnostic> {
None => (false, false), 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), class: export.js_class.as_ref().map(|s| &**s),
method,
consumed,
is_constructor: export.is_constructor,
function: shared_function(&export.function, intern),
comments: export.comments.iter().map(|s| &**s).collect(), comments: export.comments.iter().map(|s| &**s).collect(),
consumed,
function: shared_function(&export.function, intern),
method_kind,
start: export.start, start: export.start,
} })
} }
fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> { 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::<Vec<_>>(); .collect::<Vec<_>>();
Function { Function {
name: &func.name,
arg_names, arg_names,
name: &func.name,
} }
} }
@ -260,30 +262,7 @@ fn shared_import_function<'a>(
) -> Result<ImportFunction<'a>, Diagnostic> { ) -> Result<ImportFunction<'a>, Diagnostic> {
let method = match &i.kind { let method = match &i.kind {
ast::ImportFunctionKind::Method { class, kind, .. } => { ast::ImportFunctionKind::Method { class, kind, .. } => {
let kind = match kind { let kind = from_ast_method_kind(&i.function, intern, 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 })
}
};
Some(MethodData { class, kind }) Some(MethodData { class, kind })
} }
ast::ImportFunctionKind::Normal => None, ast::ImportFunctionKind::Normal => None,
@ -510,3 +489,34 @@ macro_rules! encode_api {
); );
} }
wasm_bindgen_shared::shared_api!(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<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(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 })
}
})
}

View File

@ -103,8 +103,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
/// Flag this shim as a method call into Rust, so the first Rust argument /// Flag this shim as a method call into Rust, so the first Rust argument
/// passed should be `this.ptr`. /// passed should be `this.ptr`.
pub fn method(&mut self, method: bool, consumed: bool) -> &mut Self { pub fn method(&mut self, consumed: bool) -> &mut Self {
if method {
if self.cx.config.debug { if self.cx.config.debug {
self.prelude( self.prelude(
"if (this.ptr === 0) { "if (this.ptr === 0) {
@ -123,7 +122,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
} else { } else {
self.rust_arguments.insert(0, "this.ptr".to_string()); self.rust_arguments.insert(0, "this.ptr".to_string());
} }
}
self self
} }

View File

@ -1,18 +1,24 @@
use crate::decode;
use crate::descriptor::{Descriptor, VectorKind};
use crate::{Bindgen, EncodeInto, OutputMode};
use failure::{bail, Error, ResultExt};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::env;
use std::fs;
use walrus::{MemoryId, Module};
use wasm_bindgen_wasm_interpreter::Interpreter;
mod js2rust;
use self::js2rust::{ExportedShim, Js2Rust};
mod rust2js;
use self::rust2js::Rust2Js;
mod closures; 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},
env, fs,
};
use walrus::{MemoryId, Module};
use wasm_bindgen_shared::struct_function_export_name;
use wasm_bindgen_wasm_interpreter::Interpreter;
pub struct Context<'a> { pub struct Context<'a> {
pub globals: String, pub globals: String,
@ -2189,13 +2195,8 @@ impl<'a> Context<'a> {
} }
} }
fn require_class_wrap(&mut self, class: &str) { fn require_class_wrap(&mut self, name: &str) {
self.exported_classes require_class(&mut self.exported_classes, name).wrap_needed = true;
.as_mut()
.expect("classes already written")
.entry(class.to_string())
.or_insert_with(ExportedClass::default)
.wrap_needed = true;
} }
fn import_identifier(&mut self, import: Import<'a>) -> String { fn import_identifier(&mut self, import: Import<'a>) -> String {
@ -2634,64 +2635,76 @@ impl<'a, 'b> SubContext<'a, 'b> {
class_name: &'b str, class_name: &'b str,
export: &decode::Export, export: &decode::Export,
) -> Result<(), Error> { ) -> Result<(), Error> {
let wasm_name = let mut fn_name = export.function.name;
wasm_bindgen_shared::struct_function_export_name(class_name, &export.function.name); let wasm_name = struct_function_export_name(class_name, fn_name);
let descriptor = match self.cx.describe(&wasm_name) { let descriptor = match self.cx.describe(&wasm_name) {
None => return Ok(()), None => return Ok(()),
Some(d) => d, Some(d) => d,
}; };
let docs = |raw_docs| format_doc_comments(&export.comments, Some(raw_docs));
let function_name = if export.is_constructor { let method = |class: &mut ExportedClass, docs, fn_name, fn_prfx, js, ts| {
"constructor" class.contents.push_str(docs);
} else { class.contents.push_str(fn_prfx);
&export.function.name 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) let finish_j2r = |mut j2r: Js2Rust| -> Result<(_, _, _), Error> {
.method(export.method, export.consumed) Ok(j2r
.constructor(if export.is_constructor {
Some(class_name)
} else {
None
})
.process(descriptor.unwrap_function(), &export.function.arg_names)? .process(descriptor.unwrap_function(), &export.function.arg_names)?
.finish( .finish(
"", "",
&format!("wasm.{}", wasm_name), &format!("wasm.{}", wasm_name),
ExportedShim::Named(&wasm_name), ExportedShim::Named(&wasm_name),
); ))
};
let class = self match &export.method_kind {
.cx decode::MethodKind::Constructor => {
.exported_classes fn_name = "constructor";
.as_mut() let mut j2r = Js2Rust::new(fn_name, self.cx);
.expect("classes already written") j2r.constructor(Some(class_name));
.entry(class_name.to_string()) let (js, ts, raw_docs) = finish_j2r(j2r)?;
.or_insert(ExportedClass::default()); let class = require_class(&mut self.cx.exported_classes, class_name);
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 { if class.has_constructor {
bail!("found duplicate constructor `{}`", export.function.name); bail!("found duplicate constructor `{}`", export.function.name);
} }
class.has_constructor = true; class.has_constructor = true;
} else if !export.method { let docs = docs(raw_docs);
class.contents.push_str("static "); method(class, &docs, fn_name, "", &js, &ts);
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(()) 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(())
}
}
}
fn generate_import(&mut self, import: &decode::Import<'b>) -> Result<(), Error> { fn generate_import(&mut self, import: &decode::Import<'b>) -> Result<(), Error> {
match import.kind { match import.kind {
@ -2764,7 +2777,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
// the class if there's a method call. // the class if there's a method call.
let name = match &import.method { let name = match &import.method {
Some(data) => self.determine_import(info, &data.class)?, 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 // 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 set = {
let setter = ExportedShim::Named(&wasm_setter); let setter = ExportedShim::Named(&wasm_setter);
let mut cx = Js2Rust::new(&field.name, self.cx); let mut cx = Js2Rust::new(&field.name, self.cx);
cx.method(true, false) cx.method(false)
.argument(&descriptor, None)? .argument(&descriptor, None)?
.ret(&Descriptor::Unit)?; .ret(&Descriptor::Unit)?;
ts_dst.push_str(&format!( ts_dst.push_str(&format!(
@ -2885,7 +2898,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
}; };
let getter = ExportedShim::Named(&wasm_getter); let getter = ExportedShim::Named(&wasm_getter);
let (get, _ts, js_doc) = Js2Rust::new(&field.name, self.cx) let (get, _ts, js_doc) = Js2Rust::new(&field.name, self.cx)
.method(true, false) .method(false)
.ret(&descriptor)? .ret(&descriptor)?
.finish("", &format!("wasm.{}", wasm_getter), getter); .finish("", &format!("wasm.{}", wasm_getter), getter);
if !dst.ends_with("\n") { if !dst.ends_with("\n") {
@ -2903,13 +2916,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
} }
} }
let class = self let class = require_class(&mut self.cx.exported_classes, struct_.name);
.cx
.exported_classes
.as_mut()
.expect("classes already written")
.entry(struct_.name.to_string())
.or_insert_with(Default::default);
class.comments = format_doc_comments(&struct_.comments, None); class.comments = format_doc_comments(&struct_.comments, None);
class.contents.push_str(&dst); class.contents.push_str(&dst);
class.contents.push_str("\n"); class.contents.push_str("\n");
@ -3183,6 +3190,17 @@ fn format_doc_comments(comments: &[&str], js_doc_comments: Option<String>) -> St
format!("/**\n{}{}*/\n", body, doc) format!("/**\n{}{}*/\n", body, doc)
} }
fn require_class<'a>(
exported_classes: &'a mut Option<BTreeMap<String, ExportedClass>>,
name: &str,
) -> &'a mut ExportedClass {
exported_classes
.as_mut()
.expect("classes already written")
.entry(name.to_string())
.or_insert_with(ExportedClass::default)
}
#[test] #[test]
fn test_generate_identifier() { fn test_generate_identifier() {
let mut used_names: HashMap<String, usize> = HashMap::new(); let mut used_names: HashMap<String, usize> = HashMap::new();

View File

@ -384,22 +384,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a ast::ImportModule)> for syn::ForeignIte
wasm.ret.clone() wasm.ret.clone()
}; };
let mut operation_kind = ast::OperationKind::Regular; let operation_kind = operation_kind(&opts)?;
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 kind = if opts.method().is_some() { let kind = if opts.method().is_some() {
let class = wasm.arguments.get(0).ok_or_else(|| { 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)), 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(( Ok((
ast::Function { 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, arguments,
name_span,
name,
renamed_via_js_name,
ret, ret,
rust_vis: vis,
rust_attrs: attrs, rust_attrs: attrs,
rust_vis: vis,
}, },
method_self, method_self,
)) ))
@ -755,15 +743,21 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
bail_span!(&f.decl.inputs, "the start function cannot have arguments",); 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 { program.exports.push(ast::Export {
rust_class: None,
js_class: None,
method_self: None,
is_constructor: false,
comments, comments,
rust_name: f.ident.clone(),
start: opts.start().is_some(),
function: f.convert(opts)?, function: f.convert(opts)?,
js_class: None,
method_kind,
method_self: None,
rust_class: None,
rust_name,
start,
}); });
} }
syn::Item::Struct(mut s) => { 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 opts = BindgenAttrs::find(&mut self.attrs)?;
let comments = extract_doc_comments(&self.attrs); let comments = extract_doc_comments(&self.attrs);
let is_constructor = opts.constructor().is_some();
let (function, method_self) = function_from_decl( let (function, method_self) = function_from_decl(
&self.sig.ident, &self.sig.ident,
&opts, &opts,
@ -952,16 +945,22 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod {
true, true,
Some(class), 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 { program.exports.push(ast::Export {
rust_class: Some(class.clone()),
js_class: Some(js_class.to_string()),
method_self,
is_constructor,
function,
comments, comments,
start: false, function,
js_class: Some(js_class.to_string()),
method_kind,
method_self,
rust_class: Some(class.clone()),
rust_name: self.sig.ident.clone(), rust_name: self.sig.ident.clone(),
start: false,
}); });
opts.check_used()?; opts.check_used()?;
Ok(()) Ok(())
@ -1294,3 +1293,23 @@ pub fn assert_all_attrs_checked() {
assert_eq!(state.parsed.get(), state.checks.get()); assert_eq!(state.parsed.get(), state.checks.get());
}) })
} }
fn operation_kind(opts: &BindgenAttrs) -> Result<ast::OperationKind, Diagnostic> {
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)
}

View File

@ -88,11 +88,10 @@ macro_rules! shared_api {
struct Export<'a> { struct Export<'a> {
class: Option<&'a str>, class: Option<&'a str>,
method: bool,
consumed: bool,
is_constructor: bool,
function: Function<'a>,
comments: Vec<&'a str>, comments: Vec<&'a str>,
consumed: bool,
function: Function<'a>,
method_kind: MethodKind<'a>,
start: bool, start: bool,
} }

View File

@ -78,6 +78,7 @@
- [`skip`](./reference/attributes/on-rust-exports/skip.md) - [`skip`](./reference/attributes/on-rust-exports/skip.md)
- [`start`](./reference/attributes/on-rust-exports/start.md) - [`start`](./reference/attributes/on-rust-exports/start.md)
- [`typescript_custom_section`](./reference/attributes/on-rust-exports/typescript_custom_section.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) - [`web-sys`](./web-sys/index.md)
- [Using `web-sys`](./web-sys/using-web-sys.md) - [Using `web-sys`](./web-sys/using-web-sys.md)

View File

@ -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;
};
```

View File

@ -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;
}

View File

@ -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);
}

View File

@ -23,6 +23,7 @@ pub mod duplicates;
pub mod enums; pub mod enums;
#[path = "final.rs"] #[path = "final.rs"]
pub mod final_; pub mod final_;
pub mod getters_and_setters;
pub mod import_class; pub mod import_class;
pub mod imports; pub mod imports;
pub mod js_objects; pub mod js_objects;