Repository: python/mypy

Issue:
Cannot infer type argument when mixing int and float
Mypy complains about this code, even though it looks fine to me:

```
from typing import Sequence, TypeVar

_T = TypeVar('_T')

def bisect_left(a: Sequence[_T], x: _T) -> int: ...

bisect_left([1, 2, 3], 5.391)  #  Cannot infer type argument 1 of "bisect_left"
```

This doesn't seem to be related to promotions, as it also happens with user-defined types:

```
class A: pass
class B(A): pass

bisect_left([B()], A())   # same error
```



Discussion:
Just out of curiosity: What is mypy _supposed_ to do, when having

```
def f(x: _T, y: _T)
```

and a call

```
f(A(), B())
```

Should it try to "promote" either A to B or B to A? Or should it try to find a common base class between the two?

Or would the magic that makes bisect_left work not actually apply for the non-sequence case?

It looks for a value for `_T` that is compatible for all arguments. There are some additional rules, such as that we generally don't infer `Any` types, even though they would be valid everywhere, and we favor narrower types as results. It tries to look for common base classes and it also considers promotions such as `int` -> `float`. In your example, if `B` is a subclass of `A`, so we'd infer `_T = A`. `object` would also be a valid result, but since `A` is narrower we prefer that.

The non-sequence case should work already -- it's the sequence case that is broken.

any way of specifying type arguments to a function?

```python
class A: pass
class B(A): pass

bisect_left[A]([B()], A())   # same error
```

Not directly, but you can use `cast` within the arguments to make all argument types match exactly, so it's obvious what the value of a type argument will be. Example:

```py
from typing import cast

class A: pass
class B(A): pass

bisect_left([cast(A, B())], A())  # ok
```
@JukkaL here's an issue about what I mean https://github.com/python/mypy/issues/3096

Resolving patch:
diff --git a/mypy/constraints.py b/mypy/constraints.py
--- a/mypy/constraints.py
+++ b/mypy/constraints.py
@@ -12,6 +12,7 @@
 import mypy.subtypes
 from mypy.sametypes import is_same_type
 from mypy.erasetype import erase_typevars
+from mypy.nodes import COVARIANT, CONTRAVARIANT
 
 MYPY = False
 if MYPY:
@@ -337,25 +338,30 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
             if (self.direction == SUBTYPE_OF and
                     template.type.has_base(instance.type.fullname())):
                 mapped = map_instance_to_supertype(template, instance.type)
+                tvars = mapped.type.defn.type_vars
                 for i in range(len(instance.args)):
-                    # The constraints for generic type parameters are
-                    # invariant. Include constraints from both directions
-                    # to achieve the effect.
-                    res.extend(infer_constraints(
-                        mapped.args[i], instance.args[i], self.direction))
-                    res.extend(infer_constraints(
-                        mapped.args[i], instance.args[i], neg_op(self.direction)))
+                    # The constraints for generic type parameters depend on variance.
+                    # Include constraints from both directions if invariant.
+                    if tvars[i].variance != CONTRAVARIANT:
+                        res.extend(infer_constraints(
+                            mapped.args[i], instance.args[i], self.direction))
+                    if tvars[i].variance != COVARIANT:
+                        res.extend(infer_constraints(
+                            mapped.args[i], instance.args[i], neg_op(self.direction)))
                 return res
             elif (self.direction == SUPERTYPE_OF and
                     instance.type.has_base(template.type.fullname())):
                 mapped = map_instance_to_supertype(instance, template.type)
+                tvars = template.type.defn.type_vars
                 for j in range(len(template.args)):
