webidl: add support for static attributes

This commit is contained in:
R. Andrew Ohana 2018-06-14 22:48:32 -07:00
parent fe5cde8636
commit 0938858aa8
6 changed files with 275 additions and 175 deletions

View File

@ -701,24 +701,6 @@ impl ImportFunction {
} }
fn shared(&self) -> shared::ImportFunction { fn shared(&self) -> shared::ImportFunction {
let mut method = false;
let mut js_new = false;
let mut class_name = None;
match self.kind {
ImportFunctionKind::Method {
ref class,
ref kind,
..
} => {
match kind {
MethodKind::Normal => method = true,
MethodKind::Constructor => js_new = true,
MethodKind::Static => {}
}
class_name = Some(class);
}
ImportFunctionKind::Normal => {}
}
let mut getter = None; let mut getter = None;
let mut setter = None; let mut setter = None;
@ -730,15 +712,34 @@ impl ImportFunction {
let s = s.map(|s| s.to_string()); let s = s.map(|s| s.to_string());
setter = Some(s.unwrap_or_else(|| self.infer_setter_property())); setter = Some(s.unwrap_or_else(|| self.infer_setter_property()));
} }
let mut method = None;
match self.kind {
ImportFunctionKind::Method {
ref class,
ref kind,
..
} => {
let kind = match kind {
MethodKind::Normal => shared::MethodKind::Normal,
MethodKind::Constructor => shared::MethodKind::Constructor,
MethodKind::Static => shared::MethodKind::Static,
};
method = Some(shared::MethodData {
class: class.clone(),
kind,
getter,
setter,
});
}
ImportFunctionKind::Normal => {}
}
shared::ImportFunction { shared::ImportFunction {
shim: self.shim.to_string(), shim: self.shim.to_string(),
catch: self.function.opts.catch(), catch: self.function.opts.catch(),
method, method,
js_new,
structural: self.function.opts.structural(), structural: self.function.opts.structural(),
getter,
setter,
class: class_name.cloned(),
function: self.function.shared(), function: self.function.shared(),
} }
} }

View File

