Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,6 @@ def test_boolcontext(self):
def test_conjugate(self):
self.assertEqual(complex(5.3, 9.8).conjugate(), 5.3-9.8j)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: DeprecationWarning not triggered
def test_constructor(self):
def check(z, x, y):
self.assertIs(type(z), complex)
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ def test_float_mod(self):
self.assertEqualAndEqualSign(mod(1e-100, -1.0), -1.0)
self.assertEqualAndEqualSign(mod(1.0, -1.0), -0.0)

@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: must be real number, not complex
@support.requires_IEEE_754
def test_float_pow(self):
# test builtin pow and ** operator for IEEE 754 special cases.
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_funcattrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def test(): pass
test.__code__ = self.b.__code__
self.assertEqual(test(), 3) # self.b always returns 3, arbitrarily

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: DeprecationWarning not triggered
def test_invalid___code___assignment(self):
def A(): pass
def B(): yield
Expand Down
182 changes: 125 additions & 57 deletions crates/vm/src/builtins/complex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,26 @@ impl PyObjectRef {
if let Some(complex) = self.downcast_ref_if_exact::<PyComplex>(vm) {
return Ok(Some((complex.as_complex(), true)));
}
if let Some(value) = self.try_complex_special_method(vm)? {
return Ok(Some((value.as_complex(), true)));
}
// `complex` does not have a `__complex__` by default, so subclasses might not either,
// use the actual stored value in this case
if let Some(complex) = self.downcast_ref::<PyComplex>() {
return Ok(Some((complex.as_complex(), true)));
}

if let Some(float) = self.try_float_opt(vm) {
return Ok(Some((Complex64::new(float?.to_f64(), 0.0), false)));
}

Ok(None)
}

fn try_complex_special_method(
&self,
vm: &VirtualMachine,
) -> PyResult<Option<PyRef<PyComplex>>> {
if let Some(method) = vm.get_method(self.clone(), identifier!(vm, __complex__)) {
let result = method?.call((), vm)?;

Expand All @@ -115,27 +135,17 @@ impl PyObjectRef {
vm,
)?;

return Ok(Some((ret.as_complex(), true)));
return Ok(Some(ret.to_owned()));
}

return match result.downcast_ref::<PyComplex>() {
Some(complex_obj) => Ok(Some((complex_obj.as_complex(), true))),
Some(complex_obj) => Ok(Some(complex_obj.to_owned())),
None => Err(vm.new_type_error(format!(
"__complex__ returned non-complex (type '{}')",
result.class().name()
))),
};
}
// `complex` does not have a `__complex__` by default, so subclasses might not either,
// use the actual stored value in this case
if let Some(complex) = self.downcast_ref::<PyComplex>() {
return Ok(Some((complex.as_complex(), true)));
}

if let Some(float) = self.try_float_opt(vm) {
return Ok(Some((Complex64::new(float?.to_f64(), 0.0), false)));
}