-                    # The constraints for generic type parameters are
-                    # invariant.
-                    res.extend(infer_constraints(
-                        template.args[j], mapped.args[j], self.direction))
-                    res.extend(infer_constraints(
-                        template.args[j], mapped.args[j], neg_op(self.direction)))
+                    # The constraints for generic type parameters depend on variance.
+                    # Include constraints from both directions if invariant.
+                    if tvars[j].variance != CONTRAVARIANT:
+                        res.extend(infer_constraints(
+                            template.args[j], mapped.args[j], self.direction))
+                    if tvars[j].variance != COVARIANT:
+                        res.extend(infer_constraints(
+                            template.args[j], mapped.args[j], neg_op(self.direction)))
                 return res
             if (template.type.is_protocol and self.direction == SUPERTYPE_OF and
                     # We avoid infinite recursion for structural subtypes by checking


Test patch:
diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test
--- a/test-data/unit/check-expressions.test
+++ b/test-data/unit/check-expressions.test
@@ -1666,8 +1666,8 @@ it = [('x', 1)]
 d = dict(it, x=1)
 d() # E: "Dict[str, int]" not callable
 
-d2 = dict(it, x='') # E: Cannot infer type argument 2 of "dict"
-d2() # E: "Dict[Any, Any]" not callable
+d2 = dict(it, x='')
+d2() # E: "Dict[str, object]" not callable
 
 d3 = dict(it, x='') # type: Dict[str, int] # E: Argument "x" to "dict" has incompatible type "str"; expected "int"
 [builtins fixtures/dict.pyi]
@@ -1691,8 +1691,8 @@ d = dict(it, **kw)
 d() # E: "Dict[str, int]" not callable
 
 kw2 = {'x': ''}
-d2 = dict(it, **kw2) # E: Cannot infer type argument 2 of "dict"
-d2() # E: "Dict[Any, Any]" not callable
+d2 = dict(it, **kw2)
+d2() # E: "Dict[str, object]" not callable
 
 d3 = dict(it, **kw2) # type: Dict[str, int] # E: Argument 2 to "dict" has incompatible type "**Dict[str, str]"; expected "int"
 [builtins fixtures/dict.pyi]
diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test
--- a/test-data/unit/check-inference-context.test
+++ b/test-data/unit/check-inference-context.test
@@ -921,6 +921,71 @@ class C:
         g: Callable[[], int] = lambda: 1 or self.x
         self.x = int()
 
+[case testInferTypeVariableFromTwoGenericTypes1]
+from typing import TypeVar, List, Sequence
+
+T = TypeVar('T')
+
+class C: ...
+class D(C): ...
+
+def f(x: Sequence[T], y: Sequence[T]) -> List[T]: ...
+
+reveal_type(f([C()], [D()])) # E: Revealed type is 'builtins.list[__main__.C*]'
+[builtins fixtures/list.pyi]
+
+[case testInferTypeVariableFromTwoGenericTypes2]
+from typing import TypeVar, List
+
+T = TypeVar('T')
+
+class C: ...
+class D(C): ...
+
+def f(x: List[T], y: List[T]) -> List[T]: ...
+
+f([C()], [D()]) # E: Cannot infer type argument 1 of "f"
+[builtins fixtures/list.pyi]
+
+[case testInferTypeVariableFromTwoGenericTypes3]
+from typing import Generic, TypeVar
+
+T = TypeVar('T')
+T_contra = TypeVar('T_contra', contravariant=True)
+
+class A(Generic[T_contra]): pass
+class B(A[T]): pass
+
+class C: ...
+class D(C): ...
+
+def f(x: A[T], y: A[T]) -> B[T]: ...
+
+c: B[C]
+d: B[D]
+reveal_type(f(c, d)) # E: Revealed type is '__main__.B[__main__.D*]'
+
+[case testInferTypeVariableFromTwoGenericTypes4]
+from typing import Generic, TypeVar, Callable, List
+
+T = TypeVar('T')
+T_contra = TypeVar('T_contra', contravariant=True)
+
+class A(Generic[T_contra]): pass
+class B(A[T_contra]): pass
+
+class C: ...
+class D(C): ...
+
+def f(x: Callable[[B[T]], None],
+      y: Callable[[B[T]], None]) -> List[T]: ...
+
+def gc(x: A[C]) -> None: pass  # B[C]
+def gd(x: A[D]) -> None: pass  # B[C]
+
+reveal_type(f(gc, gd)) # E: Revealed type is 'builtins.list[__main__.C*]'
+[builtins fixtures/list.pyi]
+
 [case testWideOuterContextSubClassBound]
 from typing import TypeVar