Merge pull request #761 from alexcrichton/more-globals

web-sys: Add support for `Global`-scope methods
This commit is contained in:
Alex Crichton 2018-08-28 18:36:53 -07:00 committed by GitHub
commit 1565459107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 296 additions and 202 deletions

View File

@ -99,6 +99,10 @@ pub enum ImportFunctionKind {
ty: syn::Type, ty: syn::Type,
kind: MethodKind, kind: MethodKind,
}, },
ScopedMethod {
ty: syn::Type,
operation: Operation,
},
Normal, Normal,
} }
@ -407,6 +411,29 @@ impl ImportFunction {
} }
fn shared(&self) -> shared::ImportFunction { fn shared(&self) -> shared::ImportFunction {
let shared_operation = |operation: &Operation| {
let is_static = operation.is_static;
let kind = match &operation.kind {
OperationKind::Regular => shared::OperationKind::Regular,
OperationKind::Getter(g) => {
let g = g.as_ref().map(|g| g.to_string());
shared::OperationKind::Getter(
g.unwrap_or_else(|| self.infer_getter_property()),
)
}
OperationKind::Setter(s) => {
let s = s.as_ref().map(|s| s.to_string());
shared::OperationKind::Setter(
s.unwrap_or_else(|| self.infer_setter_property()),
)
}
OperationKind::IndexingGetter => shared::OperationKind::IndexingGetter,
OperationKind::IndexingSetter => shared::OperationKind::IndexingSetter,
OperationKind::IndexingDeleter => shared::OperationKind::IndexingDeleter,
};
shared::Operation { is_static, kind }
};
let method = match self.kind { let method = match self.kind {
ImportFunctionKind::Method { ImportFunctionKind::Method {
ref class, ref class,
@ -415,34 +442,21 @@ impl ImportFunction {
} => { } => {
let kind = match kind { let kind = match kind {
MethodKind::Constructor => shared::MethodKind::Constructor, MethodKind::Constructor => shared::MethodKind::Constructor,
MethodKind::Operation(Operation { is_static, kind }) => { MethodKind::Operation(op) => {
let is_static = *is_static; shared::MethodKind::Operation(shared_operation(op))
let kind = match kind {
OperationKind::Regular => shared::OperationKind::Regular,
OperationKind::Getter(g) => {
let g = g.as_ref().map(|g| g.to_string());
shared::OperationKind::Getter(
g.unwrap_or_else(|| self.infer_getter_property()),
)
}
OperationKind::Setter(s) => {
let s = s.as_ref().map(|s| s.to_string());
shared::OperationKind::Setter(
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 })
} }
}; };
Some(shared::MethodData { Some(shared::MethodData {
class: class.clone(), class: Some(class.clone()),
kind, kind,
}) })
} }
ImportFunctionKind::ScopedMethod { ref operation, .. } => {
Some(shared::MethodData {
class: None,
kind: shared::MethodKind::Operation(shared_operation(operation)),
})
}
ImportFunctionKind::Normal => None, ImportFunctionKind::Normal => None,
}; };

View File

@ -784,6 +784,9 @@ impl TryToTokens for ast::ImportFunction {
} }
class_ty = Some(ty); class_ty = Some(ty);
} }
ast::ImportFunctionKind::ScopedMethod { ref ty, .. } => {
class_ty = Some(ty);
}
ast::ImportFunctionKind::Normal => {} ast::ImportFunctionKind::Normal => {}
} }
let vis = &self.function.rust_vis; let vis = &self.function.rust_vis;

View File

