mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Pass round() ndigits to __round__ unchanged (#8997)
round(x, ndigits) ran ndigits through __index__ before calling __round__, so a user-defined __round__ never saw a non-integer ndigits and an __index__ object reached it already converted to int. CPython's builtin_round_impl() forwards ndigits as-is and leaves the conversion to float.__round__ and int.__round__. Forward the object, and take ndigits as ArgIndex in both slots so they apply __index__ themselves. Calling the slots directly with an __index__ object now works as in CPython instead of raising TypeError. Assisted-by: Claude Code:claude-opus-5 Assisted-by: Claude Code:claude-opus-5-5
This commit is contained in:
1 parent
0e67b82f52
commit
a39d6c9676
4 files changed
+41
-11
No files matched your search
@@ -8,7 +8,9 @@ use crate::{
|
||||
class::{PyClassDef, PyClassImpl},
|
||||
common::{float_ops, hash, wtf8::Wtf8Buf},
|
||||
convert::{ToPyObject, ToPyResult},
|
||||
function::{ArgBytesLike, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue},
|
||||
function::{
|
||||
ArgBytesLike, ArgIndex, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue,
|
||||
},
|
||||
protocol::PyNumberMethods,
|
||||
types::{AsNumber, Callable, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
|
||||
};
|
||||
@@ -273,7 +275,7 @@ pub fn float_from_string(val: &PyObject, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
#[derive(FromArgs)]
|
||||
struct RoundArgs {
|
||||
#[pyarg(positional, optional)]
|
||||
ndigits: Option<PyIntRef>,
|
||||
ndigits: Option<ArgIndex>,
|
||||
}
|
||||
|
||||
#[pyclass(
|
||||
@@ -340,7 +342,7 @@ impl Py<PyFloat> {
|
||||
fn __round__(&self, args: RoundArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let ndigits = args.ndigits;
|
||||
let value = if let Some(ndigits) = ndigits {
|
||||
let ndigits = ndigits.as_bigint();
|
||||
let ndigits = ndigits.as_ref().as_bigint();
|
||||
let ndigits = match ndigits.to_i32() {
|
||||
Some(n) => n,
|
||||
None if ndigits.is_positive() => i32::MAX,
|
||||
|
||||
@@ -12,8 +12,8 @@ use crate::{
|
||||
},
|
||||
convert::{ToPyObject, ToPyResult},
|
||||
function::{
|
||||
ArgByteOrder, ArgIntoBool, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue,
|
||||
PySsize,
|
||||
ArgByteOrder, ArgIndex, ArgIntoBool, FuncArgs, OptionalArg, PyArithmeticValue,
|
||||
PyComparisonValue, PySsize,
|
||||
},
|
||||
protocol::{PyNumberMethods, handle_bytes_to_int_err, numeric_literal_from_str},
|
||||
types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
|
||||
@@ -526,7 +526,7 @@ impl Py<PyInt> {
|
||||
#[derive(FromArgs)]
|
||||
struct RoundArgs {
|
||||
#[pyarg(positional, optional)]
|
||||
ndigits: Option<PyIntRef>,
|
||||
ndigits: Option<ArgIndex>,
|
||||
}
|
||||
|
||||
#[pyclass(
|
||||
@@ -538,7 +538,7 @@ impl Py<PyInt> {
|
||||
#[pymethod]
|
||||
fn __round__(zelf: PyRef<PyInt>, args: RoundArgs, vm: &VirtualMachine) -> PyRef<PyInt> {
|
||||
if let Some(ndigits) = args.ndigits {
|
||||
let ndigits = ndigits.as_bigint();
|
||||
let ndigits = ndigits.as_ref().as_bigint();
|
||||
// round(12345, -2) == 12300
|
||||
// If precision >= 0, then any integer is already rounded correctly
|
||||
if let Some(ndigits) = ndigits.neg().to_u32()
|
||||
|
||||
@@ -1163,10 +1163,9 @@ mod builtins {
|
||||
))
|
||||
})?;
|
||||
match ndigits {
|
||||
Some(obj) => {
|
||||
let ndigits = obj.try_index(vm)?;
|
||||
meth.invoke((ndigits,), vm)
|
||||
}
|
||||
// Like CPython, pass ndigits through as-is; the __round__ implementation
|
||||
// decides what it accepts (float and int apply __index__ themselves).
|
||||
Some(obj) => meth.invoke((obj,), vm),
|
||||
None => {
|
||||
// without a parameter, the result type is coerced to int
|
||||
meth.invoke((), vm)
|
||||
|
||||
@@ -111,3 +111,32 @@ assert type(round(MyInt(5))) is int
|
||||
assert type(round(MyInt(5), 2)) is int
|
||||
# A negative ndigits already produced a fresh exact int.
|
||||
assert type(round(MyInt(15), -1)) is int
|
||||
|
||||
|
||||
# round() passes ndigits to __round__ unchanged, like CPython's builtin_round_impl().
|
||||
class Echo:
|
||||
def __round__(self, *args):
|
||||
return args
|
||||
|
||||
|
||||
assert round(Echo(), []) == ([],)
|
||||
assert round(Echo(), 1.5) == (1.5,)
|
||||
assert type(round(Echo(), True)[0]) is bool
|
||||
|
||||
|
||||
class Index:
|
||||
def __index__(self):
|
||||
return -1
|
||||
|
||||
|
||||
assert type(round(Echo(), Index())[0]) is Index
|
||||
|
||||
# int.__round__ and float.__round__ apply __index__ to ndigits themselves.
|
||||
assert round(15, Index()) == 20
|
||||
assert round(1.25, Index()) == 0.0
|
||||
assert (15).__round__(Index()) == 20
|
||||
assert (1.25).__round__(Index()) == 0.0
|
||||
with assert_raises(TypeError):
|
||||
(15).__round__(1.0)
|
||||
with assert_raises(TypeError):
|
||||
(1.25).__round__([])
|
||||
Reference in new issue
Block a user