diff --git a/crates/vm/src/stdlib/_testinternalcapi.rs b/crates/vm/src/stdlib/_testinternalcapi.rs index abd742b4c..945a7aaae 100644 --- a/crates/vm/src/stdlib/_testinternalcapi.rs +++ b/crates/vm/src/stdlib/_testinternalcapi.rs @@ -1,7 +1,5 @@ pub(crate) use _testinternalcapi::module_def; -mod pytime; - use core::sync::atomic::{AtomicUsize, Ordering}; static RARE_SET_CLASS: AtomicUsize = AtomicUsize::new(0); @@ -28,7 +26,6 @@ pub(crate) fn note_func_modification() { #[pymodule] mod _testinternalcapi { - use super::pytime::Round; use super::*; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, @@ -43,6 +40,7 @@ mod _testinternalcapi { function::{FuncArgs, OptionalArg, PyComparisonValue}, object::{Traverse, TraverseFn}, protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, + stdlib::time::pytime::{self, Round}, types::{ AsMapping, AsSequence, Comparable, IterNext, Iterable, PyComparisonOp, PyTypeFlags, SelfIter, diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index b645c1ab7..51d9cae5f 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -5,6 +5,7 @@ // https://docs.python.org/3/library/time.html pub use decl::time; +pub(crate) mod pytime; pub(crate) use decl::module_def; pub(crate) use decl::perf_counter_ns as profiler_time; @@ -19,7 +20,7 @@ mod decl { use crate::convert::{ToPyException, ToPyObject}; use crate::{ AsObject, Py, PyObjectRef, PyResult, VirtualMachine, - builtins::{PyFloat, PyStr, PyStrRef, PyTypeRef}, + builtins::{PyStr, PyStrRef, PyTypeRef}, class::PyClassDef, common::wtf8::Wtf8Buf, function::{Either, FuncArgs, OptionalArg, OptionalOption}, @@ -33,7 +34,6 @@ mod decl { use core::time::Duration; #[cfg(not(any(unix, windows)))] use jiff::{Timestamp, Zoned, civil::DateTime, tz::TimeZone}; - use num_traits::ToPrimitive; #[cfg(target_os = "wasi")] use rustpython_host_env::time::ClockId; #[cfg(any(unix, windows))] @@ -128,43 +128,9 @@ mod decl { fn sleep(object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { vm.audit("time.sleep", || (object.clone(),))?; - let overflow = || vm.new_overflow_error("timestamp out of range for C PyTime_t"); - let nanoseconds = if object.number().is_index() { - let seconds = object.try_index(vm).map_err(|e| { - if e.fast_isinstance(vm.ctx.exceptions.overflow_error) { - overflow() - } else { - e - } - })?; - seconds - .as_bigint() - .to_i64() - .and_then(|seconds| seconds.checked_mul(SEC_TO_NS)) - .ok_or_else(overflow)? - } else { - // PyFloat_AsDouble reads float subclasses without calling __float__. - let seconds = match object.downcast_ref::() { - Some(value) => value.to_f64(), - None => object.try_float(vm)?.to_f64(), - }; - if seconds.is_nan() { - return Err(vm.new_value_error("Invalid value NaN (not a number)")); - } - // _PyTime_ROUND_TIMEOUT rounds away from zero. - let nanoseconds = seconds * SEC_TO_NS as f64; - let nanoseconds = if nanoseconds >= 0.0 { - nanoseconds.ceil() - } else { - nanoseconds.floor() - }; - let minimum = i64::MIN as f64; - // The signed maximum rounds up to 2**63 as a double. - if !(minimum..-minimum).contains(&nanoseconds) { - return Err(overflow()); - } - nanoseconds as i64 - }; + // _PyTime_ROUND_TIMEOUT rounds away from zero. + let nanoseconds = + super::pytime::from_seconds_object(&object, super::pytime::Round::Up, vm)?; if nanoseconds < 0 { return Err(vm.new_value_error("sleep length must be non-negative")); } diff --git a/crates/vm/src/stdlib/_testinternalcapi/pytime.rs b/crates/vm/src/stdlib/time/pytime.rs similarity index 91% rename from crates/vm/src/stdlib/_testinternalcapi/pytime.rs rename to crates/vm/src/stdlib/time/pytime.rs index d82aa9180..0e0dd0715 100644 --- a/crates/vm/src/stdlib/_testinternalcapi/pytime.rs +++ b/crates/vm/src/stdlib/time/pytime.rs @@ -1,4 +1,4 @@ -//! Numeric conversions used by the internal PyTime test helpers. +//! Signed nanosecond timestamps and the rounding used by Python's time APIs. #![cfg_attr(target_env = "musl", allow(deprecated))] @@ -7,9 +7,9 @@ use num_traits::ToPrimitive; use rustpython_host_env::time::SEC_TO_NS; #[cfg(any(unix, windows))] -pub(super) use rustpython_host_env::time::TimeT; +pub(crate) use rustpython_host_env::time::TimeT; #[cfg(not(any(unix, windows)))] -pub(super) type TimeT = i64; +pub(crate) type TimeT = i64; const TIME_T_MIN: i64 = TimeT::MIN as _; const TIME_T_MAX: i64 = TimeT::MAX as _; @@ -17,7 +17,7 @@ const PYTIME_OVERFLOW: &str = "timestamp out of range for C PyTime_t"; const TIME_T_OVERFLOW: &str = "timestamp out of range for platform time_t"; #[derive(Clone, Copy)] -pub(super) enum Round { +pub(crate) enum Round { Floor, Ceiling, HalfEven, @@ -25,7 +25,7 @@ pub(super) enum Round { } impl Round { - pub(super) fn from_int(value: i32, vm: &VirtualMachine) -> PyResult { + pub(crate) fn from_int(value: i32, vm: &VirtualMachine) -> PyResult { match value { 0 => Ok(Self::Floor), 1 => Ok(Self::Ceiling), @@ -45,7 +45,7 @@ impl Round { } } - pub(super) fn divide(self, value: i64, divisor: i64) -> i64 { + pub(crate) fn divide(self, value: i64, divisor: i64) -> i64 { debug_assert!(divisor > 1); let quotient = value / divisor; let remainder = value % divisor; @@ -65,7 +65,7 @@ impl Round { } } -pub(super) fn from_seconds(seconds: i32) -> i64 { +pub(crate) fn from_seconds(seconds: i32) -> i64 { i64::from(seconds) * SEC_TO_NS } @@ -95,7 +95,7 @@ fn float_seconds(object: &PyObject, vm: &VirtualMachine) -> PyResult { Ok(seconds) } -pub(super) fn from_seconds_object( +pub(crate) fn from_seconds_object( object: &PyObject, round: Round, vm: &VirtualMachine, @@ -132,7 +132,7 @@ fn float_to_time_t(seconds: f64, vm: &VirtualMachine) -> PyResult { Ok(seconds as i64) } -pub(super) fn object_to_time_t( +pub(crate) fn object_to_time_t( object: &PyObject, round: Round, vm: &VirtualMachine, @@ -144,7 +144,7 @@ pub(super) fn object_to_time_t( } } -pub(super) fn object_to_denominator( +pub(crate) fn object_to_denominator( object: &PyObject, denominator: i64, round: Round,