Currently, all type checkers pass the generics_variance_inference portion of the conformance test suite. However, there is some divergence with generic methods that have bound self-type:
[mypy], [pyright], [ty], [pyrefly]
class Foo[T]:
def get(self) -> T: ...
def add[S](self: Foo[S], other: list[S], /) -> Foo[S]: ...
def test_covariance(arg: Foo[int]) -> Foo[object]:
return arg # mypy, zuban, ty: ❌️, pyright, pyrefly: ✅️
Following the variance inference specification, I believe Foo should be covariant in T, which is what pyright and pyrefly say, because Foo[Dummy].add is assignable to Foo[object].add. Intuitively, since S is method and not class scoped, and T does not appear in the signature of Foo.add, it has no bearing on variance.
However, mypy, ty and zuban think Foo is invariant in T. In python/mypy#19466 (comment), @ilevkivskyi argued that for an instance, def add[S](self: Foo[S], other: list[S], /) -> Foo[S]: ... is indistinguishable from def add(self, other: list[T], /) -> Foo[T]: ... and therefore Foo should be invariant in T.
This argumentation reveals another divergence between type checkers when it comes to subclassing generics: [mypy], [ty], [pyrefly], [pyright]
class Foo[T]:
def get(self) -> T: ...
def add[S](self: Foo[S], other: list[S], /) -> Foo[S]: ...
class Sub(Foo[int]):
# mypy, zuban, ty, pyrefly: ✅️, pyright: ❌️
def add(self, other: list[int]) -> Sub: ...
(I believe pyright is correct to error here, as for example a function that expects a Foo[int] could in principle try to call type(arg).add(Foo[str](), ["a", "b", "c"]))
Notably, if we remove the self: Foo[S] annotation, then the inference changes:
[mypy], [pyright], [pyrefly], [ty]
class Foo[T]:
def get(self) -> T: ...
def add[S](self, other: list[S], /) -> Foo[S]: ...
def test_covariance(arg: Foo[int]) -> Foo[object]:
return arg # mypy, ty, pyright, pyrefly, zuban: ✅️
class Sub(Foo[int]):
# ty: ✅️ mypy, pyright, zuban, pyrefly: ❌️
def add(self, other: list[int]) -> Sub: ...
What's needed
Type checker divergence in variance inference is quite insidious; I believe the spec should be clarified in this regard and examples like the ones in this issue should be added to the conformance suite.
I believe that pyright is the only type checker with correct behavior here at the moment, but maybe @erictraut can comment on these examples, and as there is some disagreement, at least from the mypy maintainers, I believe this needs some discussion.
References / Other Discussions
Currently, all type checkers pass the
generics_variance_inferenceportion of the conformance test suite. However, there is some divergence with generic methods that have bound self-type:[mypy], [pyright], [ty], [pyrefly]
Following the variance inference specification, I believe
Fooshould be covariant inT, which is whatpyrightandpyreflysay, becauseFoo[Dummy].addis assignable toFoo[object].add. Intuitively, sinceSis method and not class scoped, andTdoes not appear in the signature ofFoo.add, it has no bearing on variance.However,
mypy,tyandzubanthinkFoois invariant inT. In python/mypy#19466 (comment), @ilevkivskyi argued that for an instance,def add[S](self: Foo[S], other: list[S], /) -> Foo[S]: ...is indistinguishable fromdef add(self, other: list[T], /) -> Foo[T]: ...and thereforeFooshould be invariant inT.This argumentation reveals another divergence between type checkers when it comes to subclassing generics: [mypy], [ty], [pyrefly], [pyright]
(I believe
pyrightis correct to error here, as for example a function that expects aFoo[int]could in principle try to calltype(arg).add(Foo[str](), ["a", "b", "c"]))Notably, if we remove the
self: Foo[S]annotation, then the inference changes:[mypy], [pyright], [pyrefly], [ty]
What's needed
Type checker divergence in variance inference is quite insidious; I believe the spec should be clarified in this regard and examples like the ones in this issue should be added to the conformance suite.
I believe that
pyrightis the only type checker with correct behavior here at the moment, but maybe @erictraut can comment on these examples, and as there is some disagreement, at least from themypymaintainers, I believe this needs some discussion.References / Other Discussions
Selftype on an attribute mypy#18334