Repository: python/mypy

Issue:
TypeVar in generic union member gets restricted to first constraint
In the below:
```
from typing import TypeVar, Generic, Union

AnyStr = TypeVar('AnyStr', str, bytes)

class PathLike(Generic[AnyStr]):
    def __fspath__(self) -> AnyStr: ...

def readlink(s: Union[AnyStr, PathLike[AnyStr]]) -> AnyStr:
    ...

class BP(PathLike[bytes]):
    def __fspath__(self) -> bytes:
        return b''

class SP(PathLike[str]):
    def __fspath__(self) -> str:
        return ''

reveal_type(readlink(''))
reveal_type(readlink(b''))
reveal_type(readlink(BP()))  # E: Argument 1 to "readlink" has incompatible type "BP"; expected "Union[str, PathLike[str]]"
reveal_type(readlink(SP()))
```
I ran mypy current master with no extra options.

The second to last line should type check and the type should be bytes, but instead mypy somehow expects `Union[str, PathLike[str]]`. If I switch around the order of the constraints on the TypeVar, it complains about the last line instead.

The following all make the error go away:
- Changing the argument type of readlink to `Union[str, PathLike[AnyStr]]`
- Changing the argument type to `Union[AnyStr, PathLike]`
Changing the order of the Union doesn't make a difference.

Guido found this bug in a comment on python/typeshed#1441. I couldn't find any other reports in the tracker, but wasn't sure what to search for.


Discussion:
Here's is a slightly simplified repro:

```py
from typing import TypeVar, Generic, Union

T = TypeVar('T', str, int)

class C(Generic[T]): ...

def f(s: Union[T, C[T]]) -> T:  ...

ci: C[int]
cs: C[str]

reveal_type(f(''))
reveal_type(f(1))
reveal_type(f(ci))  # Argument 1 to "f" has incompatible type C[int]; expected "Union[str, C[str]]"
reveal_type(f(cs))
```
This seems like another example of this issue (reported by a coworker):

```py
from typing import Callable, TypeVar, Generic, Union

_U = TypeVar('_U')
_T = TypeVar('_T')

class Future(Generic[_T]):
    
    def __init__(self, t: _T):
        self._t = t

    def then(self, fn: Callable[[_T], Future[_U]]) -> Future[_U]:
        return fn(self._t)
    
    def then2(self, fn: Callable[[_T], _U]) -> Future[_U]:
        return Future(fn(self._t))
        
    def then3(self, fn: Union[Callable[[_T], Future[_U]], Callable[[_T], _U]]) -> Future[_U]:
        ret = fn(self._t)
        if isinstance(ret, Future):
            return ret
        else:
            return Future(ret)

def foo(t: int) -> Future[str]:
    return Future("str")

fut = Future(5)

reveal_type(fut.then(foo)) # is a Future[str], good

def foo2(t: int) -> str:
    return "str"
    
reveal_type(fut.then2(foo2)) # also is a Future[str], good

reveal_type(fut.then3(foo)) # <<==== error, Future[<nothing>]
reveal_type(fut.then3(foo2)) 
```

Resolving patch:
diff --git a/mypy/constraints.py b/mypy/constraints.py
--- a/mypy/constraints.py
+++ b/mypy/constraints.py
@@ -420,8 +420,12 @@ def filter_satisfiable(option: list[Constraint] | None) -> list[Constraint] | No
         return option
     satisfiable = []
     for c in option:
-        # TODO: add similar logic for TypeVar values (also in various other places)?
-        if mypy.subtypes.is_subtype(c.target, c.origin_type_var.upper_bound):
+        if isinstance(c.origin_type_var, TypeVarType) and c.origin_type_var.values:
+            if any(
+                mypy.subtypes.is_subtype(c.target, value) for value in c.origin_type_var.values
+            ):
+                satisfiable.append(c)
+        elif mypy.subtypes.is_subtype(c.target, c.origin_type_var.upper_bound):
             satisfiable.append(c)
     if not satisfiable:
         return None


Test patch:
diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test
--- a/test-data/unit/check-unions.test
+++ b/test-data/unit/check-unions.test
@@ -1171,6 +1171,25 @@ def foo(
 foo([1])
 [builtins fixtures/list.pyi]
 
+[case testGenericUnionMemberWithTypeVarConstraints]
+
+from typing import Generic, TypeVar, Union
+
+T = TypeVar('T', str, int)
+
+class C(Generic[T]): ...
+
+def f(s: Union[T, C[T]]) -> T:  ...
+
+ci: C[int]
+cs: C[str]
+
+reveal_type(f(1))  # N: Revealed type is "builtins.int"
+reveal_type(f(''))  # N: Revealed type is "builtins.str"
+reveal_type(f(ci))  # N: Revealed type is "builtins.int"
+reveal_type(f(cs))  # N: Revealed type is "builtins.str"
+
+
 [case testNestedInstanceTypeAliasUnsimplifiedUnion]
 from typing import TypeVar, Union, Iterator, List, Any
 T = TypeVar("T")