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_reprlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ def test_lambda(self):
self.assertStartsWith(r, "<function ReprTests.test_lambda.<locals>.<lambda")
# XXX anonymous functions? see func_repr

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_builtin_function(self):
eq = self.assertEqual
# Functions
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,6 @@ def func(frame, event, arg):
sys.settrace(func)
""")

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_join_nondaemon_on_shutdown(self):
# Issue 1722344
# Raising SystemExit skipped threading._shutdown
Expand Down
196 changes: 90 additions & 106 deletions crates/vm/src/builtins/builtin_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
use alloc::fmt;

// PyCFunctionObject in CPython
#[repr(C)]
#[pyclass(name = "builtin_function_or_method", module = false)]
pub struct PyNativeFunction {
pub(crate) value: &'static PyMethodDef,
Expand Down Expand Up @@ -68,13 +69,65 @@ impl Callable for PyNativeFunction {
#[inline]
fn call(zelf: &Py<Self>, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
if let Some(z) = &zelf.zelf {
args.prepend_arg(z.clone());
// STATIC methods store the class in zelf for qualname/repr purposes,
// but should not prepend it to args (the Rust function doesn't expect it).
if !zelf.value.flags.contains(PyMethodFlags::STATIC) {
args.prepend_arg(z.clone());
}
}
(zelf.value.func)(vm, args)
}
}

#[pyclass(with(Callable), flags(HAS_DICT, DISALLOW_INSTANTIATION))]
// meth_richcompare in CPython
impl Comparable for PyNativeFunction {
fn cmp(
zelf: &Py<Self>,
other: &PyObject,
op: PyComparisonOp,
_vm: &VirtualMachine,
) -> PyResult<PyComparisonValue> {
op.eq_only(|| {
if let Some(other) = other.downcast_ref::<Self>() {
let eq = match (zelf.zelf.as_ref(), other.zelf.as_ref()) {
(Some(z), Some(o)) => z.is(o),
(None, None) => true,
_ => false,
};
let eq = eq && core::ptr::eq(zelf.value, other.value);
Ok(eq.into())
} else {
Ok(PyComparisonValue::NotImplemented)
}
})
}
}

// meth_repr in CPython
impl Representable for PyNativeFunction {
#[inline]
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
if let Some(bound) = zelf
.zelf
.as_ref()
.filter(|b| !b.class().is(vm.ctx.types.module_type))
{
Ok(format!(
"<built-in method {} of {} object at {:#x}>",
zelf.value.name,
bound.class().name(),
bound.get_id()
))
} else {
Ok(format!("<built-in function {}>", zelf.value.name))
}
}
}

#[pyclass(
with(Callable, Comparable, Representable),
flags(HAS_DICT, DISALLOW_INSTANTIATION)
)]
impl PyNativeFunction {
#[pygetset]
fn __module__(zelf: NativeFunctionOrMethod) -> Option<&'static PyStrInterned> {
Expand All @@ -86,20 +139,19 @@ impl PyNativeFunction {
zelf.0.value.name
}

// meth_get__qualname__ in CPython
#[pygetset]
fn __qualname__(zelf: NativeFunctionOrMethod, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let zelf = zelf.0;
let flags = zelf.value.flags;
// if flags.contains(PyMethodFlags::CLASS) || flags.contains(PyMethodFlags::STATIC) {
let qualname = if let Some(bound) = &zelf.zelf {
let prefix = if flags.contains(PyMethodFlags::CLASS) {
bound
.get_attr("__qualname__", vm)
.unwrap()
.str(vm)
.unwrap()
.to_string()
if bound.class().is(vm.ctx.types.module_type) {
return Ok(vm.ctx.intern_str(zelf.value.name).to_owned());
}
let prefix = if bound.class().is(vm.ctx.types.type_type) {
// m_self is a type: use PyType_GetQualName(m_self)
bound.get_attr("__qualname__", vm)?.str(vm)?.to_string()
} else {
// m_self is an instance: use Py_TYPE(m_self).__qualname__
bound.class().name().to_string()
};
vm.ctx.new_str(format!("{}.{}", prefix, &zelf.value.name))
Expand All @@ -114,15 +166,23 @@ impl PyNativeFunction {
zelf.0.value.doc
}

// meth_get__self__ in CPython
#[pygetset]
fn __self__(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.none()
fn __self__(zelf: NativeFunctionOrMethod, vm: &VirtualMachine) -> PyObjectRef {
zelf.0.zelf.clone().unwrap_or_else(|| vm.ctx.none())
}

// meth_reduce in CPython
#[pymethod]
const fn __reduce__(&self) -> &'static str {
// TODO: return (getattr, (self.object, self.name)) if this is a method
self.value.name
fn __reduce__(zelf: NativeFunctionOrMethod, vm: &VirtualMachine) -> PyResult {
let zelf = zelf.0;
if zelf.zelf.is_none() || zelf.module.is_some() {
Ok(vm.ctx.new_str(zelf.value.name).into())
} else {
let getattr = vm.builtins.get_attr("getattr", vm)?;
let target = zelf.zelf.clone().unwrap();
Ok(vm.new_tuple((getattr, (target, zelf.value.name))).into())
}
}

#[pymethod]
Expand All @@ -138,54 +198,20 @@ impl PyNativeFunction {
}
}

impl Representable for PyNativeFunction {
#[inline]
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
Ok(format!("<built-in function {}>", zelf.value.name))
}
}

// `PyCMethodObject` in CPython
#[pyclass(name = "builtin_method", module = false, base = PyNativeFunction, ctx = "builtin_method_type")]
// PyCMethodObject in CPython
// repr(C) ensures `func` is at offset 0, allowing safe cast from PyNativeMethod to PyNativeFunction
#[repr(C)]
#[pyclass(name = "builtin_function_or_method", module = false, base = PyNativeFunction, ctx = "builtin_function_or_method_type")]
pub struct PyNativeMethod {
pub(crate) func: PyNativeFunction,
pub(crate) class: &'static Py<PyType>, // TODO: the actual life is &'self
}

#[pyclass(
with(Callable, Comparable, Representable),
flags(HAS_DICT, DISALLOW_INSTANTIATION)
)]
impl PyNativeMethod {
#[pygetset]
fn __qualname__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let prefix = zelf.class.name().to_string();
Ok(vm
.ctx
.new_str(format!("{}.{}", prefix, &zelf.func.value.name)))
}

#[pymethod]
fn __reduce__(
&self,
vm: &VirtualMachine,
) -> PyResult<(PyObjectRef, (PyObjectRef, &'static str))> {
// TODO: return (getattr, (self.object, self.name)) if this is a method
let getattr = vm.builtins.get_attr("getattr", vm)?;
let target = self
.func
.zelf
.clone()
.unwrap_or_else(|| self.class.to_owned().into());
let name = self.func.value.name;
Ok((getattr, (target, name)))
}

#[pygetset]
fn __self__(zelf: PyRef<Self>, _vm: &VirtualMachine) -> Option<PyObjectRef> {
zelf.func.zelf.clone()
}
}
// All Python-visible behavior (getters, slots) is registered by PyNativeFunction::extend_class.
// PyNativeMethod only extends the Rust-side struct with the defining class reference.
// The func field at offset 0 (#[repr(C)]) allows NativeFunctionOrMethod to read it safely.
#[pyclass(flags(HAS_DICT, DISALLOW_INSTANTIATION))]
impl PyNativeMethod {}

impl fmt::Debug for PyNativeMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Expand All @@ -198,63 +224,21 @@ impl fmt::Debug for PyNativeMethod {
}
}

impl Comparable for PyNativeMethod {
fn cmp(
zelf: &Py<Self>,
other: &PyObject,
op: PyComparisonOp,
_vm: &VirtualMachine,
) -> PyResult<PyComparisonValue> {
op.eq_only(|| {
if let Some(other) = other.downcast_ref::<Self>() {
let eq = match (zelf.func.zelf.as_ref(), other.func.zelf.as_ref()) {
(Some(z), Some(o)) => z.is(o),
(None, None) => true,
_ => false,
};
let eq = eq && core::ptr::eq(zelf.func.value, other.func.value);
Ok(eq.into())
} else {
Ok(PyComparisonValue::NotImplemented)
}
})
}
}

impl Callable for PyNativeMethod {
type Args = FuncArgs;

#[inline]
fn call(zelf: &Py<Self>, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
if let Some(zelf) = &zelf.func.zelf {
args.prepend_arg(zelf.clone());
}
(zelf.func.value.func)(vm, args)
}
}

impl Representable for PyNativeMethod {
#[inline]
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
Ok(format!(
"<built-in method {} of {} object at ...>",
&zelf.func.value.name,
zelf.class.name()
))
}
}

pub fn init(context: &Context) {
PyNativeFunction::extend_class(context, context.types.builtin_function_or_method_type);
PyNativeMethod::extend_class(context, context.types.builtin_method_type);
}

/// Wrapper that provides access to the common PyNativeFunction data
/// for both PyNativeFunction and PyNativeMethod (which has func as its first field).
struct NativeFunctionOrMethod(PyRef<PyNativeFunction>);

impl TryFromObject for NativeFunctionOrMethod {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let class = vm.ctx.types.builtin_function_or_method_type;
if obj.fast_isinstance(class) {
// Both PyNativeFunction and PyNativeMethod share the same type now.
// PyNativeMethod has `func: PyNativeFunction` as its first field,
// so we can safely treat the data pointer as PyNativeFunction for reading.
Ok(Self(unsafe { obj.downcast_unchecked() }))
} else {
Err(vm.new_downcast_type_error(class, &obj))
Expand Down
33 changes: 33 additions & 0 deletions crates/vm/src/builtins/capsule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use super::PyType;
use crate::{Context, Py, PyPayload, PyResult, class::PyClassImpl, types::Representable};

/// PyCapsule - a container for C pointers.
/// In RustPython, this is a minimal implementation for compatibility.
#[pyclass(module = false, name = "PyCapsule")]
#[derive(Debug, Clone, Copy)]
pub struct PyCapsule {
// Capsules store opaque pointers; we don't expose the actual pointer functionality
// since RustPython doesn't have the same C extension model as CPython.
_private: (),
}

impl PyPayload for PyCapsule {
#[inline]
fn class(ctx: &Context) -> &'static Py<PyType> {
ctx.types.capsule_type
}
}

#[pyclass(with(Representable), flags(DISALLOW_INSTANTIATION))]
impl PyCapsule {}

impl Representable for PyCapsule {
#[inline]
fn repr_str(_zelf: &Py<Self>, _vm: &crate::VirtualMachine) -> PyResult<String> {
Ok("<capsule object>".to_string())
}
}

pub fn init(context: &Context) {
PyCapsule::extend_class(context, context.types.capsule_type);
}
2 changes: 2 additions & 0 deletions crates/vm/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub(crate) mod bytearray;
pub use bytearray::PyByteArray;
pub(crate) mod bytes;
pub use bytes::{PyBytes, PyBytesRef};
pub(crate) mod capsule;
pub use capsule::PyCapsule;
pub(crate) mod classmethod;
pub use classmethod::PyClassMethod;
pub(crate) mod code;
Expand Down
10 changes: 8 additions & 2 deletions crates/vm/src/function/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ impl PyMethodDef {
) -> PyRef<PyNativeMethod> {
PyRef::new_ref(
self.to_bound_method(obj, class),
ctx.types.builtin_method_type.to_owned(),
ctx.types.builtin_function_or_method_type.to_owned(),
None,
)
}
Expand All @@ -211,7 +211,13 @@ impl PyMethodDef {
class: &'static Py<PyType>,
) -> PyRef<PyNativeMethod> {
debug_assert!(self.flags.contains(PyMethodFlags::STATIC));
let func = self.to_function();
// Set zelf to the class, matching CPython's m_self = type for static methods.
// Callable::call skips prepending when STATIC flag is set.
let func = PyNativeFunction {
zelf: Some(class.to_owned().into()),
value: self,
module: None,
};
PyNativeMethod { func, class }.into_ref(ctx)
}

Expand Down
Loading
Loading