Skip to content

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

Open

Description

@aidangallagher4

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

Activity

  1. A5rocks commented on Apr 25, 2025

    @A5rocks
    Collaborator

    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.

  2. sterliakov commented on Apr 25, 2025

    @sterliakov
    Collaborator

    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.

  3. aidangallagher4 commented on Apr 26, 2025

    @aidangallagher4
    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 :)

  4. sterliakov commented on Apr 26, 2025

    @sterliakov
    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).

  5. A5rocks commented on Apr 27, 2025

    @A5rocks
    Collaborator

    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...)

  6. aidangallagher4 commented on Apr 28, 2025

    @aidangallagher4
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions