Constrained TypeVar narrowing does not work with Callable
Suppose you have a function that can process a few different types (Foo, Bar), and return an object of the same type. But the result is only guaranteed to be a Foo if the input was a Foo and a Bar if it was a Bar; it's not guaranteed to be the specific Foo subclass you might have passed. (This is the same setup as https://github.com/microsoft/pyright/issues/10931). But also, Foo and Bar have a specific call signature, and the function can also handle generic callables with that signature.
This can be represented using overloads, but it's verbose. A more concise approach is to use a TypeVar constrained to Foo, Bar, MyFn. Then you can write this:
from typing import TypeVar, Callable, Protocol
MyFn = Callable[[str, int], int]
class MyFnP(Protocol):
def __call__(self, name: str, x: int, /) -> int: ...
class Foo:
def __call__(self, name: str, x: int) -> int:
return 0
class FooDerived(Foo):
def __call__(self, name: str, x: int) -> int:
return 1
class Bar:
def __call__(self, name: str, x: int) -> int:
return 2
X = TypeVar("X", Foo, Bar, MyFnP)
def process(x: X) -> X:
if isinstance(x, Bar):
return Bar()
elif isinstance(x, Foo):
return Foo()
else:
return x
reveal_type(process(Foo())) # Foo
reveal_type(process(Bar())) # Bar
reveal_type(process(FooDerived())) # Foo
And it seems to work fine. But if you use a Callable instead of a Protocol, it doesn't work:
X2 = TypeVar("X2", Foo, Bar, MyFn)
def process_callable(x: X2) -> X2:
if isinstance(x, Bar): # Type "Bar" is not assignable to return type "X2@process_callable"
return Bar()
elif isinstance(x, Foo): # Type "Foo" is not assignable to return type "X2@process_callable"
return Foo()
else:
return x
Refer to my comment in this issue. I recommend avoiding the use of value-constrained type variables. It's unlikely that pyright (or other type checkers that use lazy evaluation, for that matter) will handle the code above without generating type errors. Even mypy, which doesn't do lazy evaluation and performs multi-pass type evaluations for functions that use constrained type variables, generates type errors in the above code.