Open graingert opened 1 year ago
this passes on 3.10, 3.11 and 3.12
https://mypy-play.net/?mypy=latest&python=3.10&gist=1438683686842d8a03bfbc32646c7952&flags=strict https://mypy-play.net/?mypy=latest&python=3.11&gist=1438683686842d8a03bfbc32646c7952&flags=strict https://mypy-play.net/?mypy=latest&python=3.12&gist=1438683686842d8a03bfbc32646c7952&flags=strict
note that using @staticmethod
does work:
from __future__ import annotations
from typing import Protocol
class Demo(Protocol):
@staticmethod
def demo(v: int) -> int:
pass
def returns_one(v: int) -> int:
return 1
class Inheriting(Demo):
@staticmethod
def demo(v: int) -> int:
return 1
Likely "culprit" is https://github.com/python/typeshed/pull/9771: https://github.com/python/typeshed/blob/274f449edce829201ef63da2e566497b28d3d97c/stdlib/builtins.pyi#L126-L131 specifically
def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ...
which makes ... staticmethods overridable with regular callables?
@AlexWaygood tl;dr on why def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ...
is not applicable to <3.10?
Python 3.9 doesn't have __call__
Python 3.9.16 (main, Dec 7 2022, 01:12:08)
[GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> staticmethod(lambda: None).__call__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'staticmethod' object has no attribute '__call__'
but that shouldn't be considered by mypy because it's called via .__get__(...).__call__
@AlexWaygood tl;dr on why
def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ...
is not applicable to <3.10?
Staticmethods aren't callable on Python 3.9! The __call__
method was added in 3.10. If you do the following...
x = staticmethod(lambda: 42)
class Foo:
@staticmethod
def bar(): return 42
y = Foo.__dict__['bar']
... then you'll find that both x
and y
are callable on Python 3.10, but not on Python 3.9
FWIW I also don't understand how the presence of __call__
makes it work.
note that using
@staticmethod
does work:from __future__ import annotations from typing import Protocol class Demo(Protocol): @staticmethod def demo(v: int) -> int: pass def returns_one(v: int) -> int: return 1 class Inheriting(Demo): @staticmethod def demo(v: int) -> int: return 1
I think this bug is just a specific manifestation of #4574. Looks like the same bug to me.
Still unclear to me why it's "fixed" with the addition of __call__
.
Still unclear to me why it's "fixed" with the addition of
__call__
.
I'd imagine it's because mypy applies some special-casing to methods decorated with @staticmethod
that means that it considers those methods to be callable, even though that's not what the stubs say, and even though it's strictly true on Python <3.10 (the object returned by staticmethod.__get__
is callable on Python 3.9, but staticmethod instances themselves aren't). This special-casing probably predates the point even mypy added generalised support for descriptors, so it's possible it could be partly or fully removed now in 2023. I believe mypy only applies this special-casing to methods decorated with @staticmethod
, not with direct calls to staticmethod
, hence the bug.
As such, because it (incorrectly) sees methods decorated with @staticmethod
as callable (due to the special-casing), it emits an error when a staticmethod in the superclass is overridden by something that doesn't have a __call__
method in the subclass.
Bug Report
(A clear and concise description of what the bug is.)
To Reproduce
https://mypy-play.net/?mypy=latest&python=3.9&gist=1438683686842d8a03bfbc32646c7952&flags=strict
Expected Behavior
Success: no issues found in 1 source file
Actual Behavior
main.py:15: error: Incompatible types in assignment (expression has type "staticmethod[[int], int]", base class "Demo" defined the type as "Callable[[int], int]") [assignment]
Your Environment
mypy.ini
(and other config files):