Skip to content

Commit c40bc62

Browse files
authored
Merge pull request RustPython#3507 from qingshi163/number-protocol
Implement Number Protocol
2 parents 17816bc + 09fc676 commit c40bc62

File tree

15 files changed

+563
-149
lines changed

15 files changed

+563
-149
lines changed

Lib/test/test_float.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,6 @@ def test_float_with_comma(self):
177177
self.assertEqual(float(" 25.e-1 "), 2.5)
178178
self.assertAlmostEqual(float(" .25e-1 "), .025)
179179

180-
# TODO: RUSTPYTHON
181-
@unittest.expectedFailure
182180
def test_floatconversion(self):
183181
# Make sure that calls to __float__() work properly
184182
class Foo1(object):

Lib/test/test_index.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ def __index__(self):
7171
self.assertIs(type(direct_index), int)
7272
#self.assertIs(type(operator_index), int)
7373

74-
# TODO: RUSTPYTHON
75-
@unittest.expectedFailure
7674
def test_index_returns_int_subclass(self):
7775
class BadInt:
7876
def __index__(self):

Lib/test/test_int.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,6 @@ def __int__(self):
458458
self.assertEqual(my_int, 7)
459459
self.assertRaises(TypeError, int, my_int)
460460

461-
# TODO: RUSTPYTHON
462-
@unittest.expectedFailure
463461
def test_int_returns_int_subclass(self):
464462
class BadIndex:
465463
def __index__(self):

