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
commit b6a6dee7f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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))]
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 })
}

View File

@ -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"),
}
};

View File

@ -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

View File

@ -69,6 +69,9 @@ pub enum OperationKind {
Regular,
Getter(String),
Setter(String),
IndexingGetter,
IndexingSetter,
IndexingDeleter,
}
#[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 {
get un() {
return 1;

View File

@ -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]

View File

@ -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;

View File

@ -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,
)
}

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 (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)

View File

@ -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),
};

View File

@ -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