From 3f9c7901ee7348e6a64ff1975b03f84f42e77b5b Mon Sep 17 00:00:00 2001 From: Moreal Date: Thu, 21 Oct 2021 21:45:56 +0900 Subject: [PATCH] Remove usage of `py_module!` from _js --- wasm/lib/src/js_module.rs | 1123 +++++++++++++++++++------------------ 1 file changed, 572 insertions(+), 551 deletions(-) diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index 8a5ff72582..8617e6a903 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -1,617 +1,638 @@ -use crate::{ - convert, - vm_class::{stored_vm_from_wasm, WASMVirtualMachine}, - weak_vm, -}; -use js_sys::{Array, Object, Promise, Reflect}; +pub(crate) use _js::{PyJsValue, PyPromise}; use rustpython_vm::{ - builtins::{PyBaseExceptionRef, PyFloat, PyStrRef, PyType, PyTypeRef}, - function::{ArgCallable, IntoPyObject, OptionalArg, OptionalOption, PosArgs}, - protocol::PyIterReturn, - types::{IterNext, IterNextIterable}, - PyClassImpl, PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult, PyValue, TryFromObject, - VirtualMachine, + builtins::{PyBaseExceptionRef, PyType}, + PyObjectRef, VirtualMachine, }; -use std::{cell, fmt, future}; -use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; -use wasm_bindgen_futures::{future_to_promise, JsFuture}; -#[wasm_bindgen(inline_js = " -export function has_prop(target, prop) { return prop in Object(target); } -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)] - fn has_prop(target: &JsValue, prop: &JsValue) -> Result; - #[wasm_bindgen(catch)] - fn get_prop(target: &JsValue, prop: &JsValue) -> Result; - #[wasm_bindgen(catch)] - fn set_prop(target: &JsValue, prop: &JsValue, value: &JsValue) -> Result<(), JsValue>; - #[wasm_bindgen] - 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; -} +#[pymodule] +mod _js { + use crate::{ + convert, + vm_class::{stored_vm_from_wasm, WASMVirtualMachine}, + weak_vm, + }; + use js_sys::{Array, Object, Promise, Reflect}; + use rustpython_vm::{ + builtins::{PyBaseExceptionRef, PyFloat, PyStrRef, PyTypeRef}, + function::{ArgCallable, IntoPyObject, OptionalArg, OptionalOption, PosArgs}, + protocol::PyIterReturn, + types::{IterNext, IterNextIterable}, + PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult, PyValue, TryFromObject, + VirtualMachine, + }; + use std::{cell, fmt, future}; + use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; + use wasm_bindgen_futures::{future_to_promise, JsFuture}; -#[pyclass(module = "_js", name = "JSValue")] -#[derive(Debug, PyValue)] -pub struct PyJsValue { - pub(crate) value: JsValue, -} -type PyJsValueRef = PyRef; - -impl AsRef for PyJsValue { - fn as_ref(&self) -> &JsValue { - &self.value - } -} - -enum JsProperty { - Str(PyStrRef), - Js(PyJsValueRef), -} - -impl TryFromObject for JsProperty { - fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - PyStrRef::try_from_object(vm, obj.clone()) - .map(JsProperty::Str) - .or_else(|_| PyJsValueRef::try_from_object(vm, obj).map(JsProperty::Js)) - } -} - -impl JsProperty { - fn into_jsvalue(self) -> JsValue { - match self { - JsProperty::Str(s) => s.as_str().into(), - JsProperty::Js(value) => value.value.clone(), + #[wasm_bindgen(inline_js = " + export function has_prop(target, prop) { return prop in Object(target); } + 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)] + fn has_prop(target: &JsValue, prop: &JsValue) -> Result; + #[wasm_bindgen(catch)] + fn get_prop(target: &JsValue, prop: &JsValue) -> Result; + #[wasm_bindgen(catch)] + fn set_prop(target: &JsValue, prop: &JsValue, value: &JsValue) -> Result<(), JsValue>; + #[wasm_bindgen] + 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; + } -#[pyimpl] -impl PyJsValue { - #[inline] - pub fn new(value: impl Into) -> PyJsValue { - PyJsValue { - value: value.into(), + #[pyattr] + #[pyclass(module = "_js", name = "JSValue")] + #[derive(Debug, PyValue)] + pub struct PyJsValue { + pub(crate) value: JsValue, + } + type PyJsValueRef = PyRef; + + impl AsRef for PyJsValue { + fn as_ref(&self) -> &JsValue { + &self.value } } - #[pymethod] - fn null(&self) -> PyJsValue { - PyJsValue::new(JsValue::NULL) + enum JsProperty { + Str(PyStrRef), + Js(PyJsValueRef), } - #[pymethod] - fn undefined(&self) -> PyJsValue { - PyJsValue::new(JsValue::UNDEFINED) + impl TryFromObject for JsProperty { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + PyStrRef::try_from_object(vm, obj.clone()) + .map(JsProperty::Str) + .or_else(|_| PyJsValueRef::try_from_object(vm, obj).map(JsProperty::Js)) + } } - #[pymethod] - fn new_from_str(&self, s: PyStrRef) -> PyJsValue { - PyJsValue::new(s.as_str()) - } - - #[pymethod] - fn new_from_float(&self, n: PyRef) -> PyJsValue { - PyJsValue::new(n.to_f64()) - } - - #[pymethod] - fn new_closure(&self, obj: PyObjectRef, vm: &VirtualMachine) -> JsClosure { - JsClosure::new(obj, false, vm) - } - - #[pymethod] - fn new_closure_once(&self, obj: PyObjectRef, vm: &VirtualMachine) -> JsClosure { - JsClosure::new(obj, true, vm) - } - - #[pymethod] - fn new_object(&self, opts: NewObjectOptions, vm: &VirtualMachine) -> PyResult { - let value = if let Some(proto) = opts.prototype { - if let Some(proto) = proto.value.dyn_ref::() { - Object::create(proto) - } else if proto.value.is_null() { - Object::create(proto.value.unchecked_ref()) - } else { - return Err(vm.new_value_error("prototype must be an Object or null".to_owned())); + impl JsProperty { + fn into_jsvalue(self) -> JsValue { + match self { + JsProperty::Str(s) => s.as_str().into(), + JsProperty::Js(value) => value.value.clone(), } - } else { - Object::new() - }; - Ok(PyJsValue::new(value)) + } } - #[pymethod] - fn has_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult { - has_prop(&self.value, &name.into_jsvalue()).map_err(|err| new_js_error(vm, err)) - } + #[pyimpl] + impl PyJsValue { + #[inline] + pub fn new(value: impl Into) -> PyJsValue { + PyJsValue { + value: value.into(), + } + } - #[pymethod] - fn get_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult { - let name = &name.into_jsvalue(); - if has_prop(&self.value, name).map_err(|err| new_js_error(vm, err))? { - get_prop(&self.value, name) + #[pymethod] + fn null(&self) -> PyJsValue { + PyJsValue::new(JsValue::NULL) + } + + #[pymethod] + fn undefined(&self) -> PyJsValue { + PyJsValue::new(JsValue::UNDEFINED) + } + + #[pymethod] + fn new_from_str(&self, s: PyStrRef) -> PyJsValue { + PyJsValue::new(s.as_str()) + } + + #[pymethod] + fn new_from_float(&self, n: PyRef) -> PyJsValue { + PyJsValue::new(n.to_f64()) + } + + #[pymethod] + fn new_closure(&self, obj: PyObjectRef, vm: &VirtualMachine) -> JsClosure { + JsClosure::new(obj, false, vm) + } + + #[pymethod] + fn new_closure_once(&self, obj: PyObjectRef, vm: &VirtualMachine) -> JsClosure { + JsClosure::new(obj, true, vm) + } + + #[pymethod] + fn new_object(&self, opts: NewObjectOptions, vm: &VirtualMachine) -> PyResult { + let value = if let Some(proto) = opts.prototype { + if let Some(proto) = proto.value.dyn_ref::() { + Object::create(proto) + } else if proto.value.is_null() { + Object::create(proto.value.unchecked_ref()) + } else { + return Err( + vm.new_value_error("prototype must be an Object or null".to_owned()) + ); + } + } else { + Object::new() + }; + Ok(PyJsValue::new(value)) + } + + #[pymethod] + fn has_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult { + has_prop(&self.value, &name.into_jsvalue()).map_err(|err| new_js_error(vm, err)) + } + + #[pymethod] + fn get_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult { + let name = &name.into_jsvalue(); + if has_prop(&self.value, name).map_err(|err| new_js_error(vm, err))? { + get_prop(&self.value, name) + .map(PyJsValue::new) + .map_err(|err| new_js_error(vm, err)) + } else { + Err(vm.new_attribute_error(format!("No attribute {:?} on JS value", name))) + } + } + + #[pymethod] + fn set_prop( + &self, + name: JsProperty, + value: PyJsValueRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + set_prop(&self.value, &name.into_jsvalue(), &value.value) + .map_err(|err| new_js_error(vm, err)) + } + + #[pymethod] + fn call( + &self, + args: PosArgs, + opts: CallOptions, + vm: &VirtualMachine, + ) -> PyResult { + let func = self + .value + .dyn_ref::() + .ok_or_else(|| vm.new_type_error("JS value is not callable".to_owned()))?; + let js_args = args.iter().map(|x| -> &PyJsValue { x }).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: PosArgs, + vm: &VirtualMachine, + ) -> PyResult { + let js_args = args.iter().map(|x| -> &PyJsValue { x }).collect::(); + call_method(&self.value, &name.into_jsvalue(), &js_args) .map(PyJsValue::new) .map_err(|err| new_js_error(vm, err)) - } else { - Err(vm.new_attribute_error(format!("No attribute {:?} on JS value", name))) } - } - #[pymethod] - fn set_prop(&self, name: JsProperty, value: PyJsValueRef, vm: &VirtualMachine) -> PyResult<()> { - set_prop(&self.value, &name.into_jsvalue(), &value.value) - .map_err(|err| new_js_error(vm, err)) - } - - #[pymethod] - fn call( - &self, - args: PosArgs, - opts: CallOptions, - vm: &VirtualMachine, - ) -> PyResult { - let func = self - .value - .dyn_ref::() - .ok_or_else(|| vm.new_type_error("JS value is not callable".to_owned()))?; - let js_args = args.iter().map(|x| -> &PyJsValue { x }).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: PosArgs, - vm: &VirtualMachine, - ) -> PyResult { - let js_args = args.iter().map(|x| -> &PyJsValue { x }).collect::(); - call_method(&self.value, &name.into_jsvalue(), &js_args) - .map(PyJsValue::new) - .map_err(|err| new_js_error(vm, err)) - } - - #[pymethod] - fn construct( - &self, - args: PosArgs, - opts: NewObjectOptions, - vm: &VirtualMachine, - ) -> PyResult { - let ctor = self - .value - .dyn_ref::() - .ok_or_else(|| vm.new_type_error("JS value is not callable".to_owned()))?; - let proto = opts - .prototype - .as_ref() - .and_then(|proto| proto.value.dyn_ref::()); - let js_args = args.iter().map(|x| -> &PyJsValue { x }).collect::(); - let constructed_result = if let Some(proto) = proto { - Reflect::construct_with_new_target(ctor, &js_args, proto) - } else { - Reflect::construct(ctor, &js_args) - }; - - constructed_result - .map(PyJsValue::new) - .map_err(|err| new_js_error(vm, err)) - } - - #[pymethod] - fn as_str(&self) -> Option { - self.value.as_string() - } - - #[pymethod] - fn as_float(&self) -> Option { - self.value.as_f64() - } - - #[pymethod] - fn as_bool(&self) -> Option { - self.value.as_bool() - } - - #[pymethod(name = "typeof")] - fn type_of(&self) -> String { - type_of(&self.value) - } - - /// Checks that `typeof self == "object" && self !== null`. Use instead - /// of `value.typeof() == "object"` - #[pymethod] - fn is_object(&self) -> bool { - self.value.is_object() - } - - #[pymethod] - fn instanceof(&self, rhs: PyJsValueRef, vm: &VirtualMachine) -> PyResult { - instance_of(&self.value, &rhs.value).map_err(|err| new_js_error(vm, err)) - } - - #[pymethod(magic)] - fn repr(&self) -> String { - format!("{:?}", self.value) - } -} - -#[derive(FromArgs)] -struct CallOptions { - #[pyarg(named, default)] - this: Option, -} - -#[derive(FromArgs)] -struct NewObjectOptions { - #[pyarg(named, default)] - prototype: Option, -} - -type ClosureType = Closure) -> Result>; - -#[pyclass(module = "_js", name = "JSClosure")] -#[derive(PyValue)] -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") - } -} - -#[pyimpl] -impl JsClosure { - fn new(obj: PyObjectRef, once: bool, 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); - } + #[pymethod] + fn construct( + &self, + args: PosArgs, + opts: NewObjectOptions, + vm: &VirtualMachine, + ) -> PyResult { + let ctor = self + .value + .dyn_ref::() + .ok_or_else(|| vm.new_type_error("JS value is not callable".to_owned()))?; + let proto = opts + .prototype + .as_ref() + .and_then(|proto| proto.value.dyn_ref::()); + let js_args = args.iter().map(|x| -> &PyJsValue { x }).collect::(); + let constructed_result = if let Some(proto) = proto { + Reflect::construct_with_new_target(ctor, &js_args, proto) + } else { + Reflect::construct(ctor, &js_args) }; - 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: ClosureType = if once { - Closure::wrap(Box::new(f)) - } else { - Closure::once(Box::new(f)) - }; - let wrapped = PyJsValue::new(wrap_closure(closure.as_ref())).into_ref(vm); - JsClosure { - closure: Some((closure, wrapped)).into(), - destroyed: false.into(), - detached: false.into(), + + constructed_result + .map(PyJsValue::new) + .map_err(|err| new_js_error(vm, err)) + } + + #[pymethod] + fn as_str(&self) -> Option { + self.value.as_string() + } + + #[pymethod] + fn as_float(&self) -> Option { + self.value.as_f64() + } + + #[pymethod] + fn as_bool(&self) -> Option { + self.value.as_bool() + } + + #[pymethod(name = "typeof")] + fn type_of(&self) -> String { + type_of(&self.value) + } + + /// Checks that `typeof self == "object" && self !== null`. Use instead + /// of `value.typeof() == "object"` + #[pymethod] + fn is_object(&self) -> bool { + self.value.is_object() + } + + #[pymethod] + fn instanceof(&self, rhs: PyJsValueRef, vm: &VirtualMachine) -> PyResult { + instance_of(&self.value, &rhs.value).map_err(|err| new_js_error(vm, err)) + } + + #[pymethod(magic)] + fn repr(&self) -> String { + format!("{:?}", self.value) } } - #[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() + #[derive(FromArgs)] + struct CallOptions { + #[pyarg(named, default)] + this: Option, } - #[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(()) + #[derive(FromArgs)] + struct NewObjectOptions { + #[pyarg(named, default)] + prototype: Option, } - #[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 = "_js", name = "Promise")] -#[derive(Debug, Clone, PyValue)] -pub struct PyPromise { - value: PromiseKind, -} -pub type PyPromiseRef = PyRef; + type ClosureType = Closure) -> Result>; -#[derive(Debug, Clone)] -enum PromiseKind { - Js(Promise), - PyProm { then: PyObjectRef }, - PyResolved(PyObjectRef), - PyRejected(PyBaseExceptionRef), -} + #[pyattr] + #[pyclass(module = "_js", name = "JSClosure")] + #[derive(PyValue)] + struct JsClosure { + closure: cell::RefCell>, + destroyed: cell::Cell, + detached: cell::Cell, + } -#[pyimpl] -impl PyPromise { - pub fn new(value: Promise) -> PyPromise { - PyPromise { - value: PromiseKind::Js(value), - } - } - pub fn from_future(future: F) -> PyPromise - where - F: future::Future> + 'static, - { - PyPromise::new(future_to_promise(future)) - } - pub fn as_js(&self, vm: &VirtualMachine) -> Promise { - match &self.value { - PromiseKind::Js(prom) => prom.clone(), - PromiseKind::PyProm { then } => Promise::new(&mut |js_resolve, js_reject| { - let resolve = move |res: PyObjectRef, vm: &VirtualMachine| { - let _ = js_resolve.call1(&JsValue::UNDEFINED, &convert::py_to_js(vm, res)); - }; - let reject = move |err: PyBaseExceptionRef, vm: &VirtualMachine| { - let _ = - js_reject.call1(&JsValue::UNDEFINED, &convert::py_err_to_js_err(vm, &err)); - }; - let _ = vm.invoke( - then, - ( - vm.ctx.new_function("resolve", resolve), - vm.ctx.new_function("reject", reject), - ), - ); - }), - PromiseKind::PyResolved(obj) => Promise::resolve(&convert::py_to_js(vm, obj.clone())), - PromiseKind::PyRejected(err) => Promise::reject(&convert::py_err_to_js_err(vm, err)), + impl fmt::Debug for JsClosure { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad("JsClosure") } } - fn cast(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let then = vm.get_attribute_opt(obj.clone(), "then")?; - let value = if let Some(then) = then.filter(|obj| vm.is_callable(obj)) { - PromiseKind::PyProm { then } - } else { - PromiseKind::PyResolved(obj) - }; - Ok(Self { value }) - } - - fn cast_result(res: PyResult, vm: &VirtualMachine) -> PyResult { - match res { - Ok(res) => Self::cast(res, vm), - Err(e) => Ok(Self { - value: PromiseKind::PyRejected(e), - }), - } - } - - #[pyclassmethod] - fn resolve(cls: PyTypeRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult> { - Self::cast(obj, vm)?.into_ref_with_type(vm, cls) - } - - #[pyclassmethod] - fn reject( - cls: PyTypeRef, - err: PyBaseExceptionRef, - vm: &VirtualMachine, - ) -> PyResult> { - Self { - value: PromiseKind::PyRejected(err), - } - .into_ref_with_type(vm, cls) - } - - #[pymethod] - fn then( - &self, - on_fulfill: OptionalOption, - on_reject: OptionalOption, - vm: &VirtualMachine, - ) -> PyResult { - let (on_fulfill, on_reject) = (on_fulfill.flatten(), on_reject.flatten()); - if on_fulfill.is_none() && on_reject.is_none() { - return Ok(self.clone()); - } - match &self.value { - PromiseKind::Js(prom) => { - let weak_vm = weak_vm(vm); - let prom = JsFuture::from(prom.clone()); - - let ret_future = async move { - let stored_vm = &weak_vm + #[pyimpl] + impl JsClosure { + fn new(obj: PyObjectRef, once: bool, 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("that the vm is valid when the promise resolves"); - let res = prom.await; - match res { - Ok(val) => match on_fulfill { - Some(on_fulfill) => stored_vm.interp.enter(move |vm| { - let val = convert::js_to_py(vm, val); - let res = on_fulfill.invoke((val,), vm); - convert::pyresult_to_jsresult(vm, res) - }), - None => Ok(val), - }, - Err(err) => match on_reject { - Some(on_reject) => stored_vm.interp.enter(move |vm| { - let err = new_js_error(vm, err); - let res = on_reject.invoke((err,), vm); - convert::pyresult_to_jsresult(vm, res) - }), - None => Err(err), - }, + .expect("weak_py_obj to be valid if VM is valid"), + Err(err) => { + return Err(err); } }; - - Ok(PyPromise::from_future(ret_future)) + 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: ClosureType = if once { + Closure::wrap(Box::new(f)) + } else { + Closure::once(Box::new(f)) + }; + let wrapped = PyJsValue::new(wrap_closure(closure.as_ref())).into_ref(vm); + JsClosure { + closure: Some((closure, wrapped)).into(), + destroyed: false.into(), + detached: false.into(), } - PromiseKind::PyProm { then } => Self::cast_result( - vm.invoke( - then, - ( - on_fulfill.map(|c| c.into_object()), - on_reject.map(|c| c.into_object()), - ), - ), - vm, - ), - PromiseKind::PyResolved(res) => match on_fulfill { - Some(resolve) => Self::cast_result(resolve.invoke((res.clone(),), vm), vm), - None => Ok(self.clone()), - }, - PromiseKind::PyRejected(err) => match on_reject { - Some(reject) => Self::cast_result(reject.invoke((err.clone(),), vm), vm), - None => Ok(self.clone()), - }, + } + + #[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) } } - #[pymethod] - fn catch( - &self, - on_reject: OptionalOption, - vm: &VirtualMachine, - ) -> PyResult { - self.then(OptionalArg::Present(None), on_reject, vm) + #[pyattr] + #[pyclass(module = "_js", name = "Promise")] + #[derive(Debug, Clone, PyValue)] + pub struct PyPromise { + value: PromiseKind, } - #[pymethod(name = "__await__")] - fn r#await(zelf: PyRef) -> AwaitPromise { - AwaitPromise { - obj: Some(zelf.into()).into(), + #[derive(Debug, Clone)] + enum PromiseKind { + Js(Promise), + PyProm { then: PyObjectRef }, + PyResolved(PyObjectRef), + PyRejected(PyBaseExceptionRef), + } + + #[pyimpl] + impl PyPromise { + pub fn new(value: Promise) -> PyPromise { + PyPromise { + value: PromiseKind::Js(value), + } } - } -} - -#[pyclass(module = "_js", name = "AwaitPromise")] -#[derive(PyValue)] -struct AwaitPromise { - obj: cell::Cell>, -} - -impl fmt::Debug for AwaitPromise { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AwaitPromise").finish() - } -} - -#[pyimpl(with(IterNext))] -impl AwaitPromise { - #[pymethod] - fn send(&self, val: Option, vm: &VirtualMachine) -> PyResult { - match self.obj.take() { - Some(prom) => { - if val.is_some() { - Err(vm - .new_type_error("can't send non-None value to an awaitpromise".to_owned())) - } else { - Ok(PyIterReturn::Return(prom)) + pub fn from_future(future: F) -> PyPromise + where + F: future::Future> + 'static, + { + PyPromise::new(future_to_promise(future)) + } + pub fn as_js(&self, vm: &VirtualMachine) -> Promise { + match &self.value { + PromiseKind::Js(prom) => prom.clone(), + PromiseKind::PyProm { then } => Promise::new(&mut |js_resolve, js_reject| { + let resolve = move |res: PyObjectRef, vm: &VirtualMachine| { + let _ = js_resolve.call1(&JsValue::UNDEFINED, &convert::py_to_js(vm, res)); + }; + let reject = move |err: PyBaseExceptionRef, vm: &VirtualMachine| { + let _ = js_reject + .call1(&JsValue::UNDEFINED, &convert::py_err_to_js_err(vm, &err)); + }; + let _ = vm.invoke( + then, + ( + vm.ctx.new_function("resolve", resolve), + vm.ctx.new_function("reject", reject), + ), + ); + }), + PromiseKind::PyResolved(obj) => { + Promise::resolve(&convert::py_to_js(vm, obj.clone())) + } + PromiseKind::PyRejected(err) => { + Promise::reject(&convert::py_err_to_js_err(vm, err)) } } - None => Ok(PyIterReturn::StopIteration(val)), + } + + fn cast(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let then = vm.get_attribute_opt(obj.clone(), "then")?; + let value = if let Some(then) = then.filter(|obj| vm.is_callable(obj)) { + PromiseKind::PyProm { then } + } else { + PromiseKind::PyResolved(obj) + }; + Ok(Self { value }) + } + + fn cast_result(res: PyResult, vm: &VirtualMachine) -> PyResult { + match res { + Ok(res) => Self::cast(res, vm), + Err(e) => Ok(Self { + value: PromiseKind::PyRejected(e), + }), + } + } + + #[pyclassmethod] + fn resolve(cls: PyTypeRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult> { + Self::cast(obj, vm)?.into_ref_with_type(vm, cls) + } + + #[pyclassmethod] + fn reject( + cls: PyTypeRef, + err: PyBaseExceptionRef, + vm: &VirtualMachine, + ) -> PyResult> { + Self { + value: PromiseKind::PyRejected(err), + } + .into_ref_with_type(vm, cls) + } + + #[pymethod] + fn then( + &self, + on_fulfill: OptionalOption, + on_reject: OptionalOption, + vm: &VirtualMachine, + ) -> PyResult { + let (on_fulfill, on_reject) = (on_fulfill.flatten(), on_reject.flatten()); + if on_fulfill.is_none() && on_reject.is_none() { + return Ok(self.clone()); + } + match &self.value { + PromiseKind::Js(prom) => { + let weak_vm = weak_vm(vm); + let prom = JsFuture::from(prom.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) => match on_fulfill { + Some(on_fulfill) => stored_vm.interp.enter(move |vm| { + let val = convert::js_to_py(vm, val); + let res = on_fulfill.invoke((val,), vm); + convert::pyresult_to_jsresult(vm, res) + }), + None => Ok(val), + }, + Err(err) => match on_reject { + Some(on_reject) => stored_vm.interp.enter(move |vm| { + let err = new_js_error(vm, err); + let res = on_reject.invoke((err,), vm); + convert::pyresult_to_jsresult(vm, res) + }), + None => Err(err), + }, + } + }; + + Ok(PyPromise::from_future(ret_future)) + } + PromiseKind::PyProm { then } => Self::cast_result( + vm.invoke( + then, + ( + on_fulfill.map(|c| c.into_object()), + on_reject.map(|c| c.into_object()), + ), + ), + vm, + ), + PromiseKind::PyResolved(res) => match on_fulfill { + Some(resolve) => Self::cast_result(resolve.invoke((res.clone(),), vm), vm), + None => Ok(self.clone()), + }, + PromiseKind::PyRejected(err) => match on_reject { + Some(reject) => Self::cast_result(reject.invoke((err.clone(),), vm), vm), + None => Ok(self.clone()), + }, + } + } + + #[pymethod] + fn catch( + &self, + on_reject: OptionalOption, + vm: &VirtualMachine, + ) -> PyResult { + self.then(OptionalArg::Present(None), on_reject, vm) + } + + #[pymethod(name = "__await__")] + fn r#await(zelf: PyRef) -> AwaitPromise { + AwaitPromise { + obj: Some(zelf.into()).into(), + } } } - #[pymethod] - fn throw( - &self, - exc_type: PyObjectRef, - exc_val: OptionalArg, - exc_tb: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - let err = vm.normalize_exception( - exc_type, - exc_val.unwrap_or_none(vm), - exc_tb.unwrap_or_none(vm), - )?; - Err(err) + #[pyclass(noattr, module = "_js", name = "AwaitPromise")] + #[derive(PyValue)] + struct AwaitPromise { + obj: cell::Cell>, } -} -impl IterNextIterable for AwaitPromise {} -impl IterNext for AwaitPromise { - fn next(zelf: &PyObjectView, vm: &VirtualMachine) -> PyResult { - zelf.send(None, vm) + impl fmt::Debug for AwaitPromise { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AwaitPromise").finish() + } } -} -fn new_js_error(vm: &VirtualMachine, err: JsValue) -> PyBaseExceptionRef { - vm.new_exception( - vm.class("_js", "JSError"), - vec![PyJsValue::new(err).into_pyobject(vm)], - ) + #[pyimpl(with(IterNext))] + impl AwaitPromise { + #[pymethod] + fn send(&self, val: Option, vm: &VirtualMachine) -> PyResult { + match self.obj.take() { + Some(prom) => { + if val.is_some() { + Err(vm.new_type_error( + "can't send non-None value to an awaitpromise".to_owned(), + )) + } else { + Ok(PyIterReturn::Return(prom)) + } + } + None => Ok(PyIterReturn::StopIteration(val)), + } + } + + #[pymethod] + fn throw( + &self, + exc_type: PyObjectRef, + exc_val: OptionalArg, + exc_tb: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let err = vm.normalize_exception( + exc_type, + exc_val.unwrap_or_none(vm), + exc_tb.unwrap_or_none(vm), + )?; + Err(err) + } + } + + impl IterNextIterable for AwaitPromise {} + impl IterNext for AwaitPromise { + fn next(zelf: &PyObjectView, vm: &VirtualMachine) -> PyResult { + zelf.send(None, vm) + } + } + + fn new_js_error(vm: &VirtualMachine, err: JsValue) -> PyBaseExceptionRef { + vm.new_exception( + vm.class("_js", "JSError"), + vec![PyJsValue::new(err).into_pyobject(vm)], + ) + } } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { - let ctx = &vm.ctx; + let module = _js::make_module(vm); - let js_error = PyType::new_simple_ref("JSError", &ctx.exceptions.exception_type).unwrap(); + let ctx = &vm.ctx; + let js_error = PyType::new_simple_ref("JSError", &vm.ctx.exceptions.exception_type).unwrap(); extend_class!(ctx, &js_error, { "value" => ctx.new_readonly_getset("value", js_error.clone(), |exc: PyBaseExceptionRef| exc.get_arg(0)), }); - AwaitPromise::make_class(ctx); - - py_module!(vm, "_js", { + extend_module!(vm, module, { "JSError" => js_error, - "JSValue" => PyJsValue::make_class(ctx), - "JSClosure" => JsClosure::make_class(ctx), - "Promise" => PyPromise::make_class(ctx), - }) + }); + + module } pub fn setup_js_module(vm: &mut VirtualMachine) {