From b0f9589471b4f872ef6a7402343f7ff19feed0d7 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Fri, 16 Oct 2020 20:49:00 -0500 Subject: [PATCH 1/4] Update dependencies --- Cargo.lock | 87 ++++++++++++++++++++++++--------------------- vm/src/stdlib/os.rs | 12 +++---- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef0febf9d..262c5eb68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,9 +26,9 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "aho-corasick" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d" dependencies = [ "memchr", ] @@ -140,7 +140,7 @@ checksum = "f1c85344eb535a31b62f0af37be84441ba9e7f0f4111eb0530f43d15e513fe57" dependencies = [ "bitflags", "cexpr", - "cfg-if", + "cfg-if 0.1.10", "clang-sys", "clap", "env_logger", @@ -288,6 +288,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "chrono" version = "0.4.19" @@ -482,7 +488,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] @@ -492,7 +498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 0.1.10", "lazy_static 1.4.0", ] @@ -578,19 +584,19 @@ dependencies = [ [[package]] name = "dirs-next" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cbcf9241d9e8d106295bd496bbe2e9cffd5fa098f2a8c9e2bbcbf09773c11a8" +checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] [[package]] name = "dirs-sys-next" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a" +checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d" dependencies = [ "libc", "redox_users", @@ -603,7 +609,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f69635ffdfbaea44241d7cca30a5e3a2e1c892613a6a8ad8ef03deeb6803480" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "socket2", "winapi", @@ -730,7 +736,7 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "crc32fast", "libc", "libz-sys", @@ -789,7 +795,7 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "wasi 0.9.0+wasi-snapshot-preview1", "wasm-bindgen", @@ -853,7 +859,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "js-sys", "wasm-bindgen", "web-sys", @@ -959,16 +965,16 @@ checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" dependencies = [ "arrayvec", "bitflags", - "cfg-if", + "cfg-if 0.1.10", "ryu", "static_assertions", ] [[package]] name = "libc" -version = "0.2.76" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" +checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" [[package]] name = "libffi" @@ -1030,7 +1036,7 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] @@ -1117,7 +1123,7 @@ checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" dependencies = [ "bitflags", "cc", - "cfg-if", + "cfg-if 0.1.10", "libc", ] @@ -1246,7 +1252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" dependencies = [ "bitflags", - "cfg-if", + "cfg-if 0.1.10", "foreign-types", "lazy_static 1.4.0", "libc", @@ -1299,7 +1305,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "cloudabi", "instant", "libc", @@ -1354,9 +1360,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "pmutil" @@ -1512,9 +1518,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f45b719a674bf4b828ff318906d6c133264c793eff7a41e30074a45b5099e2" +checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b" dependencies = [ "aho-corasick", "memchr", @@ -1533,9 +1539,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17be88d9eaa858870aa5e48cc406c206e4600e983fc4f06bbe5750d93d09761" +checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" [[package]] name = "region" @@ -1599,7 +1605,7 @@ dependencies = [ name = "rustpython" version = "0.1.2" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "clap", "cpython", "dirs-next", @@ -1633,7 +1639,7 @@ dependencies = [ name = "rustpython-common" version = "0.0.0" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "derive_more", "hexf-parse", "lexical-core", @@ -1725,7 +1731,7 @@ dependencies = [ "bstr", "byteorder", "caseless", - "cfg-if", + "cfg-if 0.1.10", "chrono", "crc", "crc32fast", @@ -1810,7 +1816,6 @@ dependencies = [ name = "rustpython_wasm" version = "0.1.2" dependencies = [ - "cfg-if", "js-sys", "parking_lot", "rustpython-compiler", @@ -1829,7 +1834,7 @@ version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0d5e7b0219a3eadd5439498525d4765c59b7c993ef0c12244865cd2d988413" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "dirs-next", "libc", "log", @@ -1881,9 +1886,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" dependencies = [ "serde_derive", ] @@ -1902,9 +1907,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" dependencies = [ "proc-macro2", "quote", @@ -1913,9 +1918,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ "itoa", "ryu", @@ -1983,7 +1988,7 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "redox_syscall", "winapi", @@ -2399,7 +2404,7 @@ version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "wasm-bindgen-macro", ] @@ -2424,7 +2429,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "js-sys", "wasm-bindgen", "web-sys", diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 79cd18116..60c17a9d4 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -2079,27 +2079,27 @@ mod posix { #[pyfunction(name = "WIFSIGNALED")] fn wifsignaled(status: i32) -> bool { - unsafe { libc::WIFSIGNALED(status) } + libc::WIFSIGNALED(status) } #[pyfunction(name = "WIFSTOPPED")] fn wifstopped(status: i32) -> bool { - unsafe { libc::WIFSTOPPED(status) } + libc::WIFSTOPPED(status) } #[pyfunction(name = "WIFEXITED")] fn wifexited(status: i32) -> bool { - unsafe { libc::WIFEXITED(status) } + libc::WIFEXITED(status) } #[pyfunction(name = "WTERMSIG")] fn wtermsig(status: i32) -> i32 { - unsafe { libc::WTERMSIG(status) } + libc::WTERMSIG(status) } #[pyfunction(name = "WSTOPSIG")] fn wstopsig(status: i32) -> i32 { - unsafe { libc::WSTOPSIG(status) } + libc::WSTOPSIG(status) } #[pyfunction(name = "WEXITSTATUS")] fn wexitstatus(status: i32) -> i32 { - unsafe { libc::WEXITSTATUS(status) } + libc::WEXITSTATUS(status) } #[pyfunction] From 79d5872942d43b89be4125d5bdf1e01fd9333763 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Fri, 16 Oct 2020 20:49:20 -0500 Subject: [PATCH 2/4] Expose PyGetSet in builtins --- derive/src/pyclass.rs | 2 +- vm/src/builtins/mod.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 0e59ec3b8..882c8b779 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -502,7 +502,7 @@ impl ToTokens for GetSetNursery { class.set_str_attr( #name, ::rustpython_vm::pyobject::PyObject::new( - ::rustpython_vm::builtins::getset::PyGetSet::#constructor(#name.into(), &Self::#getter #setter), + ::rustpython_vm::builtins::PyGetSet::#constructor(#name.into(), &Self::#getter #setter), ctx.types.getset_type.clone(), None) ); } diff --git a/vm/src/builtins/mod.rs b/vm/src/builtins/mod.rs index a81ea0f8c..b199b89f8 100644 --- a/vm/src/builtins/mod.rs +++ b/vm/src/builtins/mod.rs @@ -29,6 +29,7 @@ pub use function::PyFunction; pub(crate) mod generator; pub use generator::PyGenerator; pub(crate) mod getset; +pub use getset::PyGetSet; pub(crate) mod int; pub use int::{PyInt, PyIntRef}; pub(crate) mod iter; From cb9c9ec4cf62ca2572b03cfe14da22db05c93295 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Fri, 16 Oct 2020 20:49:57 -0500 Subject: [PATCH 3/4] Add some utility methods to function::Args and PyBaseException --- vm/src/exceptions.rs | 6 +++++- vm/src/function.rs | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index e27f14f18..d22c882a7 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -72,6 +72,10 @@ impl PyBaseException { Ok(()) } + pub fn get_arg(&self, idx: usize) -> Option { + self.args.read().borrow_value().get(idx).cloned() + } + #[pyproperty] pub fn args(&self) -> PyTupleRef { self.args.read().clone() @@ -641,7 +645,7 @@ fn none_getter(_obj: PyObjectRef, vm: &VirtualMachine) -> PyNoneRef { } fn make_arg_getter(idx: usize) -> impl Fn(PyBaseExceptionRef) -> Option { - move |exc| exc.args.read().borrow_value().get(idx).cloned() + move |exc| exc.get_arg(idx) } fn key_error_str(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyStrRef { diff --git a/vm/src/function.rs b/vm/src/function.rs index 593242752..5decc19a8 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -352,6 +352,10 @@ impl Args { pub fn into_vec(self) -> Vec { self.0 } + + pub fn iter(&self) -> std::slice::Iter { + self.0.iter() + } } impl From> for Args { From c61759bd924b6292a9c89bc111da0c10d9acd32e Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Fri, 16 Oct 2020 20:55:31 -0500 Subject: [PATCH 4/4] Rework some of the _js module --- wasm/lib/Cargo.toml | 1 - wasm/lib/src/browser.py | 3 +- wasm/lib/src/browser_module.rs | 107 +----------- wasm/lib/src/convert.rs | 103 +++++++----- wasm/lib/src/js_module.rs | 299 +++++++++++++++++++++++++++++---- 5 files changed, 333 insertions(+), 180 deletions(-) diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index d2e0b6b36..df4a21cb4 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -19,7 +19,6 @@ rustpython-compiler = { path = "../../compiler" } rustpython-parser = { path = "../../parser" } # no threading feature for rustpython-vm -- doesn't much matter anyway, but it might be more optimized rustpython-vm = { path = "../../vm", default-features = false, features = ["compile-parse"] } -cfg-if = "0.1.2" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" serde-wasm-bindgen = "0.1" diff --git a/wasm/lib/src/browser.py b/wasm/lib/src/browser.py index b146f696a..9f6769ff7 100644 --- a/wasm/lib/src/browser.py +++ b/wasm/lib/src/browser.py @@ -1,10 +1,11 @@ from _browser import * -from _js import JsValue +from _js import JSValue, Promise from _window import window jsstr = window.new_from_str +jsclosure = window.new_closure _alert = window.get_prop("alert") diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 1173a6e41..df00a9779 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -1,20 +1,18 @@ use js_sys::Promise; -use std::future::Future; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use wasm_bindgen_futures::{future_to_promise, JsFuture}; +use wasm_bindgen_futures::JsFuture; use rustpython_vm::builtins::{PyDictRef, PyStrRef, PyTypeRef}; use rustpython_vm::common::rc::PyRc; use rustpython_vm::function::OptionalArg; use rustpython_vm::import::import_file; use rustpython_vm::pyobject::{ - BorrowValue, IntoPyObject, PyCallable, PyClassImpl, PyObject, PyObjectRef, PyRef, PyResult, - PyValue, + BorrowValue, IntoPyObject, PyCallable, PyClassImpl, PyObject, PyObjectRef, PyResult, PyValue, }; use rustpython_vm::VirtualMachine; -use crate::{convert, vm_class::weak_vm, wasm_builtins::window}; +use crate::{convert, js_module::PyPromise, vm_class::weak_vm, wasm_builtins::window}; enum FetchResponseFormat { Json, @@ -155,100 +153,6 @@ fn browser_cancel_animation_frame(id: i32, vm: &VirtualMachine) -> PyResult<()> Ok(()) } -#[pyclass(module = "browser", name = "Promise")] -#[derive(Debug)] -pub struct PyPromise { - value: Promise, -} -pub type PyPromiseRef = PyRef; - -impl PyValue for PyPromise { - fn class(vm: &VirtualMachine) -> PyTypeRef { - vm.class("browser", "Promise") - } -} - -#[pyimpl] -impl PyPromise { - pub fn new(value: Promise) -> PyPromise { - PyPromise { value } - } - pub fn from_future(future: F) -> PyPromise - where - F: Future> + 'static, - { - PyPromise::new(future_to_promise(future)) - } - pub fn value(&self) -> Promise { - self.value.clone() - } - - #[pymethod] - fn then( - &self, - on_fulfill: PyCallable, - on_reject: OptionalArg, - vm: &VirtualMachine, - ) -> PyPromiseRef { - let weak_vm = weak_vm(vm); - let prom = JsFuture::from(self.value.clone()); - - let ret_future = async move { - let stored_vm = &weak_vm - .upgrade() - .expect("that the vm is valid when the promise resolves"); - let res = prom.await; - match res { - Ok(val) => stored_vm.interp.enter(move |vm| { - let args = if val.is_null() { - vec![] - } else { - vec![convert::js_to_py(vm, val)] - }; - let res = vm.invoke(&on_fulfill.into_object(), args); - convert::pyresult_to_jsresult(vm, res) - }), - Err(err) => { - if let OptionalArg::Present(on_reject) = on_reject { - stored_vm.interp.enter(move |vm| { - let err = convert::js_to_py(vm, err); - let res = vm.invoke(&on_reject.into_object(), (err,)); - convert::pyresult_to_jsresult(vm, res) - }) - } else { - Err(err) - } - } - } - }; - - PyPromise::from_future(ret_future).into_ref(vm) - } - - #[pymethod] - fn catch(&self, on_reject: PyCallable, vm: &VirtualMachine) -> PyPromiseRef { - let weak_vm = weak_vm(vm); - let prom = JsFuture::from(self.value.clone()); - - let ret_future = async move { - let err = match prom.await { - Ok(x) => return Ok(x), - Err(e) => e, - }; - let stored_vm = weak_vm - .upgrade() - .expect("that the vm is valid when the promise resolves"); - stored_vm.interp.enter(move |vm| { - let err = convert::js_to_py(vm, err); - let res = vm.invoke(&on_reject.into_object(), (err,)); - convert::pyresult_to_jsresult(vm, res) - }) - }; - - PyPromise::from_future(ret_future).into_ref(vm) - } -} - #[pyclass(module = "browser", name)] #[derive(Debug)] struct Document { @@ -347,8 +251,6 @@ fn browser_load_module(module: PyStrRef, path: PyStrRef, vm: &VirtualMachine) -> pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; - let promise = PyPromise::make_class(ctx); - let document_class = Document::make_class(ctx); let document = PyObject::new( @@ -361,11 +263,10 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let element = Element::make_class(ctx); - py_module!(vm, "browser", { + py_module!(vm, "_browser", { "fetch" => ctx.new_function(browser_fetch), "request_animation_frame" => ctx.new_function(browser_request_animation_frame), "cancel_animation_frame" => ctx.new_function(browser_cancel_animation_frame), - "Promise" => promise, "Document" => document_class, "document" => document, "Element" => element, diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 4ce453a99..92ef307e5 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -12,7 +12,7 @@ use rustpython_vm::pyobject::{ use rustpython_vm::VirtualMachine; use rustpython_vm::{exceptions, py_serde}; -use crate::browser_module; +use crate::js_module; use crate::vm_class::{stored_vm_from_wasm, WASMVirtualMachine}; #[wasm_bindgen(inline_js = r" @@ -35,10 +35,25 @@ extern "C" { } pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyBaseExceptionRef) -> JsValue { - let res = serde_wasm_bindgen::to_value(&exceptions::SerializeException::new(vm, py_err)); - match res { - Ok(err_info) => PyError::new(err_info).into(), - Err(e) => e.into(), + let jserr = vm.try_class("_js", "JSError").ok(); + let js_arg = if jserr.map_or(false, |jserr| py_err.isinstance(&jserr)) { + py_err.get_arg(0) + } else { + None + }; + let js_arg = js_arg + .as_ref() + .and_then(|x| x.payload::()); + match js_arg { + Some(val) => val.value.clone(), + None => { + let res = + serde_wasm_bindgen::to_value(&exceptions::SerializeException::new(vm, py_err)); + match res { + Ok(err_info) => PyError::new(err_info).into(), + Err(e) => e.into(), + } + } } } @@ -74,37 +89,42 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { }; let weak_py_obj = wasm_vm.push_held_rc(py_obj).unwrap(); - let closure = - move |args: Option, kwargs: Option| -> Result { - let py_obj = match wasm_vm.assert_valid() { - Ok(_) => weak_py_obj - .upgrade() - .expect("weak_py_obj to be valid if VM is valid"), - Err(err) => { - return Err(err); - } - }; - stored_vm_from_wasm(&wasm_vm).interp.enter(move |vm| { - let mut py_func_args = FuncArgs::default(); - if let Some(ref args) = args { - for arg in args.values() { - py_func_args.args.push(js_to_py(vm, arg?)); - } - } - if let Some(ref kwargs) = kwargs { - for pair in object_entries(kwargs) { - let (key, val) = pair?; - py_func_args - .kwargs - .insert(js_sys::JsString::from(key).into(), js_to_py(vm, val)); - } - } - let result = vm.invoke(&py_obj, py_func_args); - pyresult_to_jsresult(vm, result) - }) + let closure = move |args: Option>, + kwargs: Option| + -> Result { + let py_obj = match wasm_vm.assert_valid() { + Ok(_) => weak_py_obj + .upgrade() + .expect("weak_py_obj to be valid if VM is valid"), + Err(err) => { + return Err(err); + } }; + stored_vm_from_wasm(&wasm_vm).interp.enter(move |vm| { + let args = match args { + Some(args) => Vec::from(args) + .into_iter() + .map(|arg| js_to_py(vm, arg)) + .collect::>(), + None => Vec::new(), + }; + let mut py_func_args = FuncArgs::from(args); + if let Some(ref kwargs) = kwargs { + for pair in object_entries(kwargs) { + let (key, val) = pair?; + py_func_args + .kwargs + .insert(js_sys::JsString::from(key).into(), js_to_py(vm, val)); + } + } + let result = vm.invoke(&py_obj, py_func_args); + pyresult_to_jsresult(vm, result) + }) + }; let closure = Closure::wrap(Box::new(closure) - as Box, Option) -> Result>); + as Box< + dyn FnMut(Option>, Option) -> Result, + >); let func = closure.as_ref().clone(); // stores pretty much nothing, it's fine to leak this because if it gets dropped @@ -115,8 +135,8 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { } } // the browser module might not be injected - if vm.try_class("browser", "Promise").is_ok() { - if let Some(py_prom) = py_obj.payload::() { + if vm.try_class("_js", "Promise").is_ok() { + if let Some(py_prom) = py_obj.payload::() { return py_prom.value().into(); } } @@ -157,7 +177,7 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { if let Some(promise) = js_val.dyn_ref::() { // the browser module might not be injected if vm.try_class("browser", "Promise").is_ok() { - return browser_module::PyPromise::new(promise.clone()) + return js_module::PyPromise::new(promise.clone()) .into_ref(vm) .into_object(); } @@ -205,10 +225,11 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { Reflect::set(&this, &k.into(), &py_to_js(vm, v)) .expect("property to be settable"); } - let js_args = Array::new(); - for v in args.args { - js_args.push(&py_to_js(vm, v)); - } + let js_args = args + .args + .into_iter() + .map(|v| py_to_js(vm, v)) + .collect::(); func.apply(&this, &js_args) .map(|val| js_to_py(vm, val)) .map_err(|err| js_err_to_py_err(vm, &err)) diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index 897ab69ca..83adda99f 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -1,14 +1,21 @@ -use js_sys::{Array, Object, Reflect}; +use crate::convert; +use crate::vm_class::{stored_vm_from_wasm, WASMVirtualMachine}; +use crate::weak_vm; +use js_sys::{Array, Object, Promise, Reflect}; +use std::{cell, fmt, future}; +use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; +use wasm_bindgen_futures::{future_to_promise, JsFuture}; + use rustpython_vm::builtins::{PyFloatRef, PyStrRef, PyTypeRef}; use rustpython_vm::common::rc::PyRc; use rustpython_vm::exceptions::PyBaseExceptionRef; -use rustpython_vm::function::Args; +use rustpython_vm::function::{Args, OptionalArg}; use rustpython_vm::pyobject::{ - BorrowValue, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, + BorrowValue, IntoPyObject, PyCallable, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, }; use rustpython_vm::types::create_type; use rustpython_vm::VirtualMachine; -use wasm_bindgen::{prelude::*, JsCast}; #[wasm_bindgen(inline_js = " export function has_prop(target, prop) { return prop in Object(target); } @@ -16,6 +23,13 @@ export function get_prop(target, prop) { return target[prop]; } export function set_prop(target, prop, value) { target[prop] = value; } export function type_of(a) { return typeof a; } export function instance_of(lhs, rhs) { return lhs instanceof rhs; } +export function call_func(func, args) { return func(...args); } +export function call_method(obj, method, args) { return obj[method](...args) } +export function wrap_closure(closure) { + return function pyfunction(...args) { + closure(this, args) + } +} ")] extern "C" { #[wasm_bindgen(catch)] @@ -28,22 +42,30 @@ extern "C" { fn type_of(a: &JsValue) -> String; #[wasm_bindgen(catch)] fn instance_of(lhs: &JsValue, rhs: &JsValue) -> Result; + #[wasm_bindgen(catch)] + fn call_func(func: &JsValue, args: &Array) -> Result; + #[wasm_bindgen(catch)] + fn call_method(obj: &JsValue, method: &JsValue, args: &Array) -> Result; + #[wasm_bindgen] + fn wrap_closure(closure: &JsValue) -> JsValue; } -#[pyclass(module = "_js", name = "JsValue")] +#[pyclass(module = "_js", name = "JSValue")] #[derive(Debug)] pub struct PyJsValue { - value: JsValue, + pub(crate) value: JsValue, } type PyJsValueRef = PyRef; -// TODO: Fix this when threading is supported in WASM. -unsafe impl Send for PyJsValue {} -unsafe impl Sync for PyJsValue {} - impl PyValue for PyJsValue { fn class(vm: &VirtualMachine) -> PyTypeRef { - vm.class("_js", "JsValue") + vm.class("_js", "JSValue") + } +} + +impl AsRef for PyJsValue { + fn as_ref(&self) -> &JsValue { + &self.value } } @@ -98,6 +120,11 @@ impl PyJsValue { PyJsValue::new(n.to_f64()) } + #[pymethod] + fn new_closure(&self, obj: PyObjectRef, vm: &VirtualMachine) -> JsClosure { + JsClosure::new(obj, vm) + } + #[pymethod] fn new_object(&self, opts: NewObjectOptions, vm: &VirtualMachine) -> PyResult { let value = if let Some(proto) = opts.prototype { @@ -148,15 +175,23 @@ impl PyJsValue { .value .dyn_ref::() .ok_or_else(|| vm.new_type_error("JS value is not callable".to_owned()))?; - let this = opts - .this - .map(|this| this.value.clone()) - .unwrap_or(JsValue::UNDEFINED); - let js_args = Array::new(); - for arg in args { - js_args.push(&arg.value); - } - Reflect::apply(func, &this, &js_args) + let js_args = args.iter().map(|x| x.as_ref()).collect::(); + let res = match opts.this { + Some(this) => Reflect::apply(func, &this.value, &js_args), + None => call_func(func, &js_args), + }; + res.map(PyJsValue::new).map_err(|err| new_js_error(vm, err)) + } + + #[pymethod] + fn call_method( + &self, + name: JsProperty, + args: Args, + vm: &VirtualMachine, + ) -> PyResult { + let js_args = args.iter().map(|x| x.as_ref()).collect::(); + call_method(&self.value, &name.into_jsvalue(), &js_args) .map(PyJsValue::new) .map_err(|err| new_js_error(vm, err)) } @@ -176,10 +211,7 @@ impl PyJsValue { .prototype .as_ref() .and_then(|proto| proto.value.dyn_ref::()); - let js_args = Array::new(); - for arg in args { - js_args.push(&arg.value); - } + let js_args = args.iter().map(|x| x.as_ref()).collect::(); let constructed_result = if let Some(proto) = proto { Reflect::construct_with_new_target(ctor, &js_args, &proto) } else { @@ -211,9 +243,9 @@ impl PyJsValue { type_of(&self.value) } - #[pymethod] /// Checks that `typeof self == "object" && self !== null`. Use instead /// of `value.typeof() == "object"` + #[pymethod] fn is_object(&self) -> bool { self.value.is_object() } @@ -241,22 +273,221 @@ struct NewObjectOptions { prototype: Option, } +type ClosureType = Closure) -> Result>; + +#[pyclass(module = "_js", name = "JSClosure")] +struct JsClosure { + closure: cell::RefCell>, + destroyed: cell::Cell, + detached: cell::Cell, +} + +impl fmt::Debug for JsClosure { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad("JsClosure") + } +} + +impl PyValue for JsClosure { + fn class(vm: &VirtualMachine) -> PyTypeRef { + vm.class("_js", "JSClosure") + } +} + +#[pyimpl] +impl JsClosure { + fn new(obj: PyObjectRef, vm: &VirtualMachine) -> Self { + let wasm_vm = WASMVirtualMachine { + id: vm.wasm_id.clone().unwrap(), + }; + let weak_py_obj = wasm_vm.push_held_rc(obj).unwrap(); + let f = move |this: JsValue, args: Box<[JsValue]>| { + let py_obj = match wasm_vm.assert_valid() { + Ok(_) => weak_py_obj + .upgrade() + .expect("weak_py_obj to be valid if VM is valid"), + Err(err) => { + return Err(err); + } + }; + stored_vm_from_wasm(&wasm_vm).interp.enter(move |vm| { + let mut pyargs = vec![PyJsValue::new(this).into_object(vm)]; + pyargs.extend( + Vec::from(args) + .into_iter() + .map(|arg| PyJsValue::new(arg).into_object(vm)), + ); + let res = vm.invoke(&py_obj, pyargs); + convert::pyresult_to_jsresult(vm, res) + }) + }; + let closure = Closure::wrap(Box::new(f) as _); + let wrapped = PyJsValue::new(wrap_closure(closure.as_ref())).into_ref(vm); + JsClosure { + closure: Some((closure, wrapped)).into(), + destroyed: false.into(), + detached: false.into(), + } + } + + #[pyproperty] + fn value(&self) -> Option { + self.closure + .borrow() + .as_ref() + .map(|(_, jsval)| jsval.clone()) + } + #[pyproperty] + fn destroyed(&self) -> bool { + self.destroyed.get() + } + #[pyproperty] + fn detached(&self) -> bool { + self.detached.get() + } + + #[pymethod] + fn destroy(&self, vm: &VirtualMachine) -> PyResult<()> { + let (closure, _) = self.closure.replace(None).ok_or_else(|| { + vm.new_value_error( + "can't destroy closure has already been destroyed or detached".to_owned(), + ) + })?; + drop(closure); + self.destroyed.set(true); + Ok(()) + } + #[pymethod] + fn detach(&self, vm: &VirtualMachine) -> PyResult { + let (closure, jsval) = self.closure.replace(None).ok_or_else(|| { + vm.new_value_error( + "can't detach closure has already been detached or destroyed".to_owned(), + ) + })?; + closure.forget(); + self.detached.set(true); + Ok(jsval) + } +} + +#[pyclass(module = "browser", name = "Promise")] +#[derive(Debug)] +pub struct PyPromise { + value: Promise, +} +pub type PyPromiseRef = PyRef; + +impl PyValue for PyPromise { + fn class(vm: &VirtualMachine) -> PyTypeRef { + vm.class("browser", "Promise") + } +} + +#[pyimpl] +impl PyPromise { + pub fn new(value: Promise) -> PyPromise { + PyPromise { value } + } + pub fn from_future(future: F) -> PyPromise + where + F: future::Future> + 'static, + { + PyPromise::new(future_to_promise(future)) + } + pub fn value(&self) -> Promise { + self.value.clone() + } + + #[pymethod] + fn then( + &self, + on_fulfill: PyCallable, + on_reject: OptionalArg, + vm: &VirtualMachine, + ) -> PyPromiseRef { + let weak_vm = weak_vm(vm); + let prom = JsFuture::from(self.value.clone()); + + let ret_future = async move { + let stored_vm = &weak_vm + .upgrade() + .expect("that the vm is valid when the promise resolves"); + let res = prom.await; + match res { + Ok(val) => stored_vm.interp.enter(move |vm| { + let args = if val.is_null() { + vec![] + } else { + vec![convert::js_to_py(vm, val)] + }; + let res = vm.invoke(&on_fulfill.into_object(), args); + convert::pyresult_to_jsresult(vm, res) + }), + Err(err) => { + if let OptionalArg::Present(on_reject) = on_reject { + stored_vm.interp.enter(move |vm| { + let err = convert::js_to_py(vm, err); + let res = vm.invoke(&on_reject.into_object(), (err,)); + convert::pyresult_to_jsresult(vm, res) + }) + } else { + Err(err) + } + } + } + }; + + PyPromise::from_future(ret_future).into_ref(vm) + } + + #[pymethod] + fn catch(&self, on_reject: PyCallable, vm: &VirtualMachine) -> PyPromiseRef { + let weak_vm = weak_vm(vm); + let prom = JsFuture::from(self.value.clone()); + + let ret_future = async move { + let err = match prom.await { + Ok(x) => return Ok(x), + Err(e) => e, + }; + let stored_vm = weak_vm + .upgrade() + .expect("that the vm is valid when the promise resolves"); + stored_vm.interp.enter(move |vm| { + let err = convert::js_to_py(vm, err); + let res = vm.invoke(&on_reject.into_object(), (err,)); + convert::pyresult_to_jsresult(vm, res) + }) + }; + + PyPromise::from_future(ret_future).into_ref(vm) + } +} + fn new_js_error(vm: &VirtualMachine, err: JsValue) -> PyBaseExceptionRef { - let exc = vm.new_exception_msg(vm.class("_js", "JsError"), format!("{:?}", err)); - vm.set_attr( - exc.as_object(), - "js_value", - PyJsValue::new(err).into_ref(vm), + vm.new_exception( + vm.class("_js", "JSError"), + vec![PyJsValue::new(err).into_pyobject(vm)], ) - .unwrap(); - exc } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; + + let js_error = create_type( + "JSError", + &ctx.types.type_type, + ctx.exceptions.exception_type.clone(), + ); + extend_class!(ctx, &js_error, { + "value" => ctx.new_readonly_getset("value", |exc: PyBaseExceptionRef| exc.get_arg(0)), + }); + py_module!(vm, "_js", { - "JsError" => create_type("JsError", &ctx.types.type_type, ctx.exceptions.exception_type.clone()), - "JsValue" => PyJsValue::make_class(ctx), + "JSError" => js_error, + "JSValue" => PyJsValue::make_class(ctx), + "JSClosure" => JsClosure::make_class(ctx), + "Promise" => PyPromise::make_class(ctx), }) }