Description
Bug Report
Lately I discover that partial specialization is allowed by mypy by annotating self
in the methods of a generic class, as shown in some examples in the docs (https://mypy.readthedocs.io/en/stable/more_types.html#advanced-uses-of-self-types), which is very nice. Then I went down trying to combine this with overloaded methods, but unfortunately it doesn't behaved as I expected. This got me wondering if it is a bug or if my implementation is lacking something. Any help understanding or fixing this would be very appreciated.
There is a few issues regarding this topic (e.g. #5320 and #10517), but they were facing slighted different problems and they are already closed/solved.
In summary, in the following example it seems that the second overload (def bar(self: Foo[bool], a: bytes) -> None:
) is never reached, and there is some gotchas while calling self.bar
from another method of Foo
.
To Reproduce
from typing import *
MyType: TypeAlias = int | str | bool
T = TypeVar('T', bound=MyType, covariant=True)
class Foo(Generic[T]):
def __init__(self, t: T) -> None:
self.t = t
def foo(self) -> T:
return self.t
@overload
def bar(self: Foo[int | str], a: int) -> None: pass
@overload
def bar(self: Foo[bool], a: bytes) -> None: pass
def bar(self, a: int | bytes) -> None: pass
def calls_bar(self) -> None:
self.bar(1) # Not flagged.
self.bar(b'ok') # Flagged (expected "int"), but why?
# OK, all good.
Foo(1).bar(1)
Foo('ok').bar(1)
Foo(True).bar(b'ok')
# Not OK, but mypy doesn't flag 3rd one.
Foo(1).bar('not ok')
Foo('ok').bar(b'not ok')
Foo(True).bar(1)
def func(a: MyType) -> None:
Foo(a).bar(b'ok') # This is also flagged (expected "int")
Playground link: https://mypy-play.net/?mypy=latest&python=3.10&gist=466d7197598df9ca65258d8b7aca2c84
Expected Behavior
Among all the things a bit strange (to me) happening here, my main expectation is that mypy shoud have flagged Foo(True).bar(1)
, because it falls under the def bar(self: Foo[bool], a: bytes) -> None
overload.
This tells me that the overload strategy is not working as I expected.
I'm also not sure the expected behavior of calling bar
from other methods, such as what happens inside Foo.call_bar
. At least I would expect both self.bar(1)
and self.bar(b'ok')
to fail or succeed, not only one.
One thing though regarding this last case: if I annotate self: Foo
in call_bar
, none of then are flagged -- I guess its because T
will be Any
so the call will fallback to the not-overloaded bar
which accepts both types. I wonder if this would be the correct approach?
Actual Behavior
Mypy output:
main.py:28: error: Argument 1 to "bar" of "Foo" has incompatible type "bytes"; expected "int" [arg-type]
main.py:37: error: Argument 1 to "bar" of "Foo" has incompatible type "str"; expected "int" [arg-type]
main.py:38: error: Argument 1 to "bar" of "Foo" has incompatible type "bytes"; expected "int" [arg-type]
main.py:43: error: Argument 1 to "bar" of "Foo" has incompatible type "bytes"; expected "int" [arg-type]
Found 4 errors in 1 file (checked 1 source file)
Your Environment
- Mypy version used: 1.14.1
- Mypy command-line flags: N/A
- Mypy configuration options from
mypy.ini
(and other config files): N/A - Python version used: 3.10