diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index ea21d30fda..490d6ef37f 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -221,8 +221,7 @@ fn promise_then(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } }; - ret.map(|val| convert::py_to_js(vm, val)) - .map_err(|err| convert::py_to_js(vm, err)) + convert::pyresult_to_jsresult(vm, ret) }); let ret_promise = future_to_promise(ret_future); @@ -254,9 +253,8 @@ fn promise_catch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { .upgrade() .expect("that the vm is valid when the promise resolves"); let err = convert::js_to_py(vm, err); - vm.invoke(on_reject, PyFuncArgs::new(vec![err], vec![])) - .map(|val| convert::py_to_js(vm, val)) - .map_err(|err| convert::py_to_js(vm, err)) + let res = vm.invoke(on_reject, PyFuncArgs::new(vec![err], vec![])); + convert::pyresult_to_jsresult(vm, res) } }); diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index cb37efe5a8..086b531cfd 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,14 +1,59 @@ use crate::browser_module; use crate::vm_class::{AccessibleVM, WASMVirtualMachine}; use js_sys::{Array, ArrayBuffer, Object, Promise, Reflect, Uint8Array}; -use rustpython_vm::obj::{objbytes, objtype}; -use rustpython_vm::pyobject::{self, DictProtocol, PyFuncArgs, PyObjectRef, PyResult}; +use num_traits::cast::ToPrimitive; +use rustpython_vm::obj::{objbytes, objint, objsequence, objtype}; +use rustpython_vm::pyobject::{ + self, AttributeProtocol, DictProtocol, PyFuncArgs, PyObjectRef, PyResult, +}; use rustpython_vm::VirtualMachine; use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; -pub fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { - vm.to_pystr(&py_err) - .unwrap_or_else(|_| "Error, and error getting error message".into()) +pub fn py_err_to_js_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> JsValue { + macro_rules! map_exceptions { + ($py_exc:ident, $msg:expr, { $($py_exc_ty:expr => $js_err_new:expr),*$(,)? }) => { + $(if objtype::isinstance($py_exc, $py_exc_ty) { + JsValue::from($js_err_new($msg)) + } else)* { + JsValue::from(js_sys::Error::new($msg)) + } + }; + } + let msg = match py_err + .get_attr("msg") + .and_then(|msg| vm.to_pystr(&msg).ok()) + { + Some(msg) => msg, + None => return js_sys::Error::new("error getting error").into(), + }; + let js_err = map_exceptions!(py_err,& msg, { + // TypeError is sort of a catch-all for "this value isn't what I thought it was like" + &vm.ctx.exceptions.type_error => js_sys::TypeError::new, + &vm.ctx.exceptions.value_error => js_sys::TypeError::new, + &vm.ctx.exceptions.index_error => js_sys::TypeError::new, + &vm.ctx.exceptions.key_error => js_sys::TypeError::new, + &vm.ctx.exceptions.attribute_error => js_sys::TypeError::new, + &vm.ctx.exceptions.name_error => js_sys::ReferenceError::new, + &vm.ctx.exceptions.syntax_error => js_sys::SyntaxError::new, + }); + if let Some(tb) = py_err.get_attr("__traceback__") { + if objtype::isinstance(&tb, &vm.ctx.list_type()) { + let elements = objsequence::get_elements(&tb).to_vec(); + if let Some(top) = elements.get(0) { + if objtype::isinstance(&top, &vm.ctx.tuple_type()) { + let element = objsequence::get_elements(&top); + + if let Some(lineno) = objint::to_int(vm, &element[1], 10) + .ok() + .and_then(|lineno| lineno.to_u32()) + { + Reflect::set(&js_err, &"row".into(), &lineno.into()); + } + } + } + } + } + js_err } pub fn js_py_typeerror(vm: &mut VirtualMachine, js_err: JsValue) -> PyObjectRef { @@ -110,7 +155,7 @@ pub fn object_entries(obj: &Object) -> impl Iterator Result { result .map(|value| py_to_js(vm, value)) - .map_err(|err| py_str_err(vm, &err).into()) + .map_err(|err| py_err_to_js_err(vm, &err).into()) } pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 0da541ade0..f950f2c86e 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -1,7 +1,7 @@ use crate::browser_module::setup_browser_module; use crate::convert; use crate::wasm_builtins; -use js_sys::{Object, SyntaxError, TypeError}; +use js_sys::{Object, Reflect, SyntaxError, TypeError}; use rustpython_vm::{ compile, frame::ScopeRef, @@ -318,10 +318,44 @@ impl WASMVirtualMachine { }| { source.push('\n'); let code = - compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()) - .map_err(|err| { - SyntaxError::new(&format!("Error parsing Python code: {}", err)) - })?; + compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()); + let code = code.map_err(|err| { + let js_err = SyntaxError::new(&format!("Error parsing Python code: {}", err)); + if let rustpython_vm::error::CompileError::Parse(ref parse_error) = err { + use rustpython_parser::error::ParseError; + if let ParseError::EOF(Some(ref loc)) + | ParseError::ExtraToken((ref loc, ..)) + | ParseError::InvalidToken(ref loc) + | ParseError::UnrecognizedToken((ref loc, ..), _) = parse_error + { + let _ = Reflect::set( + &js_err, + &"row".into(), + &(loc.get_row() as u32).into(), + ); + let _ = Reflect::set( + &js_err, + &"col".into(), + &(loc.get_column() as u32).into(), + ); + } + if let ParseError::ExtraToken((_, _, ref loc)) + | ParseError::UnrecognizedToken((_, _, ref loc), _) = parse_error + { + let _ = Reflect::set( + &js_err, + &"endrow".into(), + &(loc.get_row() as u32).into(), + ); + let _ = Reflect::set( + &js_err, + &"endcol".into(), + &(loc.get_column() as u32).into(), + ); + } + } + js_err + })?; let result = vm.run_code_obj(code, scope.clone()); convert::pyresult_to_jsresult(vm, result) },