False positive type error due to protocol insufficiently widened in function call
Consider the following code:
from typing import Protocol
class Getter[T,U](Protocol):
def __call__(self, x: T, /) -> U: ...
class PolymorphicListItemGetter(Protocol):
def __call__[T](self, l: list[T], /) -> T: ...
def compose_getters[T,U,V](get1: Getter[T,U], get2: Getter[U,V]) -> Getter[T,V]: ...
class HasFoo(Protocol):
@property
def foo(self) -> int:
...
def get_foo(x: HasFoo) -> int:
...
def upcast(x: PolymorphicListItemGetter) -> Getter[list[HasFoo], HasFoo]:
return x
def example(poly_getter: PolymorphicListItemGetter):
compose_getters(poly_getter, get_foo)
compose_getters(upcast(poly_getter), get_foo)
pyright doesn't raise an error in the definition of upcast, indicating PolymorphicListItemGetter can be widened to Getter[list[HasFoo], HasFoo], but when it is passed as an argument directly to compose_getters pyright is unable to widen its type.
I would expect there to be no type error, as PolymorphicListItemGetter is a subtype of Getter[list[HasFoo], HasFoo].
The error raised by pyright is
Type "(x: HasFoo) -> int" is incompatible with type "(x: T@__call__, /) -> int"
Parameter 1: type "T@__call__" is incompatible with type "HasFoo"
"object*" is incompatible with protocol "HasFoo"
"foo" is not present (reportArgumentType)
Tested with pyright cli version 1.1.370
Thanks for the issue. I agree this looks like a bug in pyright's constraint solver. I'll investigate further.
In the meantime, you can work around the issue by making the type parameter used in PolymorphicListItemGetter a class-scoped type variable rather than a method-scoped type variable.
class PolymorphicListItemGetter[T](Protocol):
def __call__(self, l: list[T], /) -> T: ...
This is addressed in pyright 1.1.374.