Skip to content

Mypy is expecting type[Never] in context when it shouldn't #18968

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
aidangallagher4 opened this issue Apr 25, 2025 · 6 comments
Open

Mypy is expecting type[Never] in context when it shouldn't #18968

aidangallagher4 opened this issue Apr 25, 2025 · 6 comments
Labels
bug mypy got something wrong

Comments

@aidangallagher4
Copy link

Bug Report

I have written (well, copied, from here) a class classproperty to handle implementing class properties. It works perfectly fine, however when I try and type it as follows (which I think is valid), mypy raises the below error

class classproperty[T, C]:
    def __init__(self, func: Callable[[type[C]], T]):
        self.func = func

    def __get__(self, _obj: C, owner: type[C]) -> T:
        return self.func(owner)

class Test:
    @classproperty
    def my_property(cls):
        return 1
error: Argument 1 to "classproperty" has incompatible type "Callable[[Test], Any]"; expected "Callable[[type[Never]], Any]"  [arg-type]

I don't think this is correct, based on my understanding of descriptors and __get__. I asked a question on stackoverflow about this and it was suggested that this is caused by incorrect assumptions about how __get__ is called

To Reproduce

Gist link here

Expected Behavior

Mypy should pass

Actual Behavior

error: Argument 1 to "classproperty" has incompatible type "Callable[[Test], Any]"; expected "Callable[[type[Never]], Any]"  [arg-type]

Your Environment

  • Python 3.13.1
  • mypy==1.15.0

No further config

@aidangallagher4 aidangallagher4 added the bug mypy got something wrong label Apr 25, 2025
@A5rocks
Copy link
Collaborator

A5rocks commented Apr 25, 2025

Never there is mypy telling you "I don't know what to put here!"

In this case, I think it's because mypy thinks my_property is a method, so the cls attribute is annotated as taking Test, which isn't a type[...], so there's nothing mypy can put in the type that makes things pass.

I guess mypy could guess the first argument's type based on __get__ and decorators but that seems... a bit tough to generalize. For now you could use def my_property(cls: type[Test]): instead. nevermind, this doesn't work. I guess this stuff is more hardcoded than you would like, e.g. detecting specifically @classmethod.

@sterliakov
Copy link
Collaborator

sterliakov commented Apr 25, 2025

mypy looks specifically for builtins.classmethod and builtins.staticmethod to identify binding target for the first argument of a method - instance, class or no binding at all.

Your decorator is a descriptor, yes, but it doesn't mean it takes a classmethod in - there's no such decorator below it, right? For a static analyzer, you take something that looks like an instance method and wrap it with some decorator, returning a descriptor. Solving this for a general case sounds borderline impossible - you can only tell whether the instance is actually used by looking at __get__ body. There's no simple "rule of thumb" to identify classmethod-like descriptors.

There's a trivial approach to this problem (~50 LOC + tests, I believe) - we could introduce --extra-classmethod-decorator-fullnames (and perhaps --extra-staticmethod-decorator-fullnames for symmetry) flags that would simply be treated exactly like @classmethod and @staticmethod, resp. (shorter name ideas are welcome!). Would this solve your problem? It works for third-party code as well, even though consumers will have to copy those flags to all dependent configs.

One alternative approach is providing a {class,static}method_like decorator resembling @dataclass_transform, but that would be more complicated - we need to recognise class and static methods early during semanal phase.

@aidangallagher4
Copy link
Author

hey @sterliakov, thanks for the reply. the flags sound cool, though i'm not sure i understand their usage? you would pass them in your config (or as a cli arg) and they would change how these methods are analysed? or you would pass them alongside the relevant lines of code somehow? thanks :)

@sterliakov
Copy link
Collaborator

Yes, I mean config/CLI flags, so that invoking mypy --extra-classmethod-decorator-fullnames yourlib.classproperty would make your code work (the decorated method will be treated as classmethod).

@A5rocks
Copy link
Collaborator

A5rocks commented Apr 27, 2025

Could we maybe infer the self type from the type in the decorator signature? Is there a reason that doesn't work?

(Is that information too late to use? Or some gotcha...)

@aidangallagher4
Copy link
Author

hey @sterliakov -- that sounds good to me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

3 participants