Ok(None)
}
}
Expand Down Expand Up @@ -395,13 +405,17 @@ impl Constructor for PyComplex {
type Args = ComplexArgs;

fn slot_new(cls: PyTypeRef, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
// Optimization: return exact complex as-is (only when imag is not provided)
if cls.is(vm.ctx.types.complex_type)
&& func_args.args.len() == 1
&& func_args.kwargs.is_empty()
&& func_args.args[0].class().is(vm.ctx.types.complex_type)
{
return Ok(func_args.args[0].clone());
if func_args.kwargs.is_empty() && func_args.args.len() <= 1 {
let payload = if let Some(value) = func_args.args.first() {
if cls.is(vm.ctx.types.complex_type) && value.class().is(vm.ctx.types.complex_type)
{
return Ok(value.clone());
}
Self::from_single_argument(value, vm)?
} else {
Self::from(Complex64::new(0.0, 0.0))
};
return payload.into_ref_with_type(vm, cls).map(Into::into);
}

let args: Self::Args = func_args.bind_for(vm, Self::NAME)?;
Expand All @@ -411,51 +425,75 @@ impl Constructor for PyComplex {

fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
let imag_missing = args.imag.is_missing();
let (real, real_was_complex) = match args.real {
OptionalArg::Missing => (Complex64::new(0.0, 0.0), false),
OptionalArg::Present(val) => {
if let Some(c) = val.try_complex(vm)? {
c
} else if let Some(s) = val.downcast_ref::<PyStr>() {
if args.imag.is_present() {
return Err(vm.new_type_error(
"complex() can't take second arg if first is a string",
));
}
if has_misplaced_underscore(s.as_wtf8().as_bytes()) {
let repr = val.repr(vm)?;
return Err(vm.new_value_error(format!(
"could not convert string to complex: {repr}"
)));
}
let (re, im) = rustpython_literal::complex::parse_str(
&crate::protocol::numeric_literal_from_str(s),
)
.ok_or_else(|| vm.new_value_error("complex() arg is a malformed string"))?;
return Ok(Self::from(Complex64 { re, im }));
} else {
return Err(vm.new_type_error(format!(
"complex() argument must be a string or a number, not {}",
val.class().slot_name()
)));
}
let original_real = match args.real {
OptionalArg::Missing => vm.ctx.new_int(0).into(),
OptionalArg::Present(value) => value,
};
let converted_real = original_real.try_complex_special_method(vm)?;
if converted_real.is_none()
&& original_real.downcast_ref::<Self>().is_none()
&& !has_real_number_conversion(&original_real)
{
return Err(vm.new_type_error(format!(
"complex() argument 'real' must be a real number, not {}",
original_real.class().fully_qualified_name(vm)?
)));
}
if let OptionalArg::Present(imag) = &args.imag
&& imag.downcast_ref::<Self>().is_none()
&& !has_real_number_conversion(imag)
{
return Err(vm.new_type_error(format!(
"complex() argument 'imag' must be a real number, not {}",
imag.class().fully_qualified_name(vm)?
)));
}

let complex_real = converted_real
.as_ref()
.map(|value| value.as_complex())
.or_else(|| {
original_real
.downcast_ref::<Self>()
.map(|value| value.as_complex())
});
let real_was_complex = complex_real.is_some();
let real = if let Some(value) = complex_real {
drop(converted_real);
if !has_real_number_conversion(&original_real) {
_warnings::warn(
vm.ctx.exceptions.deprecation_warning,
format!(
"complex() argument 'real' must be a real number, not {}",
original_real.class().fully_qualified_name(vm)?
),
1,
vm,
)?;
}
value
} else {
Complex64::new(original_real.try_float(vm)?.to_f64(), 0.0)
};

let (imag, imag_was_complex) = match args.imag {
// Copy the imaginary from the real to the real of the imaginary
// if an imaginary argument is not passed in
OptionalArg::Missing => (Complex64::new(real.im, 0.0), false),
OptionalArg::Present(obj) => {
if let Some(c) = obj.try_complex(vm)? {
c
} else if obj.class().fast_issubclass(vm.ctx.types.str_type) {
return Err(vm.new_type_error("complex() second arg can't be a string"));
if let Some(value) = obj.downcast_ref::<Self>() {
_warnings::warn(
vm.ctx.exceptions.deprecation_warning,
format!(
"complex() argument 'imag' must be a real number, not {}",
obj.class().fully_qualified_name(vm)?
),
1,
vm,
)?;
(value.as_complex(), true)
} else {
return Err(vm.new_type_error(format!(
"complex() second argument must be a number, not '{}'",
obj.class().name()
)));
(Complex64::new(obj.try_float(vm)?.to_f64(), 0.0), false)
}
}
};
Expand All @@ -476,7 +514,37 @@ impl Constructor for PyComplex {
}
}

fn has_real_number_conversion(value: &PyObject) -> bool {
let number = &value.class().slots().as_number;
number.float.load().is_some() || number.index.load().is_some()
}

impl PyComplex {
fn from_single_argument(value: &PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> {
if let Some(string) = value.downcast_ref::<PyStr>() {
if has_misplaced_underscore(string.as_wtf8().as_bytes()) {
let repr = value.repr(vm)?;
return Err(
vm.new_value_error(format!("could not convert string to complex: {repr}"))
);
}
let (re, im) = rustpython_literal::complex::parse_str(
&crate::protocol::numeric_literal_from_str(string),
)
.ok_or_else(|| vm.new_value_error("complex() arg is a malformed string"))?;
return Ok(Self::from(Complex64 { re, im }));
}
value
.try_complex(vm)?
.map(|(value, _)| Self::from(value))
.ok_or_else(|| {
vm.new_type_error(format!(
"complex() argument must be a string or a number, not {}",
value.class().slot_name()
))
})
}

#[deprecated(note = "use PyComplex::from(...).into_ref() instead")]
pub fn new_ref(value: Complex64, ctx: &Context) -> PyRef<Self> {
Self::from(value).into_ref(ctx)
Expand Down Expand Up @@ -763,10 +831,10 @@ impl Representable for PyComplex {

#[derive(FromArgs)]
pub struct ComplexArgs {
// Missing real is 0. A string real uses the imag-missing path.
// Component construction defaults the missing real part to 0.
#[pyarg(any, default, py_default = "0")]
real: OptionalArg<PyObjectRef>,
// Missing imag is 0 and is distinct from imag=0 when real is a string.
// Missing imag retains the imaginary part of a converted real argument.
#[pyarg(any, default, py_default = "0")]
imag: OptionalArg<PyObjectRef>,
}
73 changes: 62 additions & 11 deletions crates/vm/src/builtins/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,70 @@ fn inner_divmod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<(f64, f64)> {
}

pub(crate) fn float_pow(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult {
if v1.is_zero() && v2.is_sign_negative() {
Err(vm.new_zero_division_error("zero to a negative power"))
} else if v1.is_sign_negative() && (v2.floor() - v2).abs() > f64::EPSILON {
let v1 = Complex64::new(v1, 0.);
let v2 = Complex64::new(v2, 0.);
Ok(super::complex::complex_pow(v1, v2, vm)?.to_pyobject(vm))
} else {
let ans = v1.powf(v2);
if ans.is_infinite() && !(v1.is_infinite() || v2.is_infinite()) {
Err(vm.new_overflow_error("math range error"))
// Resolve special values before finite negative bases or platform pow.
if v2 == 0.0 {
return Ok(1.0.to_pyobject(vm));
}
if v1.is_nan() {
return Ok(v1.to_pyobject(vm));
}
if v2.is_nan() {
return Ok(if v1 == 1.0 { 1.0 } else { v2 }.to_pyobject(vm));
}
if v2.is_infinite() {
let magnitude = v1.abs();
let result = if magnitude == 1.0 {
1.0
} else if (v2 > 0.0) == (magnitude > 1.0) {
f64::INFINITY
} else {
0.0
};
return Ok(result.to_pyobject(vm));
}

let exponent_is_odd = v2.abs() % 2.0 == 1.0;
if v1.is_infinite() {
let result = if v2 > 0.0 {
if exponent_is_odd { v1 } else { v1.abs() }
} else if exponent_is_odd {
0.0_f64.copysign(v1)
} else {
Ok(ans.to_pyobject(vm))
0.0
};
return Ok(result.to_pyobject(vm));
}
if v1 == 0.0 {
if v2 < 0.0 {
return Err(vm.new_zero_division_error("zero to a negative power"));
}
return Ok(if exponent_is_odd { v1 } else { 0.0 }.to_pyobject(vm));
}

let mut magnitude = v1;
let negate_result = if v1 < 0.0 {
if v2 != v2.floor() {
let base = Complex64::new(v1, 0.0);
let exponent = Complex64::new(v2, 0.0);
return Ok(super::complex::complex_pow(base, exponent, vm)?.to_pyobject(vm));
}
magnitude = -v1;
exponent_is_odd
} else {
false
};
if magnitude == 1.0 {
return Ok(if negate_result { -1.0 } else { 1.0 }.to_pyobject(vm));
}

let mut result = magnitude.powf(v2);
if negate_result {
result = -result;
}
if result.is_infinite() {
Err(vm.new_overflow_error("math range error"))
} else {
Ok(result.to_pyobject(vm))
}
}

Expand Down
15 changes: 15 additions & 0 deletions crates/vm/src/builtins/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,21 @@ impl Py<PyFunction> {
n_free,
)));
}
let mask = bytecode::CodeFlags::GENERATOR
| bytecode::CodeFlags::COROUTINE
| bytecode::CodeFlags::ASYNC_GENERATOR;
let old_flags = self.code.flags;
let new_flags = code.flags;
if (old_flags & mask) != (new_flags & mask) {
crate::stdlib::_warnings::warn(
vm.ctx.exceptions.deprecation_warning,
"Assigning a code object of non-matching type is deprecated \
(e.g., from a generator to a plain function)"
.to_owned(),
1,
vm,
)?;
}
#[cfg(feature = "jit")]
let mut jit_guard = self.jitted_code.lock();
self.code.swap_to_temporary_refs(code, vm);
Expand Down
Loading
Loading