diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index a6f754591..467154f0b 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -1,6 +1,6 @@ import unittest +import sys from test import support -from test.support import os_helper from test.test_grammar import (VALID_UNDERSCORE_LITERALS, INVALID_UNDERSCORE_LITERALS) @@ -12,6 +12,14 @@ INF = float("inf") NAN = float("nan") # These tests ensure that complex math does the right thing +ZERO_DIVISION = ( + (1+1j, 0+0j), + (1+1j, 0.0), + (1+1j, 0), + (1.0, 0+0j), + (1, 0+0j), +) + class ComplexTest(unittest.TestCase): def assertAlmostEqual(self, a, b): @@ -100,20 +108,34 @@ class ComplexTest(unittest.TestCase): self.check_div(complex(random(), random()), complex(random(), random())) - self.assertRaises(ZeroDivisionError, complex.__truediv__, 1+1j, 0+0j) - self.assertRaises(OverflowError, pow, 1e200+1j, 1e200+1j) - self.assertAlmostEqual(complex.__truediv__(2+0j, 1+1j), 1-1j) - self.assertRaises(ZeroDivisionError, complex.__truediv__, 1+1j, 0+0j) for denom_real, denom_imag in [(0, NAN), (NAN, 0), (NAN, NAN)]: z = complex(0, 0) / complex(denom_real, denom_imag) self.assertTrue(isnan(z.real)) self.assertTrue(isnan(z.imag)) + def test_truediv_zero_division(self): + for a, b in ZERO_DIVISION: + with self.assertRaises(ZeroDivisionError): + a / b + def test_floordiv(self): - self.assertRaises(TypeError, complex.__floordiv__, 3+0j, 1.5+0j) - self.assertRaises(TypeError, complex.__floordiv__, 3+0j, 0+0j) + with self.assertRaises(TypeError): + (1+1j) // (1+0j) + with self.assertRaises(TypeError): + (1+1j) // 1.0 + with self.assertRaises(TypeError): + (1+1j) // 1 + with self.assertRaises(TypeError): + 1.0 // (1+0j) + with self.assertRaises(TypeError): + 1 // (1+0j) + + def test_floordiv_zero_division(self): + for a, b in ZERO_DIVISION: + with self.assertRaises(TypeError): + a // b def test_richcompare(self): self.assertIs(complex.__eq__(1+1j, 1<<10000), False) @@ -162,13 +184,32 @@ class ComplexTest(unittest.TestCase): def test_mod(self): # % is no longer supported on complex numbers - self.assertRaises(TypeError, (1+1j).__mod__, 0+0j) - self.assertRaises(TypeError, lambda: (3.33+4.43j) % 0) - self.assertRaises(TypeError, (1+1j).__mod__, 4.3j) + with self.assertRaises(TypeError): + (1+1j) % (1+0j) + with self.assertRaises(TypeError): + (1+1j) % 1.0 + with self.assertRaises(TypeError): + (1+1j) % 1 + with self.assertRaises(TypeError): + 1.0 % (1+0j) + with self.assertRaises(TypeError): + 1 % (1+0j) + + def test_mod_zero_division(self): + for a, b in ZERO_DIVISION: + with self.assertRaises(TypeError): + a % b def test_divmod(self): self.assertRaises(TypeError, divmod, 1+1j, 1+0j) - self.assertRaises(TypeError, divmod, 1+1j, 0+0j) + self.assertRaises(TypeError, divmod, 1+1j, 1.0) + self.assertRaises(TypeError, divmod, 1+1j, 1) + self.assertRaises(TypeError, divmod, 1.0, 1+0j) + self.assertRaises(TypeError, divmod, 1, 1+0j) + + def test_divmod_zero_division(self): + for a, b in ZERO_DIVISION: + self.assertRaises(TypeError, divmod, a, b) def test_pow(self): self.assertAlmostEqual(pow(1+1j, 0+0j), 1.0) @@ -177,6 +218,7 @@ class ComplexTest(unittest.TestCase): self.assertAlmostEqual(pow(1j, -1), 1/1j) self.assertAlmostEqual(pow(1j, 200), 1) self.assertRaises(ValueError, pow, 1+1j, 1+1j, 1+1j) + self.assertRaises(OverflowError, pow, 1e200+1j, 1e200+1j) a = 3.33+4.43j self.assertEqual(a ** 0j, 1) @@ -209,6 +251,54 @@ class ComplexTest(unittest.TestCase): b = 5.1+2.3j self.assertRaises(ValueError, pow, a, b, 0) + # Check some boundary conditions; some of these used to invoke + # undefined behaviour (https://bugs.python.org/issue44698). We're + # not actually checking the results of these operations, just making + # sure they don't crash (for example when using clang's + # UndefinedBehaviourSanitizer). + values = (sys.maxsize, sys.maxsize+1, sys.maxsize-1, + -sys.maxsize, -sys.maxsize+1, -sys.maxsize+1) + for real in values: + for imag in values: + with self.subTest(real=real, imag=imag): + c = complex(real, imag) + try: + c ** real + except OverflowError: + pass + try: + c ** c + except OverflowError: + pass + + def test_pow_with_small_integer_exponents(self): + # Check that small integer exponents are handled identically + # regardless of their type. + values = [ + complex(5.0, 12.0), + complex(5.0e100, 12.0e100), + complex(-4.0, INF), + complex(INF, 0.0), + ] + exponents = [-19, -5, -3, -2, -1, 0, 1, 2, 3, 5, 19] + for value in values: + for exponent in exponents: + with self.subTest(value=value, exponent=exponent): + try: + int_pow = value**exponent + except OverflowError: + int_pow = "overflow" + try: + float_pow = value**float(exponent) + except OverflowError: + float_pow = "overflow" + try: + complex_pow = value**complex(exponent) + except OverflowError: + complex_pow = "overflow" + self.assertEqual(str(float_pow), str(int_pow)) + self.assertEqual(str(complex_pow), str(int_pow)) + def test_boolcontext(self): for i in range(100): self.assertTrue(complex(random() + 1e-6, random() + 1e-6)) @@ -509,22 +599,6 @@ class ComplexTest(unittest.TestCase): def test_neg(self): self.assertEqual(-(1+6j), -1-6j) - def test_file(self): - a = 3.33+4.43j - b = 5.1+2.3j - - fo = None - try: - fo = open(os_helper.TESTFN, "w") - print(a, b, file=fo) - fo.close() - fo = open(os_helper.TESTFN, "r") - self.assertEqual(fo.read(), ("%s %s\n" % (a, b))) - finally: - if (fo is not None) and (not fo.closed): - fo.close() - os_helper.unlink(os_helper.TESTFN) - def test_getnewargs(self): self.assertEqual((1+2j).__getnewargs__(), (1.0, 2.0)) self.assertEqual((1-2j).__getnewargs__(), (1.0, -2.0)) @@ -715,8 +789,6 @@ class ComplexTest(unittest.TestCase): self.assertEqual(format(complex(INF, 1), 'F'), 'INF+1.000000j') self.assertEqual(format(complex(INF, -1), 'F'), 'INF-1.000000j') -def test_main(): - support.run_unittest(ComplexTest) if __name__ == "__main__": - test_main() + unittest.main() diff --git a/vm/src/builtins/complex.rs b/vm/src/builtins/complex.rs index 338ba5027..48bb884f4 100644 --- a/vm/src/builtins/complex.rs +++ b/vm/src/builtins/complex.rs @@ -10,7 +10,6 @@ use crate::{ use num_complex::Complex64; use num_traits::Zero; use rustpython_common::{float_ops, hash}; -use std::convert::Infallible as Never; /// Create a complex number from a real part and an optional imaginary part. /// @@ -272,16 +271,6 @@ impl PyComplex { self.value.conj() } - #[pymethod(magic)] - fn float(&self, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(String::from("Can't convert complex to float"))) - } - - #[pymethod(magic)] - fn int(&self, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(String::from("Can't convert complex to int"))) - } - #[pymethod(name = "__rmul__")] #[pymethod(magic)] fn mul( @@ -310,24 +299,6 @@ impl PyComplex { self.op(other, |a, b| inner_div(b, a, vm), vm) } - #[pymethod(name = "__mod__")] - #[pymethod(name = "__rmod__")] - fn mod_(&self, _other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("can't mod complex numbers.".to_owned())) - } - - #[pymethod(name = "__rfloordiv__")] - #[pymethod(magic)] - fn floordiv(&self, _other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("can't take floor of complex number.".to_owned())) - } - - #[pymethod(name = "__rdivmod__")] - #[pymethod(magic)] - fn divmod(&self, _other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("can't take floor or mod of complex number.".to_owned())) - } - #[pymethod(magic)] fn pos(&self) -> Complex64 { self.value