mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-10 19:41:25 +00:00
Implement readonly struct fields
Add support for `#[wasm_bindgen(readonly)]` which indicates that an exported struct field is readonly and attempting to set it in JS will throw an exception. Closes #151
This commit is contained in:
17
DESIGN.md
17
DESIGN.md
@ -1044,6 +1044,23 @@ possibilities!
|
||||
All of these functions will call `console.log` in Rust, but each identifier
|
||||
will have only one signature in Rust.
|
||||
|
||||
* `readonly` - when attached to a `pub` struct field this indicates that it's
|
||||
readonly from JS and a setter will not be generated.
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen]
|
||||
pub struct Foo {
|
||||
pub first: u32,
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub second: u32,
|
||||
}
|
||||
```
|
||||
|
||||
Here the `first` field will be both readable and writable from JS, but the
|
||||
`second` field will be a `readonly` field in JS where the setter isn't
|
||||
implemented and attempting to set it will throw an exception.
|
||||
|
||||
|
||||
## Rust Type conversions
|
||||
|
||||
Previously we've been seeing mostly abridged versions of type conversions when
|
||||
|
@ -72,6 +72,7 @@ pub struct Struct {
|
||||
}
|
||||
|
||||
pub struct StructField {
|
||||
pub opts: BindgenAttrs,
|
||||
pub name: syn::Ident,
|
||||
pub struct_name: syn::Ident,
|
||||
pub ty: syn::Type,
|
||||
@ -132,8 +133,8 @@ impl Program {
|
||||
}
|
||||
syn::Item::Struct(mut s) => {
|
||||
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut s.attrs));
|
||||
self.structs.push(Struct::from(&mut s, opts));
|
||||
s.to_tokens(tokens);
|
||||
self.structs.push(Struct::from(s, opts));
|
||||
}
|
||||
syn::Item::Impl(mut i) => {
|
||||
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut i.attrs));
|
||||
@ -668,10 +669,10 @@ impl ImportType {
|
||||
}
|
||||
|
||||
impl Struct {
|
||||
fn from(s: syn::ItemStruct, _opts: BindgenAttrs) -> Struct {
|
||||
fn from(s: &mut syn::ItemStruct, _opts: BindgenAttrs) -> Struct {
|
||||
let mut fields = Vec::new();
|
||||
if let syn::Fields::Named(names) = s.fields {
|
||||
for field in names.named.iter() {
|
||||
if let syn::Fields::Named(names) = &mut s.fields {
|
||||
for field in names.named.iter_mut() {
|
||||
match field.vis {
|
||||
syn::Visibility::Public(..) => {}
|
||||
_ => continue,
|
||||
@ -682,7 +683,9 @@ impl Struct {
|
||||
};
|
||||
let getter = shared::struct_field_get(s.ident.as_ref(), name.as_ref());
|
||||
let setter = shared::struct_field_set(s.ident.as_ref(), name.as_ref());
|
||||
let opts = BindgenAttrs::find(&mut field.attrs);
|
||||
fields.push(StructField {
|
||||
opts,
|
||||
name,
|
||||
struct_name: s.ident,
|
||||
ty: field.ty.clone(),
|
||||
@ -709,6 +712,7 @@ impl StructField {
|
||||
fn shared(&self) -> shared::StructField {
|
||||
shared::StructField {
|
||||
name: self.name.as_ref().to_string(),
|
||||
readonly: self.opts.readonly(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -800,6 +804,13 @@ impl BindgenAttrs {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn readonly(&self) -> bool {
|
||||
self.attrs.iter().any(|a| match *a {
|
||||
BindgenAttr::Readonly => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn js_name(&self) -> Option<syn::Ident> {
|
||||
self.attrs
|
||||
.iter()
|
||||
@ -836,6 +847,7 @@ enum BindgenAttr {
|
||||
Getter(Option<syn::Ident>),
|
||||
Setter(Option<syn::Ident>),
|
||||
Structural,
|
||||
Readonly,
|
||||
JsName(syn::Ident),
|
||||
}
|
||||
|
||||
@ -869,6 +881,8 @@ impl syn::synom::Synom for BindgenAttr {
|
||||
|
|
||||
call!(term, "structural") => { |_| BindgenAttr::Structural }
|
||||
|
|
||||
call!(term, "readonly") => { |_| BindgenAttr::Readonly }
|
||||
|
|
||||
do_parse!(
|
||||
call!(term, "js_namespace") >>
|
||||
punct!(=) >>
|
||||
|
@ -247,6 +247,18 @@ impl ToTokens for ast::StructField {
|
||||
)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn #desc() {
|
||||
use wasm_bindgen::describe::*;
|
||||
<#ty as WasmDescribe>::describe();
|
||||
}
|
||||
}).to_tokens(tokens);
|
||||
|
||||
if self.opts.readonly() {
|
||||
return
|
||||
}
|
||||
|
||||
(quote! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn #setter(
|
||||
js: u32,
|
||||
@ -263,13 +275,6 @@ impl ToTokens for ast::StructField {
|
||||
);
|
||||
(*js).borrow_mut().#name = val;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn #desc() {
|
||||
use wasm_bindgen::describe::*;
|
||||
<#ty as WasmDescribe>::describe();
|
||||
|
||||
}
|
||||
}).to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
@ -32,14 +32,15 @@ pub struct Context<'a> {
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExportedClass {
|
||||
pub contents: String,
|
||||
pub typescript: String,
|
||||
pub constructor: Option<String>,
|
||||
pub fields: Vec<ClassField>,
|
||||
contents: String,
|
||||
typescript: String,
|
||||
constructor: Option<String>,
|
||||
fields: Vec<ClassField>,
|
||||
}
|
||||
|
||||
pub struct ClassField {
|
||||
pub name: String,
|
||||
struct ClassField {
|
||||
name: String,
|
||||
readonly: bool,
|
||||
}
|
||||
|
||||
pub struct SubContext<'a, 'b: 'a> {
|
||||
@ -416,7 +417,8 @@ impl<'a> Context<'a> {
|
||||
cx.method(true)
|
||||
.argument(&descriptor)
|
||||
.ret(&None);
|
||||
ts_dst.push_str(&format!("{}: {}\n",
|
||||
ts_dst.push_str(&format!("{}{}: {}\n",
|
||||
if field.readonly { "readonly " } else { "" },
|
||||
field.name,
|
||||
&cx.js_arguments[0].1));
|
||||
cx.finish("", &format!("wasm.{}", wasm_setter)).0
|
||||
@ -430,9 +432,11 @@ impl<'a> Context<'a> {
|
||||
dst.push_str(&field.name);
|
||||
dst.push_str(&get);
|
||||
dst.push_str("\n");
|
||||
dst.push_str("set ");
|
||||
dst.push_str(&field.name);
|
||||
dst.push_str(&set);
|
||||
if !field.readonly {
|
||||
dst.push_str("set ");
|
||||
dst.push_str(&field.name);
|
||||
dst.push_str(&set);
|
||||
}
|
||||
}
|
||||
|
||||
dst.push_str(&format!("
|
||||
@ -1312,6 +1316,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
.extend(s.fields.iter().map(|s| {
|
||||
ClassField {
|
||||
name: s.name.clone(),
|
||||
readonly: s.readonly,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub const SCHEMA_VERSION: &str = "3";
|
||||
pub const SCHEMA_VERSION: &str = "4";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ProgramOnlySchema {
|
||||
@ -91,6 +91,7 @@ pub struct Struct {
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct StructField {
|
||||
pub name: String,
|
||||
pub readonly: bool,
|
||||
}
|
||||
|
||||
pub fn new_function(struct_name: &str) -> String {
|
||||
|
@ -558,3 +558,42 @@ fn using_self() {
|
||||
"#)
|
||||
.test();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn readonly_fields() {
|
||||
project()
|
||||
.debug(false)
|
||||
.file("src/lib.rs", r#"
|
||||
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
|
||||
|
||||
extern crate wasm_bindgen;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Default)]
|
||||
pub struct Foo {
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub a: u32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Foo {
|
||||
pub fn new() -> Foo {
|
||||
Foo::default()
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.ts", r#"
|
||||
import { Foo } from "./out";
|
||||
import * as assert from "assert";
|
||||
|
||||
export function test() {
|
||||
const a = Foo.new();
|
||||
assert.strictEqual(a.a, 0);
|
||||
assert.throws(() => (a as any).a = 3, /has only a getter/);
|
||||
a.free();
|
||||
}
|
||||
"#)
|
||||
.test();
|
||||
}
|
||||
|
Reference in New Issue
Block a user