diff --git a/Lib/bisect.py b/Lib/bisect.py index 4a4d05255..d8486a407 100644 --- a/Lib/bisect.py +++ b/Lib/bisect.py @@ -19,8 +19,6 @@ def insort_right(a, x, lo=0, hi=None): else: lo = mid+1 a.insert(lo, x) -insort = insort_right # backward compatibility - def bisect_right(a, x, lo=0, hi=None): """Return the index where to insert item x in list a, assuming a is sorted. @@ -42,8 +40,6 @@ def bisect_right(a, x, lo=0, hi=None): else: lo = mid+1 return lo -bisect = bisect_right # backward compatibility - def insort_left(a, x, lo=0, hi=None): """Insert item x in list a, and keep it sorted assuming a is sorted. @@ -85,8 +81,11 @@ def bisect_left(a, x, lo=0, hi=None): else: hi = mid return lo -# Overwrite above definitions with a fast C implementation +# Overwrite above definitions with a fast Rust implementation try: from _bisect import * except ImportError: pass + +bisect = bisect_right # backward compatibility +insort = insort_right # backward compatibility \ No newline at end of file diff --git a/Lib/test/test_bisect.py b/Lib/test/test_bisect.py index 35c0626e2..46e0be3d1 100644 --- a/Lib/test/test_bisect.py +++ b/Lib/test/test_bisect.py @@ -4,7 +4,7 @@ from test import support from collections import UserList py_bisect = support.import_fresh_module('bisect', blocked=['_bisect']) -c_bisect = None +rust_bisect = support.import_fresh_module('bisect', fresh=['bisect']) class Range(object): """A trivial range()-like object that has an insert() method.""" @@ -202,9 +202,8 @@ class TestBisect: class TestBisectPython(TestBisect, unittest.TestCase): module = py_bisect -@unittest.skip("TODO: RUSTPYTHON, _bisect module") -class TestBisectC(TestBisect, unittest.TestCase): - module = c_bisect +class TestBisectRust(TestBisect, unittest.TestCase): + module = rust_bisect #============================================================================== @@ -238,9 +237,8 @@ class TestInsort: class TestInsortPython(TestInsort, unittest.TestCase): module = py_bisect -@unittest.skip("TODO: RUSTPYTHON, _bisect module") -class TestInsortC(TestInsort, unittest.TestCase): - module = c_bisect +class TestInsortRust(TestInsort, unittest.TestCase): + module = rust_bisect #============================================================================== @@ -294,9 +292,8 @@ class TestErrorHandling: class TestErrorHandlingPython(TestErrorHandling, unittest.TestCase): module = py_bisect -@unittest.skip("TODO: RUSTPYTHON, _bisect module") -class TestErrorHandlingC(TestErrorHandling, unittest.TestCase): - module = c_bisect +class TestErrorHandlingRust(TestErrorHandling, unittest.TestCase): + module = rust_bisect #============================================================================== @@ -322,9 +319,8 @@ class TestDocExample: class TestDocExamplePython(TestDocExample, unittest.TestCase): module = py_bisect -@unittest.skip("TODO: RUSTPYTHON, _bisect module") -class TestDocExampleC(TestDocExample, unittest.TestCase): - module = c_bisect +class TestDocExampleRust(TestDocExample, unittest.TestCase): + module = rust_bisect #------------------------------------------------------------------------------ diff --git a/vm/src/stdlib/bisect.rs b/vm/src/stdlib/bisect.rs new file mode 100644 index 000000000..0a02c06b4 --- /dev/null +++ b/vm/src/stdlib/bisect.rs @@ -0,0 +1,154 @@ +pub(crate) use _bisect::make_module; + +#[pymodule] +mod _bisect { + use std::convert::TryFrom; + + use crate::function::OptionalArg; + use crate::slots::PyComparisonOp::Lt; + use crate::vm::VirtualMachine; + use crate::{ItemProtocol, PyObjectRef, PyResult}; + use num_traits::ToPrimitive; + + #[derive(FromArgs)] + struct BisectArgs { + #[pyarg(any, name = "a")] + a: PyObjectRef, + #[pyarg(any, name = "x")] + x: PyObjectRef, + #[pyarg(any, optional, name = "lo")] + lo: OptionalArg, + #[pyarg(any, optional, name = "hi")] + hi: OptionalArg, + } + + // Handles objects that implement __index__ and makes sure index fits in needed isize. + #[inline] + fn handle_default( + arg: OptionalArg, + default: Option, + vm: &VirtualMachine, + ) -> PyResult> { + Ok(match arg { + OptionalArg::Present(v) => { + Some(vm.to_index(&v)?.as_bigint().to_isize().ok_or_else(|| { + vm.new_index_error("cannot fit 'int' into an index-sized integer".to_owned()) + })?) + } + OptionalArg::Missing => default, + }) + } + + // Handles defaults for lo, hi. + // + // - lo must be >= 0 with a default of 0. + // - hi, while it could be negative, defaults to `0` and the same effect is achieved + // (while loop isn't entered); this way we keep it a usize (and, if my understanding + // is correct, issue 13496 is handled). Its default value is set to the length of the + // input sequence. + #[inline] + fn as_usize( + lo: OptionalArg, + hi: OptionalArg, + seq_len: usize, + vm: &VirtualMachine, + ) -> PyResult<(usize, usize)> { + // We only deal with positives for lo, try_from can't fail. + // Default is always a Some so we can safely unwrap. + let lo = handle_default(lo, Some(0), vm)? + .map(|value| { + if value < 0 { + return Err(vm.new_value_error("lo must be non-negative".to_owned())); + } + Ok(usize::try_from(value).unwrap()) + }) + .unwrap()?; + let hi = handle_default(hi, None, vm)? + .map(|value| { + if value < 0 { + 0 + } else { + // Can't fail. + usize::try_from(value).unwrap() + } + }) + .unwrap_or(seq_len); + Ok((lo, hi)) + } + + #[inline] + fn bisect_left_impl( + BisectArgs { a, x, lo, hi }: BisectArgs, + vm: &VirtualMachine, + ) -> PyResult { + let (mut lo, mut hi) = as_usize(lo, hi, vm.obj_len(&a)?, vm)?; + + while lo < hi { + // Handles issue 13496. + let mid = (lo + hi) / 2; + if vm.bool_cmp(&a.get_item(mid, vm)?, &x, Lt)? { + lo = mid + 1; + } else { + hi = mid; + } + } + Ok(lo) + } + + #[inline] + fn bisect_right_impl( + BisectArgs { a, x, lo, hi }: BisectArgs, + vm: &VirtualMachine, + ) -> PyResult { + let (mut lo, mut hi) = as_usize(lo, hi, vm.obj_len(&a)?, vm)?; + + while lo < hi { + // Handles issue 13496. + let mid = (lo + hi) / 2; + if vm.bool_cmp(&x, &a.get_item(mid, vm)?, Lt)? { + hi = mid; + } else { + lo = mid + 1; + } + } + Ok(lo) + } + + #[pyfunction] + fn bisect_left(args: BisectArgs, vm: &VirtualMachine) -> PyResult { + bisect_left_impl(args, vm) + } + + #[pyfunction] + fn bisect_right(args: BisectArgs, vm: &VirtualMachine) -> PyResult { + bisect_right_impl(args, vm) + } + + #[pyfunction] + fn insort_left(BisectArgs { a, x, lo, hi }: BisectArgs, vm: &VirtualMachine) -> PyResult { + let index = bisect_left_impl( + BisectArgs { + a: a.clone(), + x: x.clone(), + lo, + hi, + }, + vm, + )?; + vm.call_method(&a, "insert", (index, x)) + } + + #[pyfunction] + fn insort_right(BisectArgs { a, x, lo, hi }: BisectArgs, vm: &VirtualMachine) -> PyResult { + let index = bisect_right_impl( + BisectArgs { + a: a.clone(), + x: x.clone(), + lo, + hi, + }, + vm, + )?; + vm.call_method(&a, "insert", (index, x)) + } +} diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 90b62e010..46a70298d 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -8,6 +8,7 @@ pub mod array; pub(crate) mod ast; mod atexit; mod binascii; +mod bisect; mod codecs; mod collections; mod csv; @@ -104,6 +105,7 @@ pub fn get_module_inits() -> StdlibMap { "array" => array::make_module, "atexit" => atexit::make_module, "binascii" => binascii::make_module, + "_bisect" => bisect::make_module, "_codecs" => codecs::make_module, "_collections" => collections::make_module, "_csv" => csv::make_module,