Improve CPython compatibility related with PyBoundMethod (#7476)

* Add GetDescriptor for PyBoundMethod (return self)

CPython's method_descr_get always returns the bound method unchanged.
This preserves the original binding when __get__ is called on an
already-bound method (e.g. a.meth.__get__(b, B) still returns a).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add constructor validation for PyBoundMethod

Reject non-callable functions and None instances, matching CPython's
method_new which checks PyCallable_Check(func) and instance != Py_None.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix PyBoundMethod __reduce__ to propagate errors

Previously swallowed errors from get_attr with .ok(), silently
returning None. Now propagates errors matching CPython's method_reduce.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lee DogeonandClaude Opus 4.6 authored and GitHub committed 2026-03-21 00:33:40 +09:00
1 parent fb0d5faf3f
commit 4d99a4e541
2 files changed
+31 -7

No files matched your search

-1
View File
@@ -5130,7 +5130,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
gc.collect()
self.assertEqual(Parent.__subclasses__(), [])
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_instance_method_get_behavior(self):
# test case for gh-113157
+31 -6
View File
@@ -1216,6 +1216,17 @@ impl GetAttr for PyBoundMethod {
}
}
impl GetDescriptor for PyBoundMethod {
fn descr_get(
zelf: PyObjectRef,
_obj: Option<PyObjectRef>,
_cls: Option<PyObjectRef>,
_vm: &VirtualMachine,
) -> PyResult {
Ok(zelf)
}
}
#[derive(FromArgs)]
pub struct PyBoundMethodNewArgs {
#[pyarg(positional)]
@@ -1230,8 +1241,14 @@ impl Constructor for PyBoundMethod {
fn py_new(
_cls: &Py<PyType>,
Self::Args { function, object }: Self::Args,
_vm: &VirtualMachine,
vm: &VirtualMachine,
) -> PyResult<Self> {
if !function.is_callable() {
return Err(vm.new_type_error("first argument must be callable".to_owned()));
}
if vm.is_none(&object) {
return Err(vm.new_type_error("instance must not be None".to_owned()));
}
Ok(Self::new(object, function))
}
}
@@ -1258,7 +1275,15 @@ impl PyBoundMethod {
}
#[pyclass(
with(Callable, Comparable, Hashable, GetAttr, Constructor, Representable),
with(
Callable,
Comparable,
Hashable,
GetAttr,
GetDescriptor,
Constructor,
Representable
),
flags(IMMUTABLETYPE, HAS_WEAKREF)
)]
impl PyBoundMethod {
@@ -1266,11 +1291,11 @@ impl PyBoundMethod {
fn __reduce__(
&self,
vm: &VirtualMachine,
) -> (Option<PyObjectRef>, (PyObjectRef, Option<PyObjectRef>)) {
let builtins_getattr = vm.builtins.get_attr("getattr", vm).ok();
) -> PyResult<(PyObjectRef, (PyObjectRef, PyObjectRef))> {
let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
let func_self = self.object.clone();
let func_name = self.function.get_attr("__name__", vm).ok();
(builtins_getattr, (func_self, func_name))
let func_name = self.function.get_attr("__name__", vm)?;
Ok((builtins_getattr, (func_self, func_name)))
}
#[pygetset]