@ -246,6 +246,7 @@ impl ImportedTypes for ast::ImportFunctionKind {
{ {
match self { match self {
ast::ImportFunctionKind::Method { ty, .. } => ty.imported_types(f), ast::ImportFunctionKind::Method { ty, .. } => ty.imported_types(f),
ast::ImportFunctionKind::ScopedMethod { ty, .. } => ty.imported_types(f),
ast::ImportFunctionKind::Normal => {} ast::ImportFunctionKind::Normal => {}
} }
} }

View File

@ -1959,127 +1959,27 @@ impl<'a, 'b> SubContext<'a, 'b> {
Some(d) => d, Some(d) => d,
}; };
let target = match &import.method { let target = self.generated_import_target(info, import, &descriptor)?;
Some(shared::MethodData { class, kind }) => {
let class = self.import_name(info, class)?;
match kind {
shared::MethodKind::Constructor => format!("new {}", class),
shared::MethodKind::Operation(shared::Operation { is_static, kind }) => {
let target = if import.structural {
let location = if *is_static { &class } else { "this" };
match kind { let js = Rust2Js::new(self.cx)
shared::OperationKind::Regular => { .catch(import.catch)
let nargs = descriptor.unwrap_function().arguments.len(); .process(descriptor.unwrap_function())?
let mut s = format!("function("); .finish(&target);
for i in 0..nargs - 1 { self.cx.export(&import.shim, &js, None);
if i > 0 { Ok(())
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
}
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, binding) = if *is_static {
("", format!(".bind({})", class))
} else {
(".prototype", "".into())
};
match kind { fn generated_import_target(
shared::OperationKind::Regular => { &mut self,
format!("{}{}.{}{}", class, location, import.function.name, binding) info: &shared::Import,
} import: &shared::ImportFunction,
shared::OperationKind::Getter(g) => { descriptor: &Descriptor,
self.cx.expose_get_inherited_descriptor(); ) -> Result<String, Error> {
format!( let method_data = match &import.method {
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').get{}", Some(data) => data,
class, location, g, binding,
)
}
shared::OperationKind::Setter(s) => {
self.cx.expose_get_inherited_descriptor();
format!(
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').set{}",
class, location, s, binding,
)
}
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"),
}
};
let fallback = if import.structural {
"".to_string()
} else {
format!(
" || function() {{
throw new Error(`wasm-bindgen: {} does not exist`);
}}",
target
)
};
self.cx.global(&format!(
"
const {}_target = {} {} ;
",
import.shim, target, fallback
));
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)?;
if name.contains(".") { return Ok(if name.contains(".") {
self.cx.global(&format!( self.cx.global(&format!(
" "
const {}_target = {}; const {}_target = {};
@ -2089,16 +1989,145 @@ impl<'a, 'b> SubContext<'a, 'b> {
format!("{}_target", import.shim) format!("{}_target", import.shim)
} else { } else {
name name
} })
} }
}; };
let js = Rust2Js::new(self.cx) let class = match &method_data.class {
.catch(import.catch) Some(class) => self.import_name(info, class)?,
.process(descriptor.unwrap_function())? None => {
.finish(&target); let op = match &method_data.kind {
self.cx.export(&import.shim, &js, None); shared::MethodKind::Operation(op) => op,
Ok(()) shared::MethodKind::Constructor => {
bail!("\"no class\" methods cannot be constructors")
}
};
match &op.kind {
shared::OperationKind::Regular => {
return Ok(import.function.name.to_string())
}
shared::OperationKind::Getter(g) => {
return Ok(format!("(() => {})", g));
}
shared::OperationKind::Setter(g) => {
return Ok(format!("(v => {} = v)", g));
}
_ => bail!("\"no class\" methods must be regular/getter/setter"),
}
}
};
let op = match &method_data.kind {
shared::MethodKind::Constructor => return Ok(format!("new {}", class)),
shared::MethodKind::Operation(op) => op,
};
let target = if import.structural {
let location = if op.is_static { &class } else { "this" };
match &op.kind {
shared::OperationKind::Regular => {
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
}
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, binding) = if op.is_static {
("", format!(".bind({})", class))
} else {
(".prototype", "".into())
};
match &op.kind {
shared::OperationKind::Regular => {
format!("{}{}.{}{}", class, location, import.function.name, binding)
}
shared::OperationKind::Getter(g) => {
self.cx.expose_get_inherited_descriptor();
format!(
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').get{}",
class, location, g, binding,
)
}
shared::OperationKind::Setter(s) => {
self.cx.expose_get_inherited_descriptor();
format!(
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').set{}",
class, location, s, binding,
)
}
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"),
}
};
let fallback = if import.structural {
"".to_string()
} else {
format!(
" || function() {{
throw new Error(`wasm-bindgen: {} does not exist`);
}}",
target
)
};
self.cx.global(&format!(
"const {}_target = {}{};",
import.shim, target, fallback
));
Ok(format!(
"{}_target{}",
import.shim,
if op.is_static { "" } else { ".call" }
))
} }
fn generate_import_type( fn generate_import_type(

View File

@ -514,6 +514,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
let shim = { let shim = {
let ns = match kind { let ns = match kind {
ast::ImportFunctionKind::ScopedMethod { .. } |
ast::ImportFunctionKind::Normal => (0, "n"), ast::ImportFunctionKind::Normal => (0, "n"),
ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]), ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
}; };

View File

@ -48,7 +48,7 @@ pub struct ImportFunction {
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct MethodData { pub struct MethodData {
pub class: String, pub class: Option<String>,
pub kind: MethodKind, pub kind: MethodKind,
} }

View File

@ -0,0 +1,4 @@
global.global_no_args = () => 3;
global.global_with_args = (a, b) => a + b;
global.global_attribute = 'x';

View File

@ -0,0 +1,12 @@
use wasm_bindgen_test::*;
include!(concat!(env!("OUT_DIR"), "/global.rs"));
#[wasm_bindgen_test]
fn works() {
assert_eq!(Global::global_no_args(), 3);
assert_eq!(Global::global_with_args("a", "b"), "ab");
assert_eq!(Global::global_attribute(), "x");
Global::set_global_attribute("y");
assert_eq!(Global::global_attribute(), "y");
}

6
crates/webidl-tests/global.webidl vendored Normal file
View File

@ -0,0 +1,6 @@
[Global=x]
interface Global {
unsigned long global_no_args();
DOMString global_with_args(DOMString a, DOMString b);
attribute DOMString global_attribute;
};

View File

@ -10,3 +10,4 @@ pub mod namespace;
pub mod simple; pub mod simple;
pub mod throws; pub mod throws;
pub mod dictionary; pub mod dictionary;
pub mod global;

View File

@ -87,11 +87,7 @@ global.Unforgeable = class Unforgeable {
} }
}; };
global.GlobalMethod = class GlobalMethod { global.m = () => 123;
constructor() {
this.m = () => 123;
}
};
global.Indexing = function () { global.Indexing = function () {
return new Proxy({}, { return new Proxy({}, {

View File

@ -64,8 +64,7 @@ fn nullable_method() {
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn global_method() { fn global_method() {
let f = GlobalMethod::new().unwrap(); assert_eq!(GlobalMethod::m(), 123);
assert_eq!(f.m(), 123);
} }
#[wasm_bindgen_test] #[wasm_bindgen_test]

View File

@ -591,6 +591,11 @@ fn member_attribute<'src>(
let is_structural = util::is_structural(attrs); let is_structural = util::is_structural(attrs);
let throws = util::throws(attrs); let throws = util::throws(attrs);
let global = first_pass
.interfaces
.get(self_name)
.map(|interface_data| interface_data.global)
.unwrap_or(false);
for import_function in first_pass.create_getter( for import_function in first_pass.create_getter(
identifier, identifier,
@ -599,6 +604,7 @@ fn member_attribute<'src>(
is_static, is_static,
is_structural, is_structural,
throws, throws,
global,
) { ) {
program.imports.push(wrap_import_function(import_function)); program.imports.push(wrap_import_function(import_function));
} }
@ -611,6 +617,7 @@ fn member_attribute<'src>(
is_static, is_static,
is_structural, is_structural,
throws, throws,
global,
) { ) {
program.imports.push(wrap_import_function(import_function)); program.imports.push(wrap_import_function(import_function));
} }
@ -712,6 +719,12 @@ fn member_operation<'src>(
operation_ids.push(id); operation_ids.push(id);
} }
let global = first_pass
.interfaces
.get(self_name)
.map(|interface_data| interface_data.global)
.unwrap_or(false);
for id in operation_ids { for id in operation_ids {
let methods = first_pass let methods = first_pass
.create_basic_method( .create_basic_method(
@ -724,15 +737,10 @@ fn member_operation<'src>(
OperationId::IndexingGetter | OperationId::IndexingGetter |
OperationId::IndexingSetter | OperationId::IndexingSetter |
OperationId::IndexingDeleter => true, OperationId::IndexingDeleter => true,
_ => { _ => false,
first_pass
.interfaces
.get(self_name)
.map(|interface_data| interface_data.global)
.unwrap_or(false)
}
}, },
util::throws(attrs), util::throws(attrs),
global,
); );
for method in methods { for method in methods {

View File

@ -297,6 +297,7 @@ impl<'src> FirstPassRecord<'src> {
let rust_name = rust_ident(&rust_name); let rust_name = rust_ident(&rust_name);
let shim = { let shim = {
let ns = match kind { let ns = match kind {
backend::ast::ImportFunctionKind::ScopedMethod { .. } |
backend::ast::ImportFunctionKind::Normal => "", backend::ast::ImportFunctionKind::Normal => "",
backend::ast::ImportFunctionKind::Method { ref class, .. } => class, backend::ast::ImportFunctionKind::Method { ref class, .. } => class,
}; };
@ -389,6 +390,7 @@ impl<'src> FirstPassRecord<'src> {
is_static: bool, is_static: bool,
structural: bool, structural: bool,
catch: bool, catch: bool,
global: bool,
) -> Vec<backend::ast::ImportFunction> { ) -> Vec<backend::ast::ImportFunction> {
let (overloaded, same_argument_names) = self.get_operation_overloading( let (overloaded, same_argument_names) = self.get_operation_overloading(
arguments, arguments,
@ -410,20 +412,26 @@ impl<'src> FirstPassRecord<'src> {
first_pass::OperationId::IndexingSetter => "set", first_pass::OperationId::IndexingSetter => "set",
first_pass::OperationId::IndexingDeleter => "delete", first_pass::OperationId::IndexingDeleter => "delete",
}; };
let operation_kind = match &operation_id {
let kind = backend::ast::ImportFunctionKind::Method { first_pass::OperationId::Constructor => panic!("constructors are unsupported"),
class: self_name.to_string(), first_pass::OperationId::Operation(_) => backend::ast::OperationKind::Regular,
ty: ident_ty(rust_ident(camel_case_ident(&self_name).as_str())), first_pass::OperationId::IndexingGetter => backend::ast::OperationKind::IndexingGetter,
kind: backend::ast::MethodKind::Operation(backend::ast::Operation { first_pass::OperationId::IndexingSetter => backend::ast::OperationKind::IndexingSetter,
is_static, first_pass::OperationId::IndexingDeleter => backend::ast::OperationKind::IndexingDeleter,
kind: match &operation_id { };
first_pass::OperationId::Constructor => panic!("constructors are unsupported"), let operation = backend::ast::Operation { is_static, kind: operation_kind };
first_pass::OperationId::Operation(_) => backend::ast::OperationKind::Regular, let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str()));
first_pass::OperationId::IndexingGetter => backend::ast::OperationKind::IndexingGetter, let kind = if global {
first_pass::OperationId::IndexingSetter => backend::ast::OperationKind::IndexingSetter, backend::ast::ImportFunctionKind::ScopedMethod {
first_pass::OperationId::IndexingDeleter => backend::ast::OperationKind::IndexingDeleter, ty,
}, operation,
}), }
} else {
backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty,
kind: backend::ast::MethodKind::Operation(operation),
}
}; };
let ret = match return_type.to_idl_type(self) { let ret = match return_type.to_idl_type(self) {
@ -591,19 +599,29 @@ impl<'src> FirstPassRecord<'src> {
is_static: bool, is_static: bool,
is_structural: bool, is_structural: bool,
catch: bool, catch: bool,
global: bool,
) -> Vec<backend::ast::ImportFunction> { ) -> Vec<backend::ast::ImportFunction> {
let ret = match ty.to_idl_type(self) { let ret = match ty.to_idl_type(self) {
None => return Vec::new(), None => return Vec::new(),
Some(idl_type) => idl_type, Some(idl_type) => idl_type,
}; };
let operation = backend::ast::Operation {
is_static,
kind: backend::ast::OperationKind::Getter(Some(raw_ident(name))),
};
let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str()));
let kind = backend::ast::ImportFunctionKind::Method { let kind = if global {
class: self_name.to_string(), backend::ast::ImportFunctionKind::ScopedMethod {
ty: ident_ty(rust_ident(camel_case_ident(&self_name).as_str())), ty,
kind: backend::ast::MethodKind::Operation(backend::ast::Operation { operation,
is_static, }
kind: backend::ast::OperationKind::Getter(Some(raw_ident(name))), } else {
}), backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty,
kind: backend::ast::MethodKind::Operation(operation),
}
}; };
let doc_comment = Some(format!("The `{}` getter\n\n{}", name, mdn_doc(self_name, Some(name)))); let doc_comment = Some(format!("The `{}` getter\n\n{}", name, mdn_doc(self_name, Some(name))));
@ -614,19 +632,30 @@ impl<'src> FirstPassRecord<'src> {
pub fn create_setter( pub fn create_setter(
&self, &self,
name: &str, name: &str,
ty: weedle::types::Type, field_ty: weedle::types::Type,
self_name: &str, self_name: &str,
is_static: bool, is_static: bool,
is_structural: bool, is_structural: bool,
catch: bool, catch: bool,
global: bool,
) -> Vec<backend::ast::ImportFunction> { ) -> Vec<backend::ast::ImportFunction> {
let kind = backend::ast::ImportFunctionKind::Method { let operation = backend::ast::Operation {
class: self_name.to_string(), is_static,
ty: ident_ty(rust_ident(camel_case_ident(&self_name).as_str())), kind: backend::ast::OperationKind::Setter(Some(raw_ident(name))),
kind: backend::ast::MethodKind::Operation(backend::ast::Operation { };
is_static, let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str()));
kind: backend::ast::OperationKind::Setter(Some(raw_ident(name))),
}), let kind = if global {
backend::ast::ImportFunctionKind::ScopedMethod {
ty,
operation,
}
} else {
backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty,
kind: backend::ast::MethodKind::Operation(operation),
}
}; };
let doc_comment = Some(format!("The `{}` setter\n\n{}", name, mdn_doc(self_name, Some(name)))); let doc_comment = Some(format!("The `{}` setter\n\n{}", name, mdn_doc(self_name, Some(name))));
@ -636,7 +665,7 @@ impl<'src> FirstPassRecord<'src> {
false, false,
&[( &[(
name, name,
match ty.to_idl_type(self) { match field_ty.to_idl_type(self) {
None => return Vec::new(), None => return Vec::new(),
Some(idl_type) => idl_type, Some(idl_type) => idl_type,
}, },

View File

@ -6,13 +6,9 @@ use std::f64;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
#[wasm_bindgen]
extern "C" {
static document: web_sys::Document;
}
#[wasm_bindgen] #[wasm_bindgen]
pub fn draw() { pub fn draw() {
let document = web_sys::Window::document().unwrap();
let canvas = document.get_element_by_id("canvas").unwrap(); let canvas = document.get_element_by_id("canvas").unwrap();
let canvas: web_sys::HtmlCanvasElement = canvas let canvas: web_sys::HtmlCanvasElement = canvas
.dyn_into::<web_sys::HtmlCanvasElement>() .dyn_into::<web_sys::HtmlCanvasElement>()

View File

@ -40,11 +40,6 @@ pub struct Signature {
pub email: String, pub email: String,
} }
#[wasm_bindgen]
extern "C" {
static window: Window;
}
#[wasm_bindgen] #[wasm_bindgen]
pub fn run() -> Promise { pub fn run() -> Promise {
let mut request_options = RequestInit::new(); let mut request_options = RequestInit::new();
@ -56,7 +51,7 @@ pub fn run() -> Promise {
// the RequestInit struct will eventually support setting headers, but that's missing right now // the RequestInit struct will eventually support setting headers, but that's missing right now
req.headers().set("Accept", "application/vnd.github.v3+json").unwrap(); req.headers().set("Accept", "application/vnd.github.v3+json").unwrap();
let req_promise = window.fetch_with_request(&req); let req_promise = Window::fetch_with_request(&req);
let to_return = JsFuture::from(req_promise).and_then(|resp_value| { let to_return = JsFuture::from(req_promise).and_then(|resp_value| {
// resp_value is a Response object // resp_value is a Response object