stdlib/src/math.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,8 @@ mod math {
454454
fn ceil(x: PyObjectRef, vm: &VirtualMachine) -> PyResult {
455455
let result_or_err = try_magic_method(identifier!(vm, __ceil__), vm, &x);
456456
if result_or_err.is_err() {
457-
if let Ok(Some(v)) = x.try_to_f64(vm) {
458-
let v = try_f64_to_bigint(v.ceil(), vm)?;
457+
if let Ok(Some(v)) = x.try_float_opt(vm) {
458+
let v = try_f64_to_bigint(v.to_f64().ceil(), vm)?;
459459
return Ok(vm.ctx.new_int(v).into());
460460
}
461461
}
@@ -466,8 +466,8 @@ mod math {
466466
fn floor(x: PyObjectRef, vm: &VirtualMachine) -> PyResult {
467467
let result_or_err = try_magic_method(identifier!(vm, __floor__), vm, &x);
468468
if result_or_err.is_err() {
469-
if let Ok(Some(v)) = x.try_to_f64(vm) {
470-
let v = try_f64_to_bigint(v.floor(), vm)?;
469+
if let Ok(Some(v)) = x.try_float_opt(vm) {
470+
let v = try_f64_to_bigint(v.to_f64().floor(), vm)?;
471471
return Ok(vm.ctx.new_int(v).into());
472472
}
473473
}

vm/src/buffer.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::{
2-
builtins::{float, PyBaseExceptionRef, PyBytesRef, PyTuple, PyTupleRef, PyTypeRef},
2+
builtins::{PyBaseExceptionRef, PyBytesRef, PyTuple, PyTupleRef, PyTypeRef},
33
common::{static_cell, str::wchar_t},
44
convert::ToPyObject,
5-
function::{ArgBytesLike, ArgIntoBool},
5+
function::{ArgBytesLike, ArgIntoBool, ArgIntoFloat},
66
PyObjectRef, PyResult, TryFromObject, VirtualMachine,
77
};
88
use half::f16;
@@ -521,7 +521,7 @@ macro_rules! make_pack_float {
521521
arg: PyObjectRef,
522522
data: &mut [u8],
523523
) -> PyResult<()> {
524-
let f = float::try_float(&arg, vm)? as $T;
524+
let f = *ArgIntoFloat::try_from_object(vm, arg)? as $T;
525525
f.to_bits().pack_int::<E>(data);
526526
Ok(())
527527
}
@@ -539,7 +539,7 @@ make_pack_float!(f64);
539539

540540
impl Packable for f16 {
541541
fn pack<E: ByteOrder>(vm: &VirtualMachine, arg: PyObjectRef, data: &mut [u8]) -> PyResult<()> {
542-
let f_64 = float::try_float(&arg, vm)?;
542+
let f_64 = *ArgIntoFloat::try_from_object(vm, arg)?;
543543
let f_16 = f16::from_f64(f_64);
544544
if f_16.is_infinite() != f_64.is_infinite() {
545545
return Err(vm.new_overflow_error("float too large to pack with e format".to_owned()));

vm/src/builtins/complex.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ impl PyObjectRef {
6565
if let Some(complex) = self.payload_if_subclass::<PyComplex>(vm) {
6666
return Ok(Some((complex.value, true)));
6767
}
68-
if let Some(float) = self.try_to_f64(vm)? {
69-
return Ok(Some((Complex64::new(float, 0.0), false)));
68+
if let Some(float) = self.try_float_opt(vm)? {
69+
return Ok(Some((Complex64::new(float.to_f64(), 0.0), false)));
7070
}
7171
Ok(None)
7272
}

vm/src/builtins/float.rs

Lines changed: 84 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
use super::{
22
try_bigint_to_f64, PyByteArray, PyBytes, PyInt, PyIntRef, PyStr, PyStrRef, PyType, PyTypeRef,
33
};
4-
use crate::common::{float_ops, hash};
54
use crate::{
65
class::PyClassImpl,
7-
convert::ToPyObject,
6+
common::{float_ops, hash},
7+
convert::{ToPyObject, ToPyResult},
88
format::FormatSpec,
99
function::{
1010
ArgBytesLike, OptionalArg, OptionalOption,
1111
PyArithmeticValue::{self, *},
1212
PyComparisonValue,
1313
},
14-
identifier,
15-
types::{Comparable, Constructor, Hashable, PyComparisonOp},
14+
protocol::{PyNumber, PyNumberMethods},
15+
types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp},
1616
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
1717
TryFromBorrowedObject, TryFromObject, VirtualMachine,
1818
};
@@ -58,32 +58,13 @@ impl From<f64> for PyFloat {
5858
}
5959

6060
impl PyObject {
61-
pub fn try_to_f64(&self, vm: &VirtualMachine) -> PyResult<Option<f64>> {
62-
if let Some(float) = self.payload_if_exact::<PyFloat>(vm) {
63-
return Ok(Some(float.value));
64-
}
65-
if let Some(method) = vm.get_method(self.to_owned(), identifier!(vm, __float__)) {
66-
let result = vm.invoke(&method?, ())?;
67-
// TODO: returning strict subclasses of float in __float__ is deprecated
68-
return match result.payload::<PyFloat>() {
69-
Some(float_obj) => Ok(Some(float_obj.value)),
70-
None => Err(vm.new_type_error(format!(
71-
"__float__ returned non-float (type '{}')",
72-
result.class().name()
73-
))),
74-
};
75-
}
76-
if let Some(r) = vm.to_index_opt(self.to_owned()).transpose()? {
77-
return Ok(Some(try_bigint_to_f64(r.as_bigint(), vm)?));
78-
}
79-
Ok(None)
61+
pub fn try_float_opt(&self, vm: &VirtualMachine) -> PyResult<Option<PyRef<PyFloat>>> {
62+
PyNumber::new(self, vm).float_opt(vm)
8063
}
81-
}
8264

83-
pub fn try_float(obj: &PyObject, vm: &VirtualMachine) -> PyResult<f64> {
84-
obj.try_to_f64(vm)?.ok_or_else(|| {
85-
vm.new_type_error(format!("must be real number, not {}", obj.class().name()))
86-
})
65+
pub fn try_float(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyFloat>> {
66+
PyNumber::new(self, vm).float(vm)
67+
}
8768
}
8869

8970
pub(crate) fn to_op_float(obj: &PyObject, vm: &VirtualMachine) -> PyResult<Option<f64>> {
@@ -170,17 +151,8 @@ impl Constructor for PyFloat {
170151
let float_val = match arg {
171152
OptionalArg::Missing => 0.0,
172153
OptionalArg::Present(val) => {
173-
let val = if cls.is(vm.ctx.types.float_type) {
174-
match val.downcast_exact::<PyFloat>(vm) {
175-
Ok(f) => return Ok(f.into()),
176-
Err(val) => val,
177-
}
178-
} else {
179-
val
180-
};
181-
182-
if let Some(f) = val.try_to_f64(vm)? {
183-
f
154+
if let Some(f) = val.try_float_opt(vm)? {
155+
f.value
184156
} else {
185157
float_from_string(val, vm)?
186158
}
@@ -220,7 +192,7 @@ fn float_from_string(val: PyObjectRef, vm: &VirtualMachine) -> PyResult<f64> {
220192
})
221193
}
222194

223-
#[pyimpl(flags(BASETYPE), with(Comparable, Hashable, Constructor))]
195+
#[pyimpl(flags(BASETYPE), with(Comparable, Hashable, Constructor, AsNumber))]
224196
impl PyFloat {
225197
#[pymethod(magic)]
226198
fn format(&self, spec: PyStrRef, vm: &VirtualMachine) -> PyResult<String> {
@@ -562,6 +534,78 @@ impl Hashable for PyFloat {
562534
}
563535
}
564536

537+
impl AsNumber for PyFloat {
538+
const AS_NUMBER: PyNumberMethods = PyNumberMethods {
539+
add: Some(|number, other, vm| Self::number_float_op(number, other, |a, b| a + b, vm)),
540+
subtract: Some(|number, other, vm| Self::number_float_op(number, other, |a, b| a - b, vm)),
541+
multiply: Some(|number, other, vm| Self::number_float_op(number, other, |a, b| a * b, vm)),
542+
remainder: Some(|number, other, vm| Self::number_general_op(number, other, inner_mod, vm)),
543+
divmod: Some(|number, other, vm| Self::number_general_op(number, other, inner_divmod, vm)),
544+
power: Some(|number, other, vm| Self::number_general_op(number, other, float_pow, vm)),
545+
negative: Some(|number, vm| {
546+
let value = Self::number_downcast(number).value;
547+
(-value).to_pyresult(vm)
548+
}),
549+
positive: Some(|number, vm| Self::number_float(number, vm).to_pyresult(vm)),
550+
absolute: Some(|number, vm| {
551+
let value = Self::number_downcast(number).value;
552+
value.abs().to_pyresult(vm)
553+
}),
554+
boolean: Some(|number, _vm| Ok(Self::number_downcast(number).value.is_zero())),
555+
int: Some(|number, vm| {
556+
let value = Self::number_downcast(number).value;
557+
try_to_bigint(value, vm).map(|x| vm.ctx.new_int(x))
558+
}),
559+
float: Some(|number, vm| Ok(Self::number_float(number, vm))),
560+
floor_divide: Some(|number, other, vm| {
561+
Self::number_general_op(number, other, inner_floordiv, vm)
562+
}),
563+
true_divide: Some(|number, other, vm| {
564+
Self::number_general_op(number, other, inner_div, vm)
565+
}),
566+
..PyNumberMethods::NOT_IMPLEMENTED
567+
};
568+
}
569+
570+
impl PyFloat {
571+
fn number_general_op<F, R>(
572+
number: &PyNumber,
573+
other: &PyObject,
574+
op: F,
575+
vm: &VirtualMachine,
576+
) -> PyResult
577+
where
578+
F: FnOnce(f64, f64, &VirtualMachine) -> R,
579+
R: ToPyResult,
580+
{
581+
if let (Some(a), Some(b)) = (to_op_float(number.obj, vm)?, to_op_float(other, vm)?) {
582+
op(a, b, vm).to_pyresult(vm)
583+
} else {
584+
Ok(vm.ctx.not_implemented())
585+
}
586+
}
587+
588+
fn number_float_op<F>(
589+
number: &PyNumber,
590+
other: &PyObject,
591+
op: F,
592+
vm: &VirtualMachine,
593+
) -> PyResult
594+
where
595+
F: FnOnce(f64, f64) -> f64,
596+
{
597+
Self::number_general_op(number, other, |a, b, _vm| op(a, b), vm)
598+
}
599+
600+
fn number_float(number: &PyNumber, vm: &VirtualMachine) -> PyRef<PyFloat> {
601+
if let Some(zelf) = number.obj.downcast_ref_if_exact::<Self>(vm) {
602+
zelf.to_owned()
603+
} else {
604+
vm.ctx.new_float(Self::number_downcast(number).value)
605+
}
606+
}
607+
}
608+
565609
// Retrieve inner float value:
566610
pub(crate) fn get_value(obj: &PyObject) -> f64 {
567611
obj.payload::<PyFloat>().unwrap().value

0 commit comments

Comments
 (0)