Repository: python/mypy

Issue:
iter doesn't work on TypeVar with bound of Enum
**Bug Report**

A function that takes an argument of `Type[_E]` where `_E = TypeVar("_E", bound=Enum)` does not accept calling `iter` on that argument. Calling `iter` on a concrete enum class is accepted, which is why I think this isn't purely a typeshed issue. Using the class in a loop or calling the `__iter__` member is also accepted.

**To Reproduce**

Run mypy on the following:

```python
#!/usr/bin/env python3

import socket
from enum import Enum
from typing import Iterator, Type, TypeVar

_E = TypeVar("_E", bound=Enum)

def enum_iter(cls: Type[_E]):
    return iter(cls)

for value in enum_iter(socket.SocketKind):
    print(value)
```

**Expected Behavior**

Expected no mypy errors.

**Actual Behavior**

```
mypy_enum_iter.py:10: error: No overload variant of "iter" matches argument type "Type[_E]"
mypy_enum_iter.py:10: note: Possible overload variants:
mypy_enum_iter.py:10: note:     def [_SupportsNextT <: SupportsNext[Any]] iter(_SupportsIter[_SupportsNextT]) -> _SupportsNextT
mypy_enum_iter.py:10: note:     def [_T] iter(Callable[[], Optional[_T]], None) -> Iterator[_T]
mypy_enum_iter.py:10: note:     def [_T] iter(Callable[[], _T], object) -> Iterator[_T]
Found 1 error in 1 file (checked 1 source file)
```

What's even weirder is that adding an `Iterator[_E]` return annotation to `enum_iter` makes it pass.

**Your Environment**

- Mypy version used: 0.942
- Mypy command-line flags: none
- Mypy configuration options from `mypy.ini` (and other config files): none
- Python version used: 3.8.10
- Operating system and version: Ubuntu 20.04


Discussion:
I think [this is a repro](https://mypy-play.net/?mypy=latest&python=3.10&gist=4758edd764fadc160af552358a5f3bda) that doesn't involve enums, revealing that the issue is a general one with mypy's understanding of metaclasses rather than an issue specific to the enum plugin:

```python 
from typing import Iterator, TypeVar

M = TypeVar("M")

class Meta(type):
    def __iter__(self: type[M]) -> Iterator[M]: ...

class Foo(metaclass=Meta): ...

F = TypeVar("F", bound=Foo)

def foo_iter(cls: type[F]):
    return iter(cls)
```

Mypy output:

```
main.py:13: error: No overload variant of "iter" matches argument type "Type[F]"
main.py:13: note: Possible overload variants:
main.py:13: note:     def [_SupportsNextT <: SupportsNext[Any]] iter(__iterable, _SupportsIter[_SupportsNextT]) -> _SupportsNextT
main.py:13: note:     def [_T] iter(__function, Callable[[], Optional[_T]], None) -> Iterator[_T]
main.py:13: note:     def [_T] iter(__function, Callable[[], _T], object) -> Iterator[_T]
Found 2 errors in 1 file (checked 1 source file)
```
It probably doesn't even have anything to do with `TypeVar`s. I [can reproduce a similar error](https://mypy-play.net/?mypy=latest&python=3.10&gist=53869cd5971c3620e823f223a82cf18b) with this snippet:

```python
from typing import Iterator, TypeVar

M = TypeVar("M")

class Meta(type):
    def __iter__(self: type[M]) -> Iterator[M]: ...

class Foo(metaclass=Meta): ...

def foo_iter(cls: type[Foo]):
    return iter(cls)  # error: Argument 1 to "iter" has incompatible type "Type[Foo]"; expected "_SupportsIter[<nothing>]"

```
I guess a good question is whether subclasses of Foo are expected to have a metaclass that is a subclass (in the Liskov substitution sense) of Meta. If not, then the error would be legitimate since `cls` could be an instance of a subclass of Foo whose metaclass doesn't provide `__iter__`.

Resolving patch:
diff --git a/mypy/constraints.py b/mypy/constraints.py
--- a/mypy/constraints.py
+++ b/mypy/constraints.py
@@ -619,6 +619,17 @@ def visit_instance(self, template: Instance) -> list[Constraint]:
                         actual.item, template, subtype, template, class_obj=True
                     )
                 )
+            if self.direction == SUPERTYPE_OF:
+                # Infer constraints for Type[T] via metaclass of T when it makes sense.
+                a_item = actual.item
+                if isinstance(a_item, TypeVarType):
+                    a_item = get_proper_type(a_item.upper_bound)
+                if isinstance(a_item, Instance) and a_item.type.metaclass_type:
+                    res.extend(
+                        self.infer_constraints_from_protocol_members(
+                            a_item.type.metaclass_type, template, actual, template
+                        )
+                    )
 
         if isinstance(actual, Overloaded) and actual.fallback is not None:
             actual = actual.fallback


Test patch:
diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test
--- a/test-data/unit/check-protocols.test
+++ b/test-data/unit/check-protocols.test
@@ -3977,3 +3977,24 @@ class C:
     DEFAULT: ClassVar[C]
 
 x: P = C()
+
+[case testInferenceViaTypeTypeMetaclass]
+from typing import Iterator, Iterable, TypeVar, Type
+
+M = TypeVar("M")
+
+class Meta(type):
+    def __iter__(self: Type[M]) -> Iterator[M]: ...
+class Foo(metaclass=Meta): ...
+
+T = TypeVar("T")
+def test(x: Iterable[T]) -> T: ...
+
+reveal_type(test(Foo))  # N: Revealed type is "__main__.Foo"
+t_foo: Type[Foo]
+reveal_type(test(t_foo))  # N: Revealed type is "__main__.Foo"
+
+TF = TypeVar("TF", bound=Foo)
+def outer(cls: Type[TF]) -> TF:
+    reveal_type(test(cls))  # N: Revealed type is "TF`-1"
+    return cls()
diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -1858,3 +1858,22 @@ _testTupleWithDifferentArgsPy310.py:20: note: Revealed type is "builtins.list[Tu
 _testTupleWithDifferentArgsPy310.py:26: error: Invalid type: try using Literal[1] instead?
 _testTupleWithDifferentArgsPy310.py:27: error: Unexpected "..."
 _testTupleWithDifferentArgsPy310.py:30: note: Revealed type is "builtins.tuple[builtins.object, ...]"
+
+[case testEnumIterMetaInference]
+import socket
+from enum import Enum
+from typing import Iterable, Iterator, Type, TypeVar
+
+_E = TypeVar("_E", bound=Enum)
+
+def enum_iter(cls: Type[_E]) -> Iterable[_E]:
+    reveal_type(iter(cls))
+    reveal_type(next(iter(cls)))
+    return iter(cls)
+
+for value in enum_iter(socket.SocketKind):
+    reveal_type(value)
+[out]
+_testEnumIterMetaInference.py:8: note: Revealed type is "typing.Iterator[_E`-1]"
+_testEnumIterMetaInference.py:9: note: Revealed type is "_E`-1"
+_testEnumIterMetaInference.py:13: note: Revealed type is "socket.SocketKind"