diff --git a/tests/snippets/builtin_range.py b/tests/snippets/builtin_range.py index c822ce7e7..c8efb189d 100644 --- a/tests/snippets/builtin_range.py +++ b/tests/snippets/builtin_range.py @@ -18,6 +18,11 @@ def assert_raises(expr, exc_type): assert range(2**63+1)[2**63] == 9223372036854775808 +# len tests +assert len(range(10, 5)) == 0, 'Range with no elements should have length = 0' +assert len(range(10, 5, -2)) == 3, 'Expected length 3, for elements: 10, 8, 6' +assert len(range(5, 10, 2)) == 3, 'Expected length 3, for elements: 5, 7, 9' + # index tests assert range(10).index(6) == 6 assert range(4, 10).index(6) == 2 diff --git a/tests/snippets/division_by_zero.py b/tests/snippets/division_by_zero.py new file mode 100644 index 000000000..7cb68cd76 --- /dev/null +++ b/tests/snippets/division_by_zero.py @@ -0,0 +1,34 @@ +try: + 5 / 0 +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + 5 / -0.0 +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + 5 / (2-2) +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + 5 % 0 +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + raise ZeroDivisionError('Is an ArithmeticError subclass?') +except ArithmeticError: + pass +else: + assert False, 'Expected ZeroDivisionError' diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index c1bc982d0..f8df09a62 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -692,6 +692,11 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.exceptions.base_exception_type.clone(), ); ctx.set_attr(&py_mod, "Exception", ctx.exceptions.exception_type.clone()); + ctx.set_attr( + &py_mod, + "ArithmeticError", + ctx.exceptions.arithmetic_error.clone(), + ); ctx.set_attr( &py_mod, "AssertionError", @@ -703,6 +708,11 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.exceptions.attribute_error.clone(), ); ctx.set_attr(&py_mod, "NameError", ctx.exceptions.name_error.clone()); + ctx.set_attr( + &py_mod, + "OverflowError", + ctx.exceptions.overflow_error.clone(), + ); ctx.set_attr( &py_mod, "RuntimeError", @@ -722,6 +732,11 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "StopIteration", ctx.exceptions.stop_iteration.clone(), ); + ctx.set_attr( + &py_mod, + "ZeroDivisionError", + ctx.exceptions.zero_division_error.clone(), + ); py_mod } diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 305297919..be3652a73 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -81,6 +81,7 @@ fn exception_str(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { #[derive(Debug)] pub struct ExceptionZoo { + pub arithmetic_error: PyObjectRef, pub assertion_error: PyObjectRef, pub attribute_error: PyObjectRef, pub base_exception_type: PyObjectRef, @@ -93,12 +94,14 @@ pub struct ExceptionZoo { pub name_error: PyObjectRef, pub not_implemented_error: PyObjectRef, pub os_error: PyObjectRef, + pub overflow_error: PyObjectRef, pub permission_error: PyObjectRef, pub runtime_error: PyObjectRef, pub stop_iteration: PyObjectRef, pub syntax_error: PyObjectRef, pub type_error: PyObjectRef, pub value_error: PyObjectRef, + pub zero_division_error: PyObjectRef, } impl ExceptionZoo { @@ -113,6 +116,8 @@ impl ExceptionZoo { let exception_type = create_type("Exception", &type_type, &base_exception_type, &dict_type); + let arithmetic_error = + create_type("ArithmeticError", &type_type, &exception_type, &dict_type); let assertion_error = create_type("AssertionError", &type_type, &exception_type, &dict_type); let attribute_error = @@ -128,8 +133,18 @@ impl ExceptionZoo { let type_error = create_type("TypeError", &type_type, &exception_type, &dict_type); let value_error = create_type("ValueError", &type_type, &exception_type, &dict_type); + let overflow_error = + create_type("OverflowError", &type_type, &arithmetic_error, &dict_type); + let zero_division_error = create_type( + "ZeroDivisionError", + &type_type, + &arithmetic_error, + &dict_type, + ); + let module_not_found_error = create_type("ModuleNotFoundError", &type_type, &import_error, &dict_type); + let not_implemented_error = create_type( "NotImplementedError", &type_type, @@ -142,6 +157,7 @@ impl ExceptionZoo { let permission_error = create_type("PermissionError", &type_type, &os_error, &dict_type); ExceptionZoo { + arithmetic_error, assertion_error, attribute_error, base_exception_type, @@ -154,12 +170,14 @@ impl ExceptionZoo { name_error, not_implemented_error, os_error, + overflow_error, permission_error, runtime_error, stop_iteration, syntax_error, type_error, value_error, + zero_division_error, } } } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 6894a0492..080612463 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -366,17 +366,25 @@ fn int_truediv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(i, Some(vm.ctx.int_type())), (i2, None)] ); - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_float(v1.to_f64().unwrap() / get_value(i2).to_f64().unwrap())) + + let v1 = get_value(i) + .to_f64() + .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))?; + + let v2 = if objtype::isinstance(i2, &vm.ctx.int_type()) { + get_value(i2) + .to_f64() + .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))? } else if objtype::isinstance(i2, &vm.ctx.float_type()) { - Ok(vm - .ctx - .new_float(v1.to_f64().unwrap() / objfloat::get_value(i2))) + objfloat::get_value(i2) } else { - Err(vm.new_type_error(format!("Cannot divide {} and {}", i.borrow(), i2.borrow()))) + return Err(vm.new_type_error(format!("Cannot divide {} and {}", i.borrow(), i2.borrow()))); + }; + + if v2 == 0.0 { + Err(vm.new_zero_division_error("integer division by zero".to_string())) + } else { + Ok(vm.ctx.new_float(v1 / v2)) } } @@ -388,7 +396,13 @@ fn int_mod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ); let v1 = get_value(i); if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm.ctx.new_int(v1 % get_value(i2))) + let v2 = get_value(i2); + + if v2 != BigInt::zero() { + Ok(vm.ctx.new_int(v1 % get_value(i2))) + } else { + Err(vm.new_zero_division_error("integer modulo by zero".to_string())) + } } else { Err(vm.new_type_error(format!("Cannot modulo {} and {}", i.borrow(), i2.borrow()))) } diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index d82a4abc3..8d320084c 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -18,12 +18,24 @@ pub struct RangeType { } impl RangeType { + #[inline] + pub fn try_len(&self) -> Option { + match self.step.sign() { + Sign::Plus if self.start < self.end => ((&self.end - &self.start - 1usize) + / &self.step) + .to_usize() + .map(|sz| sz + 1), + Sign::Minus if self.start > self.end => ((&self.start - &self.end - 1usize) + / (-&self.step)) + .to_usize() + .map(|sz| sz + 1), + _ => Some(0), + } + } + #[inline] pub fn len(&self) -> usize { - ((self.end.clone() - self.start.clone()) / self.step.clone()) - .abs() - .to_usize() - .unwrap() + self.try_len().unwrap() } #[inline] @@ -76,6 +88,15 @@ impl RangeType { None } } + + #[inline] + pub fn repr(&self) -> String { + if self.step == BigInt::one() { + format!("range({}, {})", self.start, self.end) + } else { + format!("range({}, {}, {})", self.start, self.end, self.step) + } + } } pub fn init(context: &PyContext) { @@ -88,6 +109,7 @@ pub fn init(context: &PyContext) { "__getitem__", context.new_rustfunc(range_getitem), ); + context.set_attr(&range_type, "__repr__", context.new_rustfunc(range_repr)); context.set_attr(&range_type, "__bool__", context.new_rustfunc(range_bool)); context.set_attr( &range_type, @@ -153,12 +175,14 @@ fn range_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn range_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); - let len = match zelf.borrow().payload { - PyObjectPayload::Range { ref range } => range.len(), + if let Some(len) = match zelf.borrow().payload { + PyObjectPayload::Range { ref range } => range.try_len(), _ => unreachable!(), - }; - - Ok(vm.ctx.new_int(len)) + } { + Ok(vm.ctx.new_int(len)) + } else { + Err(vm.new_overflow_error("Python int too large to convert to Rust usize".to_string())) + } } fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -224,6 +248,17 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } +fn range_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); + + let s = match zelf.borrow().payload { + PyObjectPayload::Range { ref range } => range.repr(), + _ => unreachable!(), + }; + + Ok(vm.ctx.new_str(s)) +} + fn range_bool(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 8bcadd11c..435e7bcbc 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -104,6 +104,11 @@ impl VirtualMachine { self.new_exception(os_error, msg) } + pub fn new_overflow_error(&mut self, msg: String) -> PyObjectRef { + let overflow_error = self.ctx.exceptions.overflow_error.clone(); + self.new_exception(overflow_error, msg) + } + /// Create a new python ValueError object. Useful for raising errors from /// python functions implemented in rust. pub fn new_value_error(&mut self, msg: String) -> PyObjectRef { @@ -122,8 +127,13 @@ impl VirtualMachine { } pub fn new_not_implemented_error(&mut self, msg: String) -> PyObjectRef { - let value_error = self.ctx.exceptions.not_implemented_error.clone(); - self.new_exception(value_error, msg) + let not_implemented_error = self.ctx.exceptions.not_implemented_error.clone(); + self.new_exception(not_implemented_error, msg) + } + + pub fn new_zero_division_error(&mut self, msg: String) -> PyObjectRef { + let zero_division_error = self.ctx.exceptions.zero_division_error.clone(); + self.new_exception(zero_division_error, msg) } pub fn new_scope(&mut self, parent_scope: Option) -> PyObjectRef {