@ -1605,76 +1605,78 @@ impl<'a, 'b> SubContext<'a, 'b> {
{ {
let descriptor = self.cx.describe(&import.shim); let descriptor = self.cx.describe(&import.shim);
let target = match import.class { let target = match &import.method {
Some(ref class) if import.js_new => { Some(shared::MethodData { class, kind, getter, setter }) => {
format!("new {}", self.import_name(info, class)?)
}
Some(ref class) if import.method => {
let class = self.import_name(info, class)?; let class = self.import_name(info, class)?;
let target = if let Some(ref g) = import.getter { if let shared::MethodKind::Constructor = kind {
if import.structural { format!("new {}", class)
format!("function() {{
return this.{};
}}", g)
} else {
self.cx.expose_get_inherited_descriptor();
format!(
"GetOwnOrInheritedPropertyDescriptor\
({}.prototype, '{}').get",
class,
g,
)
}
} else if let Some(ref s) = import.setter {
if import.structural {
format!("function(y) {{
this.{} = y;
}}", s)
} else {
self.cx.expose_get_inherited_descriptor();
format!(
"GetOwnOrInheritedPropertyDescriptor\
({}.prototype, '{}').set",
class,
s,
)
}
} else { } else {
if import.structural { let is_static = if let shared::MethodKind::Static = kind {
let nargs = descriptor.unwrap_function().arguments.len(); true
let mut s = format!("function(");
for i in 0..nargs - 1 {
if i > 0 {
drop(write!(s, ", "));
}
drop(write!(s, "x{}", i));
}
s.push_str(") { \nreturn this.");
s.push_str(&import.function.name);
s.push_str("(");
for i in 0..nargs - 1 {
if i > 0 {
drop(write!(s, ", "));
}
drop(write!(s, "x{}", i));
}
s.push_str(");\n}");
s
} else { } else {
format!("{}.prototype.{}", class, import.function.name) false
} };
};
self.cx.global(&format!(" let target = if let Some(g) = getter {
const {}_target = {}; if import.structural {
", import.shim, target)); format!("function(y) {{
format!("{}_target.call", import.shim) return this.{};
} }}", g)
Some(ref class) => { } else {
let class = self.import_name(info, class)?; self.cx.expose_get_inherited_descriptor();
self.cx.global(&format!(" format!(
const {}_target = {}.{}; "GetOwnOrInheritedPropertyDescriptor\
", import.shim, class, import.function.name)); ({}{}, '{}').get",
format!("{}_target", import.shim) class,
if is_static { "" } else { ".prototype "},
g,
)
}
} else if let Some(s) = setter {
if import.structural {
format!("function(y) {{
this.{} = y;
}}", s)
} else {
self.cx.expose_get_inherited_descriptor();
format!(
"GetOwnOrInheritedPropertyDescriptor\
({}{}, '{}').set",
class,
if is_static { "" } else { ".prototype "},
s,
)
}
} else {
if import.structural {
let nargs = descriptor.unwrap_function().arguments.len();
let mut s = format!("function(");
for i in 0..nargs - 1 {
if i > 0 {
drop(write!(s, ", "));
}
drop(write!(s, "x{}", i));
}
s.push_str(") { \nreturn this.");
s.push_str(&import.function.name);
s.push_str("(");
for i in 0..nargs - 1 {
if i > 0 {
drop(write!(s, ", "));
}
drop(write!(s, "x{}", i));
}
s.push_str(");\n}");
s
} else {
format!("{}{}.{}", class, if is_static { "" } else { ".prototype" }, import.function.name)
}
};
self.cx.global(&format!("
const {}_target = {};
", import.shim, target));
format!("{}_target{}", import.shim, if is_static { "" } else { ".call" })
}
} }
None => { None => {
let name = self.import_name(info, &import.function.name)?; let name = self.import_name(info, &import.function.name)?;

View File

@ -39,13 +39,24 @@ pub enum ImportKind {
pub struct ImportFunction { pub struct ImportFunction {
pub shim: String, pub shim: String,
pub catch: bool, pub catch: bool,
pub method: bool, pub method: Option<MethodData>,
pub js_new: bool,
pub structural: bool, pub structural: bool,
pub function: Function,
}
#[derive(Deserialize, Serialize)]
pub struct MethodData {
pub class: String,
pub kind: MethodKind,
pub getter: Option<String>, pub getter: Option<String>,
pub setter: Option<String>, pub setter: Option<String>,
pub class: Option<String>, }
pub function: Function,
#[derive(Deserialize, Serialize)]
pub enum MethodKind {
Normal,
Constructor,
Static,
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
@ -55,8 +66,7 @@ pub struct ImportStatic {
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct ImportType { pub struct ImportType {}
}
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Export { pub struct Export {
@ -77,7 +87,7 @@ pub struct Enum {
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct EnumVariant { pub struct EnumVariant {
pub name: String, pub name: String,
pub value: u32 pub value: u32,
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
@ -101,20 +111,16 @@ pub struct StructField {
pub fn new_function(struct_name: &str) -> String { pub fn new_function(struct_name: &str) -> String {
let mut name = format!("__wbg_"); let mut name = format!("__wbg_");
name.extend(struct_name name.extend(struct_name.chars().flat_map(|s| s.to_lowercase()));
.chars()
.flat_map(|s| s.to_lowercase()));
name.push_str("_new"); name.push_str("_new");
return name return name;
} }
pub fn free_function(struct_name: &str) -> String { pub fn free_function(struct_name: &str) -> String {
let mut name = format!("__wbg_"); let mut name = format!("__wbg_");
name.extend(struct_name name.extend(struct_name.chars().flat_map(|s| s.to_lowercase()));
.chars()
.flat_map(|s| s.to_lowercase()));
name.push_str("_free"); name.push_str("_free");
return name return name;
} }
pub fn free_function_export_name(function_name: &str) -> String { pub fn free_function_export_name(function_name: &str) -> String {
@ -128,27 +134,23 @@ pub fn struct_function_export_name(struct_: &str, f: &str) -> String {
.collect::<String>(); .collect::<String>();
name.push_str("_"); name.push_str("_");
name.push_str(f); name.push_str(f);
return name return name;
} }
pub fn struct_field_get(struct_: &str, f: &str) -> String { pub fn struct_field_get(struct_: &str, f: &str) -> String {
let mut name = String::from("__wbg_get_"); let mut name = String::from("__wbg_get_");
name.extend(struct_ name.extend(struct_.chars().flat_map(|s| s.to_lowercase()));
.chars()
.flat_map(|s| s.to_lowercase()));
name.push_str("_"); name.push_str("_");
name.push_str(f); name.push_str(f);
return name return name;
} }
pub fn struct_field_set(struct_: &str, f: &str) -> String { pub fn struct_field_set(struct_: &str, f: &str) -> String {
let mut name = String::from("__wbg_set_"); let mut name = String::from("__wbg_set_");
name.extend(struct_ name.extend(struct_.chars().flat_map(|s| s.to_lowercase()));
.chars()
.flat_map(|s| s.to_lowercase()));
name.push_str("_"); name.push_str("_");
name.push_str(f); name.push_str(f);
return name return name;
} }
pub fn version() -> String { pub fn version() -> String {
@ -158,5 +160,5 @@ pub fn version() -> String {
v.push_str(s); v.push_str(s);
v.push_str(")"); v.push_str(")");
} }
return v return v;
} }

View File

@ -29,8 +29,8 @@ use quote::ToTokens;
mod util; mod util;
use util::{ use util::{
create_basic_method, create_function, ident_ty, raw_ident, rust_ident, webidl_ty_to_syn_ty, create_basic_method, create_function, create_getter, create_setter, ident_ty, rust_ident,
wrap_import_function, TypePosition, webidl_ty_to_syn_ty, wrap_import_function, TypePosition,
}; };
/// Either `Ok(t)` or `Err(failure::Error)`. /// Either `Ok(t)` or `Err(failure::Error)`.
@ -265,10 +265,11 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::InterfaceMember {
impl<'a> WebidlParse<&'a str> for webidl::ast::Attribute { impl<'a> WebidlParse<&'a str> for webidl::ast::Attribute {
fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> {
match *self { match self {
webidl::ast::Attribute::Regular(ref attr) => attr.webidl_parse(program, self_name), webidl::ast::Attribute::Regular(attr) => attr.webidl_parse(program, self_name),
webidl::ast::Attribute::Static(attr) => attr.webidl_parse(program, self_name),
// TODO // TODO
webidl::ast::Attribute::Static(_) | webidl::ast::Attribute::Stringifier(_) => { webidl::ast::Attribute::Stringifier(_) => {
warn!("Unsupported WebIDL attribute: {:?}", self); warn!("Unsupported WebIDL attribute: {:?}", self);
Ok(()) Ok(())
} }
@ -292,61 +293,46 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::Operation {
impl<'a> WebidlParse<&'a str> for webidl::ast::RegularAttribute { impl<'a> WebidlParse<&'a str> for webidl::ast::RegularAttribute {
fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> {
fn create_getter( create_getter(
this: &webidl::ast::RegularAttribute, &self.name,
self_name: &str, &self.type_,
) -> Option<backend::ast::Import> { self_name,
let ret = match webidl_ty_to_syn_ty(&this.type_, TypePosition::Return) { backend::ast::MethodKind::Normal,
None => { ).map(wrap_import_function)
warn!("Attribute's type does not yet support reading: {:?}. Skipping getter binding for {:?}", .map(|import| program.imports.push(import));
this.type_, this);
return None;
}
Some(ty) => Some(ty),
};
let kind = backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty: ident_ty(rust_ident(self_name)),
kind: backend::ast::MethodKind::Normal,
};
create_function(
&this.name,
iter::empty(),
kind,
ret,
vec![backend::ast::BindgenAttr::Getter(Some(raw_ident(
&this.name,
)))],
).map(wrap_import_function)
}
fn create_setter(
this: &webidl::ast::RegularAttribute,
self_name: &str,
) -> Option<backend::ast::Import> {
let kind = backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty: ident_ty(rust_ident(self_name)),
kind: backend::ast::MethodKind::Normal,
};
create_function(
&format!("set_{}", this.name),
iter::once((&*this.name, &*this.type_, false)),
kind,
None,
vec![backend::ast::BindgenAttr::Setter(Some(raw_ident(
&this.name,
)))],
).map(wrap_import_function)
}
create_getter(self, self_name).map(|import| program.imports.push(import));
if !self.read_only { if !self.read_only {
create_setter(self, self_name).map(|import| program.imports.push(import)); create_setter(
&self.name,
&self.type_,
self_name,
backend::ast::MethodKind::Normal,
).map(wrap_import_function)
.map(|import| program.imports.push(import));
}
Ok(())
}
}
impl<'a> WebidlParse<&'a str> for webidl::ast::StaticAttribute {
fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> {
create_getter(
&self.name,
&self.type_,
self_name,
backend::ast::MethodKind::Static,
).map(wrap_import_function)
.map(|import| program.imports.push(import));
if !self.read_only {
create_setter(
&self.name,
&self.type_,
self_name,
backend::ast::MethodKind::Static,
).map(wrap_import_function)
.map(|import| program.imports.push(import));
} }
Ok(()) Ok(())

View File

@ -1,4 +1,4 @@
use std::iter::FromIterator; use std::iter::{self, FromIterator};
use backend; use backend;
use heck::SnakeCase; use heck::SnakeCase;
@ -30,7 +30,7 @@ pub fn rust_ident(name: &str) -> Ident {
// Create an `Ident` without checking to see if it conflicts with a Rust // Create an `Ident` without checking to see if it conflicts with a Rust
// keyword. // keyword.
pub fn raw_ident(name: &str) -> Ident { fn raw_ident(name: &str) -> Ident {
Ident::new(name, proc_macro2::Span::call_site()) Ident::new(name, proc_macro2::Span::call_site())
} }
@ -288,6 +288,56 @@ pub fn create_basic_method(
) )
} }
pub fn create_getter(
name: &str,
ty: &webidl::ast::Type,
self_name: &str,
kind: backend::ast::MethodKind,
) -> Option<backend::ast::ImportFunction> {
let ret = match webidl_ty_to_syn_ty(ty, TypePosition::Return) {
None => {
warn!("Attribute's type does not yet support reading: {:?}", ty);
return None;
}
Some(ty) => Some(ty),
};
let kind = backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty: ident_ty(rust_ident(self_name)),
kind,
};
create_function(
name,
iter::empty(),
kind,
ret,
vec![backend::ast::BindgenAttr::Getter(Some(raw_ident(name)))],
)
}
pub fn create_setter(
name: &str,
ty: &webidl::ast::Type,
self_name: &str,
kind: backend::ast::MethodKind,
) -> Option<backend::ast::ImportFunction> {
let kind = backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty: ident_ty(rust_ident(self_name)),
kind,
};
create_function(
&format!("set_{}", name),
iter::once((name, ty, false)),
kind,
None,
vec![backend::ast::BindgenAttr::Setter(Some(raw_ident(name)))],
)
}
pub fn wrap_import_function(function: backend::ast::ImportFunction) -> backend::ast::Import { pub fn wrap_import_function(function: backend::ast::ImportFunction) -> backend::ast::Import {
backend::ast::Import { backend::ast::Import {
module: None, module: None,

View File

@ -184,7 +184,6 @@ fn static_method() {
"foo.webidl", "foo.webidl",
r#" r#"
interface Foo { interface Foo {
[Pure]
static double swap(double value); static double swap(double value);
}; };
"#, "#,
@ -232,3 +231,63 @@ fn static_method() {
) )
.test(); .test();
} }
#[test]
fn static_property() {
project()
.file(
"foo.webidl",
r#"
interface Foo {
static attribute double value;
};
"#,
)
.file(
"foo.ts",
r#"
export class Foo {
private static _value: number = 0;
static get value(): number {
return Foo._value;
}
static set value(_value: number) {
Foo._value = _value;
}
}
"#,
)
.file(
"src/lib.rs",
r#"
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
pub mod foo;
use foo::Foo;
#[wasm_bindgen]
pub fn test() {
let tmp = Foo::value() == 0.;
assert!(tmp);
Foo::set_value(3.14159);
let tmp = Foo::value() == 3.14159;
assert!(tmp);
let tmp = Foo::value() != 2.71828;
assert!(tmp);
Foo::set_value(2.71828);
let tmp = Foo::value() == 2.71828;
assert!(tmp);
let tmp = Foo::value() != 3.14159;
assert!(tmp);
}
"#,
)
.test();
}