python / python/mypy

Wrong type inference for class with metaclass that acts as descriptor

Open
#10,964 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug priority-2-low topic-descriptors topic-metaclasses
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Bug Report
Descriptor protocol does not work on metaclasses - defining __get__ method in a metaclass does not make classes using that metaclass work as descriptors.

To Reproduce

  1. Put this code in a py file:
from typing import Any, TYPE_CHECKING, overload

class _IntDescriptorMeta(type):
    def __get__(self, instance: Any, owner: Any) -> int:
        return 123

class IntDescriptorClass(metaclass=_IntDescriptorMeta):
    ...

class IntDescriptor:
    def __get__(self, instance: Any, owner: Any) -> int:
        return 123

class X:
    number_cls = IntDescriptorClass
    number = IntDescriptor()

print(X.number_cls)
print(X().number_cls)
print(X.number)
print(X().number)

if TYPE_CHECKING:
    reveal_type(X.number_cls)
    reveal_type(X().number_cls)
    reveal_type(X.number)
    reveal_type(X().number)
  1. Run it, you should see:
123
123
123
123
  1. Type check it with mypy and see the incorrect output.

Expected Behavior
I expected mypy to infer type of X.number_cls/X().number_cls correctly:

main.py:24: note: Revealed type is "builtins.int"
main.py:25: note: Revealed type is "builtins.int"
main.py:26: note: Revealed type is "builtins.int"
main.py:27: note: Revealed type is "builtins.int"

Note:
To simplify this example, __get__ returns an int no matter if instance is None or not but I assume a fix would also make it work properly with overloads as it does for instances of classes.

Actual Behavior

Mypy does not infer type of X.number_cls/X().number_cls correctly:

main.py:24: note: Revealed type is "def () -> __main__.IntDescriptorClass"
main.py:25: note: Revealed type is "def () -> __main__.IntDescriptorClass"
main.py:26: note: Revealed type is "builtins.int"
main.py:27: note: Revealed type is "builtins.int"

Your Environment

Additional notes
This works properly on pyright 1.1.161+, see the issue: https://github.com/microsoft/pyright/issues/2164

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the supplied Python reproduction and checking the revealed types with mypy, using the linked mypy-play URL if useful. Trace mypy's type inference for descriptors defined on metaclasses and compare it with ordinary instance descriptors. Done means all four reveals for the class-based descriptor report builtins.int while existing descriptor behavior remains correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.