mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 02:00:51 +00:00
Merge pull request #1440 from c410-f3r/getters
Getters/Setters for fields
This commit is contained in:
commit
578d59ebc0
@ -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<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.
|
||||
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.
|
||||
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<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
|
||||
// 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,
|
||||
);
|
||||
|
@ -125,7 +125,7 @@ fn shared_program<'a>(
|
||||
.exports
|
||||
.iter()
|
||||
.map(|a| shared_export(a, intern))
|
||||
.collect(),
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
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<Export<'a>, 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,
|
||||
consumed,
|
||||
is_constructor: export.is_constructor,
|
||||
function: shared_function(&export.function, intern),
|
||||
comments: export.comments.iter().map(|s| &**s).collect(),
|
||||
consumed,
|
||||
function: shared_function(&export.function, intern),
|
||||
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::<Vec<_>>();
|
||||
Function {
|
||||
name: &func.name,
|
||||
arg_names,
|
||||
name: &func.name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,30 +262,7 @@ fn shared_import_function<'a>(
|
||||
) -> Result<ImportFunction<'a>, 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<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 })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 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 globals: 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<String>) -> St
|
||||
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]
|
||||
fn test_generate_identifier() {
|
||||
let mut used_names: HashMap<String, usize> = HashMap::new();
|
||||
|
@ -92,15 +92,15 @@ fn from_char_code() {
|
||||
"½+¾="
|
||||
);
|
||||
|
||||
let codes_u16: Vec<u16> = codes.into_iter().map(|code| {
|
||||
assert!(code <= u32::from(u16::max_value()));
|
||||
code as u16
|
||||
}).collect();
|
||||
let codes_u16: Vec<u16> = 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());
|
||||
|
@ -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")]
|
||||
|
@ -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<BindgenAttrs>, &'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()),
|
||||
js_class: Some(js_class.to_string()),
|
||||
method_self,
|
||||
is_constructor,
|
||||
function,
|
||||
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(),
|
||||
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<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)
|
||||
}
|
||||
|
@ -88,11 +88,10 @@ macro_rules! shared_api {
|
||||
|
||||
struct Export<'a> {
|
||||
class: Option<&'a str>,
|
||||
method: bool,
|
||||
consumed: bool,
|
||||
is_constructor: bool,
|
||||
function: Function<'a>,
|
||||
comments: Vec<&'a str>,
|
||||
consumed: bool,
|
||||
function: Function<'a>,
|
||||
method_kind: MethodKind<'a>,
|
||||
start: bool,
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,64 @@
|
||||
# `getter` and `setter`
|
||||
|
||||
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]
|
||||
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)]
|
||||
pub fn set_field(&mut self, field: i32) {
|
||||
self.field = field;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Can be combined in `JavaScript` like in this snippet:
|
||||
|
||||
```js
|
||||
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.
|
58
tests/wasm/getters_and_setters.js
Normal file
58
tests/wasm/getters_and_setters.js
Normal file
@ -0,0 +1,58 @@
|
||||
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;
|
||||
}
|
||||
|
||||
exports.test_getter_compute = x => {
|
||||
assert.equal(x.foo, 3)
|
||||
};
|
||||
|
||||
exports.test_setter_compute = x => {
|
||||
x.foo = 97;
|
||||
};
|
163
tests/wasm/getters_and_setters.rs
Normal file
163
tests/wasm/getters_and_setters.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use std::rc::Rc;
|
||||
use std::cell::Cell;
|
||||
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;
|
||||
|
||||
fn test_getter_compute(x: GetterCompute);
|
||||
fn test_setter_compute(x: SetterCompute);
|
||||
}
|
||||
|
||||
// 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]
|
||||
#[allow(non_snake_case)]
|
||||
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);
|
||||
}
|
||||
|
||||
#[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<Cell<u32>>);
|
||||
|
||||
#[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);
|
||||
}
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user