Repository: python/mypy

Issue:
INTERNAL ERROR reported by mypy 0.610
I'm getting an internal error when testing some code I'm trying to patch.

I've been unable to reproduce the problem with a trivial example, so I've pushed the changes to my GitHub profile.

To reproduce:
```bash
git clone git@github.com:andymwood/numpy-stubs.git
cd numpy-stubs
git checkout annotations       # Currently on commit 33496555
pip install .
mypy --strict tests/pass/simple.py
```

which produces the following output:
```bash
tests/pass/simple.py:76: error: INTERNAL ERROR -- please report a bug at https://github.com/python/mypy/issues version: 0.610
tests/pass/simple.py:76: : note: please use --show-traceback to print a traceback when reporting a bug
```

Here's the traceback:
```bash
Traceback (most recent call last):
  File "/home/andrew/.conda/envs/numpy-stubs/bin/mypy", line 11, in <module>
    sys.exit(console_entry())
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/__main__.py", line 7, in console_entry
    main(None)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/main.py", line 91, in main
    res = type_check_only(sources, bin_dir, options, flush_errors, fscache)  # noqa
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/main.py", line 148, in type_check_only
    fscache=fscache)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/build.py", line 185, in build
    flush_errors, fscache)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/build.py", line 285, in _build
    graph = dispatch(sources, manager)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/build.py", line 2398, in dispatch
    process_graph(graph, manager)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/build.py", line 2691, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/build.py", line 2807, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/build.py", line 2029, in type_check_first_pass
    self.type_checker().check_first_pass()
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/checker.py", line 252, in check_first_pass
    self.accept(d)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/checker.py", line 351, in accept
    stmt.accept(self)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/nodes.py", line 883, in accept
    return visitor.visit_expression_stmt(self)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/checker.py", line 2237, in visit_expression_stmt
    self.expr_checker.accept(s.expr, allow_none_return=True, always_allow_any=True)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/checkexpr.py", line 2407, in accept
    typ = node.accept(self)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/nodes.py", line 1543, in accept
    return visitor.visit_op_expr(self)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/checkexpr.py", line 1396, in visit_op_expr
    allow_reverse=True)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/checkexpr.py", line 1523, in check_op
    local_errors)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/checkexpr.py", line 1507, in check_op_local
    callable_name=callable_name, object_type=object_type)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/checkexpr.py", line 577, in check_call
    callee, args, arg_kinds, formal_to_actual, context)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/checkexpr.py", line 829, in infer_function_type_arguments
    strict=self.chk.in_checked_function())
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/infer.py", line 31, in infer_function_type_arguments
    callee_type, arg_types, arg_kinds, formal_to_actual)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/constraints.py", line 63, in infer_constraints_for_callable
    SUPERTYPE_OF)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/constraints.py", line 170, in infer_constraints
    for t_item in template.items],
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/constraints.py", line 170, in <listcomp>
    for t_item in template.items],
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/constraints.py", line 190, in infer_constraints_if_possible
    return infer_constraints(template, actual, direction)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/constraints.py", line 174, in infer_constraints
    return template.accept(ConstraintBuilderVisitor(actual, direction))
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/types.py", line 512, in accept
    return visitor.visit_instance(self)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/constraints.py", line 351, in visit_instance
    original_actual, template)
  File "/home/andrew/.conda/envs/numpy-stubs/lib/python3.6/site-packages/mypy/constraints.py", line 393, in infer_constraints_from_protocol_members
    assert inst is not None and temp is not None
AssertionError:
```


Discussion:
OK, I think I identified the cause: it is `_promote`. A simple repro is:
```python
from typing import SupportsComplex, TypeVar, Union

T = TypeVar('T')

def f(x: Union[SupportsComplex, T]) -> Union[SupportsComplex, T]: ...

f(0)
```
For example in this particular case `int` doesn't implement `SupportsComplex` protocol, but it is considered a subtype of it because `int` is promoted to `float`, and then to `complex`. Then in the constraint inference code we assume that "if it is a subtype of a protocol, then it must have all members", which is not the case here.

A potential solution is to replace `is_subtype` with `is_protocol_implementation` in that protocol constraint inference branch.
I actually think it is relatively important and the fix may be simple so I set priority to high.

Resolving patch:
diff --git a/mypy/constraints.py b/mypy/constraints.py
--- a/mypy/constraints.py
+++ b/mypy/constraints.py
@@ -312,6 +312,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
             actual = actual.as_anonymous().fallback
         if isinstance(actual, Instance):
             instance = actual
+            erased = erase_typevars(template)
+            assert isinstance(erased, Instance)
             # We always try nominal inference if possible,
             # it is much faster than the structural one.
             if (self.direction == SUBTYPE_OF and
@@ -343,8 +345,11 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
                     # This is a conservative way break the inference cycles.
                     # It never produces any "false" constraints but gives up soon
                     # on purely structural inference cycles, see #3829.
+                    # Note that we use is_protocol_implementation instead of is_subtype
+                    # because some type may be considered a subtype of a protocol
+                    # due to _promote, but still not implement the protocol.
                     not any(is_same_type(template, t) for t in template.type.inferring) and
-                    mypy.subtypes.is_subtype(instance, erase_typevars(template))):
+                    mypy.subtypes.is_protocol_implementation(instance, erased)):
                 template.type.inferring.append(template)
                 self.infer_constraints_from_protocol_members(res, instance, template,
                                                              original_actual, template)
@@ -353,7 +358,7 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
             elif (instance.type.is_protocol and self.direction == SUBTYPE_OF and
                   # We avoid infinite recursion for structural subtypes also here.
                   not any(is_same_type(instance, i) for i in instance.type.inferring) and
-                  mypy.subtypes.is_subtype(erase_typevars(template), instance)):
+                  mypy.subtypes.is_protocol_implementation(erased, instance)):
                 instance.type.inferring.append(instance)
                 self.infer_constraints_from_protocol_members(res, instance, template,
                                                              template, instance)


Test patch:
diff --git a/test-data/unit/check-type-promotion.test b/test-data/unit/check-type-promotion.test
--- a/test-data/unit/check-type-promotion.test
+++ b/test-data/unit/check-type-promotion.test
@@ -37,3 +37,15 @@ y = 0.0
 y = 1
 y.x # E: "int" has no attribute "x"
 [builtins fixtures/primitives.pyi]
+
+[case testTypePromotionsDontInterfereWithProtocols]
+from typing import TypeVar, Union, Protocol
+
+class SupportsFloat(Protocol):
+    def __float__(self) -> float: pass
+
+T = TypeVar('T')
+def f(x: Union[SupportsFloat, T]) -> Union[SupportsFloat, T]: pass
+f(0)  # should not crash
+[builtins fixtures/primitives.pyi]
+[out]