Skip to content

Commit 052a53e

Browse files
committed
Getter/Setter for fields
1 parent b670ea4 commit 052a53e

File tree

9 files changed

+231
-89
lines changed

9 files changed

+231
-89
lines changed

crates/backend/src/ast.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,18 @@ pub struct Program {
3434
#[cfg_attr(feature = "extra-traits", derive(Debug))]
3535
#[derive(Clone)]
3636
pub struct Export {
37-
/// The struct name, in Rust, this is attached to
38-
pub rust_class: Option<Ident>,
37+
/// Comments extracted from the rust source.
38+
pub comments: Vec<String>,
39+
/// The rust function
40+
pub function: Function,
3941
/// The class name in JS this is attached to
4042
pub js_class: Option<String>,
43+
/// The kind (static, named, regular)
44+
pub method_kind: MethodKind,
4145
/// The type of `self` (either `self`, `&self`, or `&mut self`)
4246
pub method_self: Option<MethodSelf>,
43-
/// Whether or not this export is flagged as a constructor, returning an
44-
/// instance of the `impl` type
45-
pub is_constructor: bool,
46-
/// The rust function
47-
pub function: Function,
48-
/// Comments extracted from the rust source.
49-
pub comments: Vec<String>,
47+
/// The struct name, in Rust, this is attached to
48+
pub rust_class: Option<Ident>,
5049
/// The name of the rust function/method on the rust side.
5150
pub rust_name: Ident,
5251
/// Whether or not this function should be flagged as the wasm start
@@ -341,28 +340,28 @@ impl ImportKind {
341340
}
342341
}
343342

344-
impl ImportFunction {
343+
impl Function {
345344
/// If the rust object has a `fn xxx(&self) -> MyType` method, get the name for a getter in
346345
/// javascript (in this case `xxx`, so you can write `val = obj.xxx`)
347346
pub fn infer_getter_property(&self) -> &str {
348-
&self.function.name
347+
&self.name
349348
}
350349

351350
/// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name
352351
/// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`)
353352
pub fn infer_setter_property(&self) -> Result<String, Diagnostic> {
354-
let name = self.function.name.to_string();
353+
let name = self.name.to_string();
355354

356355
// if `#[wasm_bindgen(js_name = "...")]` is used then that explicitly
357356
// because it was hand-written anyway.
358-
if self.function.renamed_via_js_name {
357+
if self.renamed_via_js_name {
359358
return Ok(name);
360359
}
361360

362361
// Otherwise we infer names based on the Rust function name.
363362
if !name.starts_with("set_") {
364363
bail_span!(
365-
syn::token::Pub(self.function.name_span),
364+
syn::token::Pub(self.name_span),
366365
"setters must start with `set_`, found: {}",
367366
name,
368367
);

crates/backend/src/encode.rs

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ fn shared_program<'a>(
125125
.exports
126126
.iter()
127127
.map(|a| shared_export(a, intern))
128-
.collect(),
128+
.collect::<Result<Vec<_>, _>>()?,
129129
structs: prog
130130
.structs
131131
.iter()
@@ -172,21 +172,23 @@ fn shared_program<'a>(
172172
})
173173
}
174174

175-
fn shared_export<'a>(export: &'a ast::Export, intern: &'a Interner) -> Export<'a> {
176-
let (method, consumed) = match export.method_self {
177-
Some(ast::MethodSelf::ByValue) => (true, true),
178-
Some(_) => (true, false),
179-
None => (false, false),
175+
fn shared_export<'a>(
176+
export: &'a ast::Export,
177+
intern: &'a Interner,
178+
) -> Result<Export<'a>, Diagnostic> {
179+
let consumed = match export.method_self {
180+
Some(ast::MethodSelf::ByValue) => true,
181+
_ => false,
180182
};
181-
Export {
183+
let method_kind = from_ast_method_kind(&export.function, intern, &export.method_kind)?;
184+
Ok(Export {
182185
class: export.js_class.as_ref().map(|s| &**s),
183-
method,
186+
comments: export.comments.iter().map(|s| &**s).collect(),
184187
consumed,
185-
is_constructor: export.is_constructor,
186188
function: shared_function(&export.function, intern),
187-
comments: export.comments.iter().map(|s| &**s).collect(),
189+
method_kind,
188190
start: export.start,
189-
}
191+
})
190192
}
191193

192194
fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> {
@@ -260,30 +262,7 @@ fn shared_import_function<'a>(
260262
) -> Result<ImportFunction<'a>, Diagnostic> {
261263
let method = match &i.kind {
262264
ast::ImportFunctionKind::Method { class, kind, .. } => {
263-
let kind = match kind {
264-
ast::MethodKind::Constructor => MethodKind::Constructor,
265-
ast::MethodKind::Operation(ast::Operation { is_static, kind }) => {
266-
let is_static = *is_static;
267-
let kind = match kind {
268-
ast::OperationKind::Regular => OperationKind::Regular,
269-
ast::OperationKind::Getter(g) => {
270-
let g = g.as_ref().map(|g| intern.intern(g));
271-
OperationKind::Getter(g.unwrap_or_else(|| i.infer_getter_property()))
272-
}
273-
ast::OperationKind::Setter(s) => {
274-
let s = s.as_ref().map(|s| intern.intern(s));
275-
OperationKind::Setter(match s {
276-
Some(s) => s,
277-
None => intern.intern_str(&i.infer_setter_property()?),
278-
})
279-
}
280-
ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter,
281-
ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter,
282-
ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter,
283-
};
284-
MethodKind::Operation(Operation { is_static, kind })
285-
}
286-
};
265+
let kind = from_ast_method_kind(&i.function, intern, kind)?;
287266
Some(MethodData { class, kind })
288267
}
289268
ast::ImportFunctionKind::Normal => None,
@@ -507,3 +486,34 @@ macro_rules! encode_api {
507486
);
508487
}
509488
wasm_bindgen_shared::shared_api!(encode_api);
489+
490+
fn from_ast_method_kind<'a>(
491+
function: &'a ast::Function,
492+
intern: &'a Interner,
493+
method_kind: &'a ast::MethodKind,
494+
) -> Result<MethodKind<'a>, Diagnostic> {
495+
Ok(match method_kind {
496+
ast::MethodKind::Constructor => MethodKind::Constructor,
497+
ast::MethodKind::Operation(ast::Operation { is_static, kind }) => {
498+
let is_static = *is_static;
499+
let kind = match kind {
500+
ast::OperationKind::Getter(g) => {
501+
let g = g.as_ref().map(|g| intern.intern(g));
502+
OperationKind::Getter(g.unwrap_or_else(|| function.infer_getter_property()))
503+
}
504+
ast::OperationKind::Regular => OperationKind::Regular,
505+
ast::OperationKind::Setter(s) => {
506+
let s = s.as_ref().map(|s| intern.intern(s));
507+
OperationKind::Setter(match s {
508+
Some(s) => s,
509+
None => intern.intern_str(&function.infer_setter_property()?),
510+
})
511+
}
512+
ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter,
513+
ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter,
514+
ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter,
515+
};
516+
MethodKind::Operation(Operation { is_static, kind })
517+
}
518+
})
519+
}

crates/cli-support/src/js/mod.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2641,14 +2641,15 @@ impl<'a, 'b> SubContext<'a, 'b> {
26412641
Some(d) => d,
26422642
};
26432643

2644-
let function_name = if export.is_constructor {
2645-
"constructor"
2646-
} else {
2647-
&export.function.name
2644+
let (function_name, is_constructor, has_self_in_fn) = match &export.method_kind {
2645+
decode::MethodKind::Constructor => ("constructor", true, false),
2646+
decode::MethodKind::Operation(operation) => {
2647+
(export.function.name, false, !operation.is_static)
2648+
}
26482649
};
26492650
let (js, ts, js_doc) = Js2Rust::new(function_name, self.cx)
2650-
.method(export.method, export.consumed)
2651-
.constructor(if export.is_constructor {
2651+
.method(has_self_in_fn, export.consumed)
2652+
.constructor(if is_constructor {
26522653
Some(class_name)
26532654
} else {
26542655
None
@@ -2673,12 +2674,12 @@ impl<'a, 'b> SubContext<'a, 'b> {
26732674

26742675
class.typescript.push_str(" "); // Indentation
26752676

2676-
if export.is_constructor {
2677+
if is_constructor {
26772678
if class.has_constructor {
26782679
bail!("found duplicate constructor `{}`", export.function.name);
26792680
}
26802681
class.has_constructor = true;
2681-
} else if !export.method {
2682+
} else if !has_self_in_fn {
26822683
class.contents.push_str("static ");
26832684
class.typescript.push_str("static ");
26842685
}

crates/macro-support/src/parser.rs

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -373,22 +373,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a ast::ImportModule)> for syn::ForeignIte
373373
wasm.ret.clone()
374374
};
375375

376-
let mut operation_kind = ast::OperationKind::Regular;
377-
if let Some(g) = opts.getter() {
378-
operation_kind = ast::OperationKind::Getter(g.clone());
379-
}
380-
if let Some(s) = opts.setter() {
381-
operation_kind = ast::OperationKind::Setter(s.clone());
382-
}
383-
if opts.indexing_getter().is_some() {
384-
operation_kind = ast::OperationKind::IndexingGetter;
385-
}
386-
if opts.indexing_setter().is_some() {
387-
operation_kind = ast::OperationKind::IndexingSetter;
388-
}
389-
if opts.indexing_deleter().is_some() {
390-
operation_kind = ast::OperationKind::IndexingDeleter;
391-
}
376+
let operation_kind = operation_kind(&opts);
392377

393378
let kind = if opts.method().is_some() {
394379
let class = wasm.arguments.get(0).ok_or_else(|| {
@@ -742,15 +727,21 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
742727
bail_span!(&f.decl.inputs, "the start function cannot have arguments",);
743728
}
744729
}
730+
let method_kind = ast::MethodKind::Operation(ast::Operation {
731+
is_static: true,
732+
kind: ast::OperationKind::Regular,
733+
});
734+
let rust_name = f.ident.clone();
735+
let start = opts.start().is_some();
745736
program.exports.push(ast::Export {
746-
rust_class: None,
747-
js_class: None,
748-
method_self: None,
749-
is_constructor: false,
750737
comments,
751-
rust_name: f.ident.clone(),
752-
start: opts.start().is_some(),
753738
function: f.convert(opts)?,
739+
js_class: None,
740+
method_kind,
741+
method_self: None,
742+
rust_class: None,
743+
rust_name,
744+
start,
754745
});
755746
}
756747
syn::Item::Struct(mut s) => {
@@ -929,7 +920,6 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod {
929920

930921
let opts = BindgenAttrs::find(&mut self.attrs)?;
931922
let comments = extract_doc_comments(&self.attrs);
932-
let is_constructor = opts.constructor().is_some();
933923
let (function, method_self) = function_from_decl(
934924
&self.sig.ident,
935925
&opts,
@@ -939,16 +929,23 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod {
939929
true,
940930
Some(class),
941931
)?;
942-
932+
let method_kind = if opts.constructor().is_some() {
933+
ast::MethodKind::Constructor
934+
} else {
935+
ast::MethodKind::Operation(ast::Operation {
936+
is_static: false,
937+
kind: ast::OperationKind::Regular
938+
})
939+
};
943940
program.exports.push(ast::Export {
944-
rust_class: Some(class.clone()),
941+
comments,
942+
function,
945943
js_class: Some(js_class.to_string()),
944+
method_kind,
946945
method_self,
947-
is_constructor,
948-
function,
949-
comments,
950-
start: false,
946+
rust_class: Some(class.clone()),
951947
rust_name: self.sig.ident.clone(),
948+
start: false,
952949
});
953950
opts.check_used()?;
954951
Ok(())
@@ -1281,3 +1278,23 @@ pub fn assert_all_attrs_checked() {
12811278
assert_eq!(state.parsed.get(), state.checks.get());
12821279
})
12831280
}
1281+
1282+
fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind {
1283+
let mut operation_kind = ast::OperationKind::Regular;
1284+
if let Some(g) = opts.getter() {
1285+
operation_kind = ast::OperationKind::Getter(g.clone());
1286+
}
1287+
if let Some(s) = opts.setter() {
1288+
operation_kind = ast::OperationKind::Setter(s.clone());
1289+
}
1290+
if opts.indexing_getter().is_some() {
1291+
operation_kind = ast::OperationKind::IndexingGetter;
1292+
}
1293+
if opts.indexing_setter().is_some() {
1294+
operation_kind = ast::OperationKind::IndexingSetter;
1295+
}
1296+
if opts.indexing_deleter().is_some() {
1297+
operation_kind = ast::OperationKind::IndexingDeleter;
1298+
}
1299+
operation_kind
1300+
}

crates/shared/src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,10 @@ macro_rules! shared_api {
8888

8989
struct Export<'a> {
9090
class: Option<&'a str>,
91-
method: bool,
91+
comments: Vec<&'a str>,
9292
consumed: bool,
93-
is_constructor: bool,
9493
function: Function<'a>,
95-
comments: Vec<&'a str>,
94+
method_kind: MethodKind<'a>,
9695
start: bool,
9796
}
9897

guide/src/reference/attributes/on-js-imports/getter-and-setter.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,62 @@ extern "C" {
5959
}
6060
```
6161

62+
It is also possible to export `Rust` types with getters and setters into `JavaScript` types. For example, the following:
63+
64+
```rust
65+
#[derive(Default)]
66+
#[wasm_bindgen]
67+
pub struct Foo {
68+
field1: Option<f64>,
69+
field2: Option<i32>,
70+
}
71+
72+
#[wasm_bindgen]
73+
impl Foo {
74+
#[wasm_bindgen(constructor)]
75+
pub fn new() -> Self {
76+
Self::default()
77+
}
78+
79+
#[wasm_bindgen(getter)]
80+
pub fn field1(&self) -> Option<f64> {
81+
self.field1
82+
}
83+
84+
#[wasm_bindgen(setter)]
85+
pub fn set_field1(&mut self, field1: f64) {
86+
self.field1 = Some(field1);
87+
}
88+
89+
#[wasm_bindgen(method, getter)]
90+
pub fn field2(&self) -> Option<i32> {
91+
self.field2
92+
}
93+
94+
#[wasm_bindgen(method, setter)]
95+
pub fn set_field(&mut self, field2: i32) {
96+
self.field2 = Some(field2);
97+
}
98+
}
99+
```
100+
101+
Can be used in `JavaScript` like in this snippet:
102+
103+
104+
```js
105+
function stuff() {
106+
let foo = Foo.new();
107+
108+
foo.field1 = 3.14;
109+
console.log(foo.field1);
110+
111+
foo.set_field2(123);
112+
console.log(foo.get_field2());
113+
}
114+
```
115+
116+
Would
117+
62118
Heads up! `getter` and `setter` functions are found on the constructor's
63119
prototype chain once at load time, cached, and then the cached accessor is
64120
invoked on each access. If you need to dynamically walk the prototype chain on

0 commit comments

Comments
 (0)