mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-27 20:15:14 +03:00
Merge pull request #652 from afdw/master
Add support for getters, setters and deleters
This commit is contained in:
commit
b6a6dee7f1
@ -105,8 +105,11 @@ pub struct Operation {
|
||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||
pub enum OperationKind {
|
||||
Regular,
|
||||
Setter(Option<Ident>),
|
||||
Getter(Option<Ident>),
|
||||
Setter(Option<Ident>),
|
||||
IndexingGetter,
|
||||
IndexingSetter,
|
||||
IndexingDeleter,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||
@ -369,6 +372,9 @@ impl ImportFunction {
|
||||
s.unwrap_or_else(|| self.infer_setter_property()),
|
||||
)
|
||||
}
|
||||
OperationKind::IndexingGetter => shared::OperationKind::IndexingGetter,
|
||||
OperationKind::IndexingSetter => shared::OperationKind::IndexingSetter,
|
||||
OperationKind::IndexingDeleter => shared::OperationKind::IndexingDeleter,
|
||||
};
|
||||
shared::MethodKind::Operation(shared::Operation { is_static, kind })
|
||||
}
|
||||
|
@ -1808,18 +1808,6 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
let location = if *is_static { &class } else { "this" };
|
||||
|
||||
match kind {
|
||||
shared::OperationKind::Getter(g) => format!(
|
||||
"function() {{
|
||||
return {}.{};
|
||||
}}",
|
||||
location, g
|
||||
),
|
||||
shared::OperationKind::Setter(s) => format!(
|
||||
"function(y) {{
|
||||
{}.{} = y;
|
||||
}}",
|
||||
location, s
|
||||
),
|
||||
shared::OperationKind::Regular => {
|
||||
let nargs = descriptor.unwrap_function().arguments.len();
|
||||
let mut s = format!("function(");
|
||||
@ -1841,11 +1829,44 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
s.push_str(");\n}");
|
||||
s
|
||||
}
|
||||
shared::OperationKind::Getter(g) => format!(
|
||||
"function() {{
|
||||
return {}.{};
|
||||
}}",
|
||||
location, g
|
||||
),
|
||||
shared::OperationKind::Setter(s) => format!(
|
||||
"function(y) {{
|
||||
{}.{} = y;
|
||||
}}",
|
||||
location, s
|
||||
),
|
||||
shared::OperationKind::IndexingGetter => format!(
|
||||
"function(y) {{
|
||||
return {}[y];
|
||||
}}",
|
||||
location
|
||||
),
|
||||
shared::OperationKind::IndexingSetter => format!(
|
||||
"function(y, z) {{
|
||||
{}[y] = z;
|
||||
}}",
|
||||
location
|
||||
),
|
||||
shared::OperationKind::IndexingDeleter => format!(
|
||||
"function(y) {{
|
||||
delete {}[y];
|
||||
}}",
|
||||
location
|
||||
),
|
||||
}
|
||||
} else {
|
||||
let location = if *is_static { "" } else { ".prototype" };
|
||||
|
||||
match kind {
|
||||
shared::OperationKind::Regular => {
|
||||
format!("{}{}.{}", class, location, import.function.name)
|
||||
}
|
||||
shared::OperationKind::Getter(g) => {
|
||||
self.cx.expose_get_inherited_descriptor();
|
||||
format!(
|
||||
@ -1860,9 +1881,9 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
class, location, s,
|
||||
)
|
||||
}
|
||||
shared::OperationKind::Regular => {
|
||||
format!("{}{}.{}", class, location, import.function.name)
|
||||
}
|
||||
shared::OperationKind::IndexingGetter => panic!("indexing getter should be structural"),
|
||||
shared::OperationKind::IndexingSetter => panic!("indexing setter should be structural"),
|
||||
shared::OperationKind::IndexingDeleter => panic!("indexing deleter should be structural"),
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -121,6 +121,30 @@ impl BindgenAttrs {
|
||||
.next()
|
||||
}
|
||||
|
||||
/// Whether the indexing getter attributes is present
|
||||
fn indexing_getter(&self) -> bool {
|
||||
self.attrs.iter().any(|a| match *a {
|
||||
BindgenAttr::IndexingGetter => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the indexing setter attributes is present
|
||||
fn indexing_setter(&self) -> bool {
|
||||
self.attrs.iter().any(|a| match *a {
|
||||
BindgenAttr::IndexingSetter => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the indexing deleter attributes is present
|
||||
fn indexing_deleter(&self) -> bool {
|
||||
self.attrs.iter().any(|a| match *a {
|
||||
BindgenAttr::IndexingDeleter => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the structural attributes is present
|
||||
fn structural(&self) -> bool {
|
||||
self.attrs.iter().any(|a| match *a {
|
||||
@ -186,6 +210,9 @@ pub enum BindgenAttr {
|
||||
Module(String),
|
||||
Getter(Option<Ident>),
|
||||
Setter(Option<Ident>),
|
||||
IndexingGetter,
|
||||
IndexingSetter,
|
||||
IndexingDeleter,
|
||||
Structural,
|
||||
Readonly,
|
||||
JsName(String),
|
||||
@ -227,6 +254,12 @@ impl syn::synom::Synom for BindgenAttr {
|
||||
(val)
|
||||
)=> { BindgenAttr::Setter }
|
||||
|
|
||||
call!(term, "indexing_getter") => { |_| BindgenAttr::IndexingGetter }
|
||||
|
|
||||
call!(term, "indexing_setter") => { |_| BindgenAttr::IndexingSetter }
|
||||
|
|
||||
call!(term, "indexing_deleter") => { |_| BindgenAttr::IndexingDeleter }
|
||||
|
|
||||
call!(term, "structural") => { |_| BindgenAttr::Structural }
|
||||
|
|
||||
call!(term, "readonly") => { |_| BindgenAttr::Readonly }
|
||||
@ -381,6 +414,15 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
|
||||
if let Some(s) = opts.setter() {
|
||||
operation_kind = ast::OperationKind::Setter(s);
|
||||
}
|
||||
if opts.indexing_getter() {
|
||||
operation_kind = ast::OperationKind::IndexingGetter;
|
||||
}
|
||||
if opts.indexing_setter() {
|
||||
operation_kind = ast::OperationKind::IndexingSetter;
|
||||
}
|
||||
if opts.indexing_deleter() {
|
||||
operation_kind = ast::OperationKind::IndexingDeleter;
|
||||
}
|
||||
|
||||
let kind = if opts.method() {
|
||||
let class = wasm
|
||||
|
@ -69,6 +69,9 @@ pub enum OperationKind {
|
||||
Regular,
|
||||
Getter(String),
|
||||
Setter(String),
|
||||
IndexingGetter,
|
||||
IndexingSetter,
|
||||
IndexingDeleter,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
@ -93,6 +93,20 @@ global.GlobalMethod = class GlobalMethod {
|
||||
}
|
||||
};
|
||||
|
||||
global.Indexing = function () {
|
||||
return new Proxy({}, {
|
||||
get(obj, prop) {
|
||||
return obj.hasOwnProperty(prop) ? obj[prop] : -1;
|
||||
},
|
||||
set(obj, prop, value) {
|
||||
obj[prop] = value;
|
||||
},
|
||||
deleteProperty(obj, prop) {
|
||||
delete obj[prop];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
global.PartialInterface = class PartialInterface {
|
||||
get un() {
|
||||
return 1;
|
||||
|
@ -65,7 +65,17 @@ fn optional_method() {
|
||||
#[wasm_bindgen_test]
|
||||
fn global_method() {
|
||||
let f = GlobalMethod::new().unwrap();
|
||||
assert!(f.m() == 123);
|
||||
assert_eq!(f.m(), 123);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn indexing() {
|
||||
let f = Indexing::new().unwrap();
|
||||
assert_eq!(f.get(123), -1);
|
||||
f.set(123, 456);
|
||||
assert_eq!(f.get(123), 456);
|
||||
f.delete(123);
|
||||
assert_eq!(f.get(123), -1);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
|
7
crates/webidl-tests/simple.webidl
vendored
7
crates/webidl-tests/simple.webidl
vendored
@ -40,6 +40,13 @@ interface GlobalMethod {
|
||||
octet m();
|
||||
};
|
||||
|
||||
[Constructor()]
|
||||
interface Indexing {
|
||||
getter short (unsigned long index);
|
||||
setter void (unsigned long index, short value);
|
||||
deleter void (unsigned long index);
|
||||
};
|
||||
|
||||
[Constructor()]
|
||||
interface Unforgeable {
|
||||
[Unforgeable] readonly attribute short uno;
|
||||
|
@ -41,7 +41,10 @@ pub(crate) struct InterfaceData<'src> {
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub(crate) enum OperationId<'src> {
|
||||
Constructor,
|
||||
Operation(Option<&'src str>)
|
||||
Operation(Option<&'src str>),
|
||||
IndexingGetter,
|
||||
IndexingSetter,
|
||||
IndexingDeleter,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -243,7 +246,7 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::InterfaceMember<'sr
|
||||
|
||||
impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceMember<'src> {
|
||||
fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str) -> Result<()> {
|
||||
if self.specials.len() > 0 {
|
||||
if !self.specials.is_empty() && self.specials.len() != 1 {
|
||||
warn!("Unsupported webidl operation {:?}", self);
|
||||
return Ok(())
|
||||
}
|
||||
@ -254,7 +257,16 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceM
|
||||
first_pass_operation(
|
||||
record,
|
||||
self_name,
|
||||
OperationId::Operation(self.identifier.map(|s| s.0)),
|
||||
match self.identifier.map(|s| s.0) {
|
||||
None => match self.specials.get(0) {
|
||||
None => OperationId::Operation(None),
|
||||
Some(weedle::interface::Special::Getter(weedle::term::Getter)) => OperationId::IndexingGetter,
|
||||
Some(weedle::interface::Special::Setter(weedle::term::Setter)) => OperationId::IndexingSetter,
|
||||
Some(weedle::interface::Special::Deleter(weedle::term::Deleter)) => OperationId::IndexingDeleter,
|
||||
Some(weedle::interface::Special::LegacyCaller(weedle::term::LegacyCaller)) => return Ok(()),
|
||||
},
|
||||
Some(ref name) => OperationId::Operation(Some(name.clone())),
|
||||
},
|
||||
&self.args.body.list,
|
||||
)
|
||||
}
|
||||
|
@ -299,7 +299,7 @@ impl<'src> WebidlParse<'src, &'src weedle::InterfaceDefinition<'src>> for Extend
|
||||
let mut add_constructor = |arguments: &[Argument], class: &str| {
|
||||
let (overloaded, same_argument_names) = first_pass.get_operation_overloading(
|
||||
arguments,
|
||||
::first_pass::OperationId::Constructor,
|
||||
&::first_pass::OperationId::Constructor,
|
||||
interface.identifier.0,
|
||||
);
|
||||
|
||||
@ -644,18 +644,32 @@ fn member_operation<'src>(
|
||||
Some(Static(_)) => true,
|
||||
None => false,
|
||||
};
|
||||
if specials.len() > 0 {
|
||||
warn!("Unsupported specials on type {:?}", (self_name, identifier));
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
first_pass
|
||||
.create_basic_method(
|
||||
args,
|
||||
identifier.map(|s| s.0),
|
||||
match identifier.map(|s| s.0) {
|
||||
None if specials.is_empty() => ::first_pass::OperationId::Operation(None),
|
||||
None if specials.len() == 1 => match specials[0] {
|
||||
weedle::interface::Special::Getter(weedle::term::Getter) => ::first_pass::OperationId::IndexingGetter,
|
||||
weedle::interface::Special::Setter(weedle::term::Setter) => ::first_pass::OperationId::IndexingSetter,
|
||||
weedle::interface::Special::Deleter(weedle::term::Deleter) => ::first_pass::OperationId::IndexingDeleter,
|
||||
weedle::interface::Special::LegacyCaller(weedle::term::LegacyCaller) => return Ok(()),
|
||||
},
|
||||
Some(ref name) if specials.is_empty() => ::first_pass::OperationId::Operation(Some(name.clone())),
|
||||
_ => {
|
||||
warn!("Unsupported specials on type {:?}", (self_name, identifier));
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
return_type,
|
||||
self_name,
|
||||
statik,
|
||||
specials.len() == 1 || first_pass
|
||||
.interfaces
|
||||
.get(self_name)
|
||||
.map(|interface_data| interface_data.global)
|
||||
.unwrap_or(false),
|
||||
util::throws(attrs),
|
||||
)
|
||||
.map(wrap_import_function)
|
||||
|
@ -910,24 +910,31 @@ impl<'src> FirstPassRecord<'src> {
|
||||
pub fn create_basic_method(
|
||||
&self,
|
||||
arguments: &[weedle::argument::Argument],
|
||||
name: Option<&str>,
|
||||
operation_id: ::first_pass::OperationId,
|
||||
return_type: &weedle::types::ReturnType,
|
||||
self_name: &str,
|
||||
is_static: bool,
|
||||
structural: bool,
|
||||
catch: bool,
|
||||
) -> Option<backend::ast::ImportFunction> {
|
||||
let (overloaded, same_argument_names) = self.get_operation_overloading(
|
||||
arguments,
|
||||
::first_pass::OperationId::Operation(name),
|
||||
&operation_id,
|
||||
self_name,
|
||||
);
|
||||
|
||||
let name = match name {
|
||||
None => {
|
||||
warn!("Operations without a name are unsupported");
|
||||
return None;
|
||||
}
|
||||
Some(ref name) => name,
|
||||
let name = match &operation_id {
|
||||
::first_pass::OperationId::Constructor => panic!("constructors are unsupported"),
|
||||
::first_pass::OperationId::Operation(name) => match name {
|
||||
None => {
|
||||
warn!("Operations without a name are unsupported");
|
||||
return None;
|
||||
}
|
||||
Some(name) => name.to_string(),
|
||||
},
|
||||
::first_pass::OperationId::IndexingGetter => "get".to_string(),
|
||||
::first_pass::OperationId::IndexingSetter => "set".to_string(),
|
||||
::first_pass::OperationId::IndexingDeleter => "delete".to_string(),
|
||||
};
|
||||
|
||||
let kind = backend::ast::ImportFunctionKind::Method {
|
||||
@ -935,7 +942,13 @@ impl<'src> FirstPassRecord<'src> {
|
||||
ty: ident_ty(rust_ident(camel_case_ident(&self_name).as_str())),
|
||||
kind: backend::ast::MethodKind::Operation(backend::ast::Operation {
|
||||
is_static,
|
||||
kind: backend::ast::OperationKind::Regular,
|
||||
kind: match &operation_id {
|
||||
::first_pass::OperationId::Constructor => panic!("constructors are unsupported"),
|
||||
::first_pass::OperationId::Operation(_) => backend::ast::OperationKind::Regular,
|
||||
::first_pass::OperationId::IndexingGetter => backend::ast::OperationKind::IndexingGetter,
|
||||
::first_pass::OperationId::IndexingSetter => backend::ast::OperationKind::IndexingSetter,
|
||||
::first_pass::OperationId::IndexingDeleter => backend::ast::OperationKind::IndexingDeleter,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
@ -951,7 +964,19 @@ impl<'src> FirstPassRecord<'src> {
|
||||
}
|
||||
}
|
||||
};
|
||||
let doc_comment = Some(format!("The `{}()` method\n\n{}", name, mdn_doc(self_name, Some(name))));
|
||||
let doc_comment = match &operation_id {
|
||||
::first_pass::OperationId::Constructor => panic!("constructors are unsupported"),
|
||||
::first_pass::OperationId::Operation(_) => Some(
|
||||
format!(
|
||||
"The `{}()` method\n\n{}",
|
||||
name,
|
||||
mdn_doc(self_name, Some(&name))
|
||||
)
|
||||
),
|
||||
::first_pass::OperationId::IndexingGetter => Some("The indexing getter\n\n".to_string()),
|
||||
::first_pass::OperationId::IndexingSetter => Some("The indexing setter\n\n".to_string()),
|
||||
::first_pass::OperationId::IndexingDeleter => Some("The indexing deleter\n\n".to_string()),
|
||||
};
|
||||
|
||||
self.create_function(
|
||||
&name,
|
||||
@ -960,11 +985,7 @@ impl<'src> FirstPassRecord<'src> {
|
||||
arguments,
|
||||
ret,
|
||||
kind,
|
||||
self
|
||||
.interfaces
|
||||
.get(self_name)
|
||||
.map(|interface_data| interface_data.global)
|
||||
.unwrap_or(false),
|
||||
structural,
|
||||
catch,
|
||||
doc_comment,
|
||||
)
|
||||
@ -975,14 +996,14 @@ impl<'src> FirstPassRecord<'src> {
|
||||
pub fn get_operation_overloading(
|
||||
&self,
|
||||
arguments: &[weedle::argument::Argument],
|
||||
id: ::first_pass::OperationId,
|
||||
id: &::first_pass::OperationId,
|
||||
self_name: &str,
|
||||
) -> (bool, bool) {
|
||||
let data = match self.interfaces.get(self_name) {
|
||||
Some(data) => data,
|
||||
None => return (false, false),
|
||||
};
|
||||
let data = match data.operations.get(&id) {
|
||||
let data = match data.operations.get(id) {
|
||||
Some(data) => data,
|
||||
None => return (false, false),
|
||||
};
|
||||
|
@ -126,7 +126,30 @@ possibilities!
|
||||
Properties in JS are accessed through `Object.getOwnPropertyDescriptor`. Note
|
||||
that this typically only works for class-like-defined properties which aren't
|
||||
just attached properties on any old object. For accessing any old property on
|
||||
an object we can use...
|
||||
an object we can use the `structural` flag.
|
||||
|
||||
* `indexing_getter`, `indexing_setter` and `indexing_deleter` - these three
|
||||
attributes can be combined with `method` to indicate that this is a indexing
|
||||
getter, indexing setter or indexing deleter method. They are different from
|
||||
`getter` and `setter` in a way that `getter` and `setter` can only access
|
||||
properties that have a name corresponding to the function name or their
|
||||
argument, but `indexing_getter`, `indexing_setter` and `indexing_deleter`
|
||||
work in a dynamic manner, similarly to the indexing syntax in JS
|
||||
(`object[propertyName]`), hence the name. Should always be used together with
|
||||
the `structural` flag. For example:
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
type Foo;
|
||||
#[wasm_bindgen(method, structural, indexing_getter)]
|
||||
fn get(this: &Foo, prop: &str) -> u32;
|
||||
#[wasm_bindgen(method, structural, indexing_setter)]
|
||||
fn set(this: &Foo, prop: &str, val: u32);
|
||||
#[wasm_bindgen(method, structural, indexing_deleter)]
|
||||
fn delete(this: &Foo, prop: &str);
|
||||
}
|
||||
```
|
||||
|
||||
* `structural` - this is a flag to `method` annotations which indicates that the
|
||||
method being accessed (or property with getters/setters) should be accessed in
|
||||
|
Loading…
Reference in New Issue
Block a user