Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
function_or_method
  • Loading branch information
youknowone committed Feb 8, 2026
commit 592821c37803c6e97d62a09fd073aab3c6e7132a
160 changes: 81 additions & 79 deletions crates/vm/src/builtins/builtin_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,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 @@ -87,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 @@ -115,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 @@ -139,14 +198,7 @@ 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
// 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")]
Expand All @@ -155,14 +207,11 @@ pub struct PyNativeMethod {
pub(crate) class: &'static Py<PyType>, // TODO: the actual life is &'self
}

#[pyclass(
with(Callable, Comparable, Representable),
flags(HAS_DICT, DISALLOW_INSTANTIATION)
)]
impl PyNativeMethod {
// __qualname__, __self__, and __reduce__ are inherited from PyNativeFunction
// via NativeFunctionOrMethod wrapper since we share the same Python type.
}
// 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 @@ -175,55 +224,8 @@ 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_function_or_method_type);
}

/// Wrapper that provides access to the common PyNativeFunction data
Expand Down
8 changes: 7 additions & 1 deletion crates/vm/src/function/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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