microsoft / pyright

Static Type Checker for Python
Other
13.12k stars 1.4k forks source link

overriding metaclass doesn't work #8848

Closed beauxq closed 2 weeks ago

beauxq commented 2 weeks ago

Describe the bug

A class is unable to override types from a metaclass.

Code or Screenshots

from typing import Any, ClassVar, reveal_type

class M(type):
    x: Any = ""

class C(metaclass=M):
    x: ClassVar[str] = "hello"

reveal_type(C.x)  # Any should be str

def foo(cls_m: M, cls_c: type[C]) -> None:
    reveal_type(cls_m.x)  # correctly reports Any
    reveal_type(cls_c.x)  # Any should be str

mypy reports the correct types with reveal_type

https://pyright-play.net/?code=GYJw9gtgBALgngBwJYDsDmUkQWEMoCCKcANFAMIA2AhgM60Bq1IZIApgG5vWUD68CNgCgRAYxr0oAWQAUAtgEoAXEKhqoADyWFiIoeLq0KMiGxjUD9ALxTlq9VooTGzANq0YIALpQrUAEQAFmyUlGD%2BeuxcPPyIbDLkAHQaCmoAxDpwULSBYACulAAmUABGbNmeeoVswFDAYGAy4rS8ENpSZM28otryruReqQC0AHxQAHJgKGwq6lBR3HzyTZQtEMmpUBmiuOyiMJRZ7Dh4RkRw9moLMctdohvpmdm5BcVlFSBCQA

https://mypy-play.net/?mypy=latest&python=3.12&gist=aa7ff2a0a471b12f28d270f6f97deb9e

pyright 1.1.377

erictraut commented 2 weeks ago

I think pyright's behavior here is defensible. This isn't an override so much as a redefinition. An instance variable in a metaclass is the same variable as a class variable in a class that is constructed from that metaclass. The actual type of the variable depends on who writes to it last. This isn't like a variable that is overridden, where there's a clear hierarchy.

Arguably, pyright should report a redefinition error because you're supplying a different type definition for the same variable, but this is a pretty esoteric edge case and probably not worth adding that extra logic.

If your intent is for this variable to be a class variable in M and a class variable in C, then it is a distinctly different variable. If you specify x: ClassVar[Any] = "" in class M, pyright will treat the two variables as distinct.