Add _types module (#6807)

* _types

* builtin_function_or_method

* PyCapsule

* function_or_method

* Remove expectedFailure for test_builtin_function and test_join_nondaemon_on_shutdown
This commit is contained in:
Jeong, YunWon
2026-02-08 19:17:03 +09:00
committed by GitHub
parent 07fc6ee3c7
commit d2d8eeea2f
9 changed files with 311 additions and 118 deletions

View File

@@ -207,7 +207,6 @@ class ReprTests(unittest.TestCase):
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

View File

@@ -492,7 +492,6 @@ class ThreadTests(BaseTestCase):
sys.settrace(func)
""")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_join_nondaemon_on_shutdown(self):
# Issue 1722344
# Raising SystemExit skipped threading._shutdown

View File

@@ -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,
@@ -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> {
@@ -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))
@@ -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<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 {
@@ -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))

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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,
)
}
@@ -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)
}

View File

@@ -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()
}
}

View File

@@ -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),

View File

@@ -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<PyType>,
pub bool_type: &'static Py<PyType>,
pub callable_iterator: &'static Py<PyType>,
pub capsule_type: &'static Py<PyType>,
pub cell_type: &'static Py<PyType>,
pub classmethod_type: &'static Py<PyType>,
pub code_type: &'static Py<PyType>,
@@ -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);