mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-12 04:21:21 +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
|
All of these functions will call `console.log` in Rust, but each identifier
|
||||||
will have only one signature in Rust.
|
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
|
## Rust Type conversions
|
||||||
|
|
||||||
Previously we've been seeing mostly abridged versions of type conversions when
|
Previously we've been seeing mostly abridged versions of type conversions when
|
||||||
|
@ -72,6 +72,7 @@ pub struct Struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct StructField {
|
pub struct StructField {
|
||||||
|
pub opts: BindgenAttrs,
|
||||||
pub name: syn::Ident,
|
pub name: syn::Ident,
|
||||||
pub struct_name: syn::Ident,
|
pub struct_name: syn::Ident,
|
||||||
pub ty: syn::Type,
|
pub ty: syn::Type,
|
||||||
@ -132,8 +133,8 @@ impl Program {
|
|||||||
}
|
}
|
||||||
syn::Item::Struct(mut s) => {
|
syn::Item::Struct(mut s) => {
|
||||||
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut s.attrs));
|
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut s.attrs));
|
||||||
|
self.structs.push(Struct::from(&mut s, opts));
|
||||||
s.to_tokens(tokens);
|
s.to_tokens(tokens);
|
||||||
self.structs.push(Struct::from(s, opts));
|
|
||||||
}
|
}
|
||||||
syn::Item::Impl(mut i) => {
|
syn::Item::Impl(mut i) => {
|
||||||
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut i.attrs));
|
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut i.attrs));
|
||||||
@ -668,10 +669,10 @@ impl ImportType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Struct {
|
impl Struct {
|
||||||
fn from(s: syn::ItemStruct, _opts: BindgenAttrs) -> Struct {
|
fn from(s: &mut syn::ItemStruct, _opts: BindgenAttrs) -> Struct {
|
||||||
let mut fields = Vec::new();
|
let mut fields = Vec::new();
|
||||||
if let syn::Fields::Named(names) = s.fields {
|
if let syn::Fields::Named(names) = &mut s.fields {
|
||||||
for field in names.named.iter() {
|
for field in names.named.iter_mut() {
|
||||||
match field.vis {
|
match field.vis {
|
||||||
syn::Visibility::Public(..) => {}
|
syn::Visibility::Public(..) => {}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
@ -682,7 +683,9 @@ impl Struct {
|
|||||||
};
|
};
|
||||||
let getter = shared::struct_field_get(s.ident.as_ref(), name.as_ref());
|
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 setter = shared::struct_field_set(s.ident.as_ref(), name.as_ref());
|
||||||
|
let opts = BindgenAttrs::find(&mut field.attrs);
|
||||||
fields.push(StructField {
|
fields.push(StructField {
|
||||||
|
opts,
|
||||||
name,
|
name,
|
||||||
struct_name: s.ident,
|
struct_name: s.ident,
|
||||||
ty: field.ty.clone(),
|
ty: field.ty.clone(),
|
||||||
@ -709,6 +712,7 @@ impl StructField {
|
|||||||
fn shared(&self) -> shared::StructField {
|
fn shared(&self) -> shared::StructField {
|
||||||
shared::StructField {
|
shared::StructField {
|
||||||
name: self.name.as_ref().to_string(),
|
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> {
|
pub fn js_name(&self) -> Option<syn::Ident> {
|
||||||
self.attrs
|
self.attrs
|
||||||
.iter()
|
.iter()
|
||||||
@ -836,6 +847,7 @@ enum BindgenAttr {
|
|||||||
Getter(Option<syn::Ident>),
|
Getter(Option<syn::Ident>),
|
||||||
Setter(Option<syn::Ident>),
|
Setter(Option<syn::Ident>),
|
||||||
Structural,
|
Structural,
|
||||||
|
Readonly,
|
||||||
JsName(syn::Ident),
|
JsName(syn::Ident),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -869,6 +881,8 @@ impl syn::synom::Synom for BindgenAttr {
|
|||||||
|
|
|
|
||||||
call!(term, "structural") => { |_| BindgenAttr::Structural }
|
call!(term, "structural") => { |_| BindgenAttr::Structural }
|
||||||
|
|
|
|
||||||
|
call!(term, "readonly") => { |_| BindgenAttr::Readonly }
|
||||||
|
|
|
||||||
do_parse!(
|
do_parse!(
|
||||||
call!(term, "js_namespace") >>
|
call!(term, "js_namespace") >>
|
||||||
punct!(=) >>
|
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]
|
#[no_mangle]
|
||||||
pub unsafe extern fn #setter(
|
pub unsafe extern fn #setter(
|
||||||
js: u32,
|
js: u32,
|
||||||
@ -263,13 +275,6 @@ impl ToTokens for ast::StructField {
|
|||||||
);
|
);
|
||||||
(*js).borrow_mut().#name = val;
|
(*js).borrow_mut().#name = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern fn #desc() {
|
|
||||||
use wasm_bindgen::describe::*;
|
|
||||||
<#ty as WasmDescribe>::describe();
|
|
||||||
|
|
||||||
}
|
|
||||||
}).to_tokens(tokens);
|
}).to_tokens(tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,14 +32,15 @@ pub struct Context<'a> {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ExportedClass {
|
pub struct ExportedClass {
|
||||||
pub contents: String,
|
contents: String,
|
||||||
pub typescript: String,
|
typescript: String,
|
||||||
pub constructor: Option<String>,
|
constructor: Option<String>,
|
||||||
pub fields: Vec<ClassField>,
|
fields: Vec<ClassField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClassField {
|
struct ClassField {
|
||||||
pub name: String,
|
name: String,
|
||||||
|
readonly: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SubContext<'a, 'b: 'a> {
|
pub struct SubContext<'a, 'b: 'a> {
|
||||||
@ -416,7 +417,8 @@ impl<'a> Context<'a> {
|
|||||||
cx.method(true)
|
cx.method(true)
|
||||||
.argument(&descriptor)
|
.argument(&descriptor)
|
||||||
.ret(&None);
|
.ret(&None);
|
||||||
ts_dst.push_str(&format!("{}: {}\n",
|
ts_dst.push_str(&format!("{}{}: {}\n",
|
||||||
|
if field.readonly { "readonly " } else { "" },
|
||||||
field.name,
|
field.name,
|
||||||
&cx.js_arguments[0].1));
|
&cx.js_arguments[0].1));
|
||||||
cx.finish("", &format!("wasm.{}", wasm_setter)).0
|
cx.finish("", &format!("wasm.{}", wasm_setter)).0
|
||||||
@ -430,9 +432,11 @@ impl<'a> Context<'a> {
|
|||||||
dst.push_str(&field.name);
|
dst.push_str(&field.name);
|
||||||
dst.push_str(&get);
|
dst.push_str(&get);
|
||||||
dst.push_str("\n");
|
dst.push_str("\n");
|
||||||
dst.push_str("set ");
|
if !field.readonly {
|
||||||
dst.push_str(&field.name);
|
dst.push_str("set ");
|
||||||
dst.push_str(&set);
|
dst.push_str(&field.name);
|
||||||
|
dst.push_str(&set);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.push_str(&format!("
|
dst.push_str(&format!("
|
||||||
@ -1312,6 +1316,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
|||||||
.extend(s.fields.iter().map(|s| {
|
.extend(s.fields.iter().map(|s| {
|
||||||
ClassField {
|
ClassField {
|
||||||
name: s.name.clone(),
|
name: s.name.clone(),
|
||||||
|
readonly: s.readonly,
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
pub const SCHEMA_VERSION: &str = "3";
|
pub const SCHEMA_VERSION: &str = "4";
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ProgramOnlySchema {
|
pub struct ProgramOnlySchema {
|
||||||
@ -91,6 +91,7 @@ pub struct Struct {
|
|||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct StructField {
|
pub struct StructField {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub readonly: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_function(struct_name: &str) -> String {
|
pub fn new_function(struct_name: &str) -> String {
|
||||||
|
@ -558,3 +558,42 @@ fn using_self() {
|
|||||||
"#)
|
"#)
|
||||||
.test();
|
.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