diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 173b49e83d1..34aba4cfd35 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -207,7 +207,6 @@ def test_lambda(self): self.assertStartsWith(r, "., 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, + other: &PyObject, + op: PyComparisonOp, + _vm: &VirtualMachine, + ) -> PyResult { + op.eq_only(|| { + if let Some(other) = other.downcast_ref::() { + 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, vm: &VirtualMachine) -> PyResult { + if let Some(bound) = zelf + .zelf + .as_ref() + .filter(|b| !b.class().is(vm.ctx.types.module_type)) + { + Ok(format!( + "", + zelf.value.name, + bound.class().name(), + bound.get_id() + )) + } else { + Ok(format!("", zelf.value.name)) + } + } +} + +#[pyclass( + with(Callable, Comparable, Representable), + flags(HAS_DICT, DISALLOW_INSTANTIATION) +)] impl PyNativeFunction { #[pygetset] fn __module__(zelf: NativeFunctionOrMethod) -> Option<&'static PyStrInterned> { @@ -86,20 +139,19 @@ impl PyNativeFunction { zelf.0.value.name } + // meth_get__qualname__ in CPython #[pygetset] fn __qualname__(zelf: NativeFunctionOrMethod, vm: &VirtualMachine) -> PyResult { 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)) @@ -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] @@ -138,54 +198,20 @@ impl PyNativeFunction { } } -impl Representable for PyNativeFunction { - #[inline] - fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { - Ok(format!("", 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, // TODO: the actual life is &'self } -#[pyclass( - with(Callable, Comparable, Representable), - flags(HAS_DICT, DISALLOW_INSTANTIATION) -)] -impl PyNativeMethod { - #[pygetset] - fn __qualname__(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - 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, _vm: &VirtualMachine) -> Option { - 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 { @@ -198,63 +224,21 @@ impl fmt::Debug for PyNativeMethod { } } -impl Comparable for PyNativeMethod { - fn cmp( - zelf: &Py, - other: &PyObject, - op: PyComparisonOp, - _vm: &VirtualMachine, - ) -> PyResult { - op.eq_only(|| { - if let Some(other) = other.downcast_ref::() { - 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, 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, _vm: &VirtualMachine) -> PyResult { - Ok(format!( - "", - &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); impl TryFromObject for NativeFunctionOrMethod { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { 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)) diff --git a/crates/vm/src/builtins/capsule.rs b/crates/vm/src/builtins/capsule.rs new file mode 100644 index 00000000000..7e263c4cde2 --- /dev/null +++ b/crates/vm/src/builtins/capsule.rs @@ -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 { + ctx.types.capsule_type + } +} + +#[pyclass(with(Representable), flags(DISALLOW_INSTANTIATION))] +impl PyCapsule {} + +impl Representable for PyCapsule { + #[inline] + fn repr_str(_zelf: &Py, _vm: &crate::VirtualMachine) -> PyResult { + Ok("".to_string()) + } +} + +pub fn init(context: &Context) { + PyCapsule::extend_class(context, context.types.capsule_type); +} diff --git a/crates/vm/src/builtins/mod.rs b/crates/vm/src/builtins/mod.rs index 994a4f9c758..fa7ab1b854e 100644 --- a/crates/vm/src/builtins/mod.rs +++ b/crates/vm/src/builtins/mod.rs @@ -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; diff --git a/crates/vm/src/function/method.rs b/crates/vm/src/function/method.rs index 6440fd801fc..52624cbbf86 100644 --- a/crates/vm/src/function/method.rs +++ b/crates/vm/src/function/method.rs @@ -188,7 +188,7 @@ impl PyMethodDef { ) -> PyRef { 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, ) } @@ -211,7 +211,13 @@ impl PyMethodDef { class: &'static Py, ) -> PyRef { 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) } diff --git a/crates/vm/src/stdlib/_types.rs b/crates/vm/src/stdlib/_types.rs new file mode 100644 index 00000000000..385ecf8cf5a --- /dev/null +++ b/crates/vm/src/stdlib/_types.rs @@ -0,0 +1,157 @@ +//! Implementation of the `_types` module. +//! +//! This module exposes built-in types that are used by the `types` module. + +pub(crate) use _types::module_def; + +#[pymodule] +#[allow(non_snake_case)] +mod _types { + use crate::{PyObjectRef, VirtualMachine}; + + #[pyattr] + fn AsyncGeneratorType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.async_generator.to_owned().into() + } + + #[pyattr] + fn BuiltinFunctionType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx + .types + .builtin_function_or_method_type + .to_owned() + .into() + } + + #[pyattr] + fn BuiltinMethodType(vm: &VirtualMachine) -> PyObjectRef { + // Same as BuiltinFunctionType in CPython + vm.ctx + .types + .builtin_function_or_method_type + .to_owned() + .into() + } + + #[pyattr] + fn CapsuleType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.capsule_type.to_owned().into() + } + + #[pyattr] + fn CellType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.cell_type.to_owned().into() + } + + #[pyattr] + fn CodeType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.code_type.to_owned().into() + } + + #[pyattr] + fn CoroutineType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.coroutine_type.to_owned().into() + } + + #[pyattr] + fn EllipsisType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.ellipsis_type.to_owned().into() + } + + #[pyattr] + fn FrameType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.frame_type.to_owned().into() + } + + #[pyattr] + fn FunctionType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.function_type.to_owned().into() + } + + #[pyattr] + fn GeneratorType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.generator_type.to_owned().into() + } + + #[pyattr] + fn GenericAlias(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.generic_alias_type.to_owned().into() + } + + #[pyattr] + fn GetSetDescriptorType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.getset_type.to_owned().into() + } + + #[pyattr] + fn LambdaType(vm: &VirtualMachine) -> PyObjectRef { + // Same as FunctionType in CPython + vm.ctx.types.function_type.to_owned().into() + } + + #[pyattr] + fn MappingProxyType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.mappingproxy_type.to_owned().into() + } + + #[pyattr] + fn MemberDescriptorType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.member_descriptor_type.to_owned().into() + } + + #[pyattr] + fn MethodDescriptorType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.method_descriptor_type.to_owned().into() + } + + #[pyattr] + fn ClassMethodDescriptorType(vm: &VirtualMachine) -> PyObjectRef { + // TODO: implement as separate type + vm.ctx.types.method_descriptor_type.to_owned().into() + } + + #[pyattr] + fn MethodType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.bound_method_type.to_owned().into() + } + + #[pyattr] + fn MethodWrapperType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.method_wrapper_type.to_owned().into() + } + + #[pyattr] + fn ModuleType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.module_type.to_owned().into() + } + + #[pyattr] + fn NoneType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.none_type.to_owned().into() + } + + #[pyattr] + fn NotImplementedType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.not_implemented_type.to_owned().into() + } + + #[pyattr] + fn SimpleNamespace(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.namespace_type.to_owned().into() + } + + #[pyattr] + fn TracebackType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.traceback_type.to_owned().into() + } + + #[pyattr] + fn UnionType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.union_type.to_owned().into() + } + + #[pyattr] + fn WrapperDescriptorType(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.wrapper_descriptor_type.to_owned().into() + } +} diff --git a/crates/vm/src/stdlib/mod.rs b/crates/vm/src/stdlib/mod.rs index 1d401e50e02..74a498f5472 100644 --- a/crates/vm/src/stdlib/mod.rs +++ b/crates/vm/src/stdlib/mod.rs @@ -1,4 +1,5 @@ mod _abc; +mod _types; #[cfg(feature = "ast")] pub(crate) mod ast; pub mod atexit; @@ -73,6 +74,7 @@ use crate::{Context, builtins::PyModuleDef}; pub fn builtin_module_defs(ctx: &Context) -> Vec<&'static PyModuleDef> { vec![ _abc::module_def(ctx), + _types::module_def(ctx), #[cfg(feature = "ast")] ast::module_def(ctx), atexit::module_def(ctx), diff --git a/crates/vm/src/types/zoo.rs b/crates/vm/src/types/zoo.rs index 9fa4e777a1a..2b60e37f316 100644 --- a/crates/vm/src/types/zoo.rs +++ b/crates/vm/src/types/zoo.rs @@ -1,7 +1,7 @@ use crate::{ Py, builtins::{ - asyncgenerator, bool_, builtin_func, bytearray, bytes, classmethod, code, complex, + asyncgenerator, bool_, builtin_func, bytearray, bytes, capsule, classmethod, code, complex, coroutine, descriptor, dict, enumerate, filter, float, frame, function, generator, genericalias, getset, int, interpolation, iter, list, map, mappingproxy, memory, module, namespace, object, property, pystr, range, set, singletons, slice, staticmethod, super_, @@ -28,6 +28,7 @@ pub struct TypeZoo { pub bytearray_iterator_type: &'static Py, pub bool_type: &'static Py, pub callable_iterator: &'static Py, + pub capsule_type: &'static Py, pub cell_type: &'static Py, pub classmethod_type: &'static Py, pub code_type: &'static Py, @@ -108,12 +109,20 @@ impl TypeZoo { #[cold] pub(crate) fn init() -> Self { let (type_type, object_type, weakref_type) = crate::object::init_type_hierarchy(); + // the order matters for type, object, weakref, and int - must be initialized first + let type_type = type_::PyType::init_manually(type_type); + let object_type = object::PyBaseObject::init_manually(object_type); + let weakref_type = weakref::PyWeak::init_manually(weakref_type); + let int_type = int::PyInt::init_builtin_type(); + + // builtin_function_or_method and builtin_method share the same type (CPython behavior) + let builtin_function_or_method_type = builtin_func::PyNativeFunction::init_builtin_type(); + Self { - // the order matters for type, object, weakref, and int - type_type: type_::PyType::init_manually(type_type), - object_type: object::PyBaseObject::init_manually(object_type), - weakref_type: weakref::PyWeak::init_manually(weakref_type), - int_type: int::PyInt::init_builtin_type(), + type_type, + object_type, + weakref_type, + int_type, // types exposed as builtins bool_type: bool_::PyBool::init_builtin_type(), @@ -147,11 +156,12 @@ impl TypeZoo { asyncgenerator::PyAsyncGenWrappedValue::init_builtin_type(), anext_awaitable: asyncgenerator::PyAnextAwaitable::init_builtin_type(), bound_method_type: function::PyBoundMethod::init_builtin_type(), - builtin_function_or_method_type: builtin_func::PyNativeFunction::init_builtin_type(), - builtin_method_type: builtin_func::PyNativeMethod::init_builtin_type(), + builtin_function_or_method_type, + builtin_method_type: builtin_function_or_method_type, bytearray_iterator_type: bytearray::PyByteArrayIterator::init_builtin_type(), bytes_iterator_type: bytes::PyBytesIterator::init_builtin_type(), callable_iterator: iter::PyCallableIterator::init_builtin_type(), + capsule_type: capsule::PyCapsule::init_builtin_type(), cell_type: function::PyCell::init_builtin_type(), code_type: code::PyCode::init_builtin_type(), coroutine_type: coroutine::PyCoroutine::init_builtin_type(), @@ -225,6 +235,7 @@ impl TypeZoo { complex::init(context); bytes::init(context); bytearray::init(context); + capsule::init(context); property::init(context); getset::init(context); memory::init(context);