Merge pull request #652 from afdw/master

Add support for getters, setters and deleters
This commit is contained in:
Alex Crichton
2018-08-06 21:43:53 -05:00
committed by GitHub
11 changed files with 217 additions and 44 deletions

View File

@ -105,8 +105,11 @@ pub struct Operation {
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum OperationKind { pub enum OperationKind {
Regular, Regular,
Setter(Option<Ident>),
Getter(Option<Ident>), Getter(Option<Ident>),
Setter(Option<Ident>),
IndexingGetter,
IndexingSetter,
IndexingDeleter,
} }
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@ -369,6 +372,9 @@ impl ImportFunction {
s.unwrap_or_else(|| self.infer_setter_property()), 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 }) shared::MethodKind::Operation(shared::Operation { is_static, kind })
} }

View File

@ -1808,18 +1808,6 @@ impl<'a, 'b> SubContext<'a, 'b> {
let location = if *is_static { &class } else { "this" }; let location = if *is_static { &class } else { "this" };
match kind { match kind {
shared::OperationKind::Getter(g) => format!(
"function() {{
return {}.{};
}}",
location, g
),
shared::OperationKind::Setter(s) => format!(
"function(y) {{
{}.{} = y;
}}",
location, s
),
shared::OperationKind::Regular => { shared::OperationKind::Regular => {
let nargs = descriptor.unwrap_function().arguments.len(); let nargs = descriptor.unwrap_function().arguments.len();
let mut s = format!("function("); let mut s = format!("function(");
@ -1841,11 +1829,44 @@ impl<'a, 'b> SubContext<'a, 'b> {
s.push_str(");\n}"); s.push_str(");\n}");
s 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 { } else {
let location = if *is_static { "" } else { ".prototype" }; let location = if *is_static { "" } else { ".prototype" };
match kind { match kind {
shared::OperationKind::Regular => {
format!("{}{}.{}", class, location, import.function.name)
}
shared::OperationKind::Getter(g) => { shared::OperationKind::Getter(g) => {
self.cx.expose_get_inherited_descriptor(); self.cx.expose_get_inherited_descriptor();
format!( format!(
@ -1860,9 +1881,9 @@ impl<'a, 'b> SubContext<'a, 'b> {
class, location, s, class, location, s,
) )
} }
shared::OperationKind::Regular => { shared::OperationKind::IndexingGetter => panic!("indexing getter should be structural"),
format!("{}{}.{}", class, location, import.function.name) shared::OperationKind::IndexingSetter => panic!("indexing setter should be structural"),
} shared::OperationKind::IndexingDeleter => panic!("indexing deleter should be structural"),
} }
}; };

View File

@ -121,6 +121,30 @@ impl BindgenAttrs {
.next() .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 /// Whether the structural attributes is present
fn structural(&self) -> bool { fn structural(&self) -> bool {
self.attrs.iter().any(|a| match *a { self.attrs.iter().any(|a| match *a {
@ -186,6 +210,9 @@ pub enum BindgenAttr {
Module(String), Module(String),
Getter(Option<Ident>), Getter(Option<Ident>),
Setter(Option<Ident>), Setter(Option<Ident>),
IndexingGetter,
IndexingSetter,
IndexingDeleter,
Structural, Structural,
Readonly, Readonly,
JsName(String), JsName(String),
@ -227,6 +254,12 @@ impl syn::synom::Synom for BindgenAttr {
(val) (val)
)=> { BindgenAttr::Setter } )=> { 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, "structural") => { |_| BindgenAttr::Structural }
| |
call!(term, "readonly") => { |_| BindgenAttr::Readonly } 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() { if let Some(s) = opts.setter() {
operation_kind = ast::OperationKind::Setter(s); 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 kind = if opts.method() {
let class = wasm let class = wasm

View File

@ -69,6 +69,9 @@ pub enum OperationKind {
Regular, Regular,
Getter(String), Getter(String),
Setter(String), Setter(String),
IndexingGetter,
IndexingSetter,
IndexingDeleter,
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]

View File

@ -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 { global.PartialInterface = class PartialInterface {
get un() { get un() {
return 1; return 1;

View File

@ -65,7 +65,17 @@ fn optional_method() {
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn global_method() { fn global_method() {
let f = GlobalMethod::new().unwrap(); 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] #[wasm_bindgen_test]

View File

@ -40,6 +40,13 @@ interface GlobalMethod {
octet m(); octet m();
}; };
[Constructor()]
interface Indexing {
getter short (unsigned long index);
setter void (unsigned long index, short value);
deleter void (unsigned long index);
};
[Constructor()] [Constructor()]
interface Unforgeable { interface Unforgeable {
[Unforgeable] readonly attribute short uno; [Unforgeable] readonly attribute short uno;

View File

@ -41,7 +41,10 @@ pub(crate) struct InterfaceData<'src> {
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum OperationId<'src> { pub(crate) enum OperationId<'src> {
Constructor, Constructor,
Operation(Option<&'src str>) Operation(Option<&'src str>),
IndexingGetter,
IndexingSetter,
IndexingDeleter,
} }
#[derive(Default)] #[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> { 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<()> { 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); warn!("Unsupported webidl operation {:?}", self);
return Ok(()) return Ok(())
} }
@ -254,7 +257,16 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceM
first_pass_operation( first_pass_operation(
record, record,
self_name, 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, &self.args.body.list,
) )
} }

View File

@ -299,7 +299,7 @@ impl<'src> WebidlParse<'src, &'src weedle::InterfaceDefinition<'src>> for Extend
let mut add_constructor = |arguments: &[Argument], class: &str| { let mut add_constructor = |arguments: &[Argument], class: &str| {
let (overloaded, same_argument_names) = first_pass.get_operation_overloading( let (overloaded, same_argument_names) = first_pass.get_operation_overloading(
arguments, arguments,
::first_pass::OperationId::Constructor, &::first_pass::OperationId::Constructor,
interface.identifier.0, interface.identifier.0,
); );
@ -644,18 +644,32 @@ fn member_operation<'src>(
Some(Static(_)) => true, Some(Static(_)) => true,
None => false, None => false,
}; };
if specials.len() > 0 {
warn!("Unsupported specials on type {:?}", (self_name, identifier));
return Ok(())
}
first_pass first_pass
.create_basic_method( .create_basic_method(
args, 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, return_type,
self_name, self_name,
statik, statik,
specials.len() == 1 || first_pass
.interfaces
.get(self_name)
.map(|interface_data| interface_data.global)
.unwrap_or(false),
util::throws(attrs), util::throws(attrs),
) )
.map(wrap_import_function) .map(wrap_import_function)

View File

@ -910,24 +910,31 @@ impl<'src> FirstPassRecord<'src> {
pub fn create_basic_method( pub fn create_basic_method(
&self, &self,
arguments: &[weedle::argument::Argument], arguments: &[weedle::argument::Argument],
name: Option<&str>, operation_id: ::first_pass::OperationId,
return_type: &weedle::types::ReturnType, return_type: &weedle::types::ReturnType,
self_name: &str, self_name: &str,
is_static: bool, is_static: bool,
structural: bool,
catch: bool, catch: bool,
) -> Option<backend::ast::ImportFunction> { ) -> Option<backend::ast::ImportFunction> {
let (overloaded, same_argument_names) = self.get_operation_overloading( let (overloaded, same_argument_names) = self.get_operation_overloading(
arguments, arguments,
::first_pass::OperationId::Operation(name), &operation_id,
self_name, self_name,
); );
let name = match name { let name = match &operation_id {
::first_pass::OperationId::Constructor => panic!("constructors are unsupported"),
::first_pass::OperationId::Operation(name) => match name {
None => { None => {
warn!("Operations without a name are unsupported"); warn!("Operations without a name are unsupported");
return None; return None;
} }
Some(ref name) => name, 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 { 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())), ty: ident_ty(rust_ident(camel_case_ident(&self_name).as_str())),
kind: backend::ast::MethodKind::Operation(backend::ast::Operation { kind: backend::ast::MethodKind::Operation(backend::ast::Operation {
is_static, 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( self.create_function(
&name, &name,
@ -960,11 +985,7 @@ impl<'src> FirstPassRecord<'src> {
arguments, arguments,
ret, ret,
kind, kind,
self structural,
.interfaces
.get(self_name)
.map(|interface_data| interface_data.global)
.unwrap_or(false),
catch, catch,
doc_comment, doc_comment,
) )
@ -975,14 +996,14 @@ impl<'src> FirstPassRecord<'src> {
pub fn get_operation_overloading( pub fn get_operation_overloading(
&self, &self,
arguments: &[weedle::argument::Argument], arguments: &[weedle::argument::Argument],
id: ::first_pass::OperationId, id: &::first_pass::OperationId,
self_name: &str, self_name: &str,
) -> (bool, bool) { ) -> (bool, bool) {
let data = match self.interfaces.get(self_name) { let data = match self.interfaces.get(self_name) {
Some(data) => data, Some(data) => data,
None => return (false, false), None => return (false, false),
}; };
let data = match data.operations.get(&id) { let data = match data.operations.get(id) {
Some(data) => data, Some(data) => data,
None => return (false, false), None => return (false, false),
}; };

View File

@ -126,7 +126,30 @@ possibilities!
Properties in JS are accessed through `Object.getOwnPropertyDescriptor`. Note Properties in JS are accessed through `Object.getOwnPropertyDescriptor`. Note
that this typically only works for class-like-defined properties which aren't 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 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 * `structural` - this is a flag to `method` annotations which indicates that the
method being accessed (or property with getters/setters) should be accessed in method being accessed (or property with getters/setters) should be accessed in