Skip to content

method_descriptor.__get__(obj) with the owner omitted raises TypeError instead of binding #8375

Description

@devyubin

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

  1. In PyMethodDescriptor::descr_get, treat the owner (cls) as optional for the METHOD-flag branch: when obj is Some and cls is None (the owner is omitted), bind to obj instead of raising. When cls is supplied it must still be a type, so __get__(obj, non_type) keeps raising descriptor '<name>' needs a type, not '<type>', as arg 2.
  2. Unmark test_method_descriptor_crash.

Python Documentation or reference to CPython source code

  • object.__get__(self, instance, owner=None), where the owner argument is optional: https://docs.python.org/3/reference/datamodel.html#object.__get__
  • CPython's method_get (Objects/descrobject.c) returns the descriptor when obj is None; otherwise it binds to obj, and only validates the second argument (must be a type) when one is supplied.

Metadata

Metadata

Assignees

Labels

z-ca-2026Tag to track Contribution Academy 2026

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions