-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Variance of TypeVar is incompatible with base class #4111
Comments
Pyright is correct here, so this isn't a bug. The variance of You need to use TypeVars with different variances for from typing import Generic, TypeVar
T_contra = TypeVar("T_contra", contravariant=True)
T_co = TypeVar("T_co", covariant=True)
class Base(Generic[T_contra]):
def method(self, arg: T_contra) -> None:
...
class Derived(Base[frozenset[T_co]]):
def method(self, arg: frozenset[T_co]) -> None:
... |
Your code violates Liskov substitution principle. Here's why: from typing import Generic, TypeVar
T_contra = TypeVar("T_contra", contravariant=True)
T_co = TypeVar("T_co", covariant=True)
class Base(Generic[T_contra]):
def method(self, arg: T_contra) -> None:
...
class Derived(Base[frozenset[T_co]]):
def method(self, arg: frozenset[T_co]) -> None:
...
class DerivedInt(Derived[int]):
def method(self, arg: frozenset[int]) -> None:
print("expected subtype of int, got:", next(iter(arg)).__class__.__name__)
def f(d: Derived[float]) -> None:
d.method(frozenset({3.14}))
f(DerivedInt()) # there should be an error
# but there isn't, because Derived is covariant Prints:
|
Thinking about this more, you are correct that pyright should not allow This will be fixed in the next release. |
…in a class definition. This addresses #4111.
This is included in pyright 1.1.279, which I just published. It will also be included in a future version of pylance. |
@erictraut, the same bug is happening again after upgrading to pyright 1.1.298. |
@LeeeeT, hmm, that's strange. I added a unit test to detect regressions when I fixed this previously. I take a closer look. Reopening the issue. |
@erictraut, I created the following exhaustive test case: from typing import Generic, TypeVar
T_co = TypeVar("T_co", covariant=True)
T_contra = TypeVar("T_contra", contravariant=True)
class Co(Generic[T_co]): ...
class Contra(Generic[T_contra]): ...
class CoToContra(Contra[Co[T_contra]]): ...
class ContraToContra(Contra[Contra[T_co]]): ...
class CoToCo(Co[Co[T_co]]): ...
class ContraToCo(Co[Contra[T_contra]]): ... It ensures the following rules (preudocode):
So, you can think about it like this:
For the test case above pyright gives errors for all 4 classes. Interestingly, if I get rid of inheritance in this example the errors disappear: from typing import Generic, TypeVar
T_co = TypeVar("T_co", covariant=True)
T_contra = TypeVar("T_contra", contravariant=True)
class Co(Generic[T_co]): ...
class Contra(Generic[T_contra]): ...
class CoToContra(Generic[T_contra]):
def f(self, arg: Co[T_contra]) -> None: ...
class ContraToContra(Generic[T_co]):
def f(self, arg: Contra[T_co]) -> None: ...
class CoToCo(Generic[T_co]):
def f(self) -> Co[T_co]: ...
class ContraToCo(Generic[T_contra]):
def f(self) -> Contra[T_contra]: ... This code ensures the same rules but without using inheritance (uses functions instead to trigger variance checking). Maybe something is going wrong in pyright when handling inheritance? |
This will be fixed in the next release. I incorporated your test case — thanks! |
…e incompatibility. This addresses #4111.
This is included in pyright 1.1.300, which I just published. It will also be included in a future release of pylance. |
Sorry, @erictraut, your fix didn't help. You incorporated my second test example which, as I said, worked fine. The one that uses inheritance is the one that was failing in pyright 1.1.298 and is still failing on version 1.1.300, but now pyright gives errors for only 2 classes, not 4. |
Please open a new bug with a clear example of what you think is going wrong. This one has already been marked fixed and closed twice. |
Pyright gives me an error for the following code:
However, I think this code is correct. The current annotations mean I can assign:
Derived[float]
toDerived[int]
;(arg: frozenset[float]) -> None
to(arg: frozenset[int]) -> None
;frozenset[int]
tofrozenset[float]
;int
tofloat
.Surprisingly, if I use covariant TypeVar in
Derived
......I get no errors at all. But this code is completely wrong since it violates Liskov substitution principle.
pyright 1.1.277
The text was updated successfully, but these errors were encountered: