Skip to content

Commit d2d8eee

Browse files
authored
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
1 parent 07fc6ee commit d2d8eee

File tree

9 files changed

+311
-118
lines changed

9 files changed

+311
-118
lines changed

Lib/test/test_reprlib.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ def test_lambda(self):
207207
self.assertStartsWith(r, "<function ReprTests.test_lambda.<locals>.<lambda")
208208
# XXX anonymous functions? see func_repr
209209

210-
@unittest.expectedFailure # TODO: RUSTPYTHON
211210
def test_builtin_function(self):
212211
eq = self.assertEqual
213212
# Functions

Lib/test/test_threading.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,6 @@ def func(frame, event, arg):
492492
sys.settrace(func)
493493
""")
494494

495-
@unittest.expectedFailure # TODO: RUSTPYTHON
496495
def test_join_nondaemon_on_shutdown(self):
497496
# Issue 1722344
498497
# Raising SystemExit skipped threading._shutdown

crates/vm/src/builtins/builtin_func.rs

Lines changed: 90 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
use alloc::fmt;
1111

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

77-
#[pyclass(with(Callable), flags(HAS_DICT, DISALLOW_INSTANTIATION))]
82+
// meth_richcompare in CPython
83+
impl Comparable for PyNativeFunction {
84+
fn cmp(
85+
zelf: &Py<Self>,
86+
other: &PyObject,
87+
op: PyComparisonOp,
88+
_vm: &VirtualMachine,
89+
) -> PyResult<PyComparisonValue> {
90+
op.eq_only(|| {
91+
if let Some(other) = other.downcast_ref::<Self>() {
92+
let eq = match (zelf.zelf.as_ref(), other.zelf.as_ref()) {
93+
(Some(z), Some(o)) => z.is(o),
94+
(None, None) => true,
95+
_ => false,
96+
};
97+
let eq = eq && core::ptr::eq(zelf.value, other.value);
98+
Ok(eq.into())
99+
} else {
100+
Ok(PyComparisonValue::NotImplemented)
101+
}
102+
})
103+
}
104+
}
105+
106+
// meth_repr in CPython
107+
impl Representable for PyNativeFunction {
108+
#[inline]
109+
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
110+
if let Some(bound) = zelf
111+
.zelf
112+
.as_ref()
113+
.filter(|b| !b.class().is(vm.ctx.types.module_type))
114+
{
115+
Ok(format!(
116+
"<built-in method {} of {} object at {:#x}>",
117+
zelf.value.name,
118+
bound.class().name(),
119+
bound.get_id()
120+
))
121+
} else {
122+
Ok(format!("<built-in function {}>", zelf.value.name))
123+
}
124+
}
125+
}
126+
127+
#[pyclass(
128+
with(Callable, Comparable, Representable),
129+
flags(HAS_DICT, DISALLOW_INSTANTIATION)
130+
)]
78131
impl PyNativeFunction {
79132
#[pygetset]
80133
fn __module__(zelf: NativeFunctionOrMethod) -> Option<&'static PyStrInterned> {
@@ -86,20 +139,19 @@ impl PyNativeFunction {
86139
zelf.0.value.name
87140
}
88141

142+
// meth_get__qualname__ in CPython
89143
#[pygetset]
90144
fn __qualname__(zelf: NativeFunctionOrMethod, vm: &VirtualMachine) -> PyResult<PyStrRef> {
91145
let zelf = zelf.0;
92-
let flags = zelf.value.flags;
93-
// if flags.contains(PyMethodFlags::CLASS) || flags.contains(PyMethodFlags::STATIC) {
94146
let qualname = if let Some(bound) = &zelf.zelf {
95-
let prefix = if flags.contains(PyMethodFlags::CLASS) {
96-
bound
97-
.get_attr("__qualname__", vm)
98-
.unwrap()
99-
.str(vm)
100-
.unwrap()
101-
.to_string()
147+
if bound.class().is(vm.ctx.types.module_type) {
148+
return Ok(vm.ctx.intern_str(zelf.value.name).to_owned());
149+
}
150+
let prefix = if bound.class().is(vm.ctx.types.type_type) {
151+
// m_self is a type: use PyType_GetQualName(m_self)
152+
bound.get_attr("__qualname__", vm)?.str(vm)?.to_string()
102153
} else {
154+
// m_self is an instance: use Py_TYPE(m_self).__qualname__
103155
bound.class().name().to_string()
104156
};
105157
vm.ctx.new_str(format!("{}.{}", prefix, &zelf.value.name))
@@ -114,15 +166,23 @@ impl PyNativeFunction {
114166
zelf.0.value.doc
115167
}
116168

169+
// meth_get__self__ in CPython
117170
#[pygetset]
118-
fn __self__(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
119-
vm.ctx.none()
171+
fn __self__(zelf: NativeFunctionOrMethod, vm: &VirtualMachine) -> PyObjectRef {
172+
zelf.0.zelf.clone().unwrap_or_else(|| vm.ctx.none())
120173
}
121174

175+
// meth_reduce in CPython
122176
#[pymethod]
123-
const fn __reduce__(&self) -> &'static str {
124-
// TODO: return (getattr, (self.object, self.name)) if this is a method
125-
self.value.name
177+
fn __reduce__(zelf: NativeFunctionOrMethod, vm: &VirtualMachine) -> PyResult {
178+
let zelf = zelf.0;
179+
if zelf.zelf.is_none() || zelf.module.is_some() {
180+
Ok(vm.ctx.new_str(zelf.value.name).into())
181+
} else {
182+
let getattr = vm.builtins.get_attr("getattr", vm)?;
183+
let target = zelf.zelf.clone().unwrap();
184+
Ok(vm.new_tuple((getattr, (target, zelf.value.name))).into())
185+
}
126186
}
127187

128188
#[pymethod]
@@ -138,54 +198,20 @@ impl PyNativeFunction {
138198
}
139199
}
140200

141-
impl Representable for PyNativeFunction {
142-
#[inline]
143-
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
144-
Ok(format!("<built-in function {}>", zelf.value.name))
145-
}
146-
}
147-
148-
// `PyCMethodObject` in CPython
149-
#[pyclass(name = "builtin_method", module = false, base = PyNativeFunction, ctx = "builtin_method_type")]
201+
// PyCMethodObject in CPython
202+
// repr(C) ensures `func` is at offset 0, allowing safe cast from PyNativeMethod to PyNativeFunction
203+
#[repr(C)]
204+
#[pyclass(name = "builtin_function_or_method", module = false, base = PyNativeFunction, ctx = "builtin_function_or_method_type")]
150205
pub struct PyNativeMethod {
151206
pub(crate) func: PyNativeFunction,
152207
pub(crate) class: &'static Py<PyType>, // TODO: the actual life is &'self
153208
}
154209

155-
#[pyclass(
156-
with(Callable, Comparable, Representable),
157-
flags(HAS_DICT, DISALLOW_INSTANTIATION)
158-
)]
159-
impl PyNativeMethod {
160-
#[pygetset]
161-
fn __qualname__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
162-
let prefix = zelf.class.name().to_string();
163-
Ok(vm
164-
.ctx
165-
.new_str(format!("{}.{}", prefix, &zelf.func.value.name)))
166-
}
167-
168-
#[pymethod]
169-
fn __reduce__(
170-
&self,
171-
vm: &VirtualMachine,
172-
) -> PyResult<(PyObjectRef, (PyObjectRef, &'static str))> {
173-
// TODO: return (getattr, (self.object, self.name)) if this is a method
174-
let getattr = vm.builtins.get_attr("getattr", vm)?;
175-
let target = self
176-
.func
177-
.zelf
178-
.clone()
179-
.unwrap_or_else(|| self.class.to_owned().into());
180-
let name = self.func.value.name;
181-
Ok((getattr, (target, name)))
182-
}
183-
184-
#[pygetset]
185-
fn __self__(zelf: PyRef<Self>, _vm: &VirtualMachine) -> Option<PyObjectRef> {
186-
zelf.func.zelf.clone()
187-
}
188-
}
210+
// All Python-visible behavior (getters, slots) is registered by PyNativeFunction::extend_class.
211+
// PyNativeMethod only extends the Rust-side struct with the defining class reference.
212+
// The func field at offset 0 (#[repr(C)]) allows NativeFunctionOrMethod to read it safely.
213+
#[pyclass(flags(HAS_DICT, DISALLOW_INSTANTIATION))]
214+
impl PyNativeMethod {}
189215

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

201-
impl Comparable for PyNativeMethod {
202-
fn cmp(
203-
zelf: &Py<Self>,
204-
other: &PyObject,
205-
op: PyComparisonOp,
206-
_vm: &VirtualMachine,
207-
) -> PyResult<PyComparisonValue> {
208-
op.eq_only(|| {
209-
if let Some(other) = other.downcast_ref::<Self>() {
210-
let eq = match (zelf.func.zelf.as_ref(), other.func.zelf.as_ref()) {
211-
(Some(z), Some(o)) => z.is(o),
212-
(None, None) => true,
213-
_ => false,
214-
};
215-
let eq = eq && core::ptr::eq(zelf.func.value, other.func.value);
216-
Ok(eq.into())
217-
} else {
218-
Ok(PyComparisonValue::NotImplemented)
219-
}
220-
})
221-
}
222-
}
223-
224-
impl Callable for PyNativeMethod {
225-
type Args = FuncArgs;
226-
227-
#[inline]
228-
fn call(zelf: &Py<Self>, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
229-
if let Some(zelf) = &zelf.func.zelf {
230-
args.prepend_arg(zelf.clone());
231-
}
232-
(zelf.func.value.func)(vm, args)
233-
}
234-
}
235-
236-
impl Representable for PyNativeMethod {
237-
#[inline]
238-
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
239-
Ok(format!(
240-
"<built-in method {} of {} object at ...>",
241-
&zelf.func.value.name,
242-
zelf.class.name()
243-
))
244-
}
245-
}
246-
247227
pub fn init(context: &Context) {
248228
PyNativeFunction::extend_class(context, context.types.builtin_function_or_method_type);
249-
PyNativeMethod::extend_class(context, context.types.builtin_method_type);
250229
}
251230

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

254235
impl TryFromObject for NativeFunctionOrMethod {
255236
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
256237
let class = vm.ctx.types.builtin_function_or_method_type;
257238
if obj.fast_isinstance(class) {
239+
// Both PyNativeFunction and PyNativeMethod share the same type now.
240+
// PyNativeMethod has `func: PyNativeFunction` as its first field,
241+
// so we can safely treat the data pointer as PyNativeFunction for reading.
258242
Ok(Self(unsafe { obj.downcast_unchecked() }))
259243
} else {
260244
Err(vm.new_downcast_type_error(class, &obj))

crates/vm/src/builtins/capsule.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use super::PyType;
2+
use crate::{Context, Py, PyPayload, PyResult, class::PyClassImpl, types::Representable};
3+
4+
/// PyCapsule - a container for C pointers.
5+
/// In RustPython, this is a minimal implementation for compatibility.
6+
#[pyclass(module = false, name = "PyCapsule")]
7+
#[derive(Debug, Clone, Copy)]
8+
pub struct PyCapsule {
9+
// Capsules store opaque pointers; we don't expose the actual pointer functionality
10+
// since RustPython doesn't have the same C extension model as CPython.
11+
_private: (),
12+
}
13+
14+
impl PyPayload for PyCapsule {
15+
#[inline]
16+
fn class(ctx: &Context) -> &'static Py<PyType> {
17+
ctx.types.capsule_type
18+
}
19+
}
20+
21+
#[pyclass(with(Representable), flags(DISALLOW_INSTANTIATION))]
22+
impl PyCapsule {}
23+
24+
impl Representable for PyCapsule {
25+
#[inline]
26+
fn repr_str(_zelf: &Py<Self>, _vm: &crate::VirtualMachine) -> PyResult<String> {
27+
Ok("<capsule object>".to_string())
28+
}
29+
}
30+
31+
pub fn init(context: &Context) {
32+
PyCapsule::extend_class(context, context.types.capsule_type);
33+
}

crates/vm/src/builtins/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub(crate) mod bytearray;
99
pub use bytearray::PyByteArray;
1010
pub(crate) mod bytes;
1111
pub use bytes::{PyBytes, PyBytesRef};
12+
pub(crate) mod capsule;
13+
pub use capsule::PyCapsule;
1214
pub(crate) mod classmethod;
1315
pub use classmethod::PyClassMethod;
1416
pub(crate) mod code;

crates/vm/src/function/method.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ impl PyMethodDef {
188188
) -> PyRef<PyNativeMethod> {
189189
PyRef::new_ref(
190190
self.to_bound_method(obj, class),
191-
ctx.types.builtin_method_type.to_owned(),
191+
ctx.types.builtin_function_or_method_type.to_owned(),
192192
None,
193193
)
194194
}
@@ -211,7 +211,13 @@ impl PyMethodDef {
211211
class: &'static Py<PyType>,
212212
) -> PyRef<PyNativeMethod> {
213213
debug_assert!(self.flags.contains(PyMethodFlags::STATIC));
214-
let func = self.to_function();
214+
// Set zelf to the class, matching CPython's m_self = type for static methods.
215+
// Callable::call skips prepending when STATIC flag is set.
216+
let func = PyNativeFunction {
217+
zelf: Some(class.to_owned().into()),
218+
value: self,
219+
module: None,
220+
};
215221
PyNativeMethod { func, class }.into_ref(ctx)
216222
}
217223

0 commit comments

Comments
 (0)