Description
Problem
Calling a built-in method descriptor's __get__ with only the instance, omitting the owner/type argument that the descriptor protocol allows, raises a TypeError in RustPython. CPython binds the method and returns a builtin_function_or_method.
Measured against CPython 3.14.6:
| call | RustPython | CPython |
|---|---|---|
str.capitalize.__get__("x") |
TypeError: descriptor 'capitalize' needs a type, not 'str', as arg 2 |
<built-in method capitalize of str object at 0x…> |
_io._TextIOBase.read.__get__(io.StringIO()) |
TypeError: descriptor 'read' needs a type, not 'StringIO', as arg 2 |
<built-in method read of _io.StringIO object at 0x…> |
_queue.SimpleQueue.put.__get__(_queue.SimpleQueue()) |
same TypeError |
bound method |
The cause is in PyMethodDescriptor::descr_get (crates/vm/src/builtins/descriptor.rs):
Some(obj) => {
if descr.method.flags.contains(PyMethodFlags::METHOD) {
if cls.is_some_and(|c| c.fast_isinstance(vm.ctx.types.type_type)) {
obj
} else {
return Err(vm.new_type_error(format!(
"descriptor '{}' needs a type, not '{}', as arg 2",
descr.common.name.as_str(),
obj.class().name()
)));
}
} else if descr.method.flags.contains(PyMethodFlags::CLASS) {
obj.class().to_owned().into()
} else {
obj
}
}A METHOD-flagged descriptor with obj = Some(instance) requires the second argument (cls, the owner) to be a type, and throws an error otherwise. But descr.__get__(obj) omits the owner (cls = None) by design, so the code hits the error branch instead of binding to obj.
This is the root cause of the test_method_descriptor_crash failure in test_types (currently marked @unittest.expectedFailure # TODO: RUSTPYTHON). The test does:
for method, instance in [(_io._TextIOBase.read, io.StringIO()),
(_queue.SimpleQueue.put, _queue.SimpleQueue()),
(str.capitalize, "…")]:
bound = method.__get__(instance) # owner omitted
self.assertIsInstance(bound, types.BuiltinMethodType)Plan
- In
PyMethodDescriptor::descr_get, treat the owner (cls) as optional for theMETHOD-flag branch: whenobjisSomeandclsisNone(the owner is omitted), bind toobjinstead of raising. Whenclsis supplied it must still be a type, so__get__(obj, non_type)keeps raisingdescriptor '<name>' needs a type, not '<type>', as arg 2. - Unmark
test_method_descriptor_crash.
Python Documentation or reference to CPython source code
object.__get__(self, instance, owner=None), where theownerargument is optional: https://docs.python.org/3/reference/datamodel.html#object.__get__- CPython's
method_get(Objects/descrobject.c) returns the descriptor whenobj is None; otherwise it binds toobj, and only validates the second argument (must be a type) when one is supplied.
Activity
[-]method_descriptor.__get__(obj) with the owner omitted raises TypeError instead of binding (gh-132747)[/-][+]method_descriptor.__get__(obj) with the owner omitted raises TypeError instead of binding[/+]devyubin commentedon Jul 26, 2026
take