Repository: python/mypy

Issue:
"Enum" has no attribute "__iter__" on instance attributes defined with functional API
**Steps to reproduce**
Run mypy against the following code:

```
from enum import Enum


class OuterEnum(Enum):
    foo = "bar"


class Foo:
    def __init__(self,) -> None:
        self.OuterEnum = OuterEnum
        list(self.OuterEnum)
        self.InnerEnum = Enum("InnerEnum", [("foo", "bar")])
        list(self.InnerEnum)  # Error!
        [i for i in self.InnerEnum]  # Error!
        self.DictEnum = Enum("DictEnum", {"foo": "bar"})
        list(self.DictEnum)  # Error!


OtherEnum = Enum("OtherEnum", [("foo", "bar")])
list(OtherEnum)
```

**Observed behavior**
When trying to iterate over an enumeration defined as an instance attribute using the [functional API](https://docs.python.org/3.7/library/enum.html#functional-api), mypy raises an error when attempting to iterate over said attribute.

The above snippet raises three mypy errors where marked:

```
$ poetry run mypy foo.py 
foo.py:13: error: No overload variant of "list" matches argument type "Enum"
foo.py:13: note: Possible overload variant:
foo.py:13: note:     def [_T] __init__(self, iterable: Iterable[_T]) -> List[_T]
foo.py:13: note:     <1 more non-matching overload not shown>
foo.py:14: error: "Enum" has no attribute "__iter__"; maybe "__str__" or "__dir__"? (not iterable)
foo.py:16: error: No overload variant of "list" matches argument type "Enum"
foo.py:16: note: Possible overload variant:
foo.py:16: note:     def [_T] __init__(self, iterable: Iterable[_T]) -> List[_T]
foo.py:16: note:     <1 more non-matching overload not shown>
Found 3 errors in 1 file (checked 1 source file)
```

**Expected behavior**
When testing the above code, iteration over the instance attribute Enum works fine, and mypy does not raise errors when iterating over similar Enums if they're defined using the class syntax or aren't instance attributes. I would not expect mypy to raise an error here.

**Environment**

```
$ poetry run mypy --version
mypy 0.740
$ poetry run python --version
Python 3.7.4
```
Also tested against master with the same behavior
```
$ poetry run mypy --version
mypy 0.750+dev.2af0ac021335ec4a3e84a0ba739b4aea7e659c43
```

Thank you for your work on this tool!


Discussion:
It looks like this might be more general than just iteration -- mypy raises an error on almost any normal `Enum` operation:

```
        self.InnerEnum("bar")
        self.InnerEnum["foo"]
```
gives
```
"Enum" not callable
Value of type "Enum" is not indexable
```
The actual bug here is that creating enum as an attribute on self should be plain prohibited (with a clear error message), see similar issue https://github.com/python/mypy/issues/7531 and fix for it https://github.com/python/mypy/pull/7662 for named tuples. Probably the same should be also for typed dicts.
Thanks for the prompt response; that makes sense. Out of curiosity, why should generating enums and namedtuples as attributes be prohibited? It would be quite convenient for me in this case, since I'd like to build up an enumeration from data I'm reading in from the file system.
> Out of curiosity, why should generating enums and namedtuples as attributes be prohibited?

Creating a _new type_ for every _instance_ doesn't really make sense in a statically typed world (assuming we will never do full scale dependent types): `Foo().InnerEnum` and `Foo().InnerEnum` are two different classes if you call it twice.

Also this is honestly an anti-pattern. I could easily imagine this causes subtle bugs when you compare two enum values from different instances.

Currently mypy allows assigning normal class objects (i.e. non-special like enums) as attributes, because this pattern is used in unit tests (and there it kind of fine because there is typically a single instance of the test case class).
That makes sense to me. Thank you for the feedback.

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -2161,7 +2161,7 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
         """Check if s defines a typed dict."""
         if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, TypedDictExpr):
             return True  # This is a valid and analyzed typed dict definition, nothing to do here.
-        if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr):
+        if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
             return False
         lvalue = s.lvalues[0]
         name = lvalue.name
@@ -2169,6 +2169,9 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
                                                                        self.is_func_scope())
         if not is_typed_dict:
             return False
+        if isinstance(lvalue, MemberExpr):
+            self.fail("TypedDict type as attribute is not supported", lvalue)
+            return False
         # Yes, it's a valid typed dict, but defer if it is not ready.
         if not info:
             self.mark_incomplete(name, lvalue, becomes_typeinfo=True)
diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py
--- a/mypy/semanal_enum.py
+++ b/mypy/semanal_enum.py
@@ -8,7 +8,7 @@
 from mypy.nodes import (
     Expression, Context, TypeInfo, AssignmentStmt, NameExpr, CallExpr, RefExpr, StrExpr,
     UnicodeExpr, TupleExpr, ListExpr, DictExpr, Var, SymbolTableNode, MDEF, ARG_POS,
-    EnumCallExpr
+    EnumCallExpr, MemberExpr
 )
 from mypy.semanal_shared import SemanticAnalyzerInterface
 from mypy.options import Options
@@ -25,13 +25,16 @@ def process_enum_call(self, s: AssignmentStmt, is_func_scope: bool) -> bool:
         Return True if this looks like an Enum definition (but maybe with errors),
         otherwise return False.
         """
-        if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr):
+        if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
             return False
         lvalue = s.lvalues[0]
         name = lvalue.name
         enum_call = self.check_enum_call(s.rvalue, name, is_func_scope)
         if enum_call is None:
             return False
+        if isinstance(lvalue, MemberExpr):
+            self.fail("Enum type as attribute is not supported", lvalue)
+            return False
         # Yes, it's a valid Enum definition. Add it to the symbol table.
         self.api.add_symbol(name, enum_call, s)
         return True


Test patch:
diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test
--- a/test-data/unit/check-enum.test
+++ b/test-data/unit/check-enum.test
@@ -908,3 +908,12 @@ def func(x: Union[int, None, Empty] = _empty) -> int:
         reveal_type(x)  # N: Revealed type is 'builtins.int'
         return x + 2
 [builtins fixtures/primitives.pyi]
+
+[case testAssignEnumAsAttribute]
+from enum import Enum
+
+class A:
+    def __init__(self) -> None:
+        self.b = Enum("x", [("foo", "bar")])  # E: Enum type as attribute is not supported
+
+reveal_type(A().b)  # N: Revealed type is 'Any'
diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test
--- a/test-data/unit/check-typeddict.test
+++ b/test-data/unit/check-typeddict.test
@@ -1987,3 +1987,14 @@ reveal_type(foo['bar'])  # N: Revealed type is 'builtins.list[Any]'
 reveal_type(foo['baz'])  # N: Revealed type is 'builtins.list[Any]'
 [builtins fixtures/dict.pyi]
 [typing fixtures/typing-full.pyi]
+
+[case testAssignTypedDictAsAttribute]
+from typing import TypedDict
+
+class A:
+    def __init__(self) -> None:
+        self.b = TypedDict('b', {'x': int, 'y': str})  # E: TypedDict type as attribute is not supported
+
+reveal_type(A().b)  # N: Revealed type is 'Any'
+